[fcm] 01/01: Upstream 2014.09.0 release

Alastair McKinstry mckinstry at moszumanska.debian.org
Mon Jun 15 11:24:36 UTC 2015


This is an automated email from the git hooks/post-receive script.

mckinstry pushed a commit to tag upstream_2014.09.0
in repository fcm.

commit c8da138072f45848becb5e0570706e9ad6d3a586
Author: Alastair McKinstry <mckinstry at debian.org>
Date:   Sun Nov 9 17:49:20 2014 +0000

    Upstream 2014.09.0 release
---
 ACKNOWLEDGEMENT.md                                 |   19 +
 CHANGES.md                                         |  356 ++
 CONTRIBUTING.md                                    |   77 +
 COPYING                                            |  674 +++
 README.md                                          |   35 +
 bin/fcm                                            |  134 +
 bin/fcm_graphic_diff                               |  130 +
 bin/fcm_graphic_merge                              |  130 +
 bin/fcm_gui                                        | 1346 +++++
 bin/fcm_internal                                   |  629 ++
 bin/fcm_test_battery                               |   24 +
 doc/collaboration/feeding-back-patch.png           |  Bin 0 -> 11655 bytes
 doc/collaboration/index.html                       |  481 ++
 doc/collaboration/managing-local-changes.png       |  Bin 0 -> 9112 bytes
 doc/collaboration/merging-patch-multi.png          |  Bin 0 -> 4620 bytes
 doc/collaboration/merging-patch-one.png            |  Bin 0 -> 4427 bytes
 doc/collaboration/mirroring-trunk.png              |  Bin 0 -> 7604 bytes
 doc/collaboration/updating-branch.png              |  Bin 0 -> 6324 bytes
 doc/collaboration/updating-shared-branch.png       |  Bin 0 -> 9032 bytes
 doc/collaboration/working-as-collaborator.png      |  Bin 0 -> 10409 bytes
 doc/etc/bootstrap/css/bootstrap-responsive.css     | 1109 ++++
 doc/etc/bootstrap/css/bootstrap-responsive.min.css |    9 +
 doc/etc/bootstrap/css/bootstrap.css                | 6167 ++++++++++++++++++++
 doc/etc/bootstrap/css/bootstrap.min.css            |    9 +
 .../bootstrap/img/glyphicons-halflings-white.png   |  Bin 0 -> 8777 bytes
 doc/etc/bootstrap/img/glyphicons-halflings.png     |  Bin 0 -> 12799 bytes
 doc/etc/bootstrap/js/bootstrap.js                  | 2280 ++++++++
 doc/etc/bootstrap/js/bootstrap.min.js              |    6 +
 doc/etc/fcm-icon.png                               |  Bin 0 -> 1712 bytes
 doc/etc/fcm-terms-of-use.html                      |   95 +
 doc/etc/fcm-version.js                             |    1 +
 doc/etc/fcm.css                                    |   28 +
 doc/etc/fcm.js                                     |  166 +
 doc/etc/fcm.png                                    |  Bin 0 -> 6763 bytes
 doc/etc/jquery.min.js                              |    4 +
 doc/etc/moment.min.js                              |    6 +
 doc/index.html                                     |   93 +
 doc/installation/index.html                        |  354 ++
 doc/release_notes/1-1.html                         |  475 ++
 doc/release_notes/1-2.html                         |  400 ++
 doc/release_notes/1-3.html                         |  585 ++
 doc/release_notes/1-4.html                         |  395 ++
 doc/release_notes/1-5.html                         |  483 ++
 doc/release_notes/2-0.html                         |  754 +++
 doc/release_notes/2-1.html                         |  457 ++
 doc/release_notes/2-2.html                         |  600 ++
 doc/release_notes/2-3-1.html                       |  117 +
 doc/release_notes/2-3.html                         |  136 +
 doc/release_notes/index.html                       |  105 +
 doc/user_guide/annex_bld_cfg.html                  |  931 +++
 doc/user_guide/annex_cfg.html                      | 1433 +++++
 doc/user_guide/annex_ext_cfg.html                  |  397 ++
 doc/user_guide/annex_fcm_cfg.html                  |  198 +
 doc/user_guide/annex_quick_ref.html                |  256 +
 doc/user_guide/annex_quick_ref_tree_conflicts.html |  283 +
 doc/user_guide/api.html                            |  222 +
 doc/user_guide/build.html                          | 1646 ++++++
 doc/user_guide/changeset.png                       |  Bin 0 -> 58553 bytes
 doc/user_guide/code_management.html                | 1243 ++++
 doc/user_guide/command_ref.html                    | 2005 +++++++
 doc/user_guide/create_branch.png                   |  Bin 0 -> 71893 bytes
 doc/user_guide/extract.html                        | 1171 ++++
 doc/user_guide/fcm_overview.png                    |  Bin 0 -> 13033 bytes
 doc/user_guide/getting_started.html                | 1490 +++++
 doc/user_guide/gui1.png                            |  Bin 0 -> 18770 bytes
 doc/user_guide/gui2.png                            |  Bin 0 -> 32347 bytes
 doc/user_guide/index.html                          |  102 +
 doc/user_guide/introduction.html                   |  115 +
 doc/user_guide/make.html                           | 2302 ++++++++
 doc/user_guide/overview.html                       |  155 +
 doc/user_guide/system_admin.html                   |  612 ++
 doc/user_guide/working_practices.html              |  822 +++
 doc/user_guide/xxdiff1.png                         |  Bin 0 -> 27430 bytes
 doc/user_guide/xxdiff2.png                         |  Bin 0 -> 26199 bytes
 doc/user_guide/xxdiff_tutorial.png                 |  Bin 0 -> 22140 bytes
 etc/fcm/admin.cfg.example                          |   86 +
 etc/fcm/external.cfg.example                       |    9 +
 etc/fcm/keyword.cfg.example                        |    9 +
 etc/fcm/make.cfg.example                           |    9 +
 etc/svn-hooks/post-commit                          |   23 +
 etc/svn-hooks/post-revprop-change                  |   23 +
 etc/svn-hooks/pre-commit                           |   22 +
 etc/svn-hooks/pre-revprop-change                   |   22 +
 index.html                                         |   14 +
 lib/FCM/Admin/Config.pm                            |  353 ++
 lib/FCM/Admin/Project.pm                           |  260 +
 lib/FCM/Admin/Runner.pm                            |  299 +
 lib/FCM/Admin/System.pm                            | 1387 +++++
 lib/FCM/Admin/User.pm                              |   90 +
 lib/FCM/Admin/Users/LDAP.pm                        |  149 +
 lib/FCM/Admin/Users/Passwd.pm                      |  141 +
 lib/FCM/Admin/Util.pm                              |  386 ++
 lib/FCM/CLI.pm                                     |  291 +
 lib/FCM/CLI/Exception.pm                           |   57 +
 lib/FCM/CLI/Parser.pm                              |  459 ++
 lib/FCM/CLI/fcm-add.pod                            |   22 +
 lib/FCM/CLI/fcm-branch-create.pod                  |   86 +
 lib/FCM/CLI/fcm-branch-delete.pod                  |   61 +
 lib/FCM/CLI/fcm-branch-diff.pod                    |   52 +
 lib/FCM/CLI/fcm-branch-info.pod                    |   43 +
 lib/FCM/CLI/fcm-branch-list.pod                    |   52 +
 lib/FCM/CLI/fcm-branch.pod                         |   37 +
 lib/FCM/CLI/fcm-browse.pod                         |   30 +
 lib/FCM/CLI/fcm-build.pod                          |   91 +
 lib/FCM/CLI/fcm-cfg-print.pod                      |   23 +
 lib/FCM/CLI/fcm-changelist.pod                     |    9 +
 lib/FCM/CLI/fcm-cmp-ext-cfg.pod                    |   30 +
 lib/FCM/CLI/fcm-commit.pod                         |   31 +
 lib/FCM/CLI/fcm-conflicts.pod                      |   17 +
 lib/FCM/CLI/fcm-delete.pod                         |   22 +
 lib/FCM/CLI/fcm-diff.pod                           |   45 +
 lib/FCM/CLI/fcm-export-items.pod                   |   57 +
 lib/FCM/CLI/fcm-extract.pod                        |   46 +
 lib/FCM/CLI/fcm-gui.pod                            |   12 +
 lib/FCM/CLI/fcm-help.pod                           |   25 +
 lib/FCM/CLI/fcm-keyword-print.pod                  |   27 +
 lib/FCM/CLI/fcm-loc-layout.pod                     |   25 +
 lib/FCM/CLI/fcm-make.pod                           |   60 +
 lib/FCM/CLI/fcm-merge.pod                          |   76 +
 lib/FCM/CLI/fcm-mkpatch.pod                        |   61 +
 lib/FCM/CLI/fcm-project-create.pod                 |   34 +
 lib/FCM/CLI/fcm-switch.pod                         |   16 +
 lib/FCM/CLI/fcm-test-battery.pod                   |   25 +
 lib/FCM/CLI/fcm-update.pod                         |   14 +
 lib/FCM/CLI/fcm-version.pod                        |   11 +
 lib/FCM/Class/CODE.pm                              |  307 +
 lib/FCM/Class/Exception.pm                         |  134 +
 lib/FCM/Class/HASH.pm                              |  340 ++
 lib/FCM/Context/ConfigEntry.pm                     |  160 +
 lib/FCM/Context/Event.pm                           |  383 ++
 lib/FCM/Context/Keyword.pm                         |  221 +
 lib/FCM/Context/Locator.pm                         |  144 +
 lib/FCM/Context/Make.pm                            |  164 +
 lib/FCM/Context/Make/Build.pm                      |  481 ++
 lib/FCM/Context/Make/Extract.pm                    |  494 ++
 lib/FCM/Context/Make/Mirror.pm                     |  119 +
 lib/FCM/Context/Make/Share/Property.pm             |  138 +
 lib/FCM/Context/Task.pm                            |  105 +
 lib/FCM/Exception.pm                               |  113 +
 lib/FCM/System.pm                                  |  287 +
 lib/FCM/System/CM.pm                               |  709 +++
 lib/FCM/System/CM/CommitMessage.pm                 |  312 +
 lib/FCM/System/CM/Prompt.pm                        |  221 +
 lib/FCM/System/CM/ResolveConflicts.pm              |  669 +++
 lib/FCM/System/CM/SVN.pm                           | 1003 ++++
 lib/FCM/System/Exception.pm                        |  421 ++
 lib/FCM/System/Make.pm                             |  451 ++
 lib/FCM/System/Make/Build.pm                       | 1650 ++++++
 lib/FCM/System/Make/Build/FileType.pm              |  226 +
 lib/FCM/System/Make/Build/FileType/C.pm            |  156 +
 lib/FCM/System/Make/Build/FileType/CPP.pm          |   92 +
 lib/FCM/System/Make/Build/FileType/CXX.pm          |   72 +
 lib/FCM/System/Make/Build/FileType/Data.pm         |   82 +
 lib/FCM/System/Make/Build/FileType/FPP.pm          |   67 +
 lib/FCM/System/Make/Build/FileType/Fortran.pm      |  404 ++
 lib/FCM/System/Make/Build/FileType/H.pm            |  131 +
 lib/FCM/System/Make/Build/FileType/NS.pm           |  210 +
 lib/FCM/System/Make/Build/FileType/Script.pm       |  100 +
 lib/FCM/System/Make/Build/Task/Archive.pm          |  133 +
 lib/FCM/System/Make/Build/Task/Compile.pm          |  143 +
 lib/FCM/System/Make/Build/Task/Compile/C.pm        |   70 +
 lib/FCM/System/Make/Build/Task/Compile/CXX.pm      |   70 +
 lib/FCM/System/Make/Build/Task/Compile/Fortran.pm  |  114 +
 lib/FCM/System/Make/Build/Task/ExtractInterface.pm |  536 ++
 lib/FCM/System/Make/Build/Task/Install.pm          |   90 +
 lib/FCM/System/Make/Build/Task/Link.pm             |  195 +
 lib/FCM/System/Make/Build/Task/Link/C.pm           |   72 +
 lib/FCM/System/Make/Build/Task/Link/CXX.pm         |   72 +
 lib/FCM/System/Make/Build/Task/Link/Fortran.pm     |   72 +
 lib/FCM/System/Make/Build/Task/Preprocess.pm       |  131 +
 lib/FCM/System/Make/Build/Task/Preprocess/C.pm     |   66 +
 .../System/Make/Build/Task/Preprocess/Fortran.pm   |   67 +
 lib/FCM/System/Make/Build/Task/Share.pm            |   94 +
 lib/FCM/System/Make/Extract.pm                     | 1212 ++++
 lib/FCM/System/Make/Mirror.pm                      |  415 ++
 lib/FCM/System/Make/Preprocess.pm                  |   87 +
 lib/FCM/System/Make/Share/Config.pm                |  404 ++
 lib/FCM/System/Make/Share/Dest.pm                  |  438 ++
 lib/FCM/System/Make/Share/Subsystem.pm             |  322 +
 lib/FCM/System/Misc.pm                             |  354 ++
 lib/FCM/System/Old.pm                              |  142 +
 lib/FCM/Util.pm                                    | 1023 ++++
 lib/FCM/Util/ConfigReader.pm                       |  610 ++
 lib/FCM/Util/ConfigUpgrade.pm                      |  136 +
 lib/FCM/Util/Event.pm                              | 1244 ++++
 lib/FCM/Util/Exception.pm                          |  219 +
 lib/FCM/Util/Locator.pm                            |  763 +++
 lib/FCM/Util/Locator/FS.pm                         |  202 +
 lib/FCM/Util/Locator/SSH.pm                        |  251 +
 lib/FCM/Util/Locator/SVN.pm                        |  458 ++
 lib/FCM/Util/Reporter.pm                           |  382 ++
 lib/FCM/Util/Shell.pm                              |  306 +
 lib/FCM/Util/TaskRunner.pm                         |  380 ++
 lib/FCM1/Base.pm                                   |  125 +
 lib/FCM1/Build.pm                                  | 1630 ++++++
 lib/FCM1/Build/Fortran.pm                          |  549 ++
 lib/FCM1/BuildSrc.pm                               | 1508 +++++
 lib/FCM1/BuildTask.pm                              |  353 ++
 lib/FCM1/CfgFile.pm                                |  597 ++
 lib/FCM1/CfgLine.pm                                |  346 ++
 lib/FCM1/Cm.pm                                     | 2264 +++++++
 lib/FCM1/CmBranch.pm                               | 1009 ++++
 lib/FCM1/CmUrl.pm                                  |  639 ++
 lib/FCM1/Config.pm                                 |  898 +++
 lib/FCM1/ConfigSystem.pm                           |  752 +++
 lib/FCM1/Dest.pm                                   |  899 +++
 lib/FCM1/Exception.pm                              |  108 +
 lib/FCM1/Extract.pm                                | 1132 ++++
 lib/FCM1/ExtractConfigComparator.pm                |  371 ++
 lib/FCM1/ExtractFile.pm                            |  423 ++
 lib/FCM1/ExtractSrc.pm                             |  100 +
 lib/FCM1/Interactive.pm                            |  144 +
 lib/FCM1/Interactive/InputGetter.pm                |  135 +
 lib/FCM1/Interactive/InputGetter/CLI.pm            |  100 +
 lib/FCM1/Interactive/InputGetter/GUI.pm            |  261 +
 lib/FCM1/Keyword.pm                                |  175 +
 lib/FCM1/ReposBranch.pm                            |  528 ++
 lib/FCM1/SrcDirLayer.pm                            |  277 +
 lib/FCM1/Timer.pm                                  |   85 +
 lib/FCM1/Util.pm                                   |  564 ++
 lib/FCM1/Util/ClassLoader.pm                       |   93 +
 licences/Apache2                                   |  202 +
 licences/GPL3                                      |    1 +
 man/man1/fcm.1                                     |   42 +
 sbin/fcm-add-svn-repos                             |  105 +
 sbin/fcm-add-svn-repos-and-trac-env                |  119 +
 sbin/fcm-add-trac-env                              |  117 +
 sbin/fcm-backup-svn-repos                          |  137 +
 sbin/fcm-backup-trac-env                           |  118 +
 sbin/fcm-commit-update                             |  170 +
 sbin/fcm-daily-update                              |  190 +
 sbin/fcm-install-svn-hook                          |  115 +
 sbin/fcm-manage-trac-env-session                   |   96 +
 sbin/fcm-manage-users                              |  119 +
 sbin/fcm-recover-svn-repos                         |  136 +
 sbin/fcm-recover-trac-env                          |  112 +
 sbin/fcm-rpmbuild                                  |  112 +
 sbin/fcm-user-to-email                             |   66 +
 sbin/fcm-vacuum-trac-env-db                        |  101 +
 sbin/my-regular-update.example                     |   43 +
 sbin/post-commit-bg                                |  160 +
 sbin/post-commit-bg-notify-who                     |  103 +
 sbin/post-revprop-change-bg                        |  111 +
 sbin/pre-commit                                    |  110 +
 sbin/pre-commit-verify-branch-owner                |  100 +
 sbin/pre-revprop-change                            |   84 +
 sbin/svnperms.py                                   |  368 ++
 sbin/trac_hook                                     |   65 +
 t/etc/repo_files/lib/python/info/__init__.py       |    0
 t/etc/repo_files/lib/python/info/poems.py          |   24 +
 t/etc/repo_files/module/hello_constants.f90        |    5 +
 t/etc/repo_files/module/hello_constants.inc        |    1 +
 t/etc/repo_files/module/hello_constants_dummy.inc  |    1 +
 t/etc/repo_files/pro/hello.pro                     |    2 +
 t/etc/repo_files/pro/plot.pro                      |    3 +
 t/etc/repo_files/program/hello.F90                 |   20 +
 t/etc/repo_files/subroutine/hello_c.c              |    5 +
 t/etc/repo_files/subroutine/hello_sub.F90          |   24 +
 t/etc/repo_files/subroutine/hello_sub.h            |    1 +
 t/etc/repo_files/subroutine/hello_sub_dummy.h      |    1 +
 t/fcm-add-trac-env/00-basic.t                      |   83 +
 t/fcm-add-trac-env/test_header                     |    1 +
 t/fcm-add/00-simple.t                              |  139 +
 t/fcm-add/test_header                              |  232 +
 t/fcm-backup-svn-repos/00-basic.t                  |  103 +
 t/fcm-backup-svn-repos/test_header                 |    1 +
 t/fcm-branch-create/00-simple.t                    |   96 +
 t/fcm-branch-create/test_header                    |  232 +
 t/fcm-branch-delete/00-simple.t                    |   65 +
 t/fcm-branch-delete/test_header                    |  232 +
 t/fcm-branch-diff/00-simple.t                      |  664 +++
 t/fcm-branch-diff/test_header                      |  232 +
 t/fcm-branch-info/00-simple.t                      |  154 +
 t/fcm-branch-info/test_header                      |  232 +
 t/fcm-branch-list/00-simple.t                      |  133 +
 t/fcm-branch-list/test_header                      |  232 +
 t/fcm-commit/00-simple.t                           |  134 +
 t/fcm-commit/01-subtree.t                          |  137 +
 t/fcm-commit/02-bad.t                              |   48 +
 t/fcm-commit/03-message-file.t                     |   58 +
 t/fcm-commit/test_header                           |  232 +
 t/fcm-conflicts/00-tree-add-add.t                  |  154 +
 t/fcm-conflicts/01-tree-delete-delete.t            |   97 +
 t/fcm-conflicts/02-tree-delete-edit.t              |  132 +
 t/fcm-conflicts/03-tree-delete-rename.t            |  111 +
 t/fcm-conflicts/04-tree-edit-delete.t              |  130 +
 t/fcm-conflicts/05-tree-edit-rename.t              |  185 +
 t/fcm-conflicts/06-tree-rename-delete.t            |  111 +
 t/fcm-conflicts/07-tree-rename-edit.t              |  142 +
 t/fcm-conflicts/08-tree-rename-rename-diff.t       |  144 +
 t/fcm-conflicts/09-tree-rename-rename-same.t       |  220 +
 t/fcm-conflicts/10-text.t                          |  180 +
 t/fcm-conflicts/test_header                        |  232 +
 t/fcm-diff/00-simple.t                             |  239 +
 t/fcm-diff/test_header                             |  232 +
 t/fcm-install-svn-hook/00-basic.t                  |  144 +
 t/fcm-install-svn-hook/00-basic/clean-2.out        |    1 +
 t/fcm-install-svn-hook/00-basic/clean.out          |   17 +
 t/fcm-install-svn-hook/00-basic/commit-conf-2.out  |    6 +
 t/fcm-install-svn-hook/00-basic/commit-conf.out    |   10 +
 t/fcm-install-svn-hook/00-basic/new-2.out          |    4 +
 t/fcm-install-svn-hook/00-basic/new.out            |    8 +
 .../00-basic/svnperms-conf-2.out                   |    5 +
 t/fcm-install-svn-hook/00-basic/svnperms-conf.out  |    9 +
 t/fcm-install-svn-hook/01-housekeep-log.t          |  192 +
 t/fcm-install-svn-hook/01-housekeep-log/0-cmd0.out |   16 +
 t/fcm-install-svn-hook/01-housekeep-log/0-cmd1.out |    8 +
 t/fcm-install-svn-hook/01-housekeep-log/28-cmd.out |   36 +
 t/fcm-install-svn-hook/01-housekeep-log/7-cmd.out  |   32 +
 t/fcm-install-svn-hook/02-env.t                    |   59 +
 t/fcm-install-svn-hook/test_header                 |    1 +
 t/fcm-install-svn-hook/test_header_more            |   25 +
 t/fcm-keyword-print/00-simple.t                    |   67 +
 t/fcm-keyword-print/test_header                    |    1 +
 t/fcm-loc-layout/00-simple.t                       |  163 +
 t/fcm-loc-layout/test_header                       |  231 +
 t/fcm-make/00-build-basic.t                        |   85 +
 t/fcm-make/00-build-basic/bin/my-ld                |   21 +
 t/fcm-make/00-build-basic/fcm-make.cfg             |    5 +
 t/fcm-make/00-build-basic/src/hello.f90            |    4 +
 t/fcm-make/00-build-basic/src/world.f90            |    8 +
 t/fcm-make/01-build-link-opts                      |    1 +
 t/fcm-make/01-build-link-opts.t                    |   80 +
 t/fcm-make/02-build-ext-iface.t                    |   46 +
 .../02-build-ext-iface/expected/t1.interface       |   70 +
 .../02-build-ext-iface/expected/t2.interface       |    4 +
 t/fcm-make/02-build-ext-iface/fcm-make.cfg         |    4 +
 t/fcm-make/02-build-ext-iface/src/m1.f90           |   26 +
 t/fcm-make/02-build-ext-iface/src/t1.f90           |  163 +
 t/fcm-make/02-build-ext-iface/src/t2.f90           |    3 +
 t/fcm-make/03-build-include-paths.t                |   69 +
 t/fcm-make/03-build-include-paths/fcm-make.cfg     |    6 +
 .../include/world1/worldx.f90                      |    1 +
 .../include/world2/worldx.f90                      |    1 +
 t/fcm-make/03-build-include-paths/src/hello.f90    |    4 +
 t/fcm-make/03-build-include-paths/src/world.f90    |    8 +
 t/fcm-make/04-build-libs.t                         |   71 +
 t/fcm-make/04-build-libs/fcm-make.cfg              |    6 +
 t/fcm-make/04-build-libs/src-lib/earth.f90         |    4 +
 t/fcm-make/04-build-libs/src-lib/greet.f90         |    4 +
 t/fcm-make/04-build-libs/src-lib/moon.f90          |    4 +
 t/fcm-make/04-build-libs/src/hello.f90             |   13 +
 t/fcm-make/05-build-c-cxx-basic.t                  |   84 +
 t/fcm-make/05-build-c-cxx-basic/fcm-make.cfg       |    8 +
 t/fcm-make/05-build-c-cxx-basic/src/chello.c       |    6 +
 t/fcm-make/05-build-c-cxx-basic/src/cxxhello.cxx   |    6 +
 t/fcm-make/06-extract-ssh.t                        |  108 +
 t/fcm-make/07-build-ns-dep.t                       |   68 +
 t/fcm-make/07-build-ns-dep/fcm-make.cfg            |    5 +
 t/fcm-make/07-build-ns-dep/src/lib/earth.f90       |    4 +
 t/fcm-make/07-build-ns-dep/src/lib/greet.f90       |    4 +
 t/fcm-make/07-build-ns-dep/src/main/hello.f90      |   13 +
 t/fcm-make/08-build-dup-dep.t                      |   60 +
 t/fcm-make/08-build-dup-dep/fcm-make.cfg           |    4 +
 t/fcm-make/08-build-dup-dep/src/lib/earth.f90      |    4 +
 t/fcm-make/08-build-dup-dep/src/lib/greet.f90      |    4 +
 t/fcm-make/08-build-dup-dep/src/lib/moon.f90       |    4 +
 t/fcm-make/08-build-dup-dep/src/main/hello.f90     |   13 +
 t/fcm-make/09-build-dep-o.t                        |   64 +
 t/fcm-make/09-build-dep-o/fcm-make.cfg             |    4 +
 t/fcm-make/09-build-dep-o/src/lib/earth.f90        |    4 +
 t/fcm-make/09-build-dep-o/src/lib/greet.f90        |    5 +
 .../09-build-dep-o/src/lib/greet_fmt_mod.f90       |    3 +
 t/fcm-make/09-build-dep-o/src/main/hello.f90       |   13 +
 t/fcm-make/09-build-dep-o/src/main/hi.f90          |   13 +
 t/fcm-make/10-log                                  |    1 +
 t/fcm-make/10-log.t                                |   54 +
 t/fcm-make/11-preprocess-include-path.t            |   59 +
 t/fcm-make/11-preprocess-include-path/fcm-make.cfg |    4 +
 .../include/world1/worldx.h                        |    1 +
 .../include/world2/worldx.h                        |    1 +
 .../11-preprocess-include-path/src/world.F90       |    8 +
 t/fcm-make/12-build-class-prop.t                   |   68 +
 t/fcm-make/12-build-class-prop/bin/my-fc           |    2 +
 t/fcm-make/12-build-class-prop/fcm-make.cfg        |   14 +
 t/fcm-make/12-build-class-prop/src/hello.f90       |    3 +
 t/fcm-make/12-build-class-prop/src/hello_house.f90 |    3 +
 .../12-build-class-prop/src/hello_office.f90       |    3 +
 t/fcm-make/12-build-class-prop/src/hello_road.f90  |    3 +
 t/fcm-make/13-build-target-prop.t                  |   53 +
 t/fcm-make/13-build-target-prop/bin/my-fc          |   20 +
 t/fcm-make/13-build-target-prop/fcm-make.cfg       |    4 +
 t/fcm-make/13-build-target-prop/src/hello.f90      |    4 +
 t/fcm-make/13-build-target-prop/src/world.f90      |    8 +
 t/fcm-make/14-build-etc.t                          |   60 +
 t/fcm-make/14-build-etc/fcm-make.cfg               |    4 +
 t/fcm-make/14-build-etc/src/foo                    |    2 +
 t/fcm-make/14-build-etc/src/hello.txt              |    1 +
 t/fcm-make/14-build-etc/src/hi/hi-earth.txt        |    1 +
 t/fcm-make/14-build-etc/src/hi/hi-mars.txt         |    1 +
 t/fcm-make/15-extract-loc-reset.t                  |  182 +
 t/fcm-make/16-build-dep-o-2.t                      |   50 +
 t/fcm-make/16-build-dep-o-2/fcm-make.cfg           |    4 +
 t/fcm-make/16-build-dep-o-2/src/hello.f90          |    3 +
 t/fcm-make/16-build-dep-o-2/src/hello_mod.f90      |    3 +
 t/fcm-make/16-build-dep-o-2/src/hello_sub.f90      |    4 +
 t/fcm-make/17-build-cyclic.t                       |   42 +
 t/fcm-make/17-build-cyclic/fcm-make.cfg            |    3 +
 t/fcm-make/17-build-cyclic/src/bar.f90             |    9 +
 t/fcm-make/17-build-cyclic/src/baz.f90             |   10 +
 t/fcm-make/17-build-cyclic/src/foo.f90             |   10 +
 t/fcm-make/17-build-cyclic/src/hello.f90           |    7 +
 t/fcm-make/17-build-cyclic/src/meow.f90            |   11 +
 t/fcm-make/17-build-cyclic/src/quack.f90           |    9 +
 t/fcm-make/18-build-use-intrinsic.t                |   60 +
 t/fcm-make/18-build-use-intrinsic/fcm-make.cfg     |    3 +
 t/fcm-make/18-build-use-intrinsic/src/greet.f90    |   14 +
 t/fcm-make/18-build-use-intrinsic/src/hello.f90    |    4 +
 t/fcm-make/18-build-use-intrinsic/src/hi.f90       |    4 +
 t/fcm-make/19-build-inherit-prop.t                 |   50 +
 t/fcm-make/19-build-inherit-prop/fcm-make.cfg      |    4 +
 t/fcm-make/19-build-inherit-prop/src/hello.F90     |    6 +
 t/fcm-make/20-args.t                               |   50 +
 t/fcm-make/20-args/fcm-make.cfg                    |    3 +
 t/fcm-make/20-args/src/greet.f90                   |    3 +
 t/fcm-make/20-args/src/hello.f90                   |    3 +
 t/fcm-make/21-inherit-steps.t                      |   68 +
 t/fcm-make/21-inherit-steps/fcm-make.cfg           |    8 +
 t/fcm-make/21-inherit-steps/src1/hello.f90         |    3 +
 t/fcm-make/21-inherit-steps/src2/salute.f90        |    3 +
 t/fcm-make/21-inherit-steps/src3/greet.f90         |    3 +
 t/fcm-make/22-build-2-bad-mod-over-inherit.t       |   59 +
 .../22-build-2-bad-mod-over-inherit/fcm-make.cfg   |    3 +
 .../22-build-2-bad-mod-over-inherit/src-i/m1.f90   |    6 +
 .../22-build-2-bad-mod-over-inherit/src-i/m2.f90   |    6 +
 .../22-build-2-bad-mod-over-inherit/src/m1.f90     |    6 +
 .../22-build-2-bad-mod-over-inherit/src/m2.f90     |    6 +
 .../22-build-2-bad-mod-over-inherit/src/p1.f90     |    6 +
 t/fcm-make/23-build-omp.t                          |   66 +
 t/fcm-make/23-build-omp/fcm-make.cfg               |    4 +
 t/fcm-make/23-build-omp/src/i1.f90                 |    1 +
 t/fcm-make/23-build-omp/src/i2.f90                 |    1 +
 t/fcm-make/23-build-omp/src/m1.f90                 |   14 +
 t/fcm-make/23-build-omp/src/m2.f90                 |   14 +
 t/fcm-make/23-build-omp/src/p1.f90                 |   21 +
 t/fcm-make/23-build-omp/src/s3.f90                 |    7 +
 t/fcm-make/24-build-c-main-camel.t                 |   36 +
 t/fcm-make/24-build-c-main-camel/fcm-make.cfg      |    4 +
 t/fcm-make/24-build-c-main-camel/src/Hello.c       |    5 +
 t/fcm-make/25-build-cyclic-2.t                     |   40 +
 t/fcm-make/25-build-cyclic-2/fcm-make.cfg          |    3 +
 t/fcm-make/25-build-cyclic-2/src/foo.f90           |    4 +
 t/fcm-make/25-build-cyclic-2/src/m1.f90            |   12 +
 t/fcm-make/25-build-cyclic-2/src/m2.f90            |    8 +
 t/fcm-make/26-no-config.t                          |   33 +
 t/fcm-make/27-args-only.t                          |   41 +
 t/fcm-make/28-bad-arg.t                            |   41 +
 t/fcm-make/29-relative-cfg.t                       |   85 +
 t/fcm-make/30-relative-cfg-in-svn.t                |   52 +
 t/fcm-make/31-relative-cfg-in-ssh.t                |   69 +
 t/fcm-make/32-include-relative-cfg.t               |   54 +
 t/fcm-make/33-include-relative-cfg-in-svn.t        |   73 +
 t/fcm-make/34-include-relative-cfg-in-ssh.t        |   75 +
 t/fcm-make/35-include-relative-cfg-in-2-dirs.t     |   62 +
 t/fcm-make/36-build-fail-cont-basic.t              |  135 +
 t/fcm-make/36-build-fail-cont-basic/fcm-make.cfg   |    4 +
 .../36-build-fail-cont-basic/src/greet_mod.f90     |    9 +
 t/fcm-make/36-build-fail-cont-basic/src/hello.f90  |    6 +
 t/fcm-make/36-build-fail-cont-basic/src/hello2.f90 |    6 +
 t/fcm-make/36-build-fail-cont-basic/src/hello3.f90 |    6 +
 t/fcm-make/36-build-fail-cont-basic/src/hello4.f90 |    5 +
 .../36-build-fail-cont-basic/src/hello_sub.f90     |    5 +
 .../36-build-fail-cont-basic/src/world_mod.f90     |    4 +
 t/fcm-make/test_header                             |    1 +
 t/fcm-merge/00-simple.t                            |  321 +
 t/fcm-merge/01-complex.t                           | 1646 ++++++
 t/fcm-merge/test_header                            |  232 +
 t/fcm-recover-svn-repos/00-basic.t                 |  108 +
 t/fcm-recover-svn-repos/test_header                |    1 +
 t/fcm-status/00-simple.t                           |   83 +
 t/fcm-status/test_header                           |  232 +
 t/fcm-switch/00-simple.t                           |  109 +
 t/fcm-switch/01-subtree.t                          |  149 +
 t/fcm-switch/test_header                           |  232 +
 t/fcm-update/00-simple.t                           |  144 +
 t/fcm-update/01-subtree.t                          |  146 +
 t/fcm-update/test_header                           |  232 +
 t/lib/bash/test_header                             |  216 +
 t/svn-hooks/00-pre-revprop-change.t                |   83 +
 t/svn-hooks/01-post-revprop-change-bg.t            |  119 +
 t/svn-hooks/02-pre-commit.t                        |  287 +
 t/svn-hooks/03-post-commit-bg.t                    |  301 +
 t/svn-hooks/test_header                            |    1 +
 t/svn-hooks/test_header_more                       |  114 +
 t/svn-username/00-branch.t                         |  103 +
 t/svn-username/test_header                         |  232 +
 test/compare_results_fcm1                          |  217 +
 test/compare_results_fcm2                          |  205 +
 test/compare_times_fcm1-2                          |   27 +
 test/create_hpc_batch_script                       |   90 +
 test/create_repos                                  |  197 +
 test/get_hpc_results                               |   34 +
 test/perform_test_fcm1                             |  165 +
 test/perform_test_fcm2                             |  204 +
 test/report_hpc_results                            |   14 +
 test/repos/add_subroutine/hello.F90                |   26 +
 test/repos/add_subroutine/hello.F90.add_lines      |   28 +
 test/repos/add_subroutine/hello_sub2.f90           |   11 +
 test/repos/cyclic_dependency/hello.F90             |   14 +
 .../cyclic_dependency/hello_constants.f90.fail     |   19 +
 .../repos/cyclic_dependency/hello_constants.f90.ok |   19 +
 test/repos/cyclic_dependency/hello_sub2.f90        |   11 +
 test/repos/trunk/blockdata/hello_blockdata.F90     |    9 +
 test/repos/trunk/cfg/fcm1_add_directory.cfg        |    6 +
 test/repos/trunk/cfg/fcm1_add_directory_expsrc.cfg |    7 +
 test/repos/trunk/cfg/fcm1_add_file.cfg             |    6 +
 test/repos/trunk/cfg/fcm1_add_file_inherit.cfg     |    8 +
 test/repos/trunk/cfg/fcm1_base.cfg                 |    6 +
 test/repos/trunk/cfg/fcm1_base_inc.cfg             |   23 +
 test/repos/trunk/cfg/fcm1_branches_clash.cfg       |    7 +
 test/repos/trunk/cfg/fcm1_branches_merge.cfg       |    8 +
 .../cfg/fcm1_branches_merge_conflict_fail.cfg      |   10 +
 .../cfg/fcm1_branches_merge_conflict_override.cfg  |   10 +
 .../trunk/cfg/fcm1_branches_merge_inherit.cfg      |   10 +
 .../fcm1_branches_merge_inherit_wrong_include.cfg  |   12 +
 .../trunk/cfg/fcm1_branches_merge_wcopies.cfg      |    8 +
 test/repos/trunk/cfg/fcm1_branches_merge_wcopy.cfg |    8 +
 test/repos/trunk/cfg/fcm1_cflags.cfg               |    6 +
 test/repos/trunk/cfg/fcm1_change_src_type.cfg      |    7 +
 test/repos/trunk/cfg/fcm1_delete_directory.cfg     |    7 +
 .../trunk/cfg/fcm1_delete_directory_inherit.cfg    |    9 +
 test/repos/trunk/cfg/fcm1_delete_file.cfg          |    8 +
 test/repos/trunk/cfg/fcm1_delete_file_inherit.cfg  |   10 +
 test/repos/trunk/cfg/fcm1_delete_inc_file.cfg      |    6 +
 .../trunk/cfg/fcm1_delete_inc_file_inherit.cfg     |    8 +
 .../cfg/fcm1_delete_inc_file_inherit_force.cfg     |   10 +
 test/repos/trunk/cfg/fcm1_delete_pp_file.cfg       |    8 +
 .../trunk/cfg/fcm1_delete_pp_file_inherit.cfg      |   10 +
 test/repos/trunk/cfg/fcm1_delete_ppinc_file.cfg    |    6 +
 .../trunk/cfg/fcm1_delete_ppinc_file_inherit.cfg   |    8 +
 .../cfg/fcm1_delete_ppinc_file_inherit_force.cfg   |    9 +
 test/repos/trunk/cfg/fcm1_duplicate_target.cfg     |    7 +
 test/repos/trunk/cfg/fcm1_exclude_dependency.cfg   |    6 +
 test/repos/trunk/cfg/fcm1_exe_permissions.cfg      |    7 +
 test/repos/trunk/cfg/fcm1_exe_rename.cfg           |    7 +
 test/repos/trunk/cfg/fcm1_fc.cfg                   |    6 +
 test/repos/trunk/cfg/fcm1_fflags1.cfg              |    6 +
 test/repos/trunk/cfg/fcm1_fflags2.cfg              |    6 +
 test/repos/trunk/cfg/fcm1_fflags_inherit.cfg       |    8 +
 test/repos/trunk/cfg/fcm1_inc_devnull.cfg          |    9 +
 test/repos/trunk/cfg/fcm1_inherit_invalid_path.cfg |    6 +
 test/repos/trunk/cfg/fcm1_inherit_target.cfg       |    8 +
 test/repos/trunk/cfg/fcm1_invalid_base_url.cfg     |    6 +
 test/repos/trunk/cfg/fcm1_invalid_branch_url.cfg   |    6 +
 test/repos/trunk/cfg/fcm1_invalid_inc.cfg          |    4 +
 test/repos/trunk/cfg/fcm1_invalid_namespace.cfg    |    6 +
 test/repos/trunk/cfg/fcm1_invalid_variable.cfg     |    6 +
 test/repos/trunk/cfg/fcm1_ld.cfg                   |    6 +
 test/repos/trunk/cfg/fcm1_library.cfg              |    6 +
 test/repos/trunk/cfg/fcm1_library_rename.cfg       |    7 +
 test/repos/trunk/cfg/fcm1_mirror.cfg               |    8 +
 test/repos/trunk/cfg/fcm1_mirror_inherit.cfg       |   12 +
 .../trunk/cfg/fcm1_modify_subroutine_inherit.cfg   |    8 +
 .../fcm1_modify_subroutine_interface_inherit.cfg   |    8 +
 test/repos/trunk/cfg/fcm1_multi_inherit.cfg        |    9 +
 test/repos/trunk/cfg/fcm1_no_dep.cfg               |    6 +
 test/repos/trunk/cfg/fcm1_ops.cfg                  |  211 +
 test/repos/trunk/cfg/fcm1_postproc_hpc.cfg         |  209 +
 test/repos/trunk/cfg/fcm1_pp_change_blockdata.cfg  |    6 +
 test/repos/trunk/cfg/fcm1_pp_change_dependency.cfg |    6 +
 test/repos/trunk/cfg/fcm1_pp_change_include.cfg    |    6 +
 .../trunk/cfg/fcm1_pp_change_include_inherit.cfg   |    8 +
 test/repos/trunk/cfg/fcm1_pp_empty_subroutine.cfg  |    6 +
 .../trunk/cfg/fcm1_pp_empty_subroutine_inherit.cfg |    8 +
 .../cfg/fcm1_pp_empty_subroutine_inherit_force.cfg |    9 +
 test/repos/trunk/cfg/fcm1_revmatch_false.cfg       |   13 +
 test/repos/trunk/cfg/fcm1_revmatch_true.cfg        |    6 +
 test/repos/trunk/cfg/fcm1_sps.cfg                  |  129 +
 test/repos/trunk/cfg/fcm1_suite.cfg                |    8 +
 test/repos/trunk/cfg/fcm1_symbolic_link.cfg        |    6 +
 test/repos/trunk/cfg/fcm1_um.cfg                   |   54 +
 test/repos/trunk/cfg/fcm1_um_hpc.cfg               |   60 +
 test/repos/trunk/cfg/fcm1_um_inherit.cfg           |   49 +
 test/repos/trunk/cfg/fcm1_um_inherit_hpc.cfg       |   51 +
 test/repos/trunk/cfg/fcm1_var.cfg                  |  232 +
 test/repos/trunk/cfg/fcm1_var_hpc.cfg              |  238 +
 test/repos/trunk/cfg/fcm2_add_directory_expsrc.cfg |    3 +
 test/repos/trunk/cfg/fcm2_add_file.cfg             |    3 +
 test/repos/trunk/cfg/fcm2_add_file_inherit.cfg     |    3 +
 test/repos/trunk/cfg/fcm2_base.cfg                 |    3 +
 test/repos/trunk/cfg/fcm2_base_inc.cfg             |    6 +
 test/repos/trunk/cfg/fcm2_base_inc2.cfg            |   15 +
 test/repos/trunk/cfg/fcm2_branches_clash.cfg       |    5 +
 test/repos/trunk/cfg/fcm2_branches_merge.cfg       |    6 +
 .../trunk/cfg/fcm2_branches_merge_duplicate.cfg    |    6 +
 .../trunk/cfg/fcm2_branches_merge_inherit.cfg      |    6 +
 .../fcm2_branches_merge_inherit_wrong_include.cfg  |    8 +
 .../trunk/cfg/fcm2_branches_merge_wcopies.cfg      |    6 +
 test/repos/trunk/cfg/fcm2_branches_merge_wcopy.cfg |    6 +
 test/repos/trunk/cfg/fcm2_cflags.cfg               |    3 +
 test/repos/trunk/cfg/fcm2_change_variable.cfg      |    4 +
 test/repos/trunk/cfg/fcm2_cyclic_dep_fail.cfg      |    3 +
 test/repos/trunk/cfg/fcm2_cyclic_dep_ok.cfg        |    3 +
 test/repos/trunk/cfg/fcm2_delete_directory.cfg     |   21 +
 .../trunk/cfg/fcm2_delete_directory_inherit.cfg    |    7 +
 test/repos/trunk/cfg/fcm2_delete_file.cfg          |    6 +
 test/repos/trunk/cfg/fcm2_delete_file_inherit.cfg  |    6 +
 test/repos/trunk/cfg/fcm2_delete_inc_file.cfg      |    3 +
 .../trunk/cfg/fcm2_delete_inc_file_inherit.cfg     |    3 +
 .../cfg/fcm2_delete_inc_file_inherit_force.cfg     |    5 +
 test/repos/trunk/cfg/fcm2_delete_pp_file.cfg       |   24 +
 .../trunk/cfg/fcm2_delete_pp_file_inherit.cfg      |    6 +
 test/repos/trunk/cfg/fcm2_delete_ppinc_file.cfg    |    3 +
 .../trunk/cfg/fcm2_delete_ppinc_file_inherit.cfg   |    3 +
 .../cfg/fcm2_delete_ppinc_file_inherit_force.cfg   |    5 +
 test/repos/trunk/cfg/fcm2_dep_o.cfg                |    5 +
 test/repos/trunk/cfg/fcm2_dep_o_all.cfg            |    5 +
 test/repos/trunk/cfg/fcm2_dep_o_invalid.cfg        |    5 +
 test/repos/trunk/cfg/fcm2_duplicate_target.cfg     |    4 +
 test/repos/trunk/cfg/fcm2_exclude_dependency.cfg   |    3 +
 test/repos/trunk/cfg/fcm2_exe_permissions.cfg      |    6 +
 test/repos/trunk/cfg/fcm2_exe_rename.cfg           |    6 +
 .../trunk/cfg/fcm2_extract_path_excl_no_ns.cfg     |    9 +
 test/repos/trunk/cfg/fcm2_fc.cfg                   |    3 +
 test/repos/trunk/cfg/fcm2_fflags1.cfg              |    3 +
 test/repos/trunk/cfg/fcm2_fflags2.cfg              |    3 +
 test/repos/trunk/cfg/fcm2_fflags_inherit.cfg       |    3 +
 test/repos/trunk/cfg/fcm2_flag-output.cfg          |    3 +
 test/repos/trunk/cfg/fcm2_inc_devnull.cfg          |    6 +
 test/repos/trunk/cfg/fcm2_inherit_invalid_path.cfg |    1 +
 .../repos/trunk/cfg/fcm2_inherit_redefine_fail.cfg |    5 +
 test/repos/trunk/cfg/fcm2_inherit_redefine_ok.cfg  |    3 +
 test/repos/trunk/cfg/fcm2_invalid_base_url.cfg     |    4 +
 test/repos/trunk/cfg/fcm2_invalid_branch_url.cfg   |    4 +
 test/repos/trunk/cfg/fcm2_invalid_branch_url2.cfg  |    4 +
 test/repos/trunk/cfg/fcm2_invalid_inc.cfg          |    1 +
 test/repos/trunk/cfg/fcm2_invalid_label.cfg        |    3 +
 test/repos/trunk/cfg/fcm2_invalid_modifier.cfg     |    3 +
 test/repos/trunk/cfg/fcm2_invalid_modifiers.cfg    |    3 +
 test/repos/trunk/cfg/fcm2_invalid_namespace.cfg    |    3 +
 test/repos/trunk/cfg/fcm2_invalid_namespace2.cfg   |    3 +
 test/repos/trunk/cfg/fcm2_invalid_target.cfg       |    3 +
 test/repos/trunk/cfg/fcm2_invalid_variable.cfg     |    5 +
 test/repos/trunk/cfg/fcm2_library.cfg              |    3 +
 test/repos/trunk/cfg/fcm2_library_rename.cfg       |    4 +
 test/repos/trunk/cfg/fcm2_mirror.cfg               |    6 +
 test/repos/trunk/cfg/fcm2_mirror_after_pp.cfg      |    6 +
 test/repos/trunk/cfg/fcm2_mirror_inherit.cfg       |    8 +
 .../repos/trunk/cfg/fcm2_mirror_inherit_fflags.cfg |    6 +
 .../trunk/cfg/fcm2_mirror_inherit_notarget.cfg     |    6 +
 .../trunk/cfg/fcm2_modify_subroutine_inherit.cfg   |    3 +
 .../fcm2_modify_subroutine_interface_inherit.cfg   |    3 +
 test/repos/trunk/cfg/fcm2_multi_inherit.cfg        |    4 +
 test/repos/trunk/cfg/fcm2_multiple_build.cfg       |   16 +
 .../trunk/cfg/fcm2_multiple_build_inherit.cfg      |    6 +
 test/repos/trunk/cfg/fcm2_multiple_pp-build.cfg    |   25 +
 .../trunk/cfg/fcm2_multiple_pp-build_inherit.cfg   |    6 +
 test/repos/trunk/cfg/fcm2_no_dep.cfg               |    3 +
 test/repos/trunk/cfg/fcm2_ns-dep_o.cfg             |    5 +
 test/repos/trunk/cfg/fcm2_ns-dep_o_all.cfg         |    5 +
 test/repos/trunk/cfg/fcm2_ns-dep_o_file.cfg        |    5 +
 test/repos/trunk/cfg/fcm2_ns-dep_o_invalid.cfg     |    5 +
 test/repos/trunk/cfg/fcm2_ops.cfg                  |   82 +
 test/repos/trunk/cfg/fcm2_postproc_hpc.cfg         |  171 +
 test/repos/trunk/cfg/fcm2_pp_change_blockdata.cfg  |    3 +
 test/repos/trunk/cfg/fcm2_pp_change_dependency.cfg |    3 +
 test/repos/trunk/cfg/fcm2_pp_change_include.cfg    |    3 +
 .../trunk/cfg/fcm2_pp_change_include_inherit.cfg   |    3 +
 test/repos/trunk/cfg/fcm2_pp_empty_subroutine.cfg  |    3 +
 .../trunk/cfg/fcm2_pp_empty_subroutine_inherit.cfg |    3 +
 .../cfg/fcm2_pp_empty_subroutine_inherit_force.cfg |    5 +
 test/repos/trunk/cfg/fcm2_revmatch_false.cfg       |    7 +
 test/repos/trunk/cfg/fcm2_single_file.cfg          |    3 +
 test/repos/trunk/cfg/fcm2_space_in_name.cfg        |    5 +
 test/repos/trunk/cfg/fcm2_sps.cfg                  |   46 +
 test/repos/trunk/cfg/fcm2_symbolic_link.cfg        |    3 +
 test/repos/trunk/cfg/fcm2_um.cfg                   |   36 +
 test/repos/trunk/cfg/fcm2_um77.cfg                 |   74 +
 test/repos/trunk/cfg/fcm2_um77_hpc.cfg             |   89 +
 test/repos/trunk/cfg/fcm2_um77_inherit.cfg         |   15 +
 test/repos/trunk/cfg/fcm2_um77_inherit_hpc.cfg     |   17 +
 test/repos/trunk/cfg/fcm2_um_hpc.cfg               |   42 +
 test/repos/trunk/cfg/fcm2_um_inherit.cfg           |   17 +
 test/repos/trunk/cfg/fcm2_um_inherit_hpc.cfg       |   19 +
 test/repos/trunk/cfg/fcm2_var.cfg                  |   80 +
 test/repos/trunk/cfg/fcm2_var_hpc.cfg              |   87 +
 test/repos/trunk/module/hello_constants.f90        |    5 +
 test/repos/trunk/module/hello_constants.inc        |    1 +
 test/repos/trunk/module/hello_constants_dummy.inc  |    1 +
 test/repos/trunk/namelist/namelist.NL              |    3 +
 test/repos/trunk/pro/hello.pro                     |    2 +
 test/repos/trunk/pro/plot.pro                      |    3 +
 test/repos/trunk/program/hello.F90                 |   26 +
 test/repos/trunk/script/hello.sh                   |    5 +
 test/repos/trunk/subroutine/hello_c.c              |    5 +
 test/repos/trunk/subroutine/hello_sub.F90          |   24 +
 test/repos/trunk/subroutine/hello_sub.h            |    1 +
 test/repos/trunk/subroutine/hello_sub_dummy.h      |    1 +
 test/run_tests                                     |  259 +
 test/test_config/fcm1_add_directory                |    3 +
 test/test_config/fcm1_add_directory_expsrc         |    2 +
 test/test_config/fcm1_branches_clash               |    1 +
 test/test_config/fcm1_branches_merge_conflict_fail |    1 +
 test/test_config/fcm1_branches_merge_incremental   |    1 +
 .../fcm1_branches_merge_inherit_wrong_include      |    2 +
 test/test_config/fcm1_branches_merge_wcopies       |    4 +
 test/test_config/fcm1_branches_merge_wcopy         |    1 +
 test/test_config/fcm1_cflags_incremental           |    1 +
 test/test_config/fcm1_change_src_type_incremental  |    5 +
 test/test_config/fcm1_delete_directory             |    3 +
 test/test_config/fcm1_delete_directory_inherit     |    3 +
 test/test_config/fcm1_delete_file                  |    1 +
 test/test_config/fcm1_delete_file_inherit          |    1 +
 test/test_config/fcm1_delete_inc_file              |    1 +
 test/test_config/fcm1_delete_inc_file_inherit      |    4 +
 .../test_config/fcm1_delete_inc_file_inherit_force |    4 +
 test/test_config/fcm1_delete_pp_file               |    1 +
 test/test_config/fcm1_delete_pp_file_inherit       |    1 +
 test/test_config/fcm1_delete_ppinc_file            |    1 +
 test/test_config/fcm1_delete_ppinc_file_inherit    |    4 +
 .../fcm1_delete_ppinc_file_inherit_force           |    4 +
 test/test_config/fcm1_duplicate_target             |    4 +
 test/test_config/fcm1_exclude_dependency           |    1 +
 test/test_config/fcm1_exe_permissions              |    3 +
 test/test_config/fcm1_exe_rename_incremental       |    1 +
 test/test_config/fcm1_fc_incremental               |    1 +
 test/test_config/fcm1_fflags_incremental           |    1 +
 test/test_config/fcm1_inc_devnull                  |    1 +
 test/test_config/fcm1_inherit_invalid_path         |    3 +
 test/test_config/fcm1_invalid_base_url             |    1 +
 test/test_config/fcm1_invalid_branch_url           |    1 +
 test/test_config/fcm1_invalid_inc                  |    1 +
 test/test_config/fcm1_invalid_namespace            |    1 +
 test/test_config/fcm1_invalid_variable             |    1 +
 test/test_config/fcm1_ld_incremental               |    1 +
 test/test_config/fcm1_library                      |    1 +
 test/test_config/fcm1_library_rename               |    1 +
 test/test_config/fcm1_mirror                       |    1 +
 test/test_config/fcm1_mirror_inherit               |    1 +
 test/test_config/fcm1_no_dep                       |    1 +
 test/test_config/fcm1_ops_parallel                 |    2 +
 test/test_config/fcm1_pp_change_include_inherit    |    2 +
 test/test_config/fcm1_pp_change_keys_incremental   |    1 +
 test/test_config/fcm1_pp_empty_subroutine          |    1 +
 test/test_config/fcm1_pp_empty_subroutine_inherit  |    3 +
 .../fcm1_pp_empty_subroutine_inherit_force         |    5 +
 test/test_config/fcm1_revmatch_true                |    1 +
 test/test_config/fcm1_sps_parallel                 |    2 +
 test/test_config/fcm1_um                           |    2 +
 test/test_config/fcm1_um_inherit                   |    2 +
 test/test_config/fcm1_var_parallel                 |    2 +
 test/test_config/fcm2_branches_clash               |    1 +
 test/test_config/fcm2_branches_merge_incremental   |    1 +
 .../fcm2_branches_merge_inherit_wrong_include      |    2 +
 test/test_config/fcm2_branches_merge_wcopies       |    4 +
 test/test_config/fcm2_branches_merge_wcopy         |    1 +
 test/test_config/fcm2_cflags_incremental           |    1 +
 test/test_config/fcm2_cyclic_dep_fail              |    1 +
 test/test_config/fcm2_delete_file                  |    2 +
 test/test_config/fcm2_delete_file_inherit          |    2 +
 test/test_config/fcm2_delete_inc_file              |    1 +
 test/test_config/fcm2_delete_inc_file_inherit      |    1 +
 .../test_config/fcm2_delete_inc_file_inherit_force |    2 +
 test/test_config/fcm2_delete_pp_file               |    2 +
 test/test_config/fcm2_delete_pp_file_inherit       |    2 +
 test/test_config/fcm2_delete_ppinc_file            |    2 +
 test/test_config/fcm2_delete_ppinc_file_inherit    |    1 +
 .../fcm2_delete_ppinc_file_inherit_force           |    2 +
 test/test_config/fcm2_dep_o_invalid                |    1 +
 test/test_config/fcm2_duplicate_target             |    2 +
 test/test_config/fcm2_exclude_dependency           |    2 +
 test/test_config/fcm2_exe_permissions              |    3 +
 test/test_config/fcm2_exe_rename_incremental       |    1 +
 test/test_config/fcm2_fc_incremental               |    1 +
 test/test_config/fcm2_fflags_incremental           |    2 +
 test/test_config/fcm2_inc_devnull                  |    2 +
 test/test_config/fcm2_inherit_invalid_path         |    1 +
 test/test_config/fcm2_inherit_redefine_fail        |    1 +
 test/test_config/fcm2_invalid_base_url             |    1 +
 test/test_config/fcm2_invalid_branch_url           |    1 +
 test/test_config/fcm2_invalid_branch_url2          |    1 +
 test/test_config/fcm2_invalid_inc                  |    1 +
 test/test_config/fcm2_invalid_label                |    1 +
 test/test_config/fcm2_invalid_modifier             |    1 +
 test/test_config/fcm2_invalid_modifiers            |    1 +
 test/test_config/fcm2_invalid_namespace            |    2 +
 test/test_config/fcm2_invalid_namespace2           |    1 +
 test/test_config/fcm2_invalid_target               |    1 +
 test/test_config/fcm2_invalid_variable             |    2 +
 test/test_config/fcm2_library                      |    2 +
 test/test_config/fcm2_library_rename               |    1 +
 test/test_config/fcm2_mirror                       |    1 +
 test/test_config/fcm2_mirror_after_pp              |    2 +
 test/test_config/fcm2_mirror_inherit               |    1 +
 test/test_config/fcm2_mirror_inherit_fflags        |    1 +
 test/test_config/fcm2_mirror_inherit_notarget      |    1 +
 test/test_config/fcm2_modify_subroutine_inherit    |    1 +
 test/test_config/fcm2_multi_inherit                |    1 +
 test/test_config/fcm2_no_dep                       |    1 +
 test/test_config/fcm2_ns-dep_o_invalid             |    1 +
 test/test_config/fcm2_ops_parallel                 |    2 +
 test/test_config/fcm2_override_variable            |    2 +
 test/test_config/fcm2_pp_change_include_inherit    |    4 +
 test/test_config/fcm2_pp_change_keys_incremental   |    1 +
 test/test_config/fcm2_pp_empty_subroutine          |    2 +
 test/test_config/fcm2_pp_empty_subroutine_inherit  |    1 +
 .../fcm2_pp_empty_subroutine_inherit_force         |    2 +
 test/test_config/fcm2_sps_parallel                 |    2 +
 test/test_config/fcm2_um                           |    2 +
 test/test_config/fcm2_um77                         |    2 +
 test/test_config/fcm2_um77_inherit                 |    2 +
 test/test_config/fcm2_um_inherit                   |    2 +
 test/test_config/fcm2_var_parallel                 |    2 +
 test/test_include/inc/fortran.inc                  |    1 +
 test/test_include/prog/fortran.inc                 |    1 +
 test/test_include/prog/test_fortran_inc.f90        |    5 +
 test/test_include/prog/test_prepro_inc.F90         |    5 +
 test/test_include/test.sh                          |   38 +
 test/tests_functional.list                         |  158 +
 test/tests_perf_local.list                         |   23 +
 test/tests_perf_remote.list                        |   19 +
 test/wrapper_scripts/wrap_ar                       |    2 +
 test/wrapper_scripts/wrap_cc                       |    2 +
 test/wrapper_scripts/wrap_fc                       |    2 +
 test/wrapper_scripts/wrap_fc2                      |    2 +
 test/wrapper_scripts/wrap_ld                       |    2 +
 test/wrapper_scripts/wrap_ld2                      |    2 +
 test/wrapper_scripts/wrap_mpicc                    |    2 +
 test/wrapper_scripts/wrap_mpif90                   |    2 +
 test/wrapper_scripts/wrap_pp                       |    2 +
 tutorial/README                                    |   14 +
 tutorial/fcm-tutorial-repos-create                 |   80 +
 tutorial/hooks/pre-commit                          |    7 +
 tutorial/trunk-r1/doc/hello.html                   |   14 +
 tutorial/trunk-r1/fcm-make.cfg                     |    5 +
 tutorial/trunk-r1/src/module/hello_constants.f90   |    3 +
 tutorial/trunk-r1/src/module/hello_num.f90         |   17 +
 tutorial/trunk-r1/src/program/hello.f90            |   12 +
 tutorial/trunk-r1/src/subroutine/hello_c.c         |    5 +
 tutorial/trunk-r1/src/subroutine/hello_sub.f90     |   15 +
 usr/bin/fcm                                        |   31 +
 831 files changed, 101129 insertions(+)

diff --git a/ACKNOWLEDGEMENT.md b/ACKNOWLEDGEMENT.md
new file mode 100644
index 0000000..d531101
--- /dev/null
+++ b/ACKNOWLEDGEMENT.md
@@ -0,0 +1,19 @@
+# Acknowledgement for non-FCM Work
+
+Licences for non-FCM works included in this distribution can be
+found in the licences/ directory.
+
+Non-FCM works included in this distribution are listed below:
+
+doc/etc/bootstrap/:
+* Unmodified external software library copyright 2013 Twitter Inc
+  released under the Apache 2.0 license.
+  See [Bootstrap](http://getbootstrap.com/).
+
+svn-hooks/svnperms.py:
+* Subversion repository pre-commit path-based permission checking utility,
+  written by [Gustavo Niemeyer](mailto:gustavo at niemeyer.net) and released under
+  Apache 2.0 license.
+  Original source downloaded from r1295006 at:
+  https://svn.apache.org/viewvc/subversion/trunk/tools/hook-scripts/svnperms.py
+  This version is modified to allow custom permission message per repository.
diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
index 0000000..57df69a
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,356 @@
+# FCM Changes
+
+Go to https://github.com/metomi/fcm/milestones?state=closed
+for a full listing of issues for each release.
+
+--------------------------------------------------------------------------------
+
+## 2014.09.0 (2014-09-17)
+
+FCM release 20.
+
+### Highlighted Changes
+
+[#138](https://github.com/metomi/fcm/pull/138):
+fcm make: build: continue on failure.
+* The build system will continue as much as possible after a failure, and
+  only repeat failed tasks in incremental modes.
+* This change also fixes a problem where the system could lose information
+  after a failure. Tasks that would be run after the failed task would not get
+  their context recorded correctly. In a subsequent incremental build, the
+  system would end up doing more work than necessary.
+
+[#135](https://github.com/metomi/fcm/pull/135):
+fcm make: multiple config files and search paths.
+* You can now specify multiple `-F PATH` options to specify the search paths
+  for locating configuration files specified as relative paths.
+* You can now specify multiple `-f FILE` options.
+* New `include-path` configuration declaration for specifying the search path
+  for configuration files specified as relative paths.
+* Improve CLI argument diagnostics.
+  * The command dies if an argument is missing an equal sign.
+  * Suggest command line syntax if argument ends with `.cfg`.
+
+[#129](https://github.com/metomi/fcm/pull/129),
+[#136](https://github.com/metomi/fcm/pull/136),
+[#143](https://github.com/metomi/fcm/pull/143),
+[#144](https://github.com/metomi/fcm/pull/144):
+Major improvements to the admin sub-system:
+* Improve hook installation.
+  Write, store and housekeep hook logs at `$REPOS/log/`.
+  Clean options for hook installation.
+  Install `svnperms.conf` from repository root.
+  `TZ=UTC` for all hook scripts.
+* Improve diagnostics for hooks.
+  Custom configuations per repositories.
+  Configurable `pre-revprop-change` permissions.
+  Hooks to work best under Subversion 1.8+.
+  Add modified `svnperms.py` in distribution.
+  Trac 0.12+ changeset added and modified notification.
+* Trac URL template.
+* `fcm-add-trac-env`: add Trac comment edit permission.
+* Separate `InterTrac` configurations from `trac.ini` into `intertrac.ini`.
+* Fix usage of `FCM_CONF_PATH` for admin.
+* Improve documentation and logic for admin configuration.
+* Get user info via LDAP or traditional Unix password file.
+* New admin commands:
+  * `fcm-add-svn-repos-and-trac-env`
+  * `fcm-add-svn-repos`
+  * `fcm-manage-trac-env-session`
+* `pre-commit`: optionally block branch create with bad owner.
+* `post-commit-bg`: rename repository dump.
+* `post-commit-bg`: optionally notify branch owner if author is not owner.
+* `post-*` hooks: configurable notification `From:` field.
+* Test batteries for hooks, and selected admin utilities.
+
+### Noteworthy Changes
+
+[#140](https://github.com/metomi/fcm/pull/140):
+fcm mkpatch: Changes required for use with svn 1.8 + other minor bug fixes.
+
+[#139](https://github.com/metomi/fcm/pull/139):
+fcm commit: fail a commit if it includes the `#commit_message#` file.
+
+[#137](https://github.com/metomi/fcm/pull/137):
+fcm merge: basic support for `kdiff3`.
+
+[#129](https://github.com/metomi/fcm/pull/129):
+`fcm commit`/`fcm branch-rm`: fix branch owner test to use correct user ID.
+
+--------------------------------------------------------------------------------
+
+## 2014.06.0 (2014-06-10)
+
+### Highlighted Changes
+
+-none-
+
+### Noteworthy Changes
+
+[#125](https://github.com/metomi/fcm/pull/125):
+fcm make: build: handle adjacent cyclic dependency correctly.
+
+[#128](https://github.com/metomi/fcm/pull/128):
+Remove unnecessary `-r`, `-w` and `-x` tests to avoid ACL problems.
+Use Perl's `filetest` pragma where necessary to correctly handle ACL.
+
+--------------------------------------------------------------------------------
+
+## 2014-04 (2014-04-23)
+
+### Highlighted Changes
+
+[#114](https://github.com/metomi/fcm/pull/#114),
+[#117](https://github.com/metomi/fcm/pull/#117),
+[#118](https://github.com/metomi/fcm/pull/#118):
+fcm make: build: now recognises statements with Fortran
+OpenMP sentinels that affect build dependencies.
+These dependencies are normally ignored.
+However, if a relevant `build.prop{fc.flag-omp}` property is specified, the
+build system will treat these statements as normal dependency statements.
+
+### Noteworthy Changes
+
+[#121](https://github.com/metomi/fcm/pull/#121):
+fcm make: extract via SSH: improve performance by using `find -printf`
+instead of `find -exec stat`.
+
+[#120](https://github.com/metomi/fcm/pull/#120):
+fcm make: build will now correctly handle C source files that has camel
+case names and `main` functions.
+
+[#111](https://github.com/metomi/fcm/pull/#111):
+fcm make: build in inherit mode: fix incorrect success in repeated
+incremental mode.
+
+[#105](https://github.com/metomi/fcm/pull/#105):
+`FCM_CONF_PATH`: new environment variable that can be used to override
+site/user configuration paths.
+
+[#103](https://github.com/metomi/fcm/pull/#103):
+fcm make: extract: detect diff trees that are the same as the base tree.
+
+--------------------------------------------------------------------------------
+
+## 2014-03 (2014-03-03)
+
+### Highlighted Changes
+
+[#96](https://github.com/metomi/fcm/pull/#96):
+fcm make: arguments as extra configurations. This change allows the
+`fcm make` command to accept command line arguments. Each argument will be
+appended in order as a new line in the current `fcm-make.cfg`. This allows
+users to override the configuration on the command line.
+
+### Noteworthy Changes
+
+[#101](https://github.com/metomi/fcm/pull/#101):
+fcm make: do not inherit `steps` if it is already set in the current
+configuration. This allows `steps=` to be declared before `use=`.
+
+[#100](https://github.com/metomi/fcm/pull/#100):
+fcm make: reduce memory usage in incremental mode. Invoking `fcm make`
+with many steps was causing Perl to exit with SIGSEGV previously.
+
+[#98](https://github.com/metomi/fcm/pull/#98):
+fcm make: extract: fix ssh location efficiency.
+
+[#93](https://github.com/metomi/fcm/pull/#93):
+fcm make: fix `use=` properties override. This change allows `use=`
+declarations to be placed anywhere in an `fcm-make.cfg` without interfering
+other `*.prop` declarations.
+
+[#92](https://github.com/metomi/fcm/pull/#92):
+fcm branch-create/list: support alternate username using information in
+users' `~/.subversion/servers` file.
+
+[#91](https://github.com/metomi/fcm/pull/#91):
+fcm make: remove config-on-success on failure.
+
+--------------------------------------------------------------------------------
+
+## 2014-02 (2014-02-03)
+
+### Highlighted Changes
+
+[#83](https://github.com/metomi/fcm/pull/#83):
+fcm make: build: an initial attempt to support some Fortran 2K features.
+* Recognise `iso_fortran_env` as an intrinsic module.
+* Recognise `use, intrinsic ::` statements.
+* Recognise `class`, `double complex` and `procedure` as types.
+* Recognise new type declaration attributes.
+* Recognise `abstract interface` blocks.
+* Recognise `impure elemental` as a valid function or subroutine attribute.
+* Recognise `submodule` blocks.
+
+### Noteworthy Changes
+
+[#89](https://github.com/metomi/fcm/pull/#89):
+fcm merge, fcm switch, etc: Subversion 1.8 `svn upgrade` command may
+not write a `.svn/entries` file at the working copy root. Several FCM wrappers
+were failing because they were unable to determine the working copy root. This
+is fixed by using the new entry available in Subversion 1.8 `svn info` to
+determine the working copy root.
+
+[#87](https://github.com/metomi/fcm/pull/#87):
+fcm make: build: print sources to targets diagnostics on `-vv` mode and
+in the log.
+
+--------------------------------------------------------------------------------
+
+## 2014-01 (2014-01-20)
+
+### Highlighted Changes
+
+-none-
+
+### Noteworthy Changes
+
+[#81](https://github.com/metomi/fcm/pull/#81):
+fcm make: build: fix cyclic dependency logic.
+
+[#80](https://github.com/metomi/fcm/pull/#80):
+fcm make: extract: support `extract.location` declarations reset.
+
+[#79](https://github.com/metomi/fcm/pull/#79):
+fcm make: extract: SSH location: ignore dot files.
+
+--------------------------------------------------------------------------------
+
+## 2013-12 (2013-12-02)
+
+### Highlighted Changes
+
+-none-
+
+### Noteworthy Changes
+
+[#77](https://github.com/metomi/fcm/pull/#77):
+fcm make: mirror and build: fix etc files install. This was broken by
+[#65](https://github.com/metomi/fcm/pull/#65)
+which causes etc files to be installed to `bin/`.
+
+[#74](https://github.com/metomi/fcm/pull/#74):
+Handle date in `svn log --xml`, which may have trailing spaces and lines.
+
+--------------------------------------------------------------------------------
+
+## 2013-11 (2013-11-22)
+
+### Highlighted Changes
+
+[#65](https://github.com/metomi/fcm/pull/#65):
+fcm make: support declaration of class default properties using the
+syntax e.g. `build.prop{class,fc}=my-fc`.
+
+[#65](https://github.com/metomi/fcm/pull/#65):
+fcm make: build: support target name as name-space for target properties,
+e.g. `build.prop{fc}[myprog.exe]=my-fc`. N.B. Dependency properties are
+regarded as source properties, and so are not supported by this change.
+
+### Noteworthy Changes
+
+[#73](https://github.com/metomi/fcm/pull/#73):
+fcm mkpatch: use `/usr/bin/env bash` in generated scripts.
+
+[#72](https://github.com/metomi/fcm/pull/#72):
+fcm conflicts: fix incompatibility with SVN 1.8.
+
+[#70](https://github.com/metomi/fcm/pull/#70):
+fcm CLI: support new SVN 1.8 commands.
+
+[#68](https://github.com/metomi/fcm/pull/#68):
+sbin/fcm-backup-\*: hotcopy before verifying the hotcopy.
+
+[#63](https://github.com/metomi/fcm/pull/#63):
+fcm make: log file improvements. Print FCM version in beginning of log
+file.
+
+[#63](https://github.com/metomi/fcm/pull/#63):
+fcm --version: new command to print FCM version.
+
+[#63](https://github.com/metomi/fcm/pull/#63):
+FCM is no longer dependent on the `HTTP::Date` Perl module.
+
+--------------------------------------------------------------------------------
+
+## 2013-10 (2013-10-30)
+
+### Highlighted Changes
+
+Changes that have significant impact on user experience.
+
+[#52](https://github.com/metomi/fcm/pull/#52):
+fcm make: build: new properties for C++ source files, separated from
+C source files. File extension for C and C++ source files is rationalised to
+follow what is documented in the GCC manual.
+
+[#50](https://github.com/metomi/fcm/pull/#50),
+[#54](https://github.com/metomi/fcm/pull/#54):
+fcm make: build/preprocess.prop: include-paths/lib-paths/libs:
+New build properties to specify a list of include paths for compile
+tasks, and library paths and libraries for link tasks.
+
+### Noteworthy Changes
+
+Bug fixes and minor enhancements:
+
+[#59](https://github.com/metomi/fcm/pull/#59):
+fcm make: fix invalid cyclic dependency error when `build.prop{dep.o}` is
+declared on the root name-space.
+
+[#58](https://github.com/metomi/fcm/pull/#58):
+fcm make: build: improve diagnostics for duplicated targets and bad values
+in `build.prop{ns-dep.o}` declarations.
+
+[#55](https://github.com/metomi/fcm/pull/#55):
+fcm make: extract: can now extract from a location that is accessible via
+`ssh` and `rsync`.
+
+[#53](https://github.com/metomi/fcm/pull/#53):
+fcm make: `.fcm-make/log` can now be accessed as `fcm-make.log`.
+
+[#51](https://github.com/metomi/fcm/pull/#51):
+FCM documentation: style updated using Bootstrap.
+
+--------------------------------------------------------------------------------
+
+## 2013-09 (2013-09-26)
+
+### Highlighted Changes
+
+Changes that have significant impact on user experience.
+
+-None-
+
+### Noteworthy Changes
+
+Bug fixes and minor enhancements:
+
+[#45](https://github.com/metomi/fcm/pull/#45):
+An attempt to allow FCM to work under a case insensitive file system.
+
+[#39](https://github.com/metomi/fcm/pull/#39),
+[#40](https://github.com/metomi/fcm/pull/#40),
+[#41](https://github.com/metomi/fcm/pull/#41):
+CM commands are now tested under Subversion 1.8.
+
+[#37](https://github.com/metomi/fcm/pull/#37):
+fcm make: build: fixed hanging of `ext-iface` tasks when there is an
+unbalanced quote or bracket in a relevant Fortran source file.
+
+[#20](https://github.com/metomi/fcm/pull/#20):
+fcm make: build: allow separate linker command and add ability to keep
+the intermediate library archive while linking an executable.
+
+[#19](https://github.com/metomi/fcm/pull/#19):
+added test suite for code management commands to the distribution.
+
+r4955: fcm extract: fix failure caused by the checking of latest version of a
+deleted branch.
+
+--------------------------------------------------------------------------------
+
+## FCM-2-3-1 and Prior Releases
+
+See <http://metomi.github.io/fcm/doc/release_notes/>.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..abd2140
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,77 @@
+# FCM: How to Contribute
+
+## Report Bugs
+
+Report bugs and request enhancement by opening an issue on
+[FCM issues @ Github](https://github.com/metomi/fcm/issues). If reporting a
+bug, add a recipe for repeating it. If requesting an enhancement,
+describe the use case in detail.
+
+## Contribute Code
+
+All contributions to FCM are made via pull requests against the *master*
+branch of [metomi/fcm](https://github.com/metomi/fcm). New contributors
+should add their details to the [Code Contributors](#code-contributors)
+section of this file as part of their first request. The developer who
+reviews each pull request is responsible for checking that the
+contributor's name is listed in this file before merging the pull request
+into *master*.
+
+## Code Contributors
+
+The following people have contributed to this code under the terms of
+the Contributor Licence Agreement and Certificate of Origin detailed
+below:
+
+* Jim Bolton (Met Office, UK)
+* Ben Fitzpatrick (Met Office, UK)
+* Dave Matthews (Met Office, UK)
+* Stephen Oxley (Met Office, UK)
+* Matt Shin (Met Office, UK)
+* Matt Pryor (Met Office, UK)
+
+(All contributors are identifiable with email addresses in the version
+control logs or otherwise.)
+
+## Contributor Licence Agreement and Certificate of Origin
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I have
+    the right to submit it, either on my behalf or on behalf of my
+    employer, under the terms and conditions as described by this file;
+    or
+
+(b) The contribution is based upon previous work that, to the best of
+    my knowledge, is covered under an appropriate licence and I have
+    the right or permission from the copyright owner under that licence
+    to submit that work with modifications, whether created in whole or
+    in part by me, under the terms and conditions as described by
+    this file; or
+
+(c) The contribution was provided directly to me by some other person
+    who certified (a) or (b) and I have not modified it.
+
+(d) I understand and agree that this project and the contribution
+    are public and that a record of the contribution (including my
+    name and email address) is maintained indefinitely and may be
+    redistributed consistent with this project or the licence(s)
+    involved.
+
+(e) I, or my employer, grant to the UK Met Office and all recipients of
+    this software a perpetual, worldwide, non-exclusive, no-charge,
+    royalty-free, irrevocable copyright licence to reproduce, modify,
+    prepare derivative works of, publicly display, publicly perform,
+    sub-licence, and distribute this contribution and such modifications
+    and derivative works consistent with this project or the licence(s)
+    involved or other appropriate open source licence(s) specified by
+    the project and approved by the
+    [Open Source Initiative (OSI)](http://www.opensource.org/).
+
+(f) If I become aware of anything that would make any of the above
+    inaccurate, in any way, I will let the UK Met Office know as soon as
+    I become aware.
+
+(The FCM Contributor Licence Agreement and Certificate of Origin is
+inspired by the Certificate of Origin used by Enyo and the Linux
+Kernel.)
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bed2f5e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,35 @@
+# FCM
+
+FCM: a modern Fortran build system,
+and wrappers to Subversion for scientific software development
+
+[Installation](http://metomi.github.io/fcm/doc/installation/) |
+[User Guide](http://metomi.github.io/fcm/doc/user_guide/) |
+[How to Contribute](CONTRIBUTING.md)
+
+## Copyright and Terms of Use
+
+(C) British Crown Copyright 2006-14 Met Office.
+
+FCM is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+FCM is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with FCM. If not, see <http://www.gnu.org/licenses/>.
+
+FCM documentation is licensed under the British Open Government
+Licence. See doc/etc/fcm-terms-of-use.html and
+<http://www.nationalarchives.gov.uk/doc/open-government-licence/>
+
+See <http://metomi.github.io/fcm/doc/etc/fcm-terms-of-use.html>.
+
+## Acknowledgement for Non-FCM Work
+
+See [Acknowledgement for Non-FCM Work](ACKNOWLEDGEMENT.md).
diff --git a/bin/fcm b/bin/fcm
new file mode 100755
index 0000000..58c2c74
--- /dev/null
+++ b/bin/fcm
@@ -0,0 +1,134 @@
+#!/usr/bin/env perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::CLI;
+
+our $GUI;
+
+# ------------------------------------------------------------------------------
+if (!caller()) {
+    main(@ARGV);
+}
+
+# ------------------------------------------------------------------------------
+sub main {
+    my @args = @_;
+    local $ENV{'PATH'} = $ENV{'PATH'};
+    if (index($ENV{'PATH'}, $FindBin::Bin . ':') != 0) {
+        $ENV{'PATH'} = $FindBin::Bin . ':' . $ENV{'PATH'};
+    }
+    my $gui;
+    if (@args && $args[0] eq 'gui-internal') {
+        (undef, $gui, @args) = @args;
+    }
+    FCM::CLI->new({'gui' => $gui})->main(@args);
+}
+
+__END__
+
+=head1 NAME
+
+fcm
+
+=head1 SYNOPSIS
+
+    fcm [APPLICATION] [OPTIONS] [ARGUMENTS]
+
+=head1 OVERVIEW
+
+B<fcm> is the command line interface of the Flexible Configuration Management
+(FCM) system. For full detail of the system, please refer to the FCM user
+guide, which you should receive with this distribution in both HTML and PDF
+formats.
+
+Run "fcm help" to access the built-in tool documentation.
+
+=head1 ARGUMENTS
+
+B<fcm> provides the following applications:
+
+    branch-create, bcreate, bc
+    branch-delete, bdelete, bdel, brm
+    branch-diff, bdiff, bdi
+    branch-info, binfo
+    branch-list, bls
+    browse, trac, www
+    build
+    cfg-print, cfg
+    cmp-ext-cfg
+    conflicts, cf
+    export-items
+    extract
+    gui
+    keyword-print, kp
+    loc-layout
+    make
+    mkpatch
+    test-battery
+
+B<fcm> overrides the following B<svn> applications:
+
+    add
+    commit, ci
+    delete, del, remove, rm
+    diff, di
+    merge
+    switch, sw
+    update, up
+
+B<fcm> explicitly doesn't support the following B<svn> applications:
+
+    changelist
+
+Type "fcm help APPLICATION" for help on individual application.
+
+Type "svn help APPLICATION" for help on other B<svn> application.
+
+=head1 AUTHOR
+
+FCM Team L<fcm-team at metoffice.gov.uk>.
+Please feedback any bug reports or feature requests to us by e-mail.
+
+=head1 SEE ALSO
+
+L<svn (1)|svn>,
+L<perl (1)| perl>,
+L<FCM::CLI|FCM::CLI>
+
+=head1 COPYRIGHT
+
+FCM is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+FCM is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with FCM. If not, see L<http://www.gnu.org/licenses/>.
+
+=cut
diff --git a/bin/fcm_graphic_diff b/bin/fcm_graphic_diff
new file mode 100755
index 0000000..a867d0d
--- /dev/null
+++ b/bin/fcm_graphic_diff
@@ -0,0 +1,130 @@
+#!/usr/bin/env perl
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use Getopt::Long qw{GetOptions};
+use Pod::Usage qw{pod2usage};
+
+my $RE_SVN_EMPTY_FILE = qr{\.svn/empty-file}msx;
+
+my %S = (
+    'LABEL'    => "--- %s\n+++ %s",
+    'SKIP_ADD' => "Skipping since file has been added (or old file is empty)",
+    'SKIP_DEL' => "Skipping since file has been deleted (or new file is empty)",
+    'SKIP_BIN' => "Skipping binary file",
+);
+my %LABELS_HANDLER_FOR = (
+    'tkdiff' => sub {map {('-L', $_)} @_},
+    'xxdiff' => sub {('--title1', $_[0], '--title2', $_[1])},
+);
+
+if (!caller()) {
+    # svn diff expects:
+    # 0 - no diff
+    # 1 - diff
+    # other return code - fatal
+    exit main(@ARGV);
+}
+
+sub main {
+    local(@ARGV) = @_;
+    my %option;
+    my $rc = GetOptions(\%option, 'u', 'L=s@');
+    if (!$rc || @ARGV != 2 || grep {!-f $_} @ARGV) {
+        pod2usage(1);
+    }
+    my ($old, $new) = @ARGV;
+    ( $old =~ $RE_SVN_EMPTY_FILE || -z $old ? message('SKIP_ADD')
+    : $new =~ $RE_SVN_EMPTY_FILE || -z $new ? message('SKIP_DEL')
+    : -B $new                               ? message('SKIP_BIN')
+    :                                         command(\%option, @ARGV)
+    );
+}
+
+sub command {
+    my ($option_hash_ref, $old, $new) = @_;
+    my @labels;
+    if ($option_hash_ref->{'L'} && @{$option_hash_ref->{'L'}} >= 2) {
+        @labels = @{$option_hash_ref->{'L'}};
+        message('LABEL', @labels);
+    }
+    my $diff_command
+        = exists($ENV{FCM_GRAPHIC_DIFF}) ? $ENV{FCM_GRAPHIC_DIFF} : 'xxdiff';
+    if (!$diff_command) {
+        return;
+    }
+    my @command = (
+        $diff_command,
+        (   @labels && exists($LABELS_HANDLER_FOR{$diff_command})
+            ? $LABELS_HANDLER_FOR{$diff_command}->(@labels) : ()
+        ),
+        $old, $new,
+    );
+    system(@command);
+}
+
+sub message {
+    my $format = shift();
+    printf($S{$format} . "\n\n", @_);
+    1;
+}
+
+__END__
+
+=head1 NAME
+
+fcm_graphic_diff
+
+=head1 SYNOPSIS
+
+    fcm_graphic_diff [-u] [-L OLD_LABEL] [-L NEW_LABEL] OLD NEW
+
+=head1 DESCRIPTION
+
+Invokes L<xxdiff|xxdiff> (or the command specified in the FCM_GRAPHIC_DIFF
+environment variable) to compare the OLD and NEW files, where possible.
+
+If either file does not exist or is empty, or if the NEW file is a binary, the
+command will only print a diagnostic message.
+
+The -u option is not used, and is for compatibility with the L<svn diff|svn>
+command only.
+
+If OLD_LABEL and NEW_LABEL are set, they are printed in the format:
+
+    ---- OLD_LABEL
+    ++++ NEW_LABEL
+
+The command makes use of the labels when the diff command is either
+L<xxdiff|xxdiff> or L<tkdiff|tkdiff>:
+
+    xxdiff --title1 OLD_LABEL --title2 NEW_LABEL OLD NEW
+    tkdiff -L OLD_LABEL -L NEW_LABEL OLD NEW
+
+The command returns 0 if the files are the same or 1 if the files differ. All
+other return codes should be regarded as fatal errors.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/bin/fcm_graphic_merge b/bin/fcm_graphic_merge
new file mode 100755
index 0000000..6e4043f
--- /dev/null
+++ b/bin/fcm_graphic_merge
@@ -0,0 +1,130 @@
+#!/usr/bin/env perl
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+my %IMPL = ('fcm-dummy-diff' => \&_fcm_dummy_diff,
+            'xxdiff' => \&_xxdiff,
+            'kdiff3' => \&_kdiff3);
+
+my %UNRESOLVED = (
+    'nodecision' => "You have made no decision.\n",
+    'merged'     => "You have not resolved all the conflicts.\n",
+);
+
+if (!caller()) {
+    # 0 - no diff
+    # 1 - diff
+    # other return code - fatal
+    exit main(@ARGV);
+}
+
+sub main {
+    my $command = 'xxdiff';
+    if (exists($ENV{FCM_GRAPHIC_MERGE}) && $ENV{FCM_GRAPHIC_MERGE}) {
+        $command = $ENV{FCM_GRAPHIC_MERGE};
+    }
+    if (!exists($IMPL{$command})) {
+        die("$command: merge tool not yet supported.\n");
+    }
+    $IMPL{$command}->(@_);
+}
+
+sub _fcm_dummy_diff {
+    my ($base, $mine, $older, $yours) = @_;
+    my @command = (qw{diff3}, $mine, $older, $yours);
+    print(join(" ", @command) . "\n");
+    my @out_lines = qx{@command};
+    for my $line (@out_lines) {
+        print($line);
+    }
+    return 0;
+}
+
+sub _xxdiff {
+    my ($base, $mine, $older, $yours) = @_;
+    my @command = (qw{xxdiff -m -M}, $base, qw{-O -X}, $mine, $older, $yours);
+    my @out_lines = qx{@command};
+    my $rc = $?;
+    if (!@out_lines) {
+        return 2;
+    }
+    my ($decision) = map {chomp($_); lc($_);} @out_lines;
+    if ($rc && exists($UNRESOLVED{$decision})) {
+        print($UNRESOLVED{$decision});
+        return 1;
+    }
+    printf("You %s all the changes.\n", $decision);
+    return 0;
+}
+
+sub _kdiff3 {
+    my ($base, $mine, $older, $yours) = @_;
+    my @command = (qw{kdiff3 -o}, $base, $mine, $older, $yours);
+    my @out_lines = qx{@command};
+    my $rc = $?;
+    # kdiff3 produces a file called $base.orig, so we delete that
+    unlink $base . ".orig";
+    # kdiff3 doesn't produce any output, so we just assume a non-zero
+    # exit code means unresolved merges
+    if ($rc) {
+        print($UNRESOLVED{'merged'});
+        return 1;
+    }
+    printf("You merged all the changes.\n");
+    return 0;
+}
+
+__END__
+
+=head1 NAME
+
+fcm_graphic_merge
+
+=head1 SYNOPSIS
+
+    fcm_graphic_merge BASE MINE OLDER YOURS
+
+=head1 DESCRIPTION
+
+Wrap L<xxdiff|xxdiff>. Invoke L<xxdiff|xxdiff> as:
+
+    xxdiff -m -M BASE -O -X MINE OLDER YOURS
+
+Print friendlier decision messages.
+
+Return 0 if no diff remains, 1 if any diff remains, and 2 for fatal errors.
+
+=head1 ARGUMENTS
+
+BASE is the file you want to save the merge result into.
+
+MINE is the original file.
+
+YOURS is the file you want MINE to merge with.
+
+OLDER is the common ancestor of MINE and YOURS.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/bin/fcm_gui b/bin/fcm_gui
new file mode 100755
index 0000000..e1f2210
--- /dev/null
+++ b/bin/fcm_gui
@@ -0,0 +1,1346 @@
+#!/usr/bin/env perl
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Cwd qw{cwd};
+use FCM::Context::Event;
+use FCM::Util;
+use FCM1::Config;
+use FCM1::Keyword;
+use FCM1::Timer qw{timestamp_command};
+use FCM1::Util qw{get_url_of_wc get_wct is_wc};
+use File::Basename qw{basename};
+use File::Spec::Functions qw{catfile rel2abs};
+use Tk;
+use Tk::ROText;
+
+# ------------------------------------------------------------------------------
+
+# Argument
+if (@ARGV) {
+  my $dir = shift @ARGV;
+  chdir $dir if -d $dir;
+}
+
+FCM1::Keyword::set_util(FCM::Util->new());
+
+# Get configuration settings
+my $config = FCM1::Config->new ();
+$config->get_config ();
+
+# ------------------------------------------------------------------------------
+
+# FCM subcommands
+my @subcmds = qw/CHECKOUT BRANCH STATUS DIFF ADD DELETE MERGE CONFLICTS COMMIT
+                 UPDATE SWITCH/;
+
+# Subcommands allowed when CWD is not a WC
+my @nwc_subcmds = qw/CHECKOUT BRANCH/;
+
+# Subcommands allowed, when CWD is a WC
+my @wc_subcmds = qw/STATUS BRANCH DIFF ADD DELETE MERGE CONFLICTS COMMIT UPDATE
+                    SWITCH/;
+
+# Subcommands that apply to WC only
+my @wco_subcmds = qw/BRANCH STATUS DIFF ADD DELETE MERGE CONFLICTS COMMIT UPDATE
+                     SWITCH/;
+
+# Subcommands that apply to top level WC only
+my @wcto_subcmds = qw/BRANCH MERGE COMMIT UPDATE SWITCH/;
+
+# Selected subcommand
+my $selsubcmd = '';
+
+# Selected subcommand is running?
+my $cmdrunning = 0;
+
+# PID of running subcommand
+my $cmdpid = undef;
+
+# List of subcommand frames
+my %subcmd_f;
+
+# List of subcommand buttons
+my %subcmd_b;
+
+# List of subcommand button help strings
+my %subcmd_help = (
+  BRANCH    => 'list information about, create or delete a branch.',
+  CHECKOUT  => 'check out a working copy from a repository.',
+  STATUS    => 'print the status of working copy files and directories.',
+  DIFF      => 'display the differences in modified files.',
+  ADD       => 'put files and directories under version control.',
+  DELETE    => 'remove files and directories from version control.',
+  MERGE     => 'merge changes into your working copy.',
+  CONFLICTS => 'use a graphical tool to resolve conflicts in your working copy.',
+  COMMIT    => 'send changes from your working copy to the repository.',
+  UPDATE    => 'bring changes from the repository into your working copy.',
+  SWITCH    => 'update your working copy to a different URL.',
+);
+
+for (keys %subcmd_help) {
+  $subcmd_help{$_} = 'Select the "' . lc ($_) . '" sub-command - ' .
+                     $subcmd_help{$_};
+}
+
+# List of subcommand button bindings (key name and underline position)
+my %subcmd_bind = (
+  BRANCH    => {KEY => '<Alt-Key-b>', U => 0},
+  CHECKOUT  => {KEY => '<Alt-Key-o>', U => 5},
+  STATUS    => {KEY => '<Alt-Key-s>', U => 0},
+  DIFF      => {KEY => '<Alt-Key-d>', U => 0},
+  ADD       => {KEY => '<Alt-Key-a>', U => 0},
+  DELETE    => {KEY => '<Alt-Key-t>', U => 4},
+  MERGE     => {KEY => '<Alt-Key-m>', U => 0},
+  CONFLICTS => {KEY => '<Alt-Key-f>', U => 3},
+  COMMIT    => {KEY => '<Alt-Key-c>', U => 0},
+  UPDATE    => {KEY => '<Alt-Key-u>', U => 0},
+  SWITCH    => {KEY => '<Alt-Key-w>', U => 1},
+);
+
+# List of subcommand variables
+my %subcmdvar = (
+  CWD       => cwd (),
+  WCT       => '',
+  CWD_URL   => '',
+  WCT_URL   => '',
+
+  BRANCH    => {
+    OPT     => 'info',
+    URL     => '',
+    NAME    => '',
+    TYPE    => 'DEV',
+    REVFLAG => 'NORMAL',
+    TICKET  => '',
+    SRCTYPE => 'trunk',
+    S_CHD   => 0,
+    S_SIB   => 0,
+    S_OTH   => 0,
+    VERBOSE => 0,
+    OTHER   => '',
+  },
+
+  CHECKOUT  => {
+    URL     => '',
+    REV     => 'HEAD',
+    PATH    => '',
+    OTHER   => '',
+  },
+
+  STATUS    => {
+    USEWCT  => 0,
+    UPDATE  => 0,
+    VERBOSE => 0,
+    OTHER   => '',
+  },
+
+  DIFF      => {
+    USEWCT  => 0,
+    TOOL    => 'graphical',
+    BRANCH  => 0,
+    URL     => '',
+    OTHER   => '',
+  },
+
+  ADD       => {
+    USEWCT  => 0,
+    CHECK   => 1,
+    OTHER   => '',
+  },
+
+  DELETE    => {
+    USEWCT  => 0,
+    CHECK   => 1,
+    OTHER   => '',
+  },
+
+  MERGE     => {
+    USEWCT  => 1,
+    SRC     => '',
+    MODE    => 'automatic',
+    DRYRUN  => 0,
+    VERBOSE => 0,
+    REV     => '',
+    OTHER   => '',
+  },
+
+  CONFLICTS => {
+    USEWCT  => 0,
+    OTHER   => '',
+  },
+
+  COMMIT    => {
+    USEWCT  => 1,
+    DRYRUN  => 0,
+    OTHER   => '',
+  },
+
+  UPDATE    => {
+    USEWCT  => 1,
+    OTHER   => '',
+  },
+
+  SWITCH    => {
+    USEWCT  => 1,
+    URL     => '',
+    OTHER   => '',
+  },
+);
+
+# List of action buttons
+my %action_b;
+
+# List of action button help strings
+my %action_help = (
+  QUIT  => 'Quit fcm gui',
+  HELP  => 'Print help to the output text box for the selected sub-command',
+  CLEAR => 'Clear the output text box',
+  RUN   => 'Run the selected sub-command',
+);
+
+# List of action button bindings
+my %action_bind = (
+  QUIT  => {KEY => '<Control-Key-q>', U => undef},
+  HELP  => {KEY => '<F1>'           , U => undef},
+  CLEAR => {KEY => '<Alt-Key-l>'    , U => 1},
+  RUN   => {KEY => '<Alt-Key-r>'    , U => 0},
+);
+
+# List of branch subcommand options
+my %branch_opt = (
+  INFO   => undef,
+  CREATE => undef,
+  DELETE => undef,
+  LIST   => undef,
+);
+
+# List of branch create types
+my %branch_type = (
+  'DEV'         => undef,
+  'DEV::SHARE'  => undef,
+  'TEST'        => undef,
+  'TEST::SHARE' => undef,
+  'PKG'         => undef,
+  'PKG::SHARE'  => undef,
+  'PKG::CONFIG' => undef,
+  'PKG::REL'    => undef,
+);
+
+# List of branch create source type
+my %branch_srctype = (
+  TRUNK  => undef,
+  BRANCH => undef,
+);
+
+# List of branch create revision prefix option
+my %branch_revflag = (
+  NORMAL => undef,
+  NUMBER => undef,
+  NONE   => undef,
+);
+
+# List of branch info/delete options
+my %branch_info_opt = (
+  S_CHD   => 'Show children',
+  S_SIB   => 'Show siblings',
+  S_OTH   => 'Show other',
+  VERBOSE => 'Print extra information',
+);
+
+# List of diff display options
+my %diff_display_opt = (
+  default   => 'Default mode',
+  graphical => 'Graphical tool',
+  trac      => 'Trac (only for diff relative to the base of the branch)',
+);
+
+# Text in the status bar
+my $statustext = '';
+
+# ------------------------------------------------------------------------------
+
+my $mw = MainWindow->new ();
+
+my $mw_title = 'FCM GUI';
+$mw->title ($mw_title);
+
+# Frame containing subcommand selection buttons
+my $top_f = $mw->Frame ()->grid (
+  '-row'    => 0,
+  '-column' => 0,
+  '-sticky' => 'w',
+);
+
+# Frame containing subcommand options
+my $mid_f = $mw->Frame ()->grid (
+  '-row'    => 1,
+  '-column' => 0,
+  '-sticky' => 'ew',
+);
+
+# Frame containing action buttons
+my $bot_f = $mw->Frame ()->grid (
+  '-row'    => 2,
+  '-column' => 0,
+  '-sticky' => 'ew',
+);
+
+# Text box to display output
+my $out_t  = $mw->Scrolled ('ROText', '-scrollbars' => 'osow')->grid (
+  '-row'    => 3,
+  '-column' => 0,
+  '-sticky' => 'news',
+);
+
+# Text box - allow scroll with mouse wheel
+$out_t->bind (
+  '<4>' => sub {
+    $_[0]->yview ('scroll', -1, 'units') unless $Tk::strictMotif;
+  },
+);
+
+$out_t->bind (
+  '<5>' => sub {
+    $_[0]->yview ('scroll', +1, 'units') unless $Tk::strictMotif;
+  },
+);
+
+# Status bar
+$mw->Label (
+  '-textvariable' => \$statustext,
+  '-relief'       => 'groove',
+)->grid (
+  '-row'    => 4,
+  '-column' => 0,
+  '-sticky' => 'ews',
+);
+
+# Main window grid configure
+{
+  my ($cols, $rows) = $mw->gridSize ();
+  $mw->gridColumnconfigure ($_, '-weight' => 1) for (0 .. $cols - 1);
+  $mw->gridRowconfigure    ( 3, '-weight' => 1);
+}
+
+# Frame grid configure
+{
+  my ($cols, $rows) = $mid_f->gridSize ();
+  $bot_f->gridColumnconfigure (3, '-weight' => 1);
+}
+
+$mid_f->gridRowconfigure    (0, '-weight' => 1);
+$mid_f->gridColumnconfigure (0, '-weight' => 1);
+
+# ------------------------------------------------------------------------------
+
+# Buttons to select subcommands
+{
+  my $col = 0;
+  for my $name (@subcmds) {
+    $subcmd_b{$name} = $top_f->Button (
+      '-text'    => uc (substr ($name, 0, 1)) . lc (substr ($name, 1)),
+      '-command' => [\&button_clicked, $name],
+      '-width'   => 8,
+    )->grid (
+      '-row'    => 0,
+      '-column' => $col++,
+      '-sticky' => 'w',
+    );
+
+    $subcmd_b{$name}->bind ('<Enter>', sub {$statustext = $subcmd_help{$name}});
+    $subcmd_b{$name}->bind ('<Leave>', sub {$statustext = ''});
+
+    $subcmd_b{$name}->configure ('-underline' => $subcmd_bind{$name}{U})
+      if defined $subcmd_bind{$name}{U};
+
+    $mw->bind ($subcmd_bind{$name}{KEY}, sub {$subcmd_b{$name}->invoke});
+  }
+}
+
+# ------------------------------------------------------------------------------
+
+# Frames to contain subcommands options
+{
+  my %row = ();
+
+  for my $name (@subcmds) {
+    $subcmd_f{$name} = $mid_f->Frame ();
+    $subcmd_f{$name}->gridColumnconfigure (1, '-weight' => 1);
+
+    $row{$name} = 0;
+
+    # Widgets common to all sub-commands
+    $subcmd_f{$name}->Label ('-text' => 'Current working directory: ')->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+    $subcmd_f{$name}->Label ('-textvariable' => \($subcmdvar{CWD}))->grid (
+      '-row'    => $row{$name}++,
+      '-column' => 1,
+      '-sticky' => 'w',
+    );
+  }
+
+  # Widgets common to all sub-commands that apply to working copies
+  for my $name (@wco_subcmds) {
+    my @labtxts = (
+      'Corresponding URL: ',
+      'Working copy top: ',
+      'Corresponding URL: ',
+    );
+    my @varrefs = \(
+      $subcmdvar{URL_CWD},
+      $subcmdvar{WCT},
+      $subcmdvar{URL_WCT},
+    );
+
+    for my $i (0 .. $#varrefs) {
+      $subcmd_f{$name}->Label ('-text' => $labtxts[$i])->grid (
+        '-row'    => $row{$name},
+        '-column' => 0,
+        '-sticky' => 'w',
+      );
+      $subcmd_f{$name}->Label ('-textvariable' => $varrefs[$i])->grid (
+        '-row'    => $row{$name}++,
+        '-column' => 1,
+        '-sticky' => 'w',
+      );
+    }
+
+    $subcmd_f{$name}->Checkbutton (
+      '-text'     => 'Apply sub-command to working copy top',
+      '-variable' => \($subcmdvar{$name}{USEWCT}),
+      '-state'    => (grep ({$_ eq $name} @wcto_subcmds) ? 'disabled' : 'normal'),
+    )->grid (
+      '-row'        => $row{$name}++,
+      '-column'     => 0,
+      '-columnspan' => 2,
+      '-sticky'     => 'w',
+    );
+  }
+
+  # Widget for the Branch sub-command
+  {
+    my $name = 'BRANCH';
+
+    # Radio buttons to select the sub-option of the branch sub-command
+    my $opt_f = $subcmd_f{$name}->Frame ()->grid (
+      '-row'        => $row{$name}++,
+      '-column'     => 0,
+      '-columnspan' => 2,
+      '-sticky'     => 'w',
+    );
+
+    my $col = 0;
+    for my $key (sort keys %branch_opt) {
+      my $opt = lc $key;
+
+      $branch_opt{$key} = $opt_f->Radiobutton (
+        '-text'     => $opt,
+        '-value'    => $opt,
+        '-variable' => \($subcmdvar{$name}{OPT}),
+        '-state'    => 'normal',
+      )->grid (
+        '-row'      => 0,
+        '-column'   => $col++,
+        '-sticky'   => 'w',
+      );
+    }
+
+    # Label and entry box for specifying URL
+    $subcmd_f{$name}->Label ('-text' => 'URL: ')->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+    $subcmd_f{$name}->Entry (
+      '-textvariable' => \($subcmdvar{$name}{URL}),
+    )->grid (
+      '-row'    => $row{$name}++,
+      '-column' => 1,
+      '-sticky' => 'ew',
+    );
+
+    # Label and entry box for specifying create branch name
+    $subcmd_f{$name}->Label (
+      '-text' => 'Branch name (create only): ',
+    )->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+    $subcmd_f{$name}->Entry (
+      '-textvariable' => \($subcmdvar{$name}{NAME}),
+    )->grid (
+      '-row'    => $row{$name}++,
+      '-column' => 1,
+      '-sticky' => 'ew',
+    );
+
+    # Label and radio buttons box for specifying create branch type
+    $subcmd_f{$name}->Label (
+      '-text' => 'Branch type (create only): ',
+    )->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+
+    {
+      my $opt_f = $subcmd_f{$name}->Frame ()->grid (
+        '-row'    => $row{$name}++,
+        '-column' => 1,
+        '-sticky' => 'w',
+      );
+
+      my $col = 0;
+      for my $key (sort keys %branch_type) {
+        my $txt = lc $key;
+        my $opt = $key;
+
+        $branch_opt{$key} = $opt_f->Radiobutton (
+          '-text'     => $txt,
+          '-value'    => $opt,
+          '-variable' => \($subcmdvar{$name}{TYPE}),
+          '-state'    => 'normal',
+        )->grid (
+          '-row'      => 0,
+          '-column'   => $col++,
+          '-sticky'   => 'w',
+        );
+      }
+    }
+
+    # Label and radio buttons box for specifying create source type
+    $subcmd_f{$name}->Label (
+      '-text' => 'Source type (create only): ',
+    )->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+
+    {
+      my $opt_f = $subcmd_f{$name}->Frame ()->grid (
+        '-row'    => $row{$name}++,
+        '-column' => 1,
+        '-sticky' => 'w',
+      );
+
+      my $col = 0;
+      for my $key (sort keys %branch_srctype) {
+        my $txt = lc $key;
+        my $opt = lc $key;
+
+        $branch_opt{$key} = $opt_f->Radiobutton (
+          '-text'     => $txt,
+          '-value'    => $opt,
+          '-variable' => \($subcmdvar{$name}{SRCTYPE}),
+          '-state'    => 'normal',
+        )->grid (
+          '-row'      => 0,
+          '-column'   => $col++,
+          '-sticky'   => 'w',
+        );
+      }
+    }
+
+    # Label and radio buttons box for specifying create prefix option
+    $subcmd_f{$name}->Label (
+      '-text' => 'Prefix option (create only): ',
+    )->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+
+    {
+      my $opt_f = $subcmd_f{$name}->Frame ()->grid (
+        '-row'    => $row{$name}++,
+        '-column' => 1,
+        '-sticky' => 'w',
+      );
+
+      my $col = 0;
+      for my $key (sort keys %branch_revflag) {
+        my $txt = lc $key;
+        my $opt = $key;
+
+        $branch_opt{$key} = $opt_f->Radiobutton (
+          '-text'     => $txt,
+          '-value'    => $opt,
+          '-variable' => \($subcmdvar{$name}{REVFLAG}),
+          '-state'    => 'normal',
+        )->grid (
+          '-row'      => 0,
+          '-column'   => $col++,
+          '-sticky'   => 'w',
+        );
+      }
+    }
+
+    # Label and entry box for specifying ticket number
+    $subcmd_f{$name}->Label (
+      '-text' => 'Related Trac ticket(s) (create only): ',
+    )->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+    $subcmd_f{$name}->Entry (
+      '-textvariable' => \($subcmdvar{$name}{TICKET}),
+    )->grid (
+      '-row'    => $row{$name}++,
+      '-column' => 1,
+      '-sticky' => 'ew',
+    );
+
+    # Check button for info/delete
+    # --show-children, --show-siblings, --show-other, --verbose
+    $subcmd_f{$name}->Label (
+      '-text' => 'Options for info/delete only: ',
+    )->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+
+    {
+      my $opt_f = $subcmd_f{$name}->Frame ()->grid (
+        '-row'    => $row{$name}++,
+        '-column' => 1,
+        '-sticky' => 'w',
+      );
+
+      my $col = 0;
+
+      for my $key (sort keys %branch_info_opt) {
+        $opt_f->Checkbutton (
+          '-text'     => $branch_info_opt{$key},
+          '-variable' => \($subcmdvar{$name}{$key}),
+        )->grid (
+          '-row'    => 0,
+          '-column' => $col++,
+          '-sticky' => 'w',
+        );
+      }
+    }
+  }
+
+  # Widget for the Checkout sub-command
+  {
+    my $name = 'CHECKOUT';
+
+    # Label and entry boxes for specifying URL and revision
+    my @labtxts = (
+      'URL: ',
+      'Revision: ',
+      'Path: ',
+    );
+    my @varrefs = \(
+      $subcmdvar{$name}{URL},
+      $subcmdvar{$name}{REV},
+      $subcmdvar{$name}{PATH},
+    );
+
+    for my $i (0 .. $#varrefs) {
+      $subcmd_f{$name}->Label ('-text' => $labtxts[$i])->grid (
+        '-row'    => $row{$name},
+        '-column' => 0,
+        '-sticky' => 'w',
+      );
+      $subcmd_f{$name}->Entry (
+        '-textvariable' => $varrefs[$i],
+      )->grid (
+        '-row'    => $row{$name}++,
+        '-column' => 1,
+        '-sticky' => 'ew',
+      );
+    }
+  }
+
+  # Widget for the Status sub-command
+  {
+    my $name = 'STATUS';
+
+    # Checkbuttons for various options
+    my @labtxts = (
+      'Display update information',
+      'Print extra information',
+    );
+    my @varrefs = \(
+      $subcmdvar{$name}{UPDATE},
+      $subcmdvar{$name}{VERBOSE},
+    );
+
+    for my $i (0 .. $#varrefs) {
+      $subcmd_f{$name}->Checkbutton (
+        '-text'     => $labtxts[$i],
+        '-variable' => $varrefs[$i],
+      )->grid (
+        '-row'        => $row{$name}++,
+        '-column'     => 0,
+        '-columnspan' => 2,
+        '-sticky'     => 'w',
+      );
+    }
+  }
+
+  # Widget for the Diff sub-command
+  {
+    my $name = 'DIFF';
+
+    my $entry;
+    $subcmd_f{$name}->Checkbutton (
+      '-text'     => 'Show differences relative to the base of the branch',
+      '-variable' => \($subcmdvar{$name}{BRANCH}),
+      '-command'  => sub {
+        $entry->configure (
+          '-state' => ($subcmdvar{$name}{BRANCH} ? 'normal' : 'disabled'),
+        );
+      },
+    )->grid (
+      '-row'        => $row{$name}++,
+      '-column'     => 0,
+      '-columnspan' => 2,
+      '-sticky'     => 'w',
+    );
+
+    # Label and radio buttons box for specifying tool
+    $subcmd_f{$name}->Label (
+      '-text' => 'Display diff in: ',
+    )->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+
+    {
+      my $opt_f = $subcmd_f{$name}->Frame ()->grid (
+        '-row'    => $row{$name}++,
+        '-column' => 1,
+        '-sticky' => 'w',
+      );
+
+      my $col = 0;
+      for my $key (qw/default graphical trac/) {
+        my $txt = $diff_display_opt{$key};
+        my $opt = $key;
+
+        $branch_opt{$key} = $opt_f->Radiobutton (
+          '-text'     => $txt,
+          '-value'    => $opt,
+          '-variable' => \($subcmdvar{$name}{TOOL}),
+          '-state'    => 'normal',
+        )->grid (
+          '-row'      => 0,
+          '-column'   => $col++,
+          '-sticky'   => 'w',
+        );
+      }
+    }
+
+    $subcmd_f{$name}->Label ('-text' => 'Branch URL')->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+
+    $entry = $subcmd_f{$name}->Entry (
+      '-textvariable' => \($subcmdvar{$name}{URL}),
+      '-state'        => ($subcmdvar{$name}{BRANCH} ? 'normal' : 'disabled'),
+    )->grid (
+      '-row'    => $row{$name}++,
+      '-column' => 1,
+      '-sticky' => 'ew',
+    );
+  }
+
+  # Widget for the Add/Delete sub-command
+  for my $name (qw/ADD DELETE/) {
+
+    # Checkbuttons for various options
+    $subcmd_f{$name}->Checkbutton (
+      '-text'     => 'Check for files or directories not under version control',
+      '-variable' => \($subcmdvar{$name}{CHECK}),
+    )->grid (
+      '-row'        => $row{$name}++,
+      '-column'     => 0,
+      '-columnspan' => 2,
+      '-sticky'     => 'w',
+    );
+  }
+
+  # Widget for the Merge sub-command
+  {
+    my $name = 'MERGE';
+
+    # Label and radio buttons box for specifying merge mode
+    $subcmd_f{$name}->Label (
+      '-text' => 'Mode: ',
+    )->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+
+    {
+      my $opt_f = $subcmd_f{$name}->Frame ()->grid (
+        '-row'    => $row{$name}++,
+        '-column' => 1,
+        '-sticky' => 'w',
+      );
+
+      my $col = 0;
+      for my $key (qw/automatic custom reverse/) {
+        my $txt = lc $key;
+        my $opt = $key;
+
+        $branch_opt{$key} = $opt_f->Radiobutton (
+          '-text'     => $txt,
+          '-value'    => $opt,
+          '-variable' => \($subcmdvar{$name}{MODE}),
+          '-state'    => 'normal',
+        )->grid (
+          '-row'      => 0,
+          '-column'   => $col++,
+          '-sticky'   => 'w',
+        );
+      }
+    }
+
+    # Check buttons for dry-run
+    $subcmd_f{$name}->Checkbutton (
+      '-text'     => 'Dry run',
+      '-variable' => \($subcmdvar{$name}{DRYRUN}),
+    )->grid (
+      '-row'        => $row{$name}++,
+      '-column'     => 0,
+      '-columnspan' => 2,
+      '-sticky'     => 'w',
+    );
+
+    # Check buttons for verbose mode
+    $subcmd_f{$name}->Checkbutton (
+      '-text'     => 'Print extra information',
+      '-variable' => \($subcmdvar{$name}{VERBOSE}),
+    )->grid (
+      '-row'        => $row{$name}++,
+      '-column'     => 0,
+      '-columnspan' => 2,
+      '-sticky'     => 'w',
+    );
+
+    # Label and entry boxes for specifying merge source
+    $subcmd_f{$name}->Label (
+      '-text' => 'Source (automatic/custom only): ',
+    )->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+    $subcmd_f{$name}->Entry (
+      '-textvariable' => \($subcmdvar{$name}{SRC}),
+    )->grid (
+      '-row'    => $row{$name}++,
+      '-column' => 1,
+      '-sticky' => 'ew',
+    );
+
+    # Label and entry boxes for specifying merge revision (range)
+    $subcmd_f{$name}->Label (
+      '-text' => 'Revision (custom/reverse only): ',
+    )->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+    $subcmd_f{$name}->Entry (
+      '-textvariable' => \($subcmdvar{$name}{REV}),
+    )->grid (
+      '-row'    => $row{$name}++,
+      '-column' => 1,
+      '-sticky' => 'ew',
+    );
+  }
+
+  # Widget for the Commit sub-command
+  {
+    my $name = 'COMMIT';
+
+    # Checkbuttons for various options
+    $subcmd_f{$name}->Checkbutton (
+      '-text'     => 'Dry run',
+      '-variable' => \($subcmdvar{$name}{DRYRUN}),
+    )->grid (
+      '-row'        => $row{$name}++,
+      '-column'     => 0,
+      '-columnspan' => 2,
+      '-sticky'     => 'w',
+    );
+  }
+
+  # Widget for the Switch sub-command
+  {
+    my $name = 'SWITCH';
+
+    # Label and entry boxes for specifying switch URL
+    $subcmd_f{$name}->Label ('-text' => 'URL: ')->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+    $subcmd_f{$name}->Entry (
+      '-textvariable' => \($subcmdvar{$name}{URL}),
+    )->grid (
+      '-row'    => $row{$name}++,
+      '-column' => 1,
+      '-sticky' => 'ew',
+    );
+  }
+
+  # Widgets common to all sub-commands
+  for my $name (@subcmds) {
+    $subcmd_f{$name}->Label ('-text' => 'Other options: ')->grid (
+      '-row'    => $row{$name},
+      '-column' => 0,
+      '-sticky' => 'w',
+    );
+    $subcmd_f{$name}->Entry (
+      '-textvariable' => \($subcmdvar{$name}{OTHER}),
+    )->grid (
+      '-row'    => $row{$name}++,
+      '-column' => 1,
+      '-sticky' => 'ew',
+    );
+  }
+}
+
+# ------------------------------------------------------------------------------
+
+# Buttons to perform main actions
+{
+  my $col = 0;
+  for my $name (qw/QUIT HELP CLEAR RUN/) {
+    $action_b{$name} = $bot_f->Button (
+      '-text'    => uc (substr ($name, 0, 1)) . lc (substr ($name, 1)),
+      '-command' => [\&button_clicked, $name],
+      '-width'   => 8,
+    )->grid (
+      '-row'    => 0,
+      '-column' => $col++,
+      '-sticky' => ($name eq 'RUN' ? 'ew' : 'w'),
+    );
+
+    $action_b{$name}->bind ('<Enter>', sub {$statustext = $action_help{$name}});
+    $action_b{$name}->bind ('<Leave>', sub {$statustext = ''});
+
+    $action_b{$name}->configure ('-underline' => $action_bind{$name}{U})
+      if defined $action_bind{$name}{U};
+
+    $mw->bind ($action_bind{$name}{KEY}, sub {$action_b{$name}->invoke});
+  }
+}
+
+&change_cwd ($subcmdvar{CWD});
+
+# ------------------------------------------------------------------------------
+
+# Handle the situation when the user attempts to quit the window while a
+# sub-command is running
+
+$mw->protocol ('WM_DELETE_WINDOW', sub {
+  if (defined $cmdpid) {
+    my $ans = $mw->messageBox (
+      '-title'   => $mw_title,
+      '-message' => $selsubcmd . ' is still running. Really quit?',
+      '-type'    => 'YesNo',
+      '-default' => 'No',
+    );
+
+    if ($ans eq 'Yes') {
+      kill 9, $cmdpid; # Need to kill the sub-process before quitting
+
+    } else {
+      return; # Do not quit
+    }
+  }
+
+  exit;
+});
+
+MainLoop;
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   &change_cwd ($dir);
+#
+# DESCRIPTION
+#   Change current working directory to $dir
+# ------------------------------------------------------------------------------
+
+sub change_cwd {
+  my $dir = $_[0];
+  my @allowed_subcmds = (&is_wc ($dir) ? @wc_subcmds : @nwc_subcmds);
+
+  for my $subcmd (@subcmds) {
+    if (grep {$_ eq $subcmd} @allowed_subcmds) {
+      $subcmd_b{$subcmd}->configure ('-state' => 'normal');
+
+    } else {
+      $subcmd_b{$subcmd}->configure ('-state' => 'disabled');
+    }
+  }
+
+  &display_subcmd_frame ($allowed_subcmds[0])
+    if not grep {$_ eq $selsubcmd} @allowed_subcmds;
+
+  chdir $dir;
+  $subcmdvar{CWD} = $dir;
+
+  if (&is_wc ($dir)) {
+    $subcmdvar{WCT}     = &get_wct ($dir);
+    $subcmdvar{URL_CWD} = &get_url_of_wc ($dir);
+    $subcmdvar{URL_WCT} = &get_url_of_wc ($subcmdvar{WCT});
+
+    $branch_opt{INFO}  ->configure ('-state' => 'normal');
+    $branch_opt{DELETE}->configure ('-state' => 'normal');
+    $subcmdvar{BRANCH}{OPT} = 'info';
+
+  } else {
+    $branch_opt{INFO}  ->configure ('-state' => 'disabled');
+    $branch_opt{DELETE}->configure ('-state' => 'disabled');
+    $subcmdvar{BRANCH}{OPT} = 'create';
+  }
+
+  return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   &button_clicked ($name);
+#
+# DESCRIPTION
+#   Call back function to handle a click on a command button named $name.
+# ------------------------------------------------------------------------------
+
+sub button_clicked {
+  my $name = $_[0];
+
+  if (grep {$_ eq $name} keys %subcmd_b) {
+    &display_subcmd_frame ($name);
+
+  } elsif ($name eq 'CLEAR') {
+    $out_t->delete ('1.0', 'end');
+
+  } elsif ($name eq 'QUIT') {
+    exit;
+
+  } elsif ($name eq 'HELP') {
+    &invoke_cmd ('help ' . lc ($selsubcmd));
+
+  } elsif ($name eq 'RUN') {
+    &invoke_cmd (&setup_cmd ($selsubcmd));
+
+  } else {
+    $out_t->insert ('end', $name . ': function to be implemented' . "\n");
+    $out_t->yviewMoveto (1);
+  }
+
+  return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   &display_subcmd_frame ($name);
+#
+# DESCRIPTION
+#   Change selected subcommand to $name, and display the frame containing the
+#   widgets for configuring the options and arguments of that subcommand.
+# ------------------------------------------------------------------------------
+
+sub display_subcmd_frame {
+  my $name = $_[0];
+
+  if ($selsubcmd ne $name and not $cmdrunning) {
+    $subcmd_b{$name     }->configure ('-relief' => 'sunken');
+    $subcmd_b{$selsubcmd}->configure ('-relief' => 'raised') if $selsubcmd;
+
+    $subcmd_f{$name     }->grid ('-sticky' => 'new');
+    $subcmd_f{$selsubcmd}->gridForget if $selsubcmd;
+
+    $selsubcmd = $name;
+  }
+
+  return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $pos = &get_wm_pos ();
+#
+# DESCRIPTION
+#   Returns the position part of the geometry string of the main window.
+# ------------------------------------------------------------------------------
+
+sub get_wm_pos {
+  my $geometry = $mw->geometry ();
+  $geometry =~ /^=?(?:\d+x\d+)?([+-]\d+[+-]\d+)$/;
+  return $1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $command = &setup_cmd ($name);
+#
+# DESCRIPTION
+#   Setup the system command for the sub-command $name.
+# ------------------------------------------------------------------------------
+
+sub setup_cmd {
+  my $name = $_[0];
+  my $cmd  = '';
+
+  if ($name eq 'BRANCH') {
+    if ($subcmdvar{$name}{OPT} eq 'create') {
+      $cmd .= 'branch-create';
+      $cmd .= ' --svn-non-interactive';
+      $cmd .= ' -t '     . $subcmdvar{$name}{TYPE};
+      $cmd .= ' --rev-flag ' . $subcmdvar{$name}{REVFLAG};
+      $cmd .= ' -k ' . $subcmdvar{$name}{TICKET} if $subcmdvar{$name}{TICKET};
+      $cmd .= ' --branch-of-branch ' if $subcmdvar{$name}{SRCTYPE} eq 'branch';
+      $cmd .= ' ' . $subcmdvar{$name}{NAME};
+
+    } elsif ($subcmdvar{$name}{OPT} eq 'delete') {
+      $cmd .= 'branch-delete';
+      $cmd .= ' -v' if $subcmdvar{$name}{VERBOSE};
+      $cmd .= ' --svn-non-interactive';
+
+    } elsif ($subcmdvar{$name}{OPT} eq 'list') {
+      $cmd .= 'branch-list';
+
+    } else {
+      $cmd .= 'branch-info';
+      $cmd .= ' --show-children' if $subcmdvar{$name}{S_CHD};
+      $cmd .= ' --show-siblings' if $subcmdvar{$name}{S_SIB};
+      $cmd .= ' --show-other'    if $subcmdvar{$name}{S_OTH};
+      $cmd .= ' -v' if $subcmdvar{$name}{VERBOSE};
+    }
+    $cmd .= ' ' . $subcmdvar{$name}{URL}   if $subcmdvar{$name}{URL};
+    $cmd .= ' ' . $subcmdvar{$name}{OTHER} if $subcmdvar{$name}{OTHER};
+
+  } elsif ($name eq 'CHECKOUT') {
+    $cmd .= lc ($name);
+    $cmd .= ' -r' . $subcmdvar{$name}{REV} if $subcmdvar{$name}{REV};
+    $cmd .= ' ' . $subcmdvar{$name}{OTHER} if $subcmdvar{$name}{OTHER};
+    $cmd .= ' ' . $subcmdvar{$name}{URL};
+    $cmd .= ' ' . $subcmdvar{$name}{PATH} if $subcmdvar{$name}{PATH};
+
+  } elsif ($name eq 'STATUS') {
+    $cmd .= lc ($name);
+    $cmd .= ' -u' if $subcmdvar{$name}{UPDATE};
+    $cmd .= ' -v' if $subcmdvar{$name}{VERBOSE};
+    $cmd .= ' ' . $subcmdvar{$name}{OTHER} if $subcmdvar{$name}{OTHER};
+
+  } elsif ($name eq 'DIFF') {
+    if ($subcmdvar{$name}{BRANCH}) {
+      $cmd .= 'branch-diff';
+      $cmd .= ' -t' if $subcmdvar{$name}{TOOL} eq 'trac';
+      $cmd .= ' ' . $subcmdvar{$name}{URL} if $subcmdvar{$name}{URL};
+    }
+    else {
+      $cmd .= 'diff';
+    }
+
+    $cmd .= ' -g' if $subcmdvar{$name}{TOOL} eq 'graphical';
+    $cmd .= ' ' . $subcmdvar{$name}{OTHER} if $subcmdvar{$name}{OTHER};
+
+  } elsif ($name eq 'ADD' or $name eq 'DELETE') {
+    $cmd .= lc ($name);
+    $cmd .= ' -c' if $subcmdvar{$name}{CHECK};
+    $cmd .= ' --non-interactive'
+      if $name eq 'DELETE' and not $subcmdvar{$name}{CHECK};
+    $cmd .= ' ' . $subcmdvar{$name}{OTHER} if $subcmdvar{$name}{OTHER};
+
+  } elsif ($name eq 'MERGE') {
+    $cmd .= lc ($name);
+
+    if ($subcmdvar{$name}{MODE} ne 'automatic') {
+      $cmd .= ' --' . $subcmdvar{$name}{MODE};
+      $cmd .= ' --revision ' . $subcmdvar{$name}{REV} if $subcmdvar{$name}{REV};
+    }
+
+    $cmd .= ' --dry-run' if $subcmdvar{$name}{DRYRUN};
+    $cmd .= ' -v'        if $subcmdvar{$name}{VERBOSE};
+    $cmd .= ' ' . $subcmdvar{$name}{SRC}   if $subcmdvar{$name}{SRC};
+    $cmd .= ' ' . $subcmdvar{$name}{OTHER} if $subcmdvar{$name}{OTHER};
+
+  } elsif ($name eq 'CONFLICTS') {
+    $cmd .= lc ($name);
+    $cmd .= ' ' . $subcmdvar{$name}{OTHER} if $subcmdvar{$name}{OTHER};
+
+  } elsif ($name eq 'COMMIT') {
+    $cmd .= lc ($name);
+    $cmd .= ' --dry-run' if $subcmdvar{$name}{DRYRUN};
+    $cmd .= ' --svn-non-interactive';
+    $cmd .= ' ' . $subcmdvar{$name}{OTHER} if $subcmdvar{$name}{OTHER};
+
+  } elsif ($name eq 'SWITCH') {
+    $cmd .= lc ($name);
+    $cmd .= ' ' . $subcmdvar{$name}{URL}   if $subcmdvar{$name}{URL};
+    $cmd .= ' ' . $subcmdvar{$name}{OTHER} if $subcmdvar{$name}{OTHER};
+
+  } elsif ($name eq 'UPDATE') {
+    $cmd .= lc ($name);
+    $cmd .= ' ' . $subcmdvar{$name}{OTHER} if $subcmdvar{$name}{OTHER};
+
+  }
+
+  return $cmd;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   &invoke_cmd ($cmd);
+#
+# DESCRIPTION
+#   Invoke the command $cmd.
+# ------------------------------------------------------------------------------
+
+sub invoke_cmd {
+  my $cmd      = $_[0];
+  return unless $cmd;
+
+  my $disp_cmd = 'fcm ' . $cmd;
+  $cmd         = (index ($cmd, 'help ') == 0)
+                 ? $disp_cmd
+                 : ('fcm gui-internal ' . &get_wm_pos () . ' ' . $cmd);
+
+  # Change directory to working copy top if necessary
+  if ($subcmdvar{$selsubcmd}{USEWCT} and $subcmdvar{WCT} ne $subcmdvar{CWD}) {
+    chdir $subcmdvar{WCT};
+    $out_t->insert ('end', 'cd ' . $subcmdvar{WCT} . "\n");
+    $out_t->yviewMoveto (1);
+  }
+
+  # Report start of command
+  $out_t->insert ('end', timestamp_command ($disp_cmd, 'Start'));
+  $out_t->yviewMoveto (1);
+
+  # Open the command as a pipe
+  if ($cmdpid = open CMD, '-|', $cmd . ' 2>&1') {
+    # Disable all action buttons
+    $action_b{$_}->configure ('-state' => 'disabled') for (keys %action_b);
+    $cmdrunning = 1;
+
+    # Set up a file event to read output from the command
+    $mw->fileevent (\*CMD, readable => sub {
+      if (sysread CMD, my ($buf), 1024) {
+        # Insert text into the output text box as it becomes available
+        $out_t->insert ('end', $buf);
+        $out_t->yviewMoveto (1);
+
+      } else {
+        # Delete the file event and close the file when the command finishes
+        $mw->fileevent(\*CMD, readable => '');
+        close CMD;
+        $cmdpid = undef;
+
+        # Check return status
+        if ($?) {
+          $out_t->insert (
+            'end', '"' . $disp_cmd . '" failed (' . $? . ')' . "\n",
+          );
+          $out_t->yviewMoveto (1);
+        }
+
+        # Report end of command
+        $out_t->insert ('end', timestamp_command ($disp_cmd, 'End'));
+        $out_t->yviewMoveto (1);
+
+        # Change back to CWD if necessary
+        if ($subcmdvar{$selsubcmd}{USEWCT} and
+            $subcmdvar{WCT} ne $subcmdvar{CWD}) {
+          chdir $subcmdvar{CWD};
+          $out_t->insert ('end', 'cd ' . $subcmdvar{CWD} . "\n");
+          $out_t->yviewMoveto (1);
+        }
+
+        # Enable all action buttons again
+        $action_b{$_}->configure ('-state' => 'normal') for (keys %action_b);
+        $cmdrunning = 0;
+
+        # If the command is "checkout", change directory to working copy
+        if (lc ($selsubcmd) eq 'checkout' && $subcmdvar{CHECKOUT}{URL}) {
+          my $url = FCM1::Keyword::expand($subcmdvar{CHECKOUT}{URL});
+          my $dir = $subcmdvar{CHECKOUT}{PATH}
+                  ? $subcmdvar{CHECKOUT}{PATH}
+                  : basename($url);
+          $dir    = rel2abs($dir);
+          &change_cwd ($dir);
+
+        # If the command is "switch", change URL
+        } elsif (lc ($selsubcmd) eq 'switch') {
+          $subcmdvar{URL_CWD} = &get_url_of_wc ($subcmdvar{CWD}, 1);
+          $subcmdvar{URL_WCT} = &get_url_of_wc ($subcmdvar{WCT}, 1);
+        }
+      }
+      1;
+    });
+
+  } else {
+    $mw->messageBox (
+      '-title'   => 'Error',
+      '-message' => 'Error running "' . $cmd . '"',
+      '-icon'    => 'error',
+    );
+  }
+
+  return;
+}
+
+# ------------------------------------------------------------------------------
+
+__END__
+
+=head1 NAME
+
+fcm_gui
+
+=head1 SYNOPSIS
+
+fcm_gui [DIR]
+
+=head1 DESCRIPTION
+
+The fcm_gui command is a simple graphical user interface for some of the
+commands of the FCM system. The optional argument DIR modifies the initial
+working directory.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/bin/fcm_internal b/bin/fcm_internal
new file mode 100755
index 0000000..809a19e
--- /dev/null
+++ b/bin/fcm_internal
@@ -0,0 +1,629 @@
+#!/usr/bin/env perl
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FCM1::Timer qw{timestamp_command};
+
+# Function declarations
+sub catfile;
+sub basename;
+sub dirname;
+
+# ------------------------------------------------------------------------------
+
+# Module level variables
+my %unusual_tool_name = ();
+
+# ------------------------------------------------------------------------------
+
+MAIN: {
+  # Name of program
+  my $this = basename $0;
+
+  # Arguments
+  my $subcommand = shift @ARGV;
+  my ($function, $type) = split /:/, $subcommand; 
+
+  my ($srcpackage, $src, $target, $requirepp, @objects, @blockdata);
+  
+  if ($function eq 'archive') {
+    ($target, @objects) = @ARGV;
+
+  } elsif ($function eq 'load') {
+    ($srcpackage, $src, $target, @blockdata) = @ARGV;
+
+  } else {
+    ($srcpackage, $src, $target, $requirepp) = @ARGV;
+  }
+
+  # Set up hash reference for all the required information
+  my %info = (
+    SRCPACKAGE => $srcpackage,
+    SRC        => $src,
+    TYPE       => $type,
+    TARGET     => $target,
+    REQUIREPP  => $requirepp,
+    OBJECTS    => \@objects,
+    BLOCKDATA  => \@blockdata,
+  );
+
+  # Get list of unusual tools
+  my $i = 0;
+  while (my $label = &get_env ('FCM_UNUSUAL_TOOL_LABEL' . $i)) {
+    my $value = &get_env ('FCM_UNUSUAL_TOOL_VALUE' . $i);
+    $unusual_tool_name{$label} = $value;
+    $i++;
+  }
+
+  # Invoke the action
+  my $rc = 0;
+  if ($function eq 'compile') {
+    $rc = &compile (\%info);
+
+  } elsif ($function eq 'load') {
+    $rc = &load (\%info);
+
+  } elsif ($function eq 'archive') {
+    $rc = &archive (\%info);
+
+  } else {
+    print STDERR $this, ': incorrect usage, abort';
+    $rc = 1;
+  }
+
+  # Throw error if action failed
+  if ($rc) {
+    print STDERR $this, ' ', $function, ' failed (', $rc, ')', "\n";
+    exit 1;
+
+  } else {
+    exit;
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = &compile (\%info);
+#
+# DESCRIPTION
+#   This method invokes the correct compiler with the correct options to
+#   compile the source file into the required target. The argument $info is a
+#   hash reference set up in MAIN. The following environment variables are
+#   used, where * is the source file type (F for Fortran, and C for C/C++):
+# 
+#   *C          - compiler command
+#   *C_OUTPUT   - *C option to specify the name of the output file
+#   *C_DEFINE   - *C option to declare a pre-processor def
+#   *C_INCLUDE  - *C option to declare an include directory
+#   *C_MODSEARCH- *C option to declare a module search directory
+#   *C_COMPILE  - *C option to ask the compiler to perform compile only
+#   *CFLAGS     - *C user options
+#   *PPKEYS     - list of pre-processor defs (may have sub-package suffix)
+#   FCM_VERBOSE - verbose level
+#   FCM_OBJDIR  - destination directory of object file
+#   FCM_TMPDIR  - temporary destination directory of object file
+# ------------------------------------------------------------------------------
+
+sub compile {
+  my $info = shift;
+
+  # Verbose mode
+  my $verbose = &get_env ('FCM_VERBOSE');
+  $verbose    = 1 unless defined ($verbose);
+
+  my @command = ();
+
+  # Guess file type for backward compatibility
+  my $type = $info->{TYPE} ? $info->{TYPE} : &guess_file_type ($info->{SRC});
+
+  # Compiler
+  push @command, &get_env ($type . 'C', 1);
+
+  # Compile output target (typical -o option)
+  push @command, &get_env ($type . 'C_OUTPUT', 1), $info->{TARGET};
+
+  # Pre-processor definition macros
+  if ($info->{REQUIREPP}) {
+    my @ppkeys = split /\s+/, &select_flags ($info, $type . 'PPKEYS');
+    my $defopt = &get_env ($type . 'C_DEFINE', 1);
+
+    push @command, (map {$defopt . $_} @ppkeys);
+  }
+
+  # Include search path
+  my $incopt  = &get_env ($type . 'C_INCLUDE', 1);
+  my @incpath = split /:/, &get_env ('FCM_INCPATH');
+  push @command, (map {$incopt . $_} @incpath);
+
+  # Compiled module search path
+  my $modopt  = &get_env ($type . 'C_MODSEARCH');
+  if ($modopt) {
+    push @command, (map {$modopt . $_} @incpath);
+  }
+
+  # Other compiler flags
+  my $flags = &select_flags ($info, $type . 'FLAGS');
+  push @command, $flags if $flags;
+
+  my $compile_only = &get_env ($type . 'C_COMPILE');
+  if ($flags !~ /(?:^|\s)$compile_only\b/) {
+    push @command, &get_env ($type . 'C_COMPILE');
+  }
+
+  # Name of source file
+  push @command, $info->{SRC};
+
+  # Execute command
+  my $objdir = &get_env ('FCM_OBJDIR', 1);
+  my $tmpdir = &get_env ('FCM_TMPDIR', 1);
+  chdir $tmpdir;
+
+  my $command = join ' ', @command;
+  if ($verbose > 1) {
+    print 'cd ', $tmpdir, "\n";
+    print &timestamp_command ($command, 'Start');
+
+  } elsif ($verbose) {
+    print $command, "\n";
+  }
+
+  my $rc = system $command;
+
+  print &timestamp_command ($command, 'End  ') if $verbose > 1;
+
+  # Move temporary output to correct location on success
+  # Otherwise, remove temporary output
+  if ($rc) { # error
+    unlink $info->{TARGET};
+
+  } else {   # success
+    print 'mv ', $info->{TARGET}, ' ', $objdir, "\n" if $verbose > 1;
+    rename $info->{TARGET}, &catfile ($objdir, $info->{TARGET});
+  }
+
+  # Move any Fortran module definition files to the INC directory
+  my @modfiles = <*.mod *.MOD>;
+  for my $file (@modfiles) {
+    rename $file, &catfile ($incpath[0], $file);
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = &load (\%info);
+#
+# DESCRIPTION
+#   This method invokes the correct loader with the correct options to link
+#   the main program object into an executable. The argument $info is a hash
+#   reference set up in MAIN. The following environment variables are used:
+# 
+#   LD           - * linker command
+#   LD_OUTPUT    - LD option to specify the name of the output file
+#   LD_LIBSEARCH - LD option to declare a directory in the library search path
+#   LD_LIBLINK   - LD option to declare an object library
+#   LDFLAGS      - LD user options
+#   FCM_VERBOSE  - verbose level
+#   FCM_LIBDIR   - destination directory of object libraries
+#   FCM_OBJDIR   - destination directory of object files
+#   FCM_BINDIR   - destination directory of executable file
+#   FCM_TMPDIR   - temporary destination directory of executable file
+#
+#   * If LD is not set, it will attempt to guess the file type and use the
+#     compiler as the linker.
+# ------------------------------------------------------------------------------
+
+sub load {
+  my $info = shift;
+
+  my $rc = 0;
+
+  # Verbose mode
+  my $verbose = &get_env ('FCM_VERBOSE');
+  $verbose    = 1 unless defined ($verbose);
+
+  # Create temporary object library
+  (my $name   = $info->{TARGET}) =~ s/\.\S+$//;
+  my $libname = '__fcm__' . $name;
+  my $lib     = 'lib' . $libname . '.a';
+  my $libfile = catfile (&get_env ('FCM_LIBDIR', 1), $lib);
+  $rc = &archive ({TARGET => $lib});
+
+  unless ($rc) {
+    my @command = ();
+
+    # Linker
+    my $ld = &select_flags ($info, 'LD');
+    if (not $ld) {
+      # Guess file type for backward compatibility
+      my $type = $info->{TYPE} ? $info->{TYPE} : &guess_file_type ($info->{SRC});
+      $ld = &get_env ($type . 'C', 1);
+    }
+    push @command, $ld;
+
+    # Linker output target (typical -o option)
+    push @command, &get_env ('LD_OUTPUT', 1), $info->{TARGET};
+
+    # Name of main object file
+    my $mainobj = (basename ($info->{SRC}) eq $info->{SRC})
+                  ? catfile (&get_env ('FCM_OBJDIR'), $info->{SRC})
+                  : $info->{SRC};
+    push @command, $mainobj;
+
+    # Link with Fortran BLOCKDATA objects if necessary
+    if (@{ $info->{BLOCKDATA} }) {
+      my @blockdata = @{ $info->{BLOCKDATA} };
+      my @objpath   = split /:/, &get_env ('FCM_OBJPATH');
+
+      # Search each BLOCKDATA object file from the object search path
+      for my $file (@blockdata) {
+        for my $dir (@objpath) {
+          my $full = catfile ($dir, $file);
+
+          if (-f $full) {
+            $file = $full;
+            last;
+          }
+        }
+
+        push @command, $file;
+      }
+    }
+
+    # Library search path
+    my $libopt  = &get_env ('LD_LIBSEARCH', 1);
+    my @libpath = split /:/, &get_env ('FCM_LIBPATH');
+    push @command, (map {$libopt . $_} @libpath);
+
+    # Link with temporary object library if it exists
+    push @command, &get_env ('LD_LIBLINK', 1) . $libname if -f $libfile;
+
+    # Other linker flags
+    my $flags = &select_flags ($info, 'LDFLAGS');
+    push @command, $flags;
+
+    # Execute command
+    my $tmpdir = &get_env ('FCM_TMPDIR', 1);
+    my $bindir = &get_env ('FCM_BINDIR', 1);
+    chdir $tmpdir;
+
+    my $command = join ' ', @command;
+    if ($verbose > 1) {
+      print 'cd ', $tmpdir, "\n";
+      print &timestamp_command ($command, 'Start');
+
+    } elsif ($verbose) {
+      print $command, "\n";
+    }
+
+    $rc = system $command;
+
+    print &timestamp_command ($command, 'End  ') if $verbose > 1;
+
+    # Move temporary output to correct location on success
+    # Otherwise, remove temporary output
+    if ($rc) { # error
+      unlink $info->{TARGET};
+
+    } else {   # success
+      print 'mv ', $info->{TARGET}, ' ', $bindir, "\n" if $verbose > 1;
+      rename $info->{TARGET}, &catfile ($bindir, $info->{TARGET});
+    }
+  }
+
+  # Remove the temporary object library
+  unlink $libfile if -f $libfile;
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = &archive (\%info);
+#
+# DESCRIPTION
+#   This method invokes the library archiver to create an object library. The
+#   argument $info is a hash reference set up in MAIN. The following
+#   environment variables are used:
+# 
+#   AR           - archiver command
+#   ARFLAGS      - AR options to update/create an object library
+#   FCM_VERBOSE  - verbose level
+#   FCM_LIBDIR   - destination directory of object libraries
+#   FCM_OBJPATH  - search path of object files
+#   FCM_OBJDIR   - destination directory of object files
+#   FCM_TMPDIR   - temporary destination directory of executable file
+# ------------------------------------------------------------------------------
+
+sub archive {
+  my $info = shift;
+
+  my $rc = 0;
+
+  # Verbose mode
+  my $verbose = &get_env ('FCM_VERBOSE');
+  $verbose    = 1 unless defined ($verbose);
+
+  # Set up the archive command
+  my $lib     = &basename ($info->{TARGET});
+  my $tmplib  = &catfile (&get_env ('FCM_TMPDIR', 1), $lib);
+  my @ar_cmd  = ();
+  push @ar_cmd, (&get_env ('AR', 1), &get_env ('ARFLAGS', 1));
+  push @ar_cmd, $tmplib;
+
+  # Get object directories and their files
+  my %objdir;
+  if (exists $info->{OBJECTS}) {
+    # List of objects set in the argument, sort into directory/file list
+    for my $name (@{ $info->{OBJECTS} }) {
+      my $dir = (&dirname ($name) eq '.')
+                ? &get_env ('FCM_OBJDIR', 1) : &dirname ($name);
+      $objdir{$dir}{&basename ($name)} = 1;
+    }
+
+  } else {
+    # Objects not listed in argument, search object path for all files
+    my @objpath  = split /:/, &get_env ('FCM_OBJPATH', 1);
+    my %objbase  = ();
+
+    # Get registered objects into a hash (keys = objects, values = 1)
+    
+    my %objects = map {($_, 1)} split (/\s+/, &get_env ('OBJECTS', 1));
+
+    # Seach object path for all files
+    for my $dir (@objpath) {
+      next unless -d $dir;
+
+      chdir $dir;
+
+      # Use all files from each directory in the object search path
+      for ((glob ('*'))) {
+        next unless exists $objects{$_}; # consider registered objects only
+        $objdir{$dir}{$_} = 1 unless exists $objbase{$_};
+        $objbase{$_} = 1;
+      }
+    }
+  }
+
+  for my $dir (sort keys %objdir) {
+    next unless -d $dir;
+
+    # Go to each object directory and executes the library archive command 
+    chdir $dir;
+    my $command = join ' ', (@ar_cmd, sort keys %{ $objdir{$dir} });
+
+    if ($verbose > 1) {
+      print 'cd ', $dir, "\n";
+      print &timestamp_command ($command, 'Start');
+
+    } else {
+      print $command, "\n" if exists $info->{OBJECTS};
+    }
+
+    $rc = system $command;
+
+    print &timestamp_command ($command, 'End  ')
+      if $verbose > 1;
+    last if $rc;
+  }
+
+  # Move temporary output to correct location on success
+  # Otherwise, remove temporary output
+  if ($rc) { # error
+    unlink $tmplib;
+
+  } else {   # success
+    my $libdir = &get_env ('FCM_LIBDIR', 1);
+
+    print 'mv ', $tmplib, ' ', $libdir, "\n" if $verbose > 1;
+    rename $tmplib, &catfile ($libdir, $lib);
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $type = &guess_file_type ($filename);
+#
+# DESCRIPTION
+#   This function attempts to guess the file type by looking at the extension
+#   of the $filename. Only C and Fortran at the moment.
+# ------------------------------------------------------------------------------
+
+sub guess_file_type {
+  return (($_[0] =~ /\.c(\w+)?$/i) ? 'C' : 'F');
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flags = &select_flags (\%info, $set);
+#
+# DESCRIPTION
+#   This function selects the correct compiler/linker flags for the current
+#   sub-package from the environment variable prefix $set. The argument $info
+#   is a hash reference set up in MAIN.
+# ------------------------------------------------------------------------------
+
+sub select_flags {
+  my ($info, $set) = @_;
+
+  my $srcbase = &basename ($info->{SRC});
+  my @names    = ($set);
+  push @names, split (/__/, $info->{SRCPACKAGE} . '__' . $srcbase);
+
+  my $string = '';
+  for my $i (reverse (0 .. $#names)) {
+    my $var  = &get_env (join ('__', (@names[0 .. $i])));
+
+    $var = &get_env (join ('__', (@names[0 .. $i])))
+      if (not defined ($var)) and $i and $names[-1] =~ s/\.[^\.]+$//;
+
+    next unless defined $var;
+    $string = $var;
+    last;
+  }
+
+  return $string;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $variable = &get_env ($name);
+#   $variable = &get_env ($name, $compulsory);
+#
+# DESCRIPTION
+#   This internal method gets a variable from $ENV{$name}. If $compulsory is
+#   set to true, it throws an error if the variable is a not set or is an empty
+#   string. Otherwise, it returns C<undef> if the variable is not set.
+# ------------------------------------------------------------------------------
+
+sub get_env {
+  (my $name, my $compulsory) = @_;
+  my $string;
+
+  if ($name =~ /^\w+$/) {
+    # $name contains only word characters, variable is exported normally
+    die 'The environment variable "', $name, '" must be set, abort'
+      if $compulsory and not exists $ENV{$name};
+
+    $string = exists $ENV{$name} ? $ENV{$name} : undef;
+
+  } else {
+    # $name contains unusual characters
+    die 'The environment variable "', $name, '" must be set, abort'
+      if $compulsory and not exists $unusual_tool_name{$name};
+
+    $string = exists $unusual_tool_name{$name}
+              ? $unusual_tool_name{$name} : undef;
+  }
+
+  return $string;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $path = &catfile (@paths);
+#
+# DESCRIPTION
+#   This is a local implementation of what is in the File::Spec module.
+# ------------------------------------------------------------------------------
+
+sub catfile {
+  my @names = split (m!/!, join ('/', @_));
+  my $path  = shift @names;
+
+  for my $name (@names) {
+    $path .= '/' . $name if $name;
+  }
+
+  return $path;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $basename = &basename ($path);
+#
+# DESCRIPTION
+#   This is a local implementation of what is in the File::Basename module.
+# ------------------------------------------------------------------------------
+
+sub basename {
+  my $name = $_[0];
+
+  $name =~ s{/*$}{}; # remove trailing slashes
+
+  if ($name =~ m#.*/([^/]+)$#) {
+    return $1;
+
+  } else {
+    return $name;
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $dirname = &dirname ($path);
+#
+# DESCRIPTION
+#   This is a local implementation of what is in the File::Basename module.
+# ------------------------------------------------------------------------------
+
+sub dirname {
+  my $name = $_[0];
+
+  if ($name =~ m#^/+$#) {
+    return '/'; # dirname of root is root
+
+  } else {
+    $name =~ s{/*$}{}; # remove trailing slashes
+
+    if ($name =~ m#^(.*)/[^/]+$#) {
+      my $dir = $1;
+      $dir =~ s{/*$}{}; # remove trailing slashes
+      return $dir;
+
+    } else {
+      return '.';
+    }
+  }
+}
+
+# ------------------------------------------------------------------------------
+
+__END__
+
+=head1 NAME
+
+fcm_internal
+
+=head1 SYNOPSIS
+
+    fcm_internal SUBCOMMAND ARGS
+
+=head1 DESCRIPTION
+
+The fcm_internal command is a frontend for some of the internal commands of
+the FCM build system. The subcommand can be "compile", "load" or "archive"
+for invoking the compiler, loader and library archiver respectively. If
+"compile" or "load" is specified, it can be suffixed with ":TYPE" to
+specify the nature of the source file. If TYPE is not specified, it is set
+to C if the file extension begins with ".c". For all other file types, it
+is set to F (for Fortran source). For compile and load, the other arguments
+are 1) the name of the container package of the source file, 2) the path to
+the source file and 3) the target name after compiling or loading the
+source file. For compile, the 4th argument is a flag to indicate whether
+pre-processing is required for compiling the source file.  For load, the
+4th and the rest of the arguments is a list of object files that cannot be
+archived into the temporary load library and must be linked into the target
+through the linker command. (E.g. Fortran BLOCKDATA program units must be
+linked this way.) If archive is specified, the first argument should be the
+name of the library archive target and the rest should be the object files
+to be included in the archive. This command is invoked via the build system
+and should never be called directly by the user.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/bin/fcm_test_battery b/bin/fcm_test_battery
new file mode 100755
index 0000000..2ac1a6a
--- /dev/null
+++ b/bin/fcm_test_battery
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+cd $(dirname $0)/..
+if [[ "$PWD" != "$OLDPWD" ]]; then
+    echo "[INFO] cd $PWD"
+fi
+exec prove -j 9 -s -r "${@:-t}"
diff --git a/doc/collaboration/feeding-back-patch.png b/doc/collaboration/feeding-back-patch.png
new file mode 100644
index 0000000..8b05611
Binary files /dev/null and b/doc/collaboration/feeding-back-patch.png differ
diff --git a/doc/collaboration/index.html b/doc/collaboration/index.html
new file mode 100644
index 0000000..cfabe81
--- /dev/null
+++ b/doc/collaboration/index.html
@@ -0,0 +1,481 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: External Distribution & Collaboration for FCM Projects</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: Distribution & Collaboration for FCM Projects</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <h2 id="introduction">Introduction</h2>
+
+  <p>This document describes how projects configured under FCM can be
+  distributed externally. Particular attention is given to collaborative
+  distributions - where the external user regularly returns code for
+  consolidation into the central repositories which hold the master copies of
+  the code.</p>
+
+  <p><dfn>Note:</dfn> This document assumes that the repositories are
+  inaccessible to the external user, due to issues of security and
+  practicality.</p>
+
+  <h2 id="distribution">Creating a Distribution</h2>
+
+  <p>A system configured under FCM can be distributed by packaging a known
+  revision (usually corresponding to a stable release) into an archive (e.g. a
+  tarball) of directories and files. Various issues need to be considered:</p>
+
+  <ul>
+    <li>A distribution may contain a variety of different files including
+    source code, scripts, benchmark and validation tests, documentation,
+    etc.</li>
+
+    <li>A system may consist of several different <em>projects</em> which
+    should be put into separate directories in the distribution. Please refer
+    to the section <a href=
+    "../user_guide/system_admin.html#svn_design">Repository design</a> in the
+    FCM user guide for an explanation of what is meant by a project in this
+    context.</li>
+
+    <li>Some files in a project may not be included in the distribution. This
+    may be because they are of no interest to external users or because of
+    license restrictions. Such files will need to be filtered out when creating
+    the distribution.</li>
+
+    <li>The distribution may also contain some files which are not maintained
+    under FCM version control (test results for instance).</li>
+
+    <li>Some systems share code with other systems.
+
+      <ul>
+        <li>If a distribution is intended to be used standalone then the
+        necessary files from these other systems will need to be included. e.g.
+        The VAR system requires code from the OPS and GEN systems.</li>
+
+        <li>If the distribution is part of a wider collaboration then it is
+        likely that the files from the other systems will be distributed
+        separately. It is best if stable releases of the various systems can be
+        synchronised so that, for example, a VAR stable release uses code from
+        an OPS stable release which both use code from the same GEN
+        release.</li>
+      </ul>
+    </li>
+
+    <li>Release notes should be prepared to accompany a distribution which
+    explain, among other things, how the distribution is structured.</li>
+
+    <li>The distribution should contain a file which identifies the repository
+    revision(s) contained in the distribution.</li>
+
+    <li>System managers will probably wish to maintain a script which automates
+    the generation of these distributions.</li>
+  </ul>
+
+  <h2 id="feedback">Feeding Back Changes</h2>
+
+  <p>Although we would encourage all collaborators to make use of the FCM
+  system for version control, we recognise that they may already have their own
+  preferred systems in place. There is no particular problem with this. The
+  main requirement is that any proposed changes are provided as a modification
+  relative to the provided distribution. The changeset could be provided in the
+  form of a modified project tree or as a patchfile (refer to the later section
+  <a href="#patchfiles">Exchanging Changesets using Patchfiles</a> for further
+  discussion). If the change involves any renaming or removal of files or
+  directories then special instructions should be provided plus a script to
+  perform the changes.</p>
+
+  <p>At the central repository, the changeset should be applied to a branch
+  created from the repository revision which formed the basis of the changeset
+  (possibly making use of the Subversion utility <a href=
+  "http://svnbook.red-bean.com/en/1.4/svn.advanced.vendorbr.html#svn.advanced.vendorbr.svn_load_dirs">
+  svn_load_dirs.pl</a>). Note that extra care is needed with changesets
+  provided as modified project trees if there are any files in the project
+  which are excluded from the distribution. Once imported, the changeset should
+  then undergo any necessary testing or review before being merged into the
+  trunk.</p>
+
+  <h2 id="usingfcm">Collaborating Using FCM for Version Control</h2>
+
+  <p>There are a number of advantages if the FCM system is used for version
+  control by the collaborator. In particular it means that:</p>
+
+  <ul>
+    <li>Collaborators will be able to see all of the individual changesets
+    which went in to a new release rather than only being able to view each new
+    release as one big change.</li>
+
+    <li>The process of sending a proposed change to the central repository can
+    be standardised through the use of an <em>FCM patch</em> (explained
+    later).</li>
+
+    <li>The FCM Extract system can be fully utilised.</li>
+
+    <li>Common tools will help to ease communication. We will all use technical
+    terms to mean the same thing.</li>
+  </ul>
+
+  <p>This section explains the recommended way of using FCM in a
+  collaboration.</p>
+
+  <h3 id="initsvn">Initialising the Subversion Repositories</h3>
+
+  <p>The collaborator needs to set up a repository and import each of the
+  projects. Please see the section <a href=
+  "../user_guide/system_admin.html#svn_create">Creating a repository</a> in the
+  FCM user guide for advice. Collaborators may wish to use separate
+  repositories and Trac systems for each project or they may prefer to use a
+  single repository for all projects and use a single Trac system. Either
+  option should be fine so long as the same set of projects is retained.</p>
+
+  <p>After completing the initial import, the collaborator should have the
+  required set of projects available in Subversion where the initial version of
+  the trunk of each project corresponds with the initial stable release
+  provided in the distribution.</p>
+
+  <h3 id="prepchanges">Preparing Changes at the Collaborator's Site</h3>
+
+  <p>The recommended way of preparing changes is illustrated in Figure 1a:</p>
+
+  <p><strong>Figure 1a: working at the collaborator's
+  site</strong></p>
+
+  <p><img src="working-as-collaborator.png" alt=
+  "Figure 1a: working at the collaborator's site" class="img-polaroid" /></p>
+
+  <p>The collaborator will create a shared package branch from the latest
+  stable release on the trunk. This branch will contain all the changes that
+  will eventually be fed back to the central repository. Developers will also
+  create their own development branches. These may be branched from the latest
+  stable release on the trunk. Alternatively, if the change needs to build on
+  other changes then a branch can be created from the shared package branch.
+  When the changes are ready (i.e. tested, documented, reviewed, etc) then they
+  are merged into the shared package branch. The trunk is not used for the
+  shared changes as it is reserved for changes received from the central
+  repository.</p>
+
+  <p>Should it be required, a second shared package branch can be created from
+  the same point to contain any local modifications that will not be fed back
+  to the central repository. A configuration branch can then be used to combine
+  the local changes with those destined to be fed back. This is illustrated in
+  Figure 1b:</p>
+
+  <p><strong>Figure 1b: managing local changes</strong></p>
+
+  <p ><img src="managing-local-changes.png" alt=
+  "Figure 1b: managing local changes" class="img-polaroid" /></p>
+
+  <h3 id="feedbackfcm">Feeding Back Changes Using FCM</h3>
+
+  <p>Eventually, a series of changesets will exist on the first package branch.
+  These changes will be fed back to the central repository via an <em>FCM
+  patch</em>. This contains a series of differences associated with changesets
+  in a given branch of development, created by the <code>fcm mkpatch</code>
+  command. For further information about the command, please refer to its
+  <a href="../user_guide/command_ref.html#fcm-mkpatch">command
+  reference</a> in the FCM user guide.</p>
+
+  <p>At the central repository, the changeset will be applied to a branch
+  created from the repository revision which formed the basis of the changeset.
+  This is illustrated in Figure 2:</p>
+
+  <p><strong>Figure 2: feeding back changes</strong></p>
+  <p><img src="feeding-back-patch.png" alt=
+  "Figure 2: feeding back changes" class="img-polaroid" /></p>
+
+  <p>Patches will usually be exchanged in the form of a tarball. To apply the
+  patch it must first be extracted to a directory. In this directory there
+  should be a shell script called <code>fcm-import-patch</code>. A TARGET needs
+  to be specified when invoking the script. The TARGET must either be a URL or
+  a working copy of a valid project tree that can accept the import of the
+  patches. It is essential that this target matches the version of the project
+  from which the patch was created (usually this means a particular stable
+  release). The script contains a series of <code>cp</code> and
+  <code>svn</code> commands to import the changesets one by one. Note that the
+  changesets are committed automatically with no user interaction. It is worth
+  ensuring that an up to date backup of the repository is available in case of
+  problems.</p>
+
+  <h3 id="changescentral">Incorporating Changes into the Trunk of the Central
+  Repository</h3>
+
+  <p>Once the changes have undergone any necessary testing or review they can
+  be merged into the trunk. There are three ways of approaching this:</p>
+
+  <ol>
+    <li>As one changeset: all changes in the branch will be merged into the
+    trunk as a single changeset. This approach is the easiest and has the
+    advantage that any conflicts only need to be resolved once. However, the
+    drawback of this approach is that the logical changesets as fed back by the
+    collaborator will be combined into a large single changeset on the trunk,
+    which may not be the most desirable (although the logical changesets will
+    still be available to examine on the import branch). This is illustrated in
+    Figure 3a:
+    </li>
+  </ol>
+
+  <p><strong>Figure 3a: merging a patch in a single changeset</strong></p>
+
+  <p><img src="merging-patch-one.png" alt=
+  "Figure 3a: merging a patch in a single changeset"
+  class="img-polaroid" /></p>
+
+  <ol start="2">
+    <li>As multiple changesets: each changeset in the branch will be merged
+    into the trunk in order. This can be quite complicated and time consuming,
+    especially if you have a large number of changesets and there are a lot of
+    clashes. The advantage is that each logical changeset will retain its
+    logical identity, which may be more desirable in the long run, when you
+    come to inspect the history. This is illustrated in Figure 3b:
+    </li>
+  </ol>
+
+  <p><strong>Figure 3b: merging a patch in multiple changesets</strong></p>
+
+  <p><img src="merging-patch-multi.png" alt=
+  "Figure 3b: merging a patch in multiple changesets"
+  class="img-polaroid" /></p>
+
+  <ol start="3">
+    <li>As a mixture of the above: you may want to combine the above two
+    approaches when it makes sense to do so. For example, there may be a series
+    of small changesets that can be combined logically, or there may be a
+    changeset that fixes a bug introduced in the previous one. The bottom line
+    is that the project/system manager should use his/her own judgement in the
+    matter for what is best for the future of the project.</li>
+  </ol>
+
+  <h3 id="changescollab">Incorporating Updates at the Collaborator's Site</h3>
+
+  <p>Once a new stable release is available it will be supplied in the form of
+  a distribution tarball as described earlier. However, collaborators will also
+  be supplied with an <em>FCM patch</em> (as described earlier) for each
+  project containing all the changes made since the previous stable release.
+  Note that this assumes that stable releases are prepared on the trunk and not
+  in branches.</p>
+
+  <p>Each patch should be applied to the trunk of the collaborator's
+  repository. This means that the collaborator's trunk will always be mirroring
+  that of the central repository. This is illustrated in Figure 4:</p>
+
+  <p><strong>Figure 4: mirroring the trunk at the
+  collaborator's site</strong></p>
+
+  <p><img src="mirroring-trunk.png" alt=
+  "Figure 4: mirroring the trunk at the collaborator's site"
+  class="img-polaroid"/></p>
+
+  <p>In order to be certain that the patch has worked correctly, we recommend
+  that a check is performed to ensure that the new stable release on the trunk
+  matches the files provided in the distribution (preferably using a copy of
+  the repository for testing purposes before applying the patch to the live
+  repository).</p>
+
+  <h3 id="updatebranches">Updating Existing Branches</h3>
+
+  <p>Old branches that are still active at the collaborators site should be
+  updated to the latest stable release when it becomes available. Developers
+  should create a new branch from the latest stable release and then merge the
+  changes from the old branch to the new branch. The old branch should be
+  deleted once it is no longer required. This is illustrated in Figure 5a:</p>
+
+  <p><strong>Figure 5a: updating a branch to the latest
+  stable release</strong></p>
+
+  <p><img src="updating-branch.png" alt=
+  "Figure 5a: updating a branch to the latest stable release"
+  class="img-polaroid"/></p>
+
+  <p>Note that the merge will be easiest if the old branch was created from the
+  previous stable release. If it was created from the shared package branch
+  then a custom merge will be required to achieve the desired result (a normal
+  FCM merge command would choose the wrong base for comparison). This is
+  illustrated in Figure 5b:</p>
+
+  <p><strong>Figure 5b: updating a branch of the shared package
+  branch</strong></p>
+
+  <p><img src="updating-shared-branch.png" alt=
+  "Figure 5b: updating a branch of the shared package branch"
+  class="img-polaroid"/></p>
+
+  <h3 id="other">Other Scenarios</h3>
+
+  <p>The previous sections have only considered how developments on the trunk
+  of a central repository can be shared with a single collaborator. However,
+  the same techniques can be applied to more complex situations.</p>
+
+  <ul>
+    <li>If there are multiple external collaborators each working with their
+    own repository then hopefully it is clear that this does not alter things
+    in any way. Inevitably there will be an increased workload on the
+    maintainers of the central repository. There will also be an increased need
+    for coordination of planned code changes. However, the method of code
+    exchange is unaltered.</li>
+
+    <li>Sometimes there may be the need to collaborate on development of a
+    branch (i.e. to exchange code which is not yet ready to be incorporated
+    onto the trunk). The collaborator would maintain the trunk of their
+    repository as before, importing patches to keep their trunk alligned with
+    the stable releases from the central repository. In addition, they would
+    receive an <em>FCM patch</em> from the central repository representing the
+    changes on the shared branch relative to the stable release. The
+    collaborator should create a branch from the stable release and the patch
+    should then be imported onto this branch. They should then create a branch
+    from this branch on which to prepare their changes. When ready the changes
+    would be returned in the form of an <em>FCM patch</em>, and so on.
+    Hopefully it can be seen that the same process can be applied to this
+    shared branch as we have previously described for trunk developments.</li>
+  </ul>
+
+  <h3 id="alternative">An Alternative Branching Strategy</h3>
+
+  <p>We have described the branching strategy we believe will work best for
+  collaborators. However, this is by no means the only branching strategy that
+  can be used. In particular, some collaborators may prefer to keep the latest
+  copies of the code they are using on the trunk. This effectively means
+  getting rid of the shared package branches for shared and local changes and
+  merging all changes on to the trunk. A separate branch would be used for
+  keeping a pristine copy of the main site and merging changes from new stable
+  builds on to the trunk.</p>
+
+  <p>This approach is certainly possible and has the advantage that developers
+  at the collaborator's site may find it easier to work with. However there are
+  two disadvantages that need to be considered:</p>
+
+  <ol>
+    <li>Merging in changes from a new stable release may be more difficult. If
+    the new stable release includes changes which were fed back by the
+    collaborator then these will already be present on the collaborators trunk.
+    If these changes were modified in any way or if they overlap with other
+    changes then this will result in a conflict which could be tricky to
+    resolve.</li>
+
+    <li>Any changes which need to be fed back by the collaborator need to be
+    made relative to a stable release. However, changes will have been prepared
+    relative to some version of the trunk. This means that a separate branch
+    will need to be taken (from the branch containing the pristine copy of the
+    main site) and a custom merge will be required in order to achieve the
+    desired result.</li>
+  </ol>
+
+  <h3 id="patchfiles">Exchanging Changesets using Patchfiles</h3>
+
+  <p>In some cases, an <em>FCM patch</em> may not be the best way of exchanging
+  changesets. For instance, when distributing code changes which have not yet
+  been finalised, you probably wouldn't want to send a patch containing all the
+  individual commits to the branch on which the change is being developed. What
+  you want is a summary of the changes in a single changeset. In this case you
+  will often be better to use a patchfile (which can be applied using the Unix
+  command <code>patch</code>). A patchfile is simply the output from an
+  <code>fcm diff</code> command. For example:</p>
+  <pre>
+fcm diff --branch fcm:myproj-br/dev/frdm/r2134_my_branch > my_patchfile
+</pre>
+
+  <p>The patchfile must be applied to a working copy of the project which
+  corresponds to the same revision from which the patchfile was generated. The
+  option <code>-p0</code> must be used with the <code>patch</code> command. For
+  example:</p>
+  <pre>
+patch -p0 < my_patchfile
+</pre>
+
+  <p>Patchfiles have the advantage that they are simple to generate and
+  exchange and that they can combine the changes from a number of changsets
+  into one. However, they have a number of limitations such as:</p>
+
+  <ul>
+    <li>Binary files are ignored.</li>
+
+    <li>Deleted directories are ignored.</li>
+
+    <li>Deleted files are left as empty files.</li>
+
+    <li>Copied files appear as new files.</li>
+
+    <li>A moved file is treated as a deleted file and a new file.</li>
+  </ul>
+
+  <p>Fortunately these limitations will not be an issue for the majority of
+  changes and, where they are a problem, there are various options such as
+  providing additional instructions with the patchfile, using an <em>FCM
+  patch</em>, or exchanging a modified project tree.</p>
+
+  <h2 id="further">Further Considerations</h2>
+
+  <p>The previous sections have only considered the version control aspects of
+  a collaboration. This section lists some other aspects of the collaboration
+  which will need to be considered.</p>
+
+  <ul>
+    <li>The FCM build system can be used regardless of what version control
+    system is used. This avoids effort being wasted trying to maintain
+    compatibility with an alternate build system. It also ensures that any code
+    changes prepared by the collaborator are compatible with the coding
+    standards which the FCM build system requires. Even if there are good
+    reasons for the collaborator not to use FCM for version control, it is
+    highly recommended that the FCM build system is used (assuming that is what
+    is used at the central repository).</li>
+
+    <li>Coding standards should be agreed by all collaborators.</li>
+
+    <li>Working practices should be agreed which should define, amongst other
+    things, what level of testing, review and documentation is expected to
+    accompany any proposed change.</li>
+
+    <li>All parties in the collaboration should note the advice given in the
+    <a href="../user_guide/code_management.html#svn_problems">FCM user
+    guide</a> to avoid renaming files or directories unless you can ensure that
+    no-one is working in parallel on the affected areas of the project.</li>
+
+    <li><acronym title="intellectual property rights">IPR</acronym>, copyright
+    and license issues should be agreed by all collaborators.</li>
+  </ul>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/collaboration/managing-local-changes.png b/doc/collaboration/managing-local-changes.png
new file mode 100644
index 0000000..66b237a
Binary files /dev/null and b/doc/collaboration/managing-local-changes.png differ
diff --git a/doc/collaboration/merging-patch-multi.png b/doc/collaboration/merging-patch-multi.png
new file mode 100644
index 0000000..5239205
Binary files /dev/null and b/doc/collaboration/merging-patch-multi.png differ
diff --git a/doc/collaboration/merging-patch-one.png b/doc/collaboration/merging-patch-one.png
new file mode 100644
index 0000000..762eec3
Binary files /dev/null and b/doc/collaboration/merging-patch-one.png differ
diff --git a/doc/collaboration/mirroring-trunk.png b/doc/collaboration/mirroring-trunk.png
new file mode 100644
index 0000000..2624021
Binary files /dev/null and b/doc/collaboration/mirroring-trunk.png differ
diff --git a/doc/collaboration/updating-branch.png b/doc/collaboration/updating-branch.png
new file mode 100644
index 0000000..9cd4967
Binary files /dev/null and b/doc/collaboration/updating-branch.png differ
diff --git a/doc/collaboration/updating-shared-branch.png b/doc/collaboration/updating-shared-branch.png
new file mode 100644
index 0000000..88d68aa
Binary files /dev/null and b/doc/collaboration/updating-shared-branch.png differ
diff --git a/doc/collaboration/working-as-collaborator.png b/doc/collaboration/working-as-collaborator.png
new file mode 100644
index 0000000..5e0bd3f
Binary files /dev/null and b/doc/collaboration/working-as-collaborator.png differ
diff --git a/doc/etc/bootstrap/css/bootstrap-responsive.css b/doc/etc/bootstrap/css/bootstrap-responsive.css
new file mode 100644
index 0000000..c0bba15
--- /dev/null
+++ b/doc/etc/bootstrap/css/bootstrap-responsive.css
@@ -0,0 +1,1109 @@
+/*!
+ * Bootstrap Responsive v2.3.2
+ *
+ * Copyright 2013 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ */
+
+.clearfix {
+  *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.hide-text {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+.input-block-level {
+  display: block;
+  width: 100%;
+  min-height: 30px;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+ at -ms-viewport {
+  width: device-width;
+}
+
+.hidden {
+  display: none;
+  visibility: hidden;
+}
+
+.visible-phone {
+  display: none !important;
+}
+
+.visible-tablet {
+  display: none !important;
+}
+
+.hidden-desktop {
+  display: none !important;
+}
+
+.visible-desktop {
+  display: inherit !important;
+}
+
+ at media (min-width: 768px) and (max-width: 979px) {
+  .hidden-desktop {
+    display: inherit !important;
+  }
+  .visible-desktop {
+    display: none !important ;
+  }
+  .visible-tablet {
+    display: inherit !important;
+  }
+  .hidden-tablet {
+    display: none !important;
+  }
+}
+
+ at media (max-width: 767px) {
+  .hidden-desktop {
+    display: inherit !important;
+  }
+  .visible-desktop {
+    display: none !important;
+  }
+  .visible-phone {
+    display: inherit !important;
+  }
+  .hidden-phone {
+    display: none !important;
+  }
+}
+
+.visible-print {
+  display: none !important;
+}
+
+ at media print {
+  .visible-print {
+    display: inherit !important;
+  }
+  .hidden-print {
+    display: none !important;
+  }
+}
+
+ at media (min-width: 1200px) {
+  .row {
+    margin-left: -30px;
+    *zoom: 1;
+  }
+  .row:before,
+  .row:after {
+    display: table;
+    line-height: 0;
+    content: "";
+  }
+  .row:after {
+    clear: both;
+  }
+  [class*="span"] {
+    float: left;
+    min-height: 1px;
+    margin-left: 30px;
+  }
+  .container,
+  .navbar-static-top .container,
+  .navbar-fixed-top .container,
+  .navbar-fixed-bottom .container {
+    width: 1170px;
+  }
+  .span12 {
+    width: 1170px;
+  }
+  .span11 {
+    width: 1070px;
+  }
+  .span10 {
+    width: 970px;
+  }
+  .span9 {
+    width: 870px;
+  }
+  .span8 {
+    width: 770px;
+  }
+  .span7 {
+    width: 670px;
+  }
+  .span6 {
+    width: 570px;
+  }
+  .span5 {
+    width: 470px;
+  }
+  .span4 {
+    width: 370px;
+  }
+  .span3 {
+    width: 270px;
+  }
+  .span2 {
+    width: 170px;
+  }
+  .span1 {
+    width: 70px;
+  }
+  .offset12 {
+    margin-left: 1230px;
+  }
+  .offset11 {
+    margin-left: 1130px;
+  }
+  .offset10 {
+    margin-left: 1030px;
+  }
+  .offset9 {
+    margin-left: 930px;
+  }
+  .offset8 {
+    margin-left: 830px;
+  }
+  .offset7 {
+    margin-left: 730px;
+  }
+  .offset6 {
+    margin-left: 630px;
+  }
+  .offset5 {
+    margin-left: 530px;
+  }
+  .offset4 {
+    margin-left: 430px;
+  }
+  .offset3 {
+    margin-left: 330px;
+  }
+  .offset2 {
+    margin-left: 230px;
+  }
+  .offset1 {
+    margin-left: 130px;
+  }
+  .row-fluid {
+    width: 100%;
+    *zoom: 1;
+  }
+  .row-fluid:before,
+  .row-fluid:after {
+    display: table;
+    line-height: 0;
+    content: "";
+  }
+  .row-fluid:after {
+    clear: both;
+  }
+  .row-fluid [class*="span"] {
+    display: block;
+    float: left;
+    width: 100%;
+    min-height: 30px;
+    margin-left: 2.564102564102564%;
+    *margin-left: 2.5109110747408616%;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .row-fluid [class*="span"]:first-child {
+    margin-left: 0;
+  }
+  .row-fluid .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 2.564102564102564%;
+  }
+  .row-fluid .span12 {
+    width: 100%;
+    *width: 99.94680851063829%;
+  }
+  .row-fluid .span11 {
+    width: 91.45299145299145%;
+    *width: 91.39979996362975%;
+  }
+  .row-fluid .span10 {
+    width: 82.90598290598291%;
+    *width: 82.8527914166212%;
+  }
+  .row-fluid .span9 {
+    width: 74.35897435897436%;
+    *width: 74.30578286961266%;
+  }
+  .row-fluid .span8 {
+    width: 65.81196581196582%;
+    *width: 65.75877432260411%;
+  }
+  .row-fluid .span7 {
+    width: 57.26495726495726%;
+    *width: 57.21176577559556%;
+  }
+  .row-fluid .span6 {
+    width: 48.717948717948715%;
+    *width: 48.664757228587014%;
+  }
+  .row-fluid .span5 {
+    width: 40.17094017094017%;
+    *width: 40.11774868157847%;
+  }
+  .row-fluid .span4 {
+    width: 31.623931623931625%;
+    *width: 31.570740134569924%;
+  }
+  .row-fluid .span3 {
+    width: 23.076923076923077%;
+    *width: 23.023731587561375%;
+  }
+  .row-fluid .span2 {
+    width: 14.52991452991453%;
+    *width: 14.476723040552828%;
+  }
+  .row-fluid .span1 {
+    width: 5.982905982905983%;
+    *width: 5.929714493544281%;
+  }
+  .row-fluid .offset12 {
+    margin-left: 105.12820512820512%;
+    *margin-left: 105.02182214948171%;
+  }
+  .row-fluid .offset12:first-child {
+    margin-left: 102.56410256410257%;
+    *margin-left: 102.45771958537915%;
+  }
+  .row-fluid .offset11 {
+    margin-left: 96.58119658119658%;
+    *margin-left: 96.47481360247316%;
+  }
+  .row-fluid .offset11:first-child {
+    margin-left: 94.01709401709402%;
+    *margin-left: 93.91071103837061%;
+  }
+  .row-fluid .offset10 {
+    margin-left: 88.03418803418803%;
+    *margin-left: 87.92780505546462%;
+  }
+  .row-fluid .offset10:first-child {
+    margin-left: 85.47008547008548%;
+    *margin-left: 85.36370249136206%;
+  }
+  .row-fluid .offset9 {
+    margin-left: 79.48717948717949%;
+    *margin-left: 79.38079650845607%;
+  }
+  .row-fluid .offset9:first-child {
+    margin-left: 76.92307692307693%;
+    *margin-left: 76.81669394435352%;
+  }
+  .row-fluid .offset8 {
+    margin-left: 70.94017094017094%;
+    *margin-left: 70.83378796144753%;
+  }
+  .row-fluid .offset8:first-child {
+    margin-left: 68.37606837606839%;
+    *margin-left: 68.26968539734497%;
+  }
+  .row-fluid .offset7 {
+    margin-left: 62.393162393162385%;
+    *margin-left: 62.28677941443899%;
+  }
+  .row-fluid .offset7:first-child {
+    margin-left: 59.82905982905982%;
+    *margin-left: 59.72267685033642%;
+  }
+  .row-fluid .offset6 {
+    margin-left: 53.84615384615384%;
+    *margin-left: 53.739770867430444%;
+  }
+  .row-fluid .offset6:first-child {
+    margin-left: 51.28205128205128%;
+    *margin-left: 51.175668303327875%;
+  }
+  .row-fluid .offset5 {
+    margin-left: 45.299145299145295%;
+    *margin-left: 45.1927623204219%;
+  }
+  .row-fluid .offset5:first-child {
+    margin-left: 42.73504273504273%;
+    *margin-left: 42.62865975631933%;
+  }
+  .row-fluid .offset4 {
+    margin-left: 36.75213675213675%;
+    *margin-left: 36.645753773413354%;
+  }
+  .row-fluid .offset4:first-child {
+    margin-left: 34.18803418803419%;
+    *margin-left: 34.081651209310785%;
+  }
+  .row-fluid .offset3 {
+    margin-left: 28.205128205128204%;
+    *margin-left: 28.0987452264048%;
+  }
+  .row-fluid .offset3:first-child {
+    margin-left: 25.641025641025642%;
+    *margin-left: 25.53464266230224%;
+  }
+  .row-fluid .offset2 {
+    margin-left: 19.65811965811966%;
+    *margin-left: 19.551736679396257%;
+  }
+  .row-fluid .offset2:first-child {
+    margin-left: 17.094017094017094%;
+    *margin-left: 16.98763411529369%;
+  }
+  .row-fluid .offset1 {
+    margin-left: 11.11111111111111%;
+    *margin-left: 11.004728132387708%;
+  }
+  .row-fluid .offset1:first-child {
+    margin-left: 8.547008547008547%;
+    *margin-left: 8.440625568285142%;
+  }
+  input,
+  textarea,
+  .uneditable-input {
+    margin-left: 0;
+  }
+  .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 30px;
+  }
+  input.span12,
+  textarea.span12,
+  .uneditable-input.span12 {
+    width: 1156px;
+  }
+  input.span11,
+  textarea.span11,
+  .uneditable-input.span11 {
+    width: 1056px;
+  }
+  input.span10,
+  textarea.span10,
+  .uneditable-input.span10 {
+    width: 956px;
+  }
+  input.span9,
+  textarea.span9,
+  .uneditable-input.span9 {
+    width: 856px;
+  }
+  input.span8,
+  textarea.span8,
+  .uneditable-input.span8 {
+    width: 756px;
+  }
+  input.span7,
+  textarea.span7,
+  .uneditable-input.span7 {
+    width: 656px;
+  }
+  input.span6,
+  textarea.span6,
+  .uneditable-input.span6 {
+    width: 556px;
+  }
+  input.span5,
+  textarea.span5,
+  .uneditable-input.span5 {
+    width: 456px;
+  }
+  input.span4,
+  textarea.span4,
+  .uneditable-input.span4 {
+    width: 356px;
+  }
+  input.span3,
+  textarea.span3,
+  .uneditable-input.span3 {
+    width: 256px;
+  }
+  input.span2,
+  textarea.span2,
+  .uneditable-input.span2 {
+    width: 156px;
+  }
+  input.span1,
+  textarea.span1,
+  .uneditable-input.span1 {
+    width: 56px;
+  }
+  .thumbnails {
+    margin-left: -30px;
+  }
+  .thumbnails > li {
+    margin-left: 30px;
+  }
+  .row-fluid .thumbnails {
+    margin-left: 0;
+  }
+}
+
+ at media (min-width: 768px) and (max-width: 979px) {
+  .row {
+    margin-left: -20px;
+    *zoom: 1;
+  }
+  .row:before,
+  .row:after {
+    display: table;
+    line-height: 0;
+    content: "";
+  }
+  .row:after {
+    clear: both;
+  }
+  [class*="span"] {
+    float: left;
+    min-height: 1px;
+    margin-left: 20px;
+  }
+  .container,
+  .navbar-static-top .container,
+  .navbar-fixed-top .container,
+  .navbar-fixed-bottom .container {
+    width: 724px;
+  }
+  .span12 {
+    width: 724px;
+  }
+  .span11 {
+    width: 662px;
+  }
+  .span10 {
+    width: 600px;
+  }
+  .span9 {
+    width: 538px;
+  }
+  .span8 {
+    width: 476px;
+  }
+  .span7 {
+    width: 414px;
+  }
+  .span6 {
+    width: 352px;
+  }
+  .span5 {
+    width: 290px;
+  }
+  .span4 {
+    width: 228px;
+  }
+  .span3 {
+    width: 166px;
+  }
+  .span2 {
+    width: 104px;
+  }
+  .span1 {
+    width: 42px;
+  }
+  .offset12 {
+    margin-left: 764px;
+  }
+  .offset11 {
+    margin-left: 702px;
+  }
+  .offset10 {
+    margin-left: 640px;
+  }
+  .offset9 {
+    margin-left: 578px;
+  }
+  .offset8 {
+    margin-left: 516px;
+  }
+  .offset7 {
+    margin-left: 454px;
+  }
+  .offset6 {
+    margin-left: 392px;
+  }
+  .offset5 {
+    margin-left: 330px;
+  }
+  .offset4 {
+    margin-left: 268px;
+  }
+  .offset3 {
+    margin-left: 206px;
+  }
+  .offset2 {
+    margin-left: 144px;
+  }
+  .offset1 {
+    margin-left: 82px;
+  }
+  .row-fluid {
+    width: 100%;
+    *zoom: 1;
+  }
+  .row-fluid:before,
+  .row-fluid:after {
+    display: table;
+    line-height: 0;
+    content: "";
+  }
+  .row-fluid:after {
+    clear: both;
+  }
+  .row-fluid [class*="span"] {
+    display: block;
+    float: left;
+    width: 100%;
+    min-height: 30px;
+    margin-left: 2.7624309392265194%;
+    *margin-left: 2.709239449864817%;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .row-fluid [class*="span"]:first-child {
+    margin-left: 0;
+  }
+  .row-fluid .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 2.7624309392265194%;
+  }
+  .row-fluid .span12 {
+    width: 100%;
+    *width: 99.94680851063829%;
+  }
+  .row-fluid .span11 {
+    width: 91.43646408839778%;
+    *width: 91.38327259903608%;
+  }
+  .row-fluid .span10 {
+    width: 82.87292817679558%;
+    *width: 82.81973668743387%;
+  }
+  .row-fluid .span9 {
+    width: 74.30939226519337%;
+    *width: 74.25620077583166%;
+  }
+  .row-fluid .span8 {
+    width: 65.74585635359117%;
+    *width: 65.69266486422946%;
+  }
+  .row-fluid .span7 {
+    width: 57.18232044198895%;
+    *width: 57.12912895262725%;
+  }
+  .row-fluid .span6 {
+    width: 48.61878453038674%;
+    *width: 48.56559304102504%;
+  }
+  .row-fluid .span5 {
+    width: 40.05524861878453%;
+    *width: 40.00205712942283%;
+  }
+  .row-fluid .span4 {
+    width: 31.491712707182323%;
+    *width: 31.43852121782062%;
+  }
+  .row-fluid .span3 {
+    width: 22.92817679558011%;
+    *width: 22.87498530621841%;
+  }
+  .row-fluid .span2 {
+    width: 14.3646408839779%;
+    *width: 14.311449394616199%;
+  }
+  .row-fluid .span1 {
+    width: 5.801104972375691%;
+    *width: 5.747913483013988%;
+  }
+  .row-fluid .offset12 {
+    margin-left: 105.52486187845304%;
+    *margin-left: 105.41847889972962%;
+  }
+  .row-fluid .offset12:first-child {
+    margin-left: 102.76243093922652%;
+    *margin-left: 102.6560479605031%;
+  }
+  .row-fluid .offset11 {
+    margin-left: 96.96132596685082%;
+    *margin-left: 96.8549429881274%;
+  }
+  .row-fluid .offset11:first-child {
+    margin-left: 94.1988950276243%;
+    *margin-left: 94.09251204890089%;
+  }
+  .row-fluid .offset10 {
+    margin-left: 88.39779005524862%;
+    *margin-left: 88.2914070765252%;
+  }
+  .row-fluid .offset10:first-child {
+    margin-left: 85.6353591160221%;
+    *margin-left: 85.52897613729868%;
+  }
+  .row-fluid .offset9 {
+    margin-left: 79.8342541436464%;
+    *margin-left: 79.72787116492299%;
+  }
+  .row-fluid .offset9:first-child {
+    margin-left: 77.07182320441989%;
+    *margin-left: 76.96544022569647%;
+  }
+  .row-fluid .offset8 {
+    margin-left: 71.2707182320442%;
+    *margin-left: 71.16433525332079%;
+  }
+  .row-fluid .offset8:first-child {
+    margin-left: 68.50828729281768%;
+    *margin-left: 68.40190431409427%;
+  }
+  .row-fluid .offset7 {
+    margin-left: 62.70718232044199%;
+    *margin-left: 62.600799341718584%;
+  }
+  .row-fluid .offset7:first-child {
+    margin-left: 59.94475138121547%;
+    *margin-left: 59.838368402492065%;
+  }
+  .row-fluid .offset6 {
+    margin-left: 54.14364640883978%;
+    *margin-left: 54.037263430116376%;
+  }
+  .row-fluid .offset6:first-child {
+    margin-left: 51.38121546961326%;
+    *margin-left: 51.27483249088986%;
+  }
+  .row-fluid .offset5 {
+    margin-left: 45.58011049723757%;
+    *margin-left: 45.47372751851417%;
+  }
+  .row-fluid .offset5:first-child {
+    margin-left: 42.81767955801105%;
+    *margin-left: 42.71129657928765%;
+  }
+  .row-fluid .offset4 {
+    margin-left: 37.01657458563536%;
+    *margin-left: 36.91019160691196%;
+  }
+  .row-fluid .offset4:first-child {
+    margin-left: 34.25414364640884%;
+    *margin-left: 34.14776066768544%;
+  }
+  .row-fluid .offset3 {
+    margin-left: 28.45303867403315%;
+    *margin-left: 28.346655695309746%;
+  }
+  .row-fluid .offset3:first-child {
+    margin-left: 25.69060773480663%;
+    *margin-left: 25.584224756083227%;
+  }
+  .row-fluid .offset2 {
+    margin-left: 19.88950276243094%;
+    *margin-left: 19.783119783707537%;
+  }
+  .row-fluid .offset2:first-child {
+    margin-left: 17.12707182320442%;
+    *margin-left: 17.02068884448102%;
+  }
+  .row-fluid .offset1 {
+    margin-left: 11.32596685082873%;
+    *margin-left: 11.219583872105325%;
+  }
+  .row-fluid .offset1:first-child {
+    margin-left: 8.56353591160221%;
+    *margin-left: 8.457152932878806%;
+  }
+  input,
+  textarea,
+  .uneditable-input {
+    margin-left: 0;
+  }
+  .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 20px;
+  }
+  input.span12,
+  textarea.span12,
+  .uneditable-input.span12 {
+    width: 710px;
+  }
+  input.span11,
+  textarea.span11,
+  .uneditable-input.span11 {
+    width: 648px;
+  }
+  input.span10,
+  textarea.span10,
+  .uneditable-input.span10 {
+    width: 586px;
+  }
+  input.span9,
+  textarea.span9,
+  .uneditable-input.span9 {
+    width: 524px;
+  }
+  input.span8,
+  textarea.span8,
+  .uneditable-input.span8 {
+    width: 462px;
+  }
+  input.span7,
+  textarea.span7,
+  .uneditable-input.span7 {
+    width: 400px;
+  }
+  input.span6,
+  textarea.span6,
+  .uneditable-input.span6 {
+    width: 338px;
+  }
+  input.span5,
+  textarea.span5,
+  .uneditable-input.span5 {
+    width: 276px;
+  }
+  input.span4,
+  textarea.span4,
+  .uneditable-input.span4 {
+    width: 214px;
+  }
+  input.span3,
+  textarea.span3,
+  .uneditable-input.span3 {
+    width: 152px;
+  }
+  input.span2,
+  textarea.span2,
+  .uneditable-input.span2 {
+    width: 90px;
+  }
+  input.span1,
+  textarea.span1,
+  .uneditable-input.span1 {
+    width: 28px;
+  }
+}
+
+ at media (max-width: 767px) {
+  body {
+    padding-right: 20px;
+    padding-left: 20px;
+  }
+  .navbar-fixed-top,
+  .navbar-fixed-bottom,
+  .navbar-static-top {
+    margin-right: -20px;
+    margin-left: -20px;
+  }
+  .container-fluid {
+    padding: 0;
+  }
+  .dl-horizontal dt {
+    float: none;
+    width: auto;
+    clear: none;
+    text-align: left;
+  }
+  .dl-horizontal dd {
+    margin-left: 0;
+  }
+  .container {
+    width: auto;
+  }
+  .row-fluid {
+    width: 100%;
+  }
+  .row,
+  .thumbnails {
+    margin-left: 0;
+  }
+  .thumbnails > li {
+    float: none;
+    margin-left: 0;
+  }
+  [class*="span"],
+  .uneditable-input[class*="span"],
+  .row-fluid [class*="span"] {
+    display: block;
+    float: none;
+    width: 100%;
+    margin-left: 0;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .span12,
+  .row-fluid .span12 {
+    width: 100%;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .row-fluid [class*="offset"]:first-child {
+    margin-left: 0;
+  }
+  .input-large,
+  .input-xlarge,
+  .input-xxlarge,
+  input[class*="span"],
+  select[class*="span"],
+  textarea[class*="span"],
+  .uneditable-input {
+    display: block;
+    width: 100%;
+    min-height: 30px;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .input-prepend input,
+  .input-append input,
+  .input-prepend input[class*="span"],
+  .input-append input[class*="span"] {
+    display: inline-block;
+    width: auto;
+  }
+  .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 0;
+  }
+  .modal {
+    position: fixed;
+    top: 20px;
+    right: 20px;
+    left: 20px;
+    width: auto;
+    margin: 0;
+  }
+  .modal.fade {
+    top: -100px;
+  }
+  .modal.fade.in {
+    top: 20px;
+  }
+}
+
+ at media (max-width: 480px) {
+  .nav-collapse {
+    -webkit-transform: translate3d(0, 0, 0);
+  }
+  .page-header h1 small {
+    display: block;
+    line-height: 20px;
+  }
+  input[type="checkbox"],
+  input[type="radio"] {
+    border: 1px solid #ccc;
+  }
+  .form-horizontal .control-label {
+    float: none;
+    width: auto;
+    padding-top: 0;
+    text-align: left;
+  }
+  .form-horizontal .controls {
+    margin-left: 0;
+  }
+  .form-horizontal .control-list {
+    padding-top: 0;
+  }
+  .form-horizontal .form-actions {
+    padding-right: 10px;
+    padding-left: 10px;
+  }
+  .media .pull-left,
+  .media .pull-right {
+    display: block;
+    float: none;
+    margin-bottom: 10px;
+  }
+  .media-object {
+    margin-right: 0;
+    margin-left: 0;
+  }
+  .modal {
+    top: 10px;
+    right: 10px;
+    left: 10px;
+  }
+  .modal-header .close {
+    padding: 10px;
+    margin: -10px;
+  }
+  .carousel-caption {
+    position: static;
+  }
+}
+
+ at media (max-width: 979px) {
+  body {
+    padding-top: 0;
+  }
+  .navbar-fixed-top,
+  .navbar-fixed-bottom {
+    position: static;
+  }
+  .navbar-fixed-top {
+    margin-bottom: 20px;
+  }
+  .navbar-fixed-bottom {
+    margin-top: 20px;
+  }
+  .navbar-fixed-top .navbar-inner,
+  .navbar-fixed-bottom .navbar-inner {
+    padding: 5px;
+  }
+  .navbar .container {
+    width: auto;
+    padding: 0;
+  }
+  .navbar .brand {
+    padding-right: 10px;
+    padding-left: 10px;
+    margin: 0 0 0 -5px;
+  }
+  .nav-collapse {
+    clear: both;
+  }
+  .nav-collapse .nav {
+    float: none;
+    margin: 0 0 10px;
+  }
+  .nav-collapse .nav > li {
+    float: none;
+  }
+  .nav-collapse .nav > li > a {
+    margin-bottom: 2px;
+  }
+  .nav-collapse .nav > .divider-vertical {
+    display: none;
+  }
+  .nav-collapse .nav .nav-header {
+    color: #777777;
+    text-shadow: none;
+  }
+  .nav-collapse .nav > li > a,
+  .nav-collapse .dropdown-menu a {
+    padding: 9px 15px;
+    font-weight: bold;
+    color: #777777;
+    -webkit-border-radius: 3px;
+       -moz-border-radius: 3px;
+            border-radius: 3px;
+  }
+  .nav-collapse .btn {
+    padding: 4px 10px 4px;
+    font-weight: normal;
+    -webkit-border-radius: 4px;
+       -moz-border-radius: 4px;
+            border-radius: 4px;
+  }
+  .nav-collapse .dropdown-menu li + li a {
+    margin-bottom: 2px;
+  }
+  .nav-collapse .nav > li > a:hover,
+  .nav-collapse .nav > li > a:focus,
+  .nav-collapse .dropdown-menu a:hover,
+  .nav-collapse .dropdown-menu a:focus {
+    background-color: #f2f2f2;
+  }
+  .navbar-inverse .nav-collapse .nav > li > a,
+  .navbar-inverse .nav-collapse .dropdown-menu a {
+    color: #999999;
+  }
+  .navbar-inverse .nav-collapse .nav > li > a:hover,
+  .navbar-inverse .nav-collapse .nav > li > a:focus,
+  .navbar-inverse .nav-collapse .dropdown-menu a:hover,
+  .navbar-inverse .nav-collapse .dropdown-menu a:focus {
+    background-color: #111111;
+  }
+  .nav-collapse.in .btn-group {
+    padding: 0;
+    margin-top: 5px;
+  }
+  .nav-collapse .dropdown-menu {
+    position: static;
+    top: auto;
+    left: auto;
+    display: none;
+    float: none;
+    max-width: none;
+    padding: 0;
+    margin: 0 15px;
+    background-color: transparent;
+    border: none;
+    -webkit-border-radius: 0;
+       -moz-border-radius: 0;
+            border-radius: 0;
+    -webkit-box-shadow: none;
+       -moz-box-shadow: none;
+            box-shadow: none;
+  }
+  .nav-collapse .open > .dropdown-menu {
+    display: block;
+  }
+  .nav-collapse .dropdown-menu:before,
+  .nav-collapse .dropdown-menu:after {
+    display: none;
+  }
+  .nav-collapse .dropdown-menu .divider {
+    display: none;
+  }
+  .nav-collapse .nav > li > .dropdown-menu:before,
+  .nav-collapse .nav > li > .dropdown-menu:after {
+    display: none;
+  }
+  .nav-collapse .navbar-form,
+  .nav-collapse .navbar-search {
+    float: none;
+    padding: 10px 15px;
+    margin: 10px 0;
+    border-top: 1px solid #f2f2f2;
+    border-bottom: 1px solid #f2f2f2;
+    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+       -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+  }
+  .navbar-inverse .nav-collapse .navbar-form,
+  .navbar-inverse .nav-collapse .navbar-search {
+    border-top-color: #111111;
+    border-bottom-color: #111111;
+  }
+  .navbar .nav-collapse .nav.pull-right {
+    float: none;
+    margin-left: 0;
+  }
+  .nav-collapse,
+  .nav-collapse.collapse {
+    height: 0;
+    overflow: hidden;
+  }
+  .navbar .btn-navbar {
+    display: block;
+  }
+  .navbar-static .navbar-inner {
+    padding-right: 10px;
+    padding-left: 10px;
+  }
+}
+
+ at media (min-width: 980px) {
+  .nav-collapse.collapse {
+    height: auto !important;
+    overflow: visible !important;
+  }
+}
diff --git a/doc/etc/bootstrap/css/bootstrap-responsive.min.css b/doc/etc/bootstrap/css/bootstrap-responsive.min.css
new file mode 100644
index 0000000..96a435b
--- /dev/null
+++ b/doc/etc/bootstrap/css/bootstrap-responsive.min.css
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap Responsive v2.3.2
+ *
+ * Copyright 2013 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none [...]
diff --git a/doc/etc/bootstrap/css/bootstrap.css b/doc/etc/bootstrap/css/bootstrap.css
new file mode 100644
index 0000000..5b7fe7e
--- /dev/null
+++ b/doc/etc/bootstrap/css/bootstrap.css
@@ -0,0 +1,6167 @@
+/*!
+ * Bootstrap v2.3.2
+ *
+ * Copyright 2013 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ */
+
+.clearfix {
+  *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.hide-text {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+.input-block-level {
+  display: block;
+  width: 100%;
+  min-height: 30px;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+nav,
+section {
+  display: block;
+}
+
+audio,
+canvas,
+video {
+  display: inline-block;
+  *display: inline;
+  *zoom: 1;
+}
+
+audio:not([controls]) {
+  display: none;
+}
+
+html {
+  font-size: 100%;
+  -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+}
+
+a:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+a:hover,
+a:active {
+  outline: 0;
+}
+
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+
+sup {
+  top: -0.5em;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+img {
+  width: auto\9;
+  height: auto;
+  max-width: 100%;
+  vertical-align: middle;
+  border: 0;
+  -ms-interpolation-mode: bicubic;
+}
+
+#map_canvas img,
+.google-maps img {
+  max-width: none;
+}
+
+button,
+input,
+select,
+textarea {
+  margin: 0;
+  font-size: 100%;
+  vertical-align: middle;
+}
+
+button,
+input {
+  *overflow: visible;
+  line-height: normal;
+}
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  cursor: pointer;
+  -webkit-appearance: button;
+}
+
+label,
+select,
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"],
+input[type="radio"],
+input[type="checkbox"] {
+  cursor: pointer;
+}
+
+input[type="search"] {
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+  -webkit-appearance: textfield;
+}
+
+input[type="search"]::-webkit-search-decoration,
+input[type="search"]::-webkit-search-cancel-button {
+  -webkit-appearance: none;
+}
+
+textarea {
+  overflow: auto;
+  vertical-align: top;
+}
+
+ at media print {
+  * {
+    color: #000 !important;
+    text-shadow: none !important;
+    background: transparent !important;
+    box-shadow: none !important;
+  }
+  a,
+  a:visited {
+    text-decoration: underline;
+  }
+  a[href]:after {
+    content: " (" attr(href) ")";
+  }
+  abbr[title]:after {
+    content: " (" attr(title) ")";
+  }
+  .ir a:after,
+  a[href^="javascript:"]:after,
+  a[href^="#"]:after {
+    content: "";
+  }
+  pre,
+  blockquote {
+    border: 1px solid #999;
+    page-break-inside: avoid;
+  }
+  thead {
+    display: table-header-group;
+  }
+  tr,
+  img {
+    page-break-inside: avoid;
+  }
+  img {
+    max-width: 100% !important;
+  }
+  @page  {
+    margin: 0.5cm;
+  }
+  p,
+  h2,
+  h3 {
+    orphans: 3;
+    widows: 3;
+  }
+  h2,
+  h3 {
+    page-break-after: avoid;
+  }
+}
+
+body {
+  margin: 0;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  line-height: 20px;
+  color: #333333;
+  background-color: #ffffff;
+}
+
+a {
+  color: #0088cc;
+  text-decoration: none;
+}
+
+a:hover,
+a:focus {
+  color: #005580;
+  text-decoration: underline;
+}
+
+.img-rounded {
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+}
+
+.img-polaroid {
+  padding: 4px;
+  background-color: #fff;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+     -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+          box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.img-circle {
+  -webkit-border-radius: 500px;
+     -moz-border-radius: 500px;
+          border-radius: 500px;
+}
+
+.row {
+  margin-left: -20px;
+  *zoom: 1;
+}
+
+.row:before,
+.row:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.row:after {
+  clear: both;
+}
+
+[class*="span"] {
+  float: left;
+  min-height: 1px;
+  margin-left: 20px;
+}
+
+.container,
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+  width: 940px;
+}
+
+.span12 {
+  width: 940px;
+}
+
+.span11 {
+  width: 860px;
+}
+
+.span10 {
+  width: 780px;
+}
+
+.span9 {
+  width: 700px;
+}
+
+.span8 {
+  width: 620px;
+}
+
+.span7 {
+  width: 540px;
+}
+
+.span6 {
+  width: 460px;
+}
+
+.span5 {
+  width: 380px;
+}
+
+.span4 {
+  width: 300px;
+}
+
+.span3 {
+  width: 220px;
+}
+
+.span2 {
+  width: 140px;
+}
+
+.span1 {
+  width: 60px;
+}
+
+.offset12 {
+  margin-left: 980px;
+}
+
+.offset11 {
+  margin-left: 900px;
+}
+
+.offset10 {
+  margin-left: 820px;
+}
+
+.offset9 {
+  margin-left: 740px;
+}
+
+.offset8 {
+  margin-left: 660px;
+}
+
+.offset7 {
+  margin-left: 580px;
+}
+
+.offset6 {
+  margin-left: 500px;
+}
+
+.offset5 {
+  margin-left: 420px;
+}
+
+.offset4 {
+  margin-left: 340px;
+}
+
+.offset3 {
+  margin-left: 260px;
+}
+
+.offset2 {
+  margin-left: 180px;
+}
+
+.offset1 {
+  margin-left: 100px;
+}
+
+.row-fluid {
+  width: 100%;
+  *zoom: 1;
+}
+
+.row-fluid:before,
+.row-fluid:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.row-fluid:after {
+  clear: both;
+}
+
+.row-fluid [class*="span"] {
+  display: block;
+  float: left;
+  width: 100%;
+  min-height: 30px;
+  margin-left: 2.127659574468085%;
+  *margin-left: 2.074468085106383%;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+.row-fluid [class*="span"]:first-child {
+  margin-left: 0;
+}
+
+.row-fluid .controls-row [class*="span"] + [class*="span"] {
+  margin-left: 2.127659574468085%;
+}
+
+.row-fluid .span12 {
+  width: 100%;
+  *width: 99.94680851063829%;
+}
+
+.row-fluid .span11 {
+  width: 91.48936170212765%;
+  *width: 91.43617021276594%;
+}
+
+.row-fluid .span10 {
+  width: 82.97872340425532%;
+  *width: 82.92553191489361%;
+}
+
+.row-fluid .span9 {
+  width: 74.46808510638297%;
+  *width: 74.41489361702126%;
+}
+
+.row-fluid .span8 {
+  width: 65.95744680851064%;
+  *width: 65.90425531914893%;
+}
+
+.row-fluid .span7 {
+  width: 57.44680851063829%;
+  *width: 57.39361702127659%;
+}
+
+.row-fluid .span6 {
+  width: 48.93617021276595%;
+  *width: 48.88297872340425%;
+}
+
+.row-fluid .span5 {
+  width: 40.42553191489362%;
+  *width: 40.37234042553192%;
+}
+
+.row-fluid .span4 {
+  width: 31.914893617021278%;
+  *width: 31.861702127659576%;
+}
+
+.row-fluid .span3 {
+  width: 23.404255319148934%;
+  *width: 23.351063829787233%;
+}
+
+.row-fluid .span2 {
+  width: 14.893617021276595%;
+  *width: 14.840425531914894%;
+}
+
+.row-fluid .span1 {
+  width: 6.382978723404255%;
+  *width: 6.329787234042553%;
+}
+
+.row-fluid .offset12 {
+  margin-left: 104.25531914893617%;
+  *margin-left: 104.14893617021275%;
+}
+
+.row-fluid .offset12:first-child {
+  margin-left: 102.12765957446808%;
+  *margin-left: 102.02127659574467%;
+}
+
+.row-fluid .offset11 {
+  margin-left: 95.74468085106382%;
+  *margin-left: 95.6382978723404%;
+}
+
+.row-fluid .offset11:first-child {
+  margin-left: 93.61702127659574%;
+  *margin-left: 93.51063829787232%;
+}
+
+.row-fluid .offset10 {
+  margin-left: 87.23404255319149%;
+  *margin-left: 87.12765957446807%;
+}
+
+.row-fluid .offset10:first-child {
+  margin-left: 85.1063829787234%;
+  *margin-left: 84.99999999999999%;
+}
+
+.row-fluid .offset9 {
+  margin-left: 78.72340425531914%;
+  *margin-left: 78.61702127659572%;
+}
+
+.row-fluid .offset9:first-child {
+  margin-left: 76.59574468085106%;
+  *margin-left: 76.48936170212764%;
+}
+
+.row-fluid .offset8 {
+  margin-left: 70.2127659574468%;
+  *margin-left: 70.10638297872339%;
+}
+
+.row-fluid .offset8:first-child {
+  margin-left: 68.08510638297872%;
+  *margin-left: 67.9787234042553%;
+}
+
+.row-fluid .offset7 {
+  margin-left: 61.70212765957446%;
+  *margin-left: 61.59574468085106%;
+}
+
+.row-fluid .offset7:first-child {
+  margin-left: 59.574468085106375%;
+  *margin-left: 59.46808510638297%;
+}
+
+.row-fluid .offset6 {
+  margin-left: 53.191489361702125%;
+  *margin-left: 53.085106382978715%;
+}
+
+.row-fluid .offset6:first-child {
+  margin-left: 51.063829787234035%;
+  *margin-left: 50.95744680851063%;
+}
+
+.row-fluid .offset5 {
+  margin-left: 44.68085106382979%;
+  *margin-left: 44.57446808510638%;
+}
+
+.row-fluid .offset5:first-child {
+  margin-left: 42.5531914893617%;
+  *margin-left: 42.4468085106383%;
+}
+
+.row-fluid .offset4 {
+  margin-left: 36.170212765957444%;
+  *margin-left: 36.06382978723405%;
+}
+
+.row-fluid .offset4:first-child {
+  margin-left: 34.04255319148936%;
+  *margin-left: 33.93617021276596%;
+}
+
+.row-fluid .offset3 {
+  margin-left: 27.659574468085104%;
+  *margin-left: 27.5531914893617%;
+}
+
+.row-fluid .offset3:first-child {
+  margin-left: 25.53191489361702%;
+  *margin-left: 25.425531914893618%;
+}
+
+.row-fluid .offset2 {
+  margin-left: 19.148936170212764%;
+  *margin-left: 19.04255319148936%;
+}
+
+.row-fluid .offset2:first-child {
+  margin-left: 17.02127659574468%;
+  *margin-left: 16.914893617021278%;
+}
+
+.row-fluid .offset1 {
+  margin-left: 10.638297872340425%;
+  *margin-left: 10.53191489361702%;
+}
+
+.row-fluid .offset1:first-child {
+  margin-left: 8.51063829787234%;
+  *margin-left: 8.404255319148938%;
+}
+
+[class*="span"].hide,
+.row-fluid [class*="span"].hide {
+  display: none;
+}
+
+[class*="span"].pull-right,
+.row-fluid [class*="span"].pull-right {
+  float: right;
+}
+
+.container {
+  margin-right: auto;
+  margin-left: auto;
+  *zoom: 1;
+}
+
+.container:before,
+.container:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.container:after {
+  clear: both;
+}
+
+.container-fluid {
+  padding-right: 20px;
+  padding-left: 20px;
+  *zoom: 1;
+}
+
+.container-fluid:before,
+.container-fluid:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.container-fluid:after {
+  clear: both;
+}
+
+p {
+  margin: 0 0 10px;
+}
+
+.lead {
+  margin-bottom: 20px;
+  font-size: 21px;
+  font-weight: 200;
+  line-height: 30px;
+}
+
+small {
+  font-size: 85%;
+}
+
+strong {
+  font-weight: bold;
+}
+
+em {
+  font-style: italic;
+}
+
+cite {
+  font-style: normal;
+}
+
+.muted {
+  color: #999999;
+}
+
+a.muted:hover,
+a.muted:focus {
+  color: #808080;
+}
+
+.text-warning {
+  color: #c09853;
+}
+
+a.text-warning:hover,
+a.text-warning:focus {
+  color: #a47e3c;
+}
+
+.text-error {
+  color: #b94a48;
+}
+
+a.text-error:hover,
+a.text-error:focus {
+  color: #953b39;
+}
+
+.text-info {
+  color: #3a87ad;
+}
+
+a.text-info:hover,
+a.text-info:focus {
+  color: #2d6987;
+}
+
+.text-success {
+  color: #468847;
+}
+
+a.text-success:hover,
+a.text-success:focus {
+  color: #356635;
+}
+
+.text-left {
+  text-align: left;
+}
+
+.text-right {
+  text-align: right;
+}
+
+.text-center {
+  text-align: center;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  margin: 10px 0;
+  font-family: inherit;
+  font-weight: bold;
+  line-height: 20px;
+  color: inherit;
+  text-rendering: optimizelegibility;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small {
+  font-weight: normal;
+  line-height: 1;
+  color: #999999;
+}
+
+h1,
+h2,
+h3 {
+  line-height: 40px;
+}
+
+h1 {
+  font-size: 38.5px;
+}
+
+h2 {
+  font-size: 31.5px;
+}
+
+h3 {
+  font-size: 24.5px;
+}
+
+h4 {
+  font-size: 17.5px;
+}
+
+h5 {
+  font-size: 14px;
+}
+
+h6 {
+  font-size: 11.9px;
+}
+
+h1 small {
+  font-size: 24.5px;
+}
+
+h2 small {
+  font-size: 17.5px;
+}
+
+h3 small {
+  font-size: 14px;
+}
+
+h4 small {
+  font-size: 14px;
+}
+
+.page-header {
+  padding-bottom: 9px;
+  margin: 20px 0 30px;
+  border-bottom: 1px solid #eeeeee;
+}
+
+ul,
+ol {
+  padding: 0;
+  margin: 0 0 10px 25px;
+}
+
+ul ul,
+ul ol,
+ol ol,
+ol ul {
+  margin-bottom: 0;
+}
+
+li {
+  line-height: 20px;
+}
+
+ul.unstyled,
+ol.unstyled {
+  margin-left: 0;
+  list-style: none;
+}
+
+ul.inline,
+ol.inline {
+  margin-left: 0;
+  list-style: none;
+}
+
+ul.inline > li,
+ol.inline > li {
+  display: inline-block;
+  *display: inline;
+  padding-right: 5px;
+  padding-left: 5px;
+  *zoom: 1;
+}
+
+dl {
+  margin-bottom: 20px;
+}
+
+dt,
+dd {
+  line-height: 20px;
+}
+
+dt {
+  font-weight: bold;
+}
+
+dd {
+  margin-left: 10px;
+}
+
+.dl-horizontal {
+  *zoom: 1;
+}
+
+.dl-horizontal:before,
+.dl-horizontal:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.dl-horizontal:after {
+  clear: both;
+}
+
+.dl-horizontal dt {
+  float: left;
+  width: 160px;
+  overflow: hidden;
+  clear: left;
+  text-align: right;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.dl-horizontal dd {
+  margin-left: 180px;
+}
+
+hr {
+  margin: 20px 0;
+  border: 0;
+  border-top: 1px solid #eeeeee;
+  border-bottom: 1px solid #ffffff;
+}
+
+abbr[title],
+abbr[data-original-title] {
+  cursor: help;
+  border-bottom: 1px dotted #999999;
+}
+
+abbr.initialism {
+  font-size: 90%;
+  text-transform: uppercase;
+}
+
+blockquote {
+  padding: 0 0 0 15px;
+  margin: 0 0 20px;
+  border-left: 5px solid #eeeeee;
+}
+
+blockquote p {
+  margin-bottom: 0;
+  font-size: 17.5px;
+  font-weight: 300;
+  line-height: 1.25;
+}
+
+blockquote small {
+  display: block;
+  line-height: 20px;
+  color: #999999;
+}
+
+blockquote small:before {
+  content: '\2014 \00A0';
+}
+
+blockquote.pull-right {
+  float: right;
+  padding-right: 15px;
+  padding-left: 0;
+  border-right: 5px solid #eeeeee;
+  border-left: 0;
+}
+
+blockquote.pull-right p,
+blockquote.pull-right small {
+  text-align: right;
+}
+
+blockquote.pull-right small:before {
+  content: '';
+}
+
+blockquote.pull-right small:after {
+  content: '\00A0 \2014';
+}
+
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+  content: "";
+}
+
+address {
+  display: block;
+  margin-bottom: 20px;
+  font-style: normal;
+  line-height: 20px;
+}
+
+code,
+pre {
+  padding: 0 3px 2px;
+  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+  font-size: 12px;
+  color: #333333;
+  -webkit-border-radius: 3px;
+     -moz-border-radius: 3px;
+          border-radius: 3px;
+}
+
+code {
+  padding: 2px 4px;
+  color: #d14;
+  white-space: nowrap;
+  background-color: #f7f7f9;
+  border: 1px solid #e1e1e8;
+}
+
+pre {
+  display: block;
+  padding: 9.5px;
+  margin: 0 0 10px;
+  font-size: 13px;
+  line-height: 20px;
+  word-break: break-all;
+  word-wrap: break-word;
+  white-space: pre;
+  white-space: pre-wrap;
+  background-color: #f5f5f5;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.15);
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+pre.prettyprint {
+  margin-bottom: 20px;
+}
+
+pre code {
+  padding: 0;
+  color: inherit;
+  white-space: pre;
+  white-space: pre-wrap;
+  background-color: transparent;
+  border: 0;
+}
+
+.pre-scrollable {
+  max-height: 340px;
+  overflow-y: scroll;
+}
+
+form {
+  margin: 0 0 20px;
+}
+
+fieldset {
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+
+legend {
+  display: block;
+  width: 100%;
+  padding: 0;
+  margin-bottom: 20px;
+  font-size: 21px;
+  line-height: 40px;
+  color: #333333;
+  border: 0;
+  border-bottom: 1px solid #e5e5e5;
+}
+
+legend small {
+  font-size: 15px;
+  color: #999999;
+}
+
+label,
+input,
+button,
+select,
+textarea {
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 20px;
+}
+
+input,
+button,
+select,
+textarea {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+label {
+  display: block;
+  margin-bottom: 5px;
+}
+
+select,
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+  display: inline-block;
+  height: 20px;
+  padding: 4px 6px;
+  margin-bottom: 10px;
+  font-size: 14px;
+  line-height: 20px;
+  color: #555555;
+  vertical-align: middle;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+input,
+textarea,
+.uneditable-input {
+  width: 206px;
+}
+
+textarea {
+  height: auto;
+}
+
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+     -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+       -o-transition: border linear 0.2s, box-shadow linear 0.2s;
+          transition: border linear 0.2s, box-shadow linear 0.2s;
+}
+
+textarea:focus,
+input[type="text"]:focus,
+input[type="password"]:focus,
+input[type="datetime"]:focus,
+input[type="datetime-local"]:focus,
+input[type="date"]:focus,
+input[type="month"]:focus,
+input[type="time"]:focus,
+input[type="week"]:focus,
+input[type="number"]:focus,
+input[type="email"]:focus,
+input[type="url"]:focus,
+input[type="search"]:focus,
+input[type="tel"]:focus,
+input[type="color"]:focus,
+.uneditable-input:focus {
+  border-color: rgba(82, 168, 236, 0.8);
+  outline: 0;
+  outline: thin dotted \9;
+  /* IE6-9 */
+
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+  margin: 4px 0 0;
+  margin-top: 1px \9;
+  *margin-top: 0;
+  line-height: normal;
+}
+
+input[type="file"],
+input[type="image"],
+input[type="submit"],
+input[type="reset"],
+input[type="button"],
+input[type="radio"],
+input[type="checkbox"] {
+  width: auto;
+}
+
+select,
+input[type="file"] {
+  height: 30px;
+  /* In IE7, the height of the select element cannot be changed by height, only font-size */
+
+  *margin-top: 4px;
+  /* For IE7, add top margin to align select with labels */
+
+  line-height: 30px;
+}
+
+select {
+  width: 220px;
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+}
+
+select[multiple],
+select[size] {
+  height: auto;
+}
+
+select:focus,
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+.uneditable-input,
+.uneditable-textarea {
+  color: #999999;
+  cursor: not-allowed;
+  background-color: #fcfcfc;
+  border-color: #cccccc;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+     -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+}
+
+.uneditable-input {
+  overflow: hidden;
+  white-space: nowrap;
+}
+
+.uneditable-textarea {
+  width: auto;
+  height: auto;
+}
+
+input:-moz-placeholder,
+textarea:-moz-placeholder {
+  color: #999999;
+}
+
+input:-ms-input-placeholder,
+textarea:-ms-input-placeholder {
+  color: #999999;
+}
+
+input::-webkit-input-placeholder,
+textarea::-webkit-input-placeholder {
+  color: #999999;
+}
+
+.radio,
+.checkbox {
+  min-height: 20px;
+  padding-left: 20px;
+}
+
+.radio input[type="radio"],
+.checkbox input[type="checkbox"] {
+  float: left;
+  margin-left: -20px;
+}
+
+.controls > .radio:first-child,
+.controls > .checkbox:first-child {
+  padding-top: 5px;
+}
+
+.radio.inline,
+.checkbox.inline {
+  display: inline-block;
+  padding-top: 5px;
+  margin-bottom: 0;
+  vertical-align: middle;
+}
+
+.radio.inline + .radio.inline,
+.checkbox.inline + .checkbox.inline {
+  margin-left: 10px;
+}
+
+.input-mini {
+  width: 60px;
+}
+
+.input-small {
+  width: 90px;
+}
+
+.input-medium {
+  width: 150px;
+}
+
+.input-large {
+  width: 210px;
+}
+
+.input-xlarge {
+  width: 270px;
+}
+
+.input-xxlarge {
+  width: 530px;
+}
+
+input[class*="span"],
+select[class*="span"],
+textarea[class*="span"],
+.uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"] {
+  float: none;
+  margin-left: 0;
+}
+
+.input-append input[class*="span"],
+.input-append .uneditable-input[class*="span"],
+.input-prepend input[class*="span"],
+.input-prepend .uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"],
+.row-fluid .input-prepend [class*="span"],
+.row-fluid .input-append [class*="span"] {
+  display: inline-block;
+}
+
+input,
+textarea,
+.uneditable-input {
+  margin-left: 0;
+}
+
+.controls-row [class*="span"] + [class*="span"] {
+  margin-left: 20px;
+}
+
+input.span12,
+textarea.span12,
+.uneditable-input.span12 {
+  width: 926px;
+}
+
+input.span11,
+textarea.span11,
+.uneditable-input.span11 {
+  width: 846px;
+}
+
+input.span10,
+textarea.span10,
+.uneditable-input.span10 {
+  width: 766px;
+}
+
+input.span9,
+textarea.span9,
+.uneditable-input.span9 {
+  width: 686px;
+}
+
+input.span8,
+textarea.span8,
+.uneditable-input.span8 {
+  width: 606px;
+}
+
+input.span7,
+textarea.span7,
+.uneditable-input.span7 {
+  width: 526px;
+}
+
+input.span6,
+textarea.span6,
+.uneditable-input.span6 {
+  width: 446px;
+}
+
+input.span5,
+textarea.span5,
+.uneditable-input.span5 {
+  width: 366px;
+}
+
+input.span4,
+textarea.span4,
+.uneditable-input.span4 {
+  width: 286px;
+}
+
+input.span3,
+textarea.span3,
+.uneditable-input.span3 {
+  width: 206px;
+}
+
+input.span2,
+textarea.span2,
+.uneditable-input.span2 {
+  width: 126px;
+}
+
+input.span1,
+textarea.span1,
+.uneditable-input.span1 {
+  width: 46px;
+}
+
+.controls-row {
+  *zoom: 1;
+}
+
+.controls-row:before,
+.controls-row:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.controls-row:after {
+  clear: both;
+}
+
+.controls-row [class*="span"],
+.row-fluid .controls-row [class*="span"] {
+  float: left;
+}
+
+.controls-row .checkbox[class*="span"],
+.controls-row .radio[class*="span"] {
+  padding-top: 5px;
+}
+
+input[disabled],
+select[disabled],
+textarea[disabled],
+input[readonly],
+select[readonly],
+textarea[readonly] {
+  cursor: not-allowed;
+  background-color: #eeeeee;
+}
+
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"][readonly],
+input[type="checkbox"][readonly] {
+  background-color: transparent;
+}
+
+.control-group.warning .control-label,
+.control-group.warning .help-block,
+.control-group.warning .help-inline {
+  color: #c09853;
+}
+
+.control-group.warning .checkbox,
+.control-group.warning .radio,
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+  color: #c09853;
+}
+
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+  border-color: #c09853;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.warning input:focus,
+.control-group.warning select:focus,
+.control-group.warning textarea:focus {
+  border-color: #a47e3c;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+}
+
+.control-group.warning .input-prepend .add-on,
+.control-group.warning .input-append .add-on {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #c09853;
+}
+
+.control-group.error .control-label,
+.control-group.error .help-block,
+.control-group.error .help-inline {
+  color: #b94a48;
+}
+
+.control-group.error .checkbox,
+.control-group.error .radio,
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+  color: #b94a48;
+}
+
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+  border-color: #b94a48;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.error input:focus,
+.control-group.error select:focus,
+.control-group.error textarea:focus {
+  border-color: #953b39;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+}
+
+.control-group.error .input-prepend .add-on,
+.control-group.error .input-append .add-on {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #b94a48;
+}
+
+.control-group.success .control-label,
+.control-group.success .help-block,
+.control-group.success .help-inline {
+  color: #468847;
+}
+
+.control-group.success .checkbox,
+.control-group.success .radio,
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+  color: #468847;
+}
+
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+  border-color: #468847;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.success input:focus,
+.control-group.success select:focus,
+.control-group.success textarea:focus {
+  border-color: #356635;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+}
+
+.control-group.success .input-prepend .add-on,
+.control-group.success .input-append .add-on {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #468847;
+}
+
+.control-group.info .control-label,
+.control-group.info .help-block,
+.control-group.info .help-inline {
+  color: #3a87ad;
+}
+
+.control-group.info .checkbox,
+.control-group.info .radio,
+.control-group.info input,
+.control-group.info select,
+.control-group.info textarea {
+  color: #3a87ad;
+}
+
+.control-group.info input,
+.control-group.info select,
+.control-group.info textarea {
+  border-color: #3a87ad;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.control-group.info input:focus,
+.control-group.info select:focus,
+.control-group.info textarea:focus {
+  border-color: #2d6987;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+}
+
+.control-group.info .input-prepend .add-on,
+.control-group.info .input-append .add-on {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #3a87ad;
+}
+
+input:focus:invalid,
+textarea:focus:invalid,
+select:focus:invalid {
+  color: #b94a48;
+  border-color: #ee5f5b;
+}
+
+input:focus:invalid:focus,
+textarea:focus:invalid:focus,
+select:focus:invalid:focus {
+  border-color: #e9322d;
+  -webkit-box-shadow: 0 0 6px #f8b9b7;
+     -moz-box-shadow: 0 0 6px #f8b9b7;
+          box-shadow: 0 0 6px #f8b9b7;
+}
+
+.form-actions {
+  padding: 19px 20px 20px;
+  margin-top: 20px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border-top: 1px solid #e5e5e5;
+  *zoom: 1;
+}
+
+.form-actions:before,
+.form-actions:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.form-actions:after {
+  clear: both;
+}
+
+.help-block,
+.help-inline {
+  color: #595959;
+}
+
+.help-block {
+  display: block;
+  margin-bottom: 10px;
+}
+
+.help-inline {
+  display: inline-block;
+  *display: inline;
+  padding-left: 5px;
+  vertical-align: middle;
+  *zoom: 1;
+}
+
+.input-append,
+.input-prepend {
+  display: inline-block;
+  margin-bottom: 10px;
+  font-size: 0;
+  white-space: nowrap;
+  vertical-align: middle;
+}
+
+.input-append input,
+.input-prepend input,
+.input-append select,
+.input-prepend select,
+.input-append .uneditable-input,
+.input-prepend .uneditable-input,
+.input-append .dropdown-menu,
+.input-prepend .dropdown-menu,
+.input-append .popover,
+.input-prepend .popover {
+  font-size: 14px;
+}
+
+.input-append input,
+.input-prepend input,
+.input-append select,
+.input-prepend select,
+.input-append .uneditable-input,
+.input-prepend .uneditable-input {
+  position: relative;
+  margin-bottom: 0;
+  *margin-left: 0;
+  vertical-align: top;
+  -webkit-border-radius: 0 4px 4px 0;
+     -moz-border-radius: 0 4px 4px 0;
+          border-radius: 0 4px 4px 0;
+}
+
+.input-append input:focus,
+.input-prepend input:focus,
+.input-append select:focus,
+.input-prepend select:focus,
+.input-append .uneditable-input:focus,
+.input-prepend .uneditable-input:focus {
+  z-index: 2;
+}
+
+.input-append .add-on,
+.input-prepend .add-on {
+  display: inline-block;
+  width: auto;
+  height: 20px;
+  min-width: 16px;
+  padding: 4px 5px;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 20px;
+  text-align: center;
+  text-shadow: 0 1px 0 #ffffff;
+  background-color: #eeeeee;
+  border: 1px solid #ccc;
+}
+
+.input-append .add-on,
+.input-prepend .add-on,
+.input-append .btn,
+.input-prepend .btn,
+.input-append .btn-group > .dropdown-toggle,
+.input-prepend .btn-group > .dropdown-toggle {
+  vertical-align: top;
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.input-append .active,
+.input-prepend .active {
+  background-color: #a9dba9;
+  border-color: #46a546;
+}
+
+.input-prepend .add-on,
+.input-prepend .btn {
+  margin-right: -1px;
+}
+
+.input-prepend .add-on:first-child,
+.input-prepend .btn:first-child {
+  -webkit-border-radius: 4px 0 0 4px;
+     -moz-border-radius: 4px 0 0 4px;
+          border-radius: 4px 0 0 4px;
+}
+
+.input-append input,
+.input-append select,
+.input-append .uneditable-input {
+  -webkit-border-radius: 4px 0 0 4px;
+     -moz-border-radius: 4px 0 0 4px;
+          border-radius: 4px 0 0 4px;
+}
+
+.input-append input + .btn-group .btn:last-child,
+.input-append select + .btn-group .btn:last-child,
+.input-append .uneditable-input + .btn-group .btn:last-child {
+  -webkit-border-radius: 0 4px 4px 0;
+     -moz-border-radius: 0 4px 4px 0;
+          border-radius: 0 4px 4px 0;
+}
+
+.input-append .add-on,
+.input-append .btn,
+.input-append .btn-group {
+  margin-left: -1px;
+}
+
+.input-append .add-on:last-child,
+.input-append .btn:last-child,
+.input-append .btn-group:last-child > .dropdown-toggle {
+  -webkit-border-radius: 0 4px 4px 0;
+     -moz-border-radius: 0 4px 4px 0;
+          border-radius: 0 4px 4px 0;
+}
+
+.input-prepend.input-append input,
+.input-prepend.input-append select,
+.input-prepend.input-append .uneditable-input {
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.input-prepend.input-append input + .btn-group .btn,
+.input-prepend.input-append select + .btn-group .btn,
+.input-prepend.input-append .uneditable-input + .btn-group .btn {
+  -webkit-border-radius: 0 4px 4px 0;
+     -moz-border-radius: 0 4px 4px 0;
+          border-radius: 0 4px 4px 0;
+}
+
+.input-prepend.input-append .add-on:first-child,
+.input-prepend.input-append .btn:first-child {
+  margin-right: -1px;
+  -webkit-border-radius: 4px 0 0 4px;
+     -moz-border-radius: 4px 0 0 4px;
+          border-radius: 4px 0 0 4px;
+}
+
+.input-prepend.input-append .add-on:last-child,
+.input-prepend.input-append .btn:last-child {
+  margin-left: -1px;
+  -webkit-border-radius: 0 4px 4px 0;
+     -moz-border-radius: 0 4px 4px 0;
+          border-radius: 0 4px 4px 0;
+}
+
+.input-prepend.input-append .btn-group:first-child {
+  margin-left: 0;
+}
+
+input.search-query {
+  padding-right: 14px;
+  padding-right: 4px \9;
+  padding-left: 14px;
+  padding-left: 4px \9;
+  /* IE7-8 doesn't have border-radius, so don't indent the padding */
+
+  margin-bottom: 0;
+  -webkit-border-radius: 15px;
+     -moz-border-radius: 15px;
+          border-radius: 15px;
+}
+
+/* Allow for input prepend/append in search forms */
+
+.form-search .input-append .search-query,
+.form-search .input-prepend .search-query {
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.form-search .input-append .search-query {
+  -webkit-border-radius: 14px 0 0 14px;
+     -moz-border-radius: 14px 0 0 14px;
+          border-radius: 14px 0 0 14px;
+}
+
+.form-search .input-append .btn {
+  -webkit-border-radius: 0 14px 14px 0;
+     -moz-border-radius: 0 14px 14px 0;
+          border-radius: 0 14px 14px 0;
+}
+
+.form-search .input-prepend .search-query {
+  -webkit-border-radius: 0 14px 14px 0;
+     -moz-border-radius: 0 14px 14px 0;
+          border-radius: 0 14px 14px 0;
+}
+
+.form-search .input-prepend .btn {
+  -webkit-border-radius: 14px 0 0 14px;
+     -moz-border-radius: 14px 0 0 14px;
+          border-radius: 14px 0 0 14px;
+}
+
+.form-search input,
+.form-inline input,
+.form-horizontal input,
+.form-search textarea,
+.form-inline textarea,
+.form-horizontal textarea,
+.form-search select,
+.form-inline select,
+.form-horizontal select,
+.form-search .help-inline,
+.form-inline .help-inline,
+.form-horizontal .help-inline,
+.form-search .uneditable-input,
+.form-inline .uneditable-input,
+.form-horizontal .uneditable-input,
+.form-search .input-prepend,
+.form-inline .input-prepend,
+.form-horizontal .input-prepend,
+.form-search .input-append,
+.form-inline .input-append,
+.form-horizontal .input-append {
+  display: inline-block;
+  *display: inline;
+  margin-bottom: 0;
+  vertical-align: middle;
+  *zoom: 1;
+}
+
+.form-search .hide,
+.form-inline .hide,
+.form-horizontal .hide {
+  display: none;
+}
+
+.form-search label,
+.form-inline label,
+.form-search .btn-group,
+.form-inline .btn-group {
+  display: inline-block;
+}
+
+.form-search .input-append,
+.form-inline .input-append,
+.form-search .input-prepend,
+.form-inline .input-prepend {
+  margin-bottom: 0;
+}
+
+.form-search .radio,
+.form-search .checkbox,
+.form-inline .radio,
+.form-inline .checkbox {
+  padding-left: 0;
+  margin-bottom: 0;
+  vertical-align: middle;
+}
+
+.form-search .radio input[type="radio"],
+.form-search .checkbox input[type="checkbox"],
+.form-inline .radio input[type="radio"],
+.form-inline .checkbox input[type="checkbox"] {
+  float: left;
+  margin-right: 3px;
+  margin-left: 0;
+}
+
+.control-group {
+  margin-bottom: 10px;
+}
+
+legend + .control-group {
+  margin-top: 20px;
+  -webkit-margin-top-collapse: separate;
+}
+
+.form-horizontal .control-group {
+  margin-bottom: 20px;
+  *zoom: 1;
+}
+
+.form-horizontal .control-group:before,
+.form-horizontal .control-group:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.form-horizontal .control-group:after {
+  clear: both;
+}
+
+.form-horizontal .control-label {
+  float: left;
+  width: 160px;
+  padding-top: 5px;
+  text-align: right;
+}
+
+.form-horizontal .controls {
+  *display: inline-block;
+  *padding-left: 20px;
+  margin-left: 180px;
+  *margin-left: 0;
+}
+
+.form-horizontal .controls:first-child {
+  *padding-left: 180px;
+}
+
+.form-horizontal .help-block {
+  margin-bottom: 0;
+}
+
+.form-horizontal input + .help-block,
+.form-horizontal select + .help-block,
+.form-horizontal textarea + .help-block,
+.form-horizontal .uneditable-input + .help-block,
+.form-horizontal .input-prepend + .help-block,
+.form-horizontal .input-append + .help-block {
+  margin-top: 10px;
+}
+
+.form-horizontal .form-actions {
+  padding-left: 180px;
+}
+
+table {
+  max-width: 100%;
+  background-color: transparent;
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+.table {
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.table th,
+.table td {
+  padding: 8px;
+  line-height: 20px;
+  text-align: left;
+  vertical-align: top;
+  border-top: 1px solid #dddddd;
+}
+
+.table th {
+  font-weight: bold;
+}
+
+.table thead th {
+  vertical-align: bottom;
+}
+
+.table caption + thead tr:first-child th,
+.table caption + thead tr:first-child td,
+.table colgroup + thead tr:first-child th,
+.table colgroup + thead tr:first-child td,
+.table thead:first-child tr:first-child th,
+.table thead:first-child tr:first-child td {
+  border-top: 0;
+}
+
+.table tbody + tbody {
+  border-top: 2px solid #dddddd;
+}
+
+.table .table {
+  background-color: #ffffff;
+}
+
+.table-condensed th,
+.table-condensed td {
+  padding: 4px 5px;
+}
+
+.table-bordered {
+  border: 1px solid #dddddd;
+  border-collapse: separate;
+  *border-collapse: collapse;
+  border-left: 0;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+.table-bordered th,
+.table-bordered td {
+  border-left: 1px solid #dddddd;
+}
+
+.table-bordered caption + thead tr:first-child th,
+.table-bordered caption + tbody tr:first-child th,
+.table-bordered caption + tbody tr:first-child td,
+.table-bordered colgroup + thead tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child td,
+.table-bordered thead:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child td {
+  border-top: 0;
+}
+
+.table-bordered thead:first-child tr:first-child > th:first-child,
+.table-bordered tbody:first-child tr:first-child > td:first-child,
+.table-bordered tbody:first-child tr:first-child > th:first-child {
+  -webkit-border-top-left-radius: 4px;
+          border-top-left-radius: 4px;
+  -moz-border-radius-topleft: 4px;
+}
+
+.table-bordered thead:first-child tr:first-child > th:last-child,
+.table-bordered tbody:first-child tr:first-child > td:last-child,
+.table-bordered tbody:first-child tr:first-child > th:last-child {
+  -webkit-border-top-right-radius: 4px;
+          border-top-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+}
+
+.table-bordered thead:last-child tr:last-child > th:first-child,
+.table-bordered tbody:last-child tr:last-child > td:first-child,
+.table-bordered tbody:last-child tr:last-child > th:first-child,
+.table-bordered tfoot:last-child tr:last-child > td:first-child,
+.table-bordered tfoot:last-child tr:last-child > th:first-child {
+  -webkit-border-bottom-left-radius: 4px;
+          border-bottom-left-radius: 4px;
+  -moz-border-radius-bottomleft: 4px;
+}
+
+.table-bordered thead:last-child tr:last-child > th:last-child,
+.table-bordered tbody:last-child tr:last-child > td:last-child,
+.table-bordered tbody:last-child tr:last-child > th:last-child,
+.table-bordered tfoot:last-child tr:last-child > td:last-child,
+.table-bordered tfoot:last-child tr:last-child > th:last-child {
+  -webkit-border-bottom-right-radius: 4px;
+          border-bottom-right-radius: 4px;
+  -moz-border-radius-bottomright: 4px;
+}
+
+.table-bordered tfoot + tbody:last-child tr:last-child td:first-child {
+  -webkit-border-bottom-left-radius: 0;
+          border-bottom-left-radius: 0;
+  -moz-border-radius-bottomleft: 0;
+}
+
+.table-bordered tfoot + tbody:last-child tr:last-child td:last-child {
+  -webkit-border-bottom-right-radius: 0;
+          border-bottom-right-radius: 0;
+  -moz-border-radius-bottomright: 0;
+}
+
+.table-bordered caption + thead tr:first-child th:first-child,
+.table-bordered caption + tbody tr:first-child td:first-child,
+.table-bordered colgroup + thead tr:first-child th:first-child,
+.table-bordered colgroup + tbody tr:first-child td:first-child {
+  -webkit-border-top-left-radius: 4px;
+          border-top-left-radius: 4px;
+  -moz-border-radius-topleft: 4px;
+}
+
+.table-bordered caption + thead tr:first-child th:last-child,
+.table-bordered caption + tbody tr:first-child td:last-child,
+.table-bordered colgroup + thead tr:first-child th:last-child,
+.table-bordered colgroup + tbody tr:first-child td:last-child {
+  -webkit-border-top-right-radius: 4px;
+          border-top-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+}
+
+.table-striped tbody > tr:nth-child(odd) > td,
+.table-striped tbody > tr:nth-child(odd) > th {
+  background-color: #f9f9f9;
+}
+
+.table-hover tbody tr:hover > td,
+.table-hover tbody tr:hover > th {
+  background-color: #f5f5f5;
+}
+
+table td[class*="span"],
+table th[class*="span"],
+.row-fluid table td[class*="span"],
+.row-fluid table th[class*="span"] {
+  display: table-cell;
+  float: none;
+  margin-left: 0;
+}
+
+.table td.span1,
+.table th.span1 {
+  float: none;
+  width: 44px;
+  margin-left: 0;
+}
+
+.table td.span2,
+.table th.span2 {
+  float: none;
+  width: 124px;
+  margin-left: 0;
+}
+
+.table td.span3,
+.table th.span3 {
+  float: none;
+  width: 204px;
+  margin-left: 0;
+}
+
+.table td.span4,
+.table th.span4 {
+  float: none;
+  width: 284px;
+  margin-left: 0;
+}
+
+.table td.span5,
+.table th.span5 {
+  float: none;
+  width: 364px;
+  margin-left: 0;
+}
+
+.table td.span6,
+.table th.span6 {
+  float: none;
+  width: 444px;
+  margin-left: 0;
+}
+
+.table td.span7,
+.table th.span7 {
+  float: none;
+  width: 524px;
+  margin-left: 0;
+}
+
+.table td.span8,
+.table th.span8 {
+  float: none;
+  width: 604px;
+  margin-left: 0;
+}
+
+.table td.span9,
+.table th.span9 {
+  float: none;
+  width: 684px;
+  margin-left: 0;
+}
+
+.table td.span10,
+.table th.span10 {
+  float: none;
+  width: 764px;
+  margin-left: 0;
+}
+
+.table td.span11,
+.table th.span11 {
+  float: none;
+  width: 844px;
+  margin-left: 0;
+}
+
+.table td.span12,
+.table th.span12 {
+  float: none;
+  width: 924px;
+  margin-left: 0;
+}
+
+.table tbody tr.success > td {
+  background-color: #dff0d8;
+}
+
+.table tbody tr.error > td {
+  background-color: #f2dede;
+}
+
+.table tbody tr.warning > td {
+  background-color: #fcf8e3;
+}
+
+.table tbody tr.info > td {
+  background-color: #d9edf7;
+}
+
+.table-hover tbody tr.success:hover > td {
+  background-color: #d0e9c6;
+}
+
+.table-hover tbody tr.error:hover > td {
+  background-color: #ebcccc;
+}
+
+.table-hover tbody tr.warning:hover > td {
+  background-color: #faf2cc;
+}
+
+.table-hover tbody tr.info:hover > td {
+  background-color: #c4e3f3;
+}
+
+[class^="icon-"],
+[class*=" icon-"] {
+  display: inline-block;
+  width: 14px;
+  height: 14px;
+  margin-top: 1px;
+  *margin-right: .3em;
+  line-height: 14px;
+  vertical-align: text-top;
+  background-image: url("../img/glyphicons-halflings.png");
+  background-position: 14px 14px;
+  background-repeat: no-repeat;
+}
+
+/* White icons with optional class, or on hover/focus/active states of certain elements */
+
+.icon-white,
+.nav-pills > .active > a > [class^="icon-"],
+.nav-pills > .active > a > [class*=" icon-"],
+.nav-list > .active > a > [class^="icon-"],
+.nav-list > .active > a > [class*=" icon-"],
+.navbar-inverse .nav > .active > a > [class^="icon-"],
+.navbar-inverse .nav > .active > a > [class*=" icon-"],
+.dropdown-menu > li > a:hover > [class^="icon-"],
+.dropdown-menu > li > a:focus > [class^="icon-"],
+.dropdown-menu > li > a:hover > [class*=" icon-"],
+.dropdown-menu > li > a:focus > [class*=" icon-"],
+.dropdown-menu > .active > a > [class^="icon-"],
+.dropdown-menu > .active > a > [class*=" icon-"],
+.dropdown-submenu:hover > a > [class^="icon-"],
+.dropdown-submenu:focus > a > [class^="icon-"],
+.dropdown-submenu:hover > a > [class*=" icon-"],
+.dropdown-submenu:focus > a > [class*=" icon-"] {
+  background-image: url("../img/glyphicons-halflings-white.png");
+}
+
+.icon-glass {
+  background-position: 0      0;
+}
+
+.icon-music {
+  background-position: -24px 0;
+}
+
+.icon-search {
+  background-position: -48px 0;
+}
+
+.icon-envelope {
+  background-position: -72px 0;
+}
+
+.icon-heart {
+  background-position: -96px 0;
+}
+
+.icon-star {
+  background-position: -120px 0;
+}
+
+.icon-star-empty {
+  background-position: -144px 0;
+}
+
+.icon-user {
+  background-position: -168px 0;
+}
+
+.icon-film {
+  background-position: -192px 0;
+}
+
+.icon-th-large {
+  background-position: -216px 0;
+}
+
+.icon-th {
+  background-position: -240px 0;
+}
+
+.icon-th-list {
+  background-position: -264px 0;
+}
+
+.icon-ok {
+  background-position: -288px 0;
+}
+
+.icon-remove {
+  background-position: -312px 0;
+}
+
+.icon-zoom-in {
+  background-position: -336px 0;
+}
+
+.icon-zoom-out {
+  background-position: -360px 0;
+}
+
+.icon-off {
+  background-position: -384px 0;
+}
+
+.icon-signal {
+  background-position: -408px 0;
+}
+
+.icon-cog {
+  background-position: -432px 0;
+}
+
+.icon-trash {
+  background-position: -456px 0;
+}
+
+.icon-home {
+  background-position: 0 -24px;
+}
+
+.icon-file {
+  background-position: -24px -24px;
+}
+
+.icon-time {
+  background-position: -48px -24px;
+}
+
+.icon-road {
+  background-position: -72px -24px;
+}
+
+.icon-download-alt {
+  background-position: -96px -24px;
+}
+
+.icon-download {
+  background-position: -120px -24px;
+}
+
+.icon-upload {
+  background-position: -144px -24px;
+}
+
+.icon-inbox {
+  background-position: -168px -24px;
+}
+
+.icon-play-circle {
+  background-position: -192px -24px;
+}
+
+.icon-repeat {
+  background-position: -216px -24px;
+}
+
+.icon-refresh {
+  background-position: -240px -24px;
+}
+
+.icon-list-alt {
+  background-position: -264px -24px;
+}
+
+.icon-lock {
+  background-position: -287px -24px;
+}
+
+.icon-flag {
+  background-position: -312px -24px;
+}
+
+.icon-headphones {
+  background-position: -336px -24px;
+}
+
+.icon-volume-off {
+  background-position: -360px -24px;
+}
+
+.icon-volume-down {
+  background-position: -384px -24px;
+}
+
+.icon-volume-up {
+  background-position: -408px -24px;
+}
+
+.icon-qrcode {
+  background-position: -432px -24px;
+}
+
+.icon-barcode {
+  background-position: -456px -24px;
+}
+
+.icon-tag {
+  background-position: 0 -48px;
+}
+
+.icon-tags {
+  background-position: -25px -48px;
+}
+
+.icon-book {
+  background-position: -48px -48px;
+}
+
+.icon-bookmark {
+  background-position: -72px -48px;
+}
+
+.icon-print {
+  background-position: -96px -48px;
+}
+
+.icon-camera {
+  background-position: -120px -48px;
+}
+
+.icon-font {
+  background-position: -144px -48px;
+}
+
+.icon-bold {
+  background-position: -167px -48px;
+}
+
+.icon-italic {
+  background-position: -192px -48px;
+}
+
+.icon-text-height {
+  background-position: -216px -48px;
+}
+
+.icon-text-width {
+  background-position: -240px -48px;
+}
+
+.icon-align-left {
+  background-position: -264px -48px;
+}
+
+.icon-align-center {
+  background-position: -288px -48px;
+}
+
+.icon-align-right {
+  background-position: -312px -48px;
+}
+
+.icon-align-justify {
+  background-position: -336px -48px;
+}
+
+.icon-list {
+  background-position: -360px -48px;
+}
+
+.icon-indent-left {
+  background-position: -384px -48px;
+}
+
+.icon-indent-right {
+  background-position: -408px -48px;
+}
+
+.icon-facetime-video {
+  background-position: -432px -48px;
+}
+
+.icon-picture {
+  background-position: -456px -48px;
+}
+
+.icon-pencil {
+  background-position: 0 -72px;
+}
+
+.icon-map-marker {
+  background-position: -24px -72px;
+}
+
+.icon-adjust {
+  background-position: -48px -72px;
+}
+
+.icon-tint {
+  background-position: -72px -72px;
+}
+
+.icon-edit {
+  background-position: -96px -72px;
+}
+
+.icon-share {
+  background-position: -120px -72px;
+}
+
+.icon-check {
+  background-position: -144px -72px;
+}
+
+.icon-move {
+  background-position: -168px -72px;
+}
+
+.icon-step-backward {
+  background-position: -192px -72px;
+}
+
+.icon-fast-backward {
+  background-position: -216px -72px;
+}
+
+.icon-backward {
+  background-position: -240px -72px;
+}
+
+.icon-play {
+  background-position: -264px -72px;
+}
+
+.icon-pause {
+  background-position: -288px -72px;
+}
+
+.icon-stop {
+  background-position: -312px -72px;
+}
+
+.icon-forward {
+  background-position: -336px -72px;
+}
+
+.icon-fast-forward {
+  background-position: -360px -72px;
+}
+
+.icon-step-forward {
+  background-position: -384px -72px;
+}
+
+.icon-eject {
+  background-position: -408px -72px;
+}
+
+.icon-chevron-left {
+  background-position: -432px -72px;
+}
+
+.icon-chevron-right {
+  background-position: -456px -72px;
+}
+
+.icon-plus-sign {
+  background-position: 0 -96px;
+}
+
+.icon-minus-sign {
+  background-position: -24px -96px;
+}
+
+.icon-remove-sign {
+  background-position: -48px -96px;
+}
+
+.icon-ok-sign {
+  background-position: -72px -96px;
+}
+
+.icon-question-sign {
+  background-position: -96px -96px;
+}
+
+.icon-info-sign {
+  background-position: -120px -96px;
+}
+
+.icon-screenshot {
+  background-position: -144px -96px;
+}
+
+.icon-remove-circle {
+  background-position: -168px -96px;
+}
+
+.icon-ok-circle {
+  background-position: -192px -96px;
+}
+
+.icon-ban-circle {
+  background-position: -216px -96px;
+}
+
+.icon-arrow-left {
+  background-position: -240px -96px;
+}
+
+.icon-arrow-right {
+  background-position: -264px -96px;
+}
+
+.icon-arrow-up {
+  background-position: -289px -96px;
+}
+
+.icon-arrow-down {
+  background-position: -312px -96px;
+}
+
+.icon-share-alt {
+  background-position: -336px -96px;
+}
+
+.icon-resize-full {
+  background-position: -360px -96px;
+}
+
+.icon-resize-small {
+  background-position: -384px -96px;
+}
+
+.icon-plus {
+  background-position: -408px -96px;
+}
+
+.icon-minus {
+  background-position: -433px -96px;
+}
+
+.icon-asterisk {
+  background-position: -456px -96px;
+}
+
+.icon-exclamation-sign {
+  background-position: 0 -120px;
+}
+
+.icon-gift {
+  background-position: -24px -120px;
+}
+
+.icon-leaf {
+  background-position: -48px -120px;
+}
+
+.icon-fire {
+  background-position: -72px -120px;
+}
+
+.icon-eye-open {
+  background-position: -96px -120px;
+}
+
+.icon-eye-close {
+  background-position: -120px -120px;
+}
+
+.icon-warning-sign {
+  background-position: -144px -120px;
+}
+
+.icon-plane {
+  background-position: -168px -120px;
+}
+
+.icon-calendar {
+  background-position: -192px -120px;
+}
+
+.icon-random {
+  width: 16px;
+  background-position: -216px -120px;
+}
+
+.icon-comment {
+  background-position: -240px -120px;
+}
+
+.icon-magnet {
+  background-position: -264px -120px;
+}
+
+.icon-chevron-up {
+  background-position: -288px -120px;
+}
+
+.icon-chevron-down {
+  background-position: -313px -119px;
+}
+
+.icon-retweet {
+  background-position: -336px -120px;
+}
+
+.icon-shopping-cart {
+  background-position: -360px -120px;
+}
+
+.icon-folder-close {
+  width: 16px;
+  background-position: -384px -120px;
+}
+
+.icon-folder-open {
+  width: 16px;
+  background-position: -408px -120px;
+}
+
+.icon-resize-vertical {
+  background-position: -432px -119px;
+}
+
+.icon-resize-horizontal {
+  background-position: -456px -118px;
+}
+
+.icon-hdd {
+  background-position: 0 -144px;
+}
+
+.icon-bullhorn {
+  background-position: -24px -144px;
+}
+
+.icon-bell {
+  background-position: -48px -144px;
+}
+
+.icon-certificate {
+  background-position: -72px -144px;
+}
+
+.icon-thumbs-up {
+  background-position: -96px -144px;
+}
+
+.icon-thumbs-down {
+  background-position: -120px -144px;
+}
+
+.icon-hand-right {
+  background-position: -144px -144px;
+}
+
+.icon-hand-left {
+  background-position: -168px -144px;
+}
+
+.icon-hand-up {
+  background-position: -192px -144px;
+}
+
+.icon-hand-down {
+  background-position: -216px -144px;
+}
+
+.icon-circle-arrow-right {
+  background-position: -240px -144px;
+}
+
+.icon-circle-arrow-left {
+  background-position: -264px -144px;
+}
+
+.icon-circle-arrow-up {
+  background-position: -288px -144px;
+}
+
+.icon-circle-arrow-down {
+  background-position: -312px -144px;
+}
+
+.icon-globe {
+  background-position: -336px -144px;
+}
+
+.icon-wrench {
+  background-position: -360px -144px;
+}
+
+.icon-tasks {
+  background-position: -384px -144px;
+}
+
+.icon-filter {
+  background-position: -408px -144px;
+}
+
+.icon-briefcase {
+  background-position: -432px -144px;
+}
+
+.icon-fullscreen {
+  background-position: -456px -144px;
+}
+
+.dropup,
+.dropdown {
+  position: relative;
+}
+
+.dropdown-toggle {
+  *margin-bottom: -3px;
+}
+
+.dropdown-toggle:active,
+.open .dropdown-toggle {
+  outline: 0;
+}
+
+.caret {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  vertical-align: top;
+  border-top: 4px solid #000000;
+  border-right: 4px solid transparent;
+  border-left: 4px solid transparent;
+  content: "";
+}
+
+.dropdown .caret {
+  margin-top: 8px;
+  margin-left: 2px;
+}
+
+.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  display: none;
+  float: left;
+  min-width: 160px;
+  padding: 5px 0;
+  margin: 2px 0 0;
+  list-style: none;
+  background-color: #ffffff;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  *border-right-width: 2px;
+  *border-bottom-width: 2px;
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+     -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+          box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  -webkit-background-clip: padding-box;
+     -moz-background-clip: padding;
+          background-clip: padding-box;
+}
+
+.dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+
+.dropdown-menu .divider {
+  *width: 100%;
+  height: 1px;
+  margin: 9px 1px;
+  *margin: -5px 0 5px;
+  overflow: hidden;
+  background-color: #e5e5e5;
+  border-bottom: 1px solid #ffffff;
+}
+
+.dropdown-menu > li > a {
+  display: block;
+  padding: 3px 20px;
+  clear: both;
+  font-weight: normal;
+  line-height: 20px;
+  color: #333333;
+  white-space: nowrap;
+}
+
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus,
+.dropdown-submenu:hover > a,
+.dropdown-submenu:focus > a {
+  color: #ffffff;
+  text-decoration: none;
+  background-color: #0081c2;
+  background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+  background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+  background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+  color: #ffffff;
+  text-decoration: none;
+  background-color: #0081c2;
+  background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+  background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+  background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+  background-repeat: repeat-x;
+  outline: 0;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+
+.dropdown-menu > .disabled > a,
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  color: #999999;
+}
+
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  text-decoration: none;
+  cursor: default;
+  background-color: transparent;
+  background-image: none;
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.open {
+  *z-index: 1000;
+}
+
+.open > .dropdown-menu {
+  display: block;
+}
+
+.dropdown-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 990;
+}
+
+.pull-right > .dropdown-menu {
+  right: 0;
+  left: auto;
+}
+
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+  border-top: 0;
+  border-bottom: 4px solid #000000;
+  content: "";
+}
+
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+  top: auto;
+  bottom: 100%;
+  margin-bottom: 1px;
+}
+
+.dropdown-submenu {
+  position: relative;
+}
+
+.dropdown-submenu > .dropdown-menu {
+  top: 0;
+  left: 100%;
+  margin-top: -6px;
+  margin-left: -1px;
+  -webkit-border-radius: 0 6px 6px 6px;
+     -moz-border-radius: 0 6px 6px 6px;
+          border-radius: 0 6px 6px 6px;
+}
+
+.dropdown-submenu:hover > .dropdown-menu {
+  display: block;
+}
+
+.dropup .dropdown-submenu > .dropdown-menu {
+  top: auto;
+  bottom: 0;
+  margin-top: 0;
+  margin-bottom: -2px;
+  -webkit-border-radius: 5px 5px 5px 0;
+     -moz-border-radius: 5px 5px 5px 0;
+          border-radius: 5px 5px 5px 0;
+}
+
+.dropdown-submenu > a:after {
+  display: block;
+  float: right;
+  width: 0;
+  height: 0;
+  margin-top: 5px;
+  margin-right: -10px;
+  border-color: transparent;
+  border-left-color: #cccccc;
+  border-style: solid;
+  border-width: 5px 0 5px 5px;
+  content: " ";
+}
+
+.dropdown-submenu:hover > a:after {
+  border-left-color: #ffffff;
+}
+
+.dropdown-submenu.pull-left {
+  float: none;
+}
+
+.dropdown-submenu.pull-left > .dropdown-menu {
+  left: -100%;
+  margin-left: 10px;
+  -webkit-border-radius: 6px 0 6px 6px;
+     -moz-border-radius: 6px 0 6px 6px;
+          border-radius: 6px 0 6px 6px;
+}
+
+.dropdown .dropdown-menu .nav-header {
+  padding-right: 20px;
+  padding-left: 20px;
+}
+
+.typeahead {
+  z-index: 1051;
+  margin-top: 2px;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+.well {
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.well blockquote {
+  border-color: #ddd;
+  border-color: rgba(0, 0, 0, 0.15);
+}
+
+.well-large {
+  padding: 24px;
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+}
+
+.well-small {
+  padding: 9px;
+  -webkit-border-radius: 3px;
+     -moz-border-radius: 3px;
+          border-radius: 3px;
+}
+
+.fade {
+  opacity: 0;
+  -webkit-transition: opacity 0.15s linear;
+     -moz-transition: opacity 0.15s linear;
+       -o-transition: opacity 0.15s linear;
+          transition: opacity 0.15s linear;
+}
+
+.fade.in {
+  opacity: 1;
+}
+
+.collapse {
+  position: relative;
+  height: 0;
+  overflow: hidden;
+  -webkit-transition: height 0.35s ease;
+     -moz-transition: height 0.35s ease;
+       -o-transition: height 0.35s ease;
+          transition: height 0.35s ease;
+}
+
+.collapse.in {
+  height: auto;
+}
+
+.close {
+  float: right;
+  font-size: 20px;
+  font-weight: bold;
+  line-height: 20px;
+  color: #000000;
+  text-shadow: 0 1px 0 #ffffff;
+  opacity: 0.2;
+  filter: alpha(opacity=20);
+}
+
+.close:hover,
+.close:focus {
+  color: #000000;
+  text-decoration: none;
+  cursor: pointer;
+  opacity: 0.4;
+  filter: alpha(opacity=40);
+}
+
+button.close {
+  padding: 0;
+  cursor: pointer;
+  background: transparent;
+  border: 0;
+  -webkit-appearance: none;
+}
+
+.btn {
+  display: inline-block;
+  *display: inline;
+  padding: 4px 12px;
+  margin-bottom: 0;
+  *margin-left: .3em;
+  font-size: 14px;
+  line-height: 20px;
+  color: #333333;
+  text-align: center;
+  text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+  vertical-align: middle;
+  cursor: pointer;
+  background-color: #f5f5f5;
+  *background-color: #e6e6e6;
+  background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+  background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+  background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+  background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
+  background-repeat: repeat-x;
+  border: 1px solid #cccccc;
+  *border: 0;
+  border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  border-bottom-color: #b3b3b3;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  *zoom: 1;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+     -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn:hover,
+.btn:focus,
+.btn:active,
+.btn.active,
+.btn.disabled,
+.btn[disabled] {
+  color: #333333;
+  background-color: #e6e6e6;
+  *background-color: #d9d9d9;
+}
+
+.btn:active,
+.btn.active {
+  background-color: #cccccc \9;
+}
+
+.btn:first-child {
+  *margin-left: 0;
+}
+
+.btn:hover,
+.btn:focus {
+  color: #333333;
+  text-decoration: none;
+  background-position: 0 -15px;
+  -webkit-transition: background-position 0.1s linear;
+     -moz-transition: background-position 0.1s linear;
+       -o-transition: background-position 0.1s linear;
+          transition: background-position 0.1s linear;
+}
+
+.btn:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+
+.btn.active,
+.btn:active {
+  background-image: none;
+  outline: 0;
+  -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+     -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn.disabled,
+.btn[disabled] {
+  cursor: default;
+  background-image: none;
+  opacity: 0.65;
+  filter: alpha(opacity=65);
+  -webkit-box-shadow: none;
+     -moz-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn-large {
+  padding: 11px 19px;
+  font-size: 17.5px;
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+}
+
+.btn-large [class^="icon-"],
+.btn-large [class*=" icon-"] {
+  margin-top: 4px;
+}
+
+.btn-small {
+  padding: 2px 10px;
+  font-size: 11.9px;
+  -webkit-border-radius: 3px;
+     -moz-border-radius: 3px;
+          border-radius: 3px;
+}
+
+.btn-small [class^="icon-"],
+.btn-small [class*=" icon-"] {
+  margin-top: 0;
+}
+
+.btn-mini [class^="icon-"],
+.btn-mini [class*=" icon-"] {
+  margin-top: -1px;
+}
+
+.btn-mini {
+  padding: 0 6px;
+  font-size: 10.5px;
+  -webkit-border-radius: 3px;
+     -moz-border-radius: 3px;
+          border-radius: 3px;
+}
+
+.btn-block {
+  display: block;
+  width: 100%;
+  padding-right: 0;
+  padding-left: 0;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+.btn-block + .btn-block {
+  margin-top: 5px;
+}
+
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+  width: 100%;
+}
+
+.btn-primary.active,
+.btn-warning.active,
+.btn-danger.active,
+.btn-success.active,
+.btn-info.active,
+.btn-inverse.active {
+  color: rgba(255, 255, 255, 0.75);
+}
+
+.btn-primary {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #006dcc;
+  *background-color: #0044cc;
+  background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+  background-image: linear-gradient(to bottom, #0088cc, #0044cc);
+  background-repeat: repeat-x;
+  border-color: #0044cc #0044cc #002a80;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary:active,
+.btn-primary.active,
+.btn-primary.disabled,
+.btn-primary[disabled] {
+  color: #ffffff;
+  background-color: #0044cc;
+  *background-color: #003bb3;
+}
+
+.btn-primary:active,
+.btn-primary.active {
+  background-color: #003399 \9;
+}
+
+.btn-warning {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #faa732;
+  *background-color: #f89406;
+  background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+  background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+  background-image: -o-linear-gradient(top, #fbb450, #f89406);
+  background-image: linear-gradient(to bottom, #fbb450, #f89406);
+  background-repeat: repeat-x;
+  border-color: #f89406 #f89406 #ad6704;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-warning:hover,
+.btn-warning:focus,
+.btn-warning:active,
+.btn-warning.active,
+.btn-warning.disabled,
+.btn-warning[disabled] {
+  color: #ffffff;
+  background-color: #f89406;
+  *background-color: #df8505;
+}
+
+.btn-warning:active,
+.btn-warning.active {
+  background-color: #c67605 \9;
+}
+
+.btn-danger {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #da4f49;
+  *background-color: #bd362f;
+  background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));
+  background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: -o-linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: linear-gradient(to bottom, #ee5f5b, #bd362f);
+  background-repeat: repeat-x;
+  border-color: #bd362f #bd362f #802420;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-danger:hover,
+.btn-danger:focus,
+.btn-danger:active,
+.btn-danger.active,
+.btn-danger.disabled,
+.btn-danger[disabled] {
+  color: #ffffff;
+  background-color: #bd362f;
+  *background-color: #a9302a;
+}
+
+.btn-danger:active,
+.btn-danger.active {
+  background-color: #942a25 \9;
+}
+
+.btn-success {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #5bb75b;
+  *background-color: #51a351;
+  background-image: -moz-linear-gradient(top, #62c462, #51a351);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));
+  background-image: -webkit-linear-gradient(top, #62c462, #51a351);
+  background-image: -o-linear-gradient(top, #62c462, #51a351);
+  background-image: linear-gradient(to bottom, #62c462, #51a351);
+  background-repeat: repeat-x;
+  border-color: #51a351 #51a351 #387038;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-success:hover,
+.btn-success:focus,
+.btn-success:active,
+.btn-success.active,
+.btn-success.disabled,
+.btn-success[disabled] {
+  color: #ffffff;
+  background-color: #51a351;
+  *background-color: #499249;
+}
+
+.btn-success:active,
+.btn-success.active {
+  background-color: #408140 \9;
+}
+
+.btn-info {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #49afcd;
+  *background-color: #2f96b4;
+  background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));
+  background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4);
+  background-image: -o-linear-gradient(top, #5bc0de, #2f96b4);
+  background-image: linear-gradient(to bottom, #5bc0de, #2f96b4);
+  background-repeat: repeat-x;
+  border-color: #2f96b4 #2f96b4 #1f6377;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-info:hover,
+.btn-info:focus,
+.btn-info:active,
+.btn-info.active,
+.btn-info.disabled,
+.btn-info[disabled] {
+  color: #ffffff;
+  background-color: #2f96b4;
+  *background-color: #2a85a0;
+}
+
+.btn-info:active,
+.btn-info.active {
+  background-color: #24748c \9;
+}
+
+.btn-inverse {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #363636;
+  *background-color: #222222;
+  background-image: -moz-linear-gradient(top, #444444, #222222);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));
+  background-image: -webkit-linear-gradient(top, #444444, #222222);
+  background-image: -o-linear-gradient(top, #444444, #222222);
+  background-image: linear-gradient(to bottom, #444444, #222222);
+  background-repeat: repeat-x;
+  border-color: #222222 #222222 #000000;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.btn-inverse:hover,
+.btn-inverse:focus,
+.btn-inverse:active,
+.btn-inverse.active,
+.btn-inverse.disabled,
+.btn-inverse[disabled] {
+  color: #ffffff;
+  background-color: #222222;
+  *background-color: #151515;
+}
+
+.btn-inverse:active,
+.btn-inverse.active {
+  background-color: #080808 \9;
+}
+
+button.btn,
+input[type="submit"].btn {
+  *padding-top: 3px;
+  *padding-bottom: 3px;
+}
+
+button.btn::-moz-focus-inner,
+input[type="submit"].btn::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+
+button.btn.btn-large,
+input[type="submit"].btn.btn-large {
+  *padding-top: 7px;
+  *padding-bottom: 7px;
+}
+
+button.btn.btn-small,
+input[type="submit"].btn.btn-small {
+  *padding-top: 3px;
+  *padding-bottom: 3px;
+}
+
+button.btn.btn-mini,
+input[type="submit"].btn.btn-mini {
+  *padding-top: 1px;
+  *padding-bottom: 1px;
+}
+
+.btn-link,
+.btn-link:active,
+.btn-link[disabled] {
+  background-color: transparent;
+  background-image: none;
+  -webkit-box-shadow: none;
+     -moz-box-shadow: none;
+          box-shadow: none;
+}
+
+.btn-link {
+  color: #0088cc;
+  cursor: pointer;
+  border-color: transparent;
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.btn-link:hover,
+.btn-link:focus {
+  color: #005580;
+  text-decoration: underline;
+  background-color: transparent;
+}
+
+.btn-link[disabled]:hover,
+.btn-link[disabled]:focus {
+  color: #333333;
+  text-decoration: none;
+}
+
+.btn-group {
+  position: relative;
+  display: inline-block;
+  *display: inline;
+  *margin-left: .3em;
+  font-size: 0;
+  white-space: nowrap;
+  vertical-align: middle;
+  *zoom: 1;
+}
+
+.btn-group:first-child {
+  *margin-left: 0;
+}
+
+.btn-group + .btn-group {
+  margin-left: 5px;
+}
+
+.btn-toolbar {
+  margin-top: 10px;
+  margin-bottom: 10px;
+  font-size: 0;
+}
+
+.btn-toolbar > .btn + .btn,
+.btn-toolbar > .btn-group + .btn,
+.btn-toolbar > .btn + .btn-group {
+  margin-left: 5px;
+}
+
+.btn-group > .btn {
+  position: relative;
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.btn-group > .btn + .btn {
+  margin-left: -1px;
+}
+
+.btn-group > .btn,
+.btn-group > .dropdown-menu,
+.btn-group > .popover {
+  font-size: 14px;
+}
+
+.btn-group > .btn-mini {
+  font-size: 10.5px;
+}
+
+.btn-group > .btn-small {
+  font-size: 11.9px;
+}
+
+.btn-group > .btn-large {
+  font-size: 17.5px;
+}
+
+.btn-group > .btn:first-child {
+  margin-left: 0;
+  -webkit-border-bottom-left-radius: 4px;
+          border-bottom-left-radius: 4px;
+  -webkit-border-top-left-radius: 4px;
+          border-top-left-radius: 4px;
+  -moz-border-radius-bottomleft: 4px;
+  -moz-border-radius-topleft: 4px;
+}
+
+.btn-group > .btn:last-child,
+.btn-group > .dropdown-toggle {
+  -webkit-border-top-right-radius: 4px;
+          border-top-right-radius: 4px;
+  -webkit-border-bottom-right-radius: 4px;
+          border-bottom-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+  -moz-border-radius-bottomright: 4px;
+}
+
+.btn-group > .btn.large:first-child {
+  margin-left: 0;
+  -webkit-border-bottom-left-radius: 6px;
+          border-bottom-left-radius: 6px;
+  -webkit-border-top-left-radius: 6px;
+          border-top-left-radius: 6px;
+  -moz-border-radius-bottomleft: 6px;
+  -moz-border-radius-topleft: 6px;
+}
+
+.btn-group > .btn.large:last-child,
+.btn-group > .large.dropdown-toggle {
+  -webkit-border-top-right-radius: 6px;
+          border-top-right-radius: 6px;
+  -webkit-border-bottom-right-radius: 6px;
+          border-bottom-right-radius: 6px;
+  -moz-border-radius-topright: 6px;
+  -moz-border-radius-bottomright: 6px;
+}
+
+.btn-group > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group > .btn:active,
+.btn-group > .btn.active {
+  z-index: 2;
+}
+
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+  outline: 0;
+}
+
+.btn-group > .btn + .dropdown-toggle {
+  *padding-top: 5px;
+  padding-right: 8px;
+  *padding-bottom: 5px;
+  padding-left: 8px;
+  -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+     -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn-group > .btn-mini + .dropdown-toggle {
+  *padding-top: 2px;
+  padding-right: 5px;
+  *padding-bottom: 2px;
+  padding-left: 5px;
+}
+
+.btn-group > .btn-small + .dropdown-toggle {
+  *padding-top: 5px;
+  *padding-bottom: 4px;
+}
+
+.btn-group > .btn-large + .dropdown-toggle {
+  *padding-top: 7px;
+  padding-right: 12px;
+  *padding-bottom: 7px;
+  padding-left: 12px;
+}
+
+.btn-group.open .dropdown-toggle {
+  background-image: none;
+  -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+     -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.btn-group.open .btn.dropdown-toggle {
+  background-color: #e6e6e6;
+}
+
+.btn-group.open .btn-primary.dropdown-toggle {
+  background-color: #0044cc;
+}
+
+.btn-group.open .btn-warning.dropdown-toggle {
+  background-color: #f89406;
+}
+
+.btn-group.open .btn-danger.dropdown-toggle {
+  background-color: #bd362f;
+}
+
+.btn-group.open .btn-success.dropdown-toggle {
+  background-color: #51a351;
+}
+
+.btn-group.open .btn-info.dropdown-toggle {
+  background-color: #2f96b4;
+}
+
+.btn-group.open .btn-inverse.dropdown-toggle {
+  background-color: #222222;
+}
+
+.btn .caret {
+  margin-top: 8px;
+  margin-left: 0;
+}
+
+.btn-large .caret {
+  margin-top: 6px;
+}
+
+.btn-large .caret {
+  border-top-width: 5px;
+  border-right-width: 5px;
+  border-left-width: 5px;
+}
+
+.btn-mini .caret,
+.btn-small .caret {
+  margin-top: 8px;
+}
+
+.dropup .btn-large .caret {
+  border-bottom-width: 5px;
+}
+
+.btn-primary .caret,
+.btn-warning .caret,
+.btn-danger .caret,
+.btn-info .caret,
+.btn-success .caret,
+.btn-inverse .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+.btn-group-vertical {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+}
+
+.btn-group-vertical > .btn {
+  display: block;
+  float: none;
+  max-width: 100%;
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.btn-group-vertical > .btn + .btn {
+  margin-top: -1px;
+  margin-left: 0;
+}
+
+.btn-group-vertical > .btn:first-child {
+  -webkit-border-radius: 4px 4px 0 0;
+     -moz-border-radius: 4px 4px 0 0;
+          border-radius: 4px 4px 0 0;
+}
+
+.btn-group-vertical > .btn:last-child {
+  -webkit-border-radius: 0 0 4px 4px;
+     -moz-border-radius: 0 0 4px 4px;
+          border-radius: 0 0 4px 4px;
+}
+
+.btn-group-vertical > .btn-large:first-child {
+  -webkit-border-radius: 6px 6px 0 0;
+     -moz-border-radius: 6px 6px 0 0;
+          border-radius: 6px 6px 0 0;
+}
+
+.btn-group-vertical > .btn-large:last-child {
+  -webkit-border-radius: 0 0 6px 6px;
+     -moz-border-radius: 0 0 6px 6px;
+          border-radius: 0 0 6px 6px;
+}
+
+.alert {
+  padding: 8px 35px 8px 14px;
+  margin-bottom: 20px;
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+  background-color: #fcf8e3;
+  border: 1px solid #fbeed5;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+.alert,
+.alert h4 {
+  color: #c09853;
+}
+
+.alert h4 {
+  margin: 0;
+}
+
+.alert .close {
+  position: relative;
+  top: -2px;
+  right: -21px;
+  line-height: 20px;
+}
+
+.alert-success {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+}
+
+.alert-success h4 {
+  color: #468847;
+}
+
+.alert-danger,
+.alert-error {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #eed3d7;
+}
+
+.alert-danger h4,
+.alert-error h4 {
+  color: #b94a48;
+}
+
+.alert-info {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+}
+
+.alert-info h4 {
+  color: #3a87ad;
+}
+
+.alert-block {
+  padding-top: 14px;
+  padding-bottom: 14px;
+}
+
+.alert-block > p,
+.alert-block > ul {
+  margin-bottom: 0;
+}
+
+.alert-block p + p {
+  margin-top: 5px;
+}
+
+.nav {
+  margin-bottom: 20px;
+  margin-left: 0;
+  list-style: none;
+}
+
+.nav > li > a {
+  display: block;
+}
+
+.nav > li > a:hover,
+.nav > li > a:focus {
+  text-decoration: none;
+  background-color: #eeeeee;
+}
+
+.nav > li > a > img {
+  max-width: none;
+}
+
+.nav > .pull-right {
+  float: right;
+}
+
+.nav-header {
+  display: block;
+  padding: 3px 15px;
+  font-size: 11px;
+  font-weight: bold;
+  line-height: 20px;
+  color: #999999;
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+  text-transform: uppercase;
+}
+
+.nav li + .nav-header {
+  margin-top: 9px;
+}
+
+.nav-list {
+  padding-right: 15px;
+  padding-left: 15px;
+  margin-bottom: 0;
+}
+
+.nav-list > li > a,
+.nav-list .nav-header {
+  margin-right: -15px;
+  margin-left: -15px;
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+}
+
+.nav-list > li > a {
+  padding: 3px 15px;
+}
+
+.nav-list > .active > a,
+.nav-list > .active > a:hover,
+.nav-list > .active > a:focus {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
+  background-color: #0088cc;
+}
+
+.nav-list [class^="icon-"],
+.nav-list [class*=" icon-"] {
+  margin-right: 2px;
+}
+
+.nav-list .divider {
+  *width: 100%;
+  height: 1px;
+  margin: 9px 1px;
+  *margin: -5px 0 5px;
+  overflow: hidden;
+  background-color: #e5e5e5;
+  border-bottom: 1px solid #ffffff;
+}
+
+.nav-tabs,
+.nav-pills {
+  *zoom: 1;
+}
+
+.nav-tabs:before,
+.nav-pills:before,
+.nav-tabs:after,
+.nav-pills:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.nav-tabs:after,
+.nav-pills:after {
+  clear: both;
+}
+
+.nav-tabs > li,
+.nav-pills > li {
+  float: left;
+}
+
+.nav-tabs > li > a,
+.nav-pills > li > a {
+  padding-right: 12px;
+  padding-left: 12px;
+  margin-right: 2px;
+  line-height: 14px;
+}
+
+.nav-tabs {
+  border-bottom: 1px solid #ddd;
+}
+
+.nav-tabs > li {
+  margin-bottom: -1px;
+}
+
+.nav-tabs > li > a {
+  padding-top: 8px;
+  padding-bottom: 8px;
+  line-height: 20px;
+  border: 1px solid transparent;
+  -webkit-border-radius: 4px 4px 0 0;
+     -moz-border-radius: 4px 4px 0 0;
+          border-radius: 4px 4px 0 0;
+}
+
+.nav-tabs > li > a:hover,
+.nav-tabs > li > a:focus {
+  border-color: #eeeeee #eeeeee #dddddd;
+}
+
+.nav-tabs > .active > a,
+.nav-tabs > .active > a:hover,
+.nav-tabs > .active > a:focus {
+  color: #555555;
+  cursor: default;
+  background-color: #ffffff;
+  border: 1px solid #ddd;
+  border-bottom-color: transparent;
+}
+
+.nav-pills > li > a {
+  padding-top: 8px;
+  padding-bottom: 8px;
+  margin-top: 2px;
+  margin-bottom: 2px;
+  -webkit-border-radius: 5px;
+     -moz-border-radius: 5px;
+          border-radius: 5px;
+}
+
+.nav-pills > .active > a,
+.nav-pills > .active > a:hover,
+.nav-pills > .active > a:focus {
+  color: #ffffff;
+  background-color: #0088cc;
+}
+
+.nav-stacked > li {
+  float: none;
+}
+
+.nav-stacked > li > a {
+  margin-right: 0;
+}
+
+.nav-tabs.nav-stacked {
+  border-bottom: 0;
+}
+
+.nav-tabs.nav-stacked > li > a {
+  border: 1px solid #ddd;
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.nav-tabs.nav-stacked > li:first-child > a {
+  -webkit-border-top-right-radius: 4px;
+          border-top-right-radius: 4px;
+  -webkit-border-top-left-radius: 4px;
+          border-top-left-radius: 4px;
+  -moz-border-radius-topright: 4px;
+  -moz-border-radius-topleft: 4px;
+}
+
+.nav-tabs.nav-stacked > li:last-child > a {
+  -webkit-border-bottom-right-radius: 4px;
+          border-bottom-right-radius: 4px;
+  -webkit-border-bottom-left-radius: 4px;
+          border-bottom-left-radius: 4px;
+  -moz-border-radius-bottomright: 4px;
+  -moz-border-radius-bottomleft: 4px;
+}
+
+.nav-tabs.nav-stacked > li > a:hover,
+.nav-tabs.nav-stacked > li > a:focus {
+  z-index: 2;
+  border-color: #ddd;
+}
+
+.nav-pills.nav-stacked > li > a {
+  margin-bottom: 3px;
+}
+
+.nav-pills.nav-stacked > li:last-child > a {
+  margin-bottom: 1px;
+}
+
+.nav-tabs .dropdown-menu {
+  -webkit-border-radius: 0 0 6px 6px;
+     -moz-border-radius: 0 0 6px 6px;
+          border-radius: 0 0 6px 6px;
+}
+
+.nav-pills .dropdown-menu {
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+}
+
+.nav .dropdown-toggle .caret {
+  margin-top: 6px;
+  border-top-color: #0088cc;
+  border-bottom-color: #0088cc;
+}
+
+.nav .dropdown-toggle:hover .caret,
+.nav .dropdown-toggle:focus .caret {
+  border-top-color: #005580;
+  border-bottom-color: #005580;
+}
+
+/* move down carets for tabs */
+
+.nav-tabs .dropdown-toggle .caret {
+  margin-top: 8px;
+}
+
+.nav .active .dropdown-toggle .caret {
+  border-top-color: #fff;
+  border-bottom-color: #fff;
+}
+
+.nav-tabs .active .dropdown-toggle .caret {
+  border-top-color: #555555;
+  border-bottom-color: #555555;
+}
+
+.nav > .dropdown.active > a:hover,
+.nav > .dropdown.active > a:focus {
+  cursor: pointer;
+}
+
+.nav-tabs .open .dropdown-toggle,
+.nav-pills .open .dropdown-toggle,
+.nav > li.dropdown.open.active > a:hover,
+.nav > li.dropdown.open.active > a:focus {
+  color: #ffffff;
+  background-color: #999999;
+  border-color: #999999;
+}
+
+.nav li.dropdown.open .caret,
+.nav li.dropdown.open.active .caret,
+.nav li.dropdown.open a:hover .caret,
+.nav li.dropdown.open a:focus .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+  opacity: 1;
+  filter: alpha(opacity=100);
+}
+
+.tabs-stacked .open > a:hover,
+.tabs-stacked .open > a:focus {
+  border-color: #999999;
+}
+
+.tabbable {
+  *zoom: 1;
+}
+
+.tabbable:before,
+.tabbable:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.tabbable:after {
+  clear: both;
+}
+
+.tab-content {
+  overflow: auto;
+}
+
+.tabs-below > .nav-tabs,
+.tabs-right > .nav-tabs,
+.tabs-left > .nav-tabs {
+  border-bottom: 0;
+}
+
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+  display: none;
+}
+
+.tab-content > .active,
+.pill-content > .active {
+  display: block;
+}
+
+.tabs-below > .nav-tabs {
+  border-top: 1px solid #ddd;
+}
+
+.tabs-below > .nav-tabs > li {
+  margin-top: -1px;
+  margin-bottom: 0;
+}
+
+.tabs-below > .nav-tabs > li > a {
+  -webkit-border-radius: 0 0 4px 4px;
+     -moz-border-radius: 0 0 4px 4px;
+          border-radius: 0 0 4px 4px;
+}
+
+.tabs-below > .nav-tabs > li > a:hover,
+.tabs-below > .nav-tabs > li > a:focus {
+  border-top-color: #ddd;
+  border-bottom-color: transparent;
+}
+
+.tabs-below > .nav-tabs > .active > a,
+.tabs-below > .nav-tabs > .active > a:hover,
+.tabs-below > .nav-tabs > .active > a:focus {
+  border-color: transparent #ddd #ddd #ddd;
+}
+
+.tabs-left > .nav-tabs > li,
+.tabs-right > .nav-tabs > li {
+  float: none;
+}
+
+.tabs-left > .nav-tabs > li > a,
+.tabs-right > .nav-tabs > li > a {
+  min-width: 74px;
+  margin-right: 0;
+  margin-bottom: 3px;
+}
+
+.tabs-left > .nav-tabs {
+  float: left;
+  margin-right: 19px;
+  border-right: 1px solid #ddd;
+}
+
+.tabs-left > .nav-tabs > li > a {
+  margin-right: -1px;
+  -webkit-border-radius: 4px 0 0 4px;
+     -moz-border-radius: 4px 0 0 4px;
+          border-radius: 4px 0 0 4px;
+}
+
+.tabs-left > .nav-tabs > li > a:hover,
+.tabs-left > .nav-tabs > li > a:focus {
+  border-color: #eeeeee #dddddd #eeeeee #eeeeee;
+}
+
+.tabs-left > .nav-tabs .active > a,
+.tabs-left > .nav-tabs .active > a:hover,
+.tabs-left > .nav-tabs .active > a:focus {
+  border-color: #ddd transparent #ddd #ddd;
+  *border-right-color: #ffffff;
+}
+
+.tabs-right > .nav-tabs {
+  float: right;
+  margin-left: 19px;
+  border-left: 1px solid #ddd;
+}
+
+.tabs-right > .nav-tabs > li > a {
+  margin-left: -1px;
+  -webkit-border-radius: 0 4px 4px 0;
+     -moz-border-radius: 0 4px 4px 0;
+          border-radius: 0 4px 4px 0;
+}
+
+.tabs-right > .nav-tabs > li > a:hover,
+.tabs-right > .nav-tabs > li > a:focus {
+  border-color: #eeeeee #eeeeee #eeeeee #dddddd;
+}
+
+.tabs-right > .nav-tabs .active > a,
+.tabs-right > .nav-tabs .active > a:hover,
+.tabs-right > .nav-tabs .active > a:focus {
+  border-color: #ddd #ddd #ddd transparent;
+  *border-left-color: #ffffff;
+}
+
+.nav > .disabled > a {
+  color: #999999;
+}
+
+.nav > .disabled > a:hover,
+.nav > .disabled > a:focus {
+  text-decoration: none;
+  cursor: default;
+  background-color: transparent;
+}
+
+.navbar {
+  *position: relative;
+  *z-index: 2;
+  margin-bottom: 20px;
+  overflow: visible;
+}
+
+.navbar-inner {
+  min-height: 40px;
+  padding-right: 20px;
+  padding-left: 20px;
+  background-color: #fafafa;
+  background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));
+  background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2);
+  background-image: -o-linear-gradient(top, #ffffff, #f2f2f2);
+  background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);
+  background-repeat: repeat-x;
+  border: 1px solid #d4d4d4;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);
+  *zoom: 1;
+  -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+     -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+          box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+}
+
+.navbar-inner:before,
+.navbar-inner:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.navbar-inner:after {
+  clear: both;
+}
+
+.navbar .container {
+  width: auto;
+}
+
+.nav-collapse.collapse {
+  height: auto;
+  overflow: visible;
+}
+
+.navbar .brand {
+  display: block;
+  float: left;
+  padding: 10px 20px 10px;
+  margin-left: -20px;
+  font-size: 20px;
+  font-weight: 200;
+  color: #777777;
+  text-shadow: 0 1px 0 #ffffff;
+}
+
+.navbar .brand:hover,
+.navbar .brand:focus {
+  text-decoration: none;
+}
+
+.navbar-text {
+  margin-bottom: 0;
+  line-height: 40px;
+  color: #777777;
+}
+
+.navbar-link {
+  color: #777777;
+}
+
+.navbar-link:hover,
+.navbar-link:focus {
+  color: #333333;
+}
+
+.navbar .divider-vertical {
+  height: 40px;
+  margin: 0 9px;
+  border-right: 1px solid #ffffff;
+  border-left: 1px solid #f2f2f2;
+}
+
+.navbar .btn,
+.navbar .btn-group {
+  margin-top: 5px;
+}
+
+.navbar .btn-group .btn,
+.navbar .input-prepend .btn,
+.navbar .input-append .btn,
+.navbar .input-prepend .btn-group,
+.navbar .input-append .btn-group {
+  margin-top: 0;
+}
+
+.navbar-form {
+  margin-bottom: 0;
+  *zoom: 1;
+}
+
+.navbar-form:before,
+.navbar-form:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.navbar-form:after {
+  clear: both;
+}
+
+.navbar-form input,
+.navbar-form select,
+.navbar-form .radio,
+.navbar-form .checkbox {
+  margin-top: 5px;
+}
+
+.navbar-form input,
+.navbar-form select,
+.navbar-form .btn {
+  display: inline-block;
+  margin-bottom: 0;
+}
+
+.navbar-form input[type="image"],
+.navbar-form input[type="checkbox"],
+.navbar-form input[type="radio"] {
+  margin-top: 3px;
+}
+
+.navbar-form .input-append,
+.navbar-form .input-prepend {
+  margin-top: 5px;
+  white-space: nowrap;
+}
+
+.navbar-form .input-append input,
+.navbar-form .input-prepend input {
+  margin-top: 0;
+}
+
+.navbar-search {
+  position: relative;
+  float: left;
+  margin-top: 5px;
+  margin-bottom: 0;
+}
+
+.navbar-search .search-query {
+  padding: 4px 14px;
+  margin-bottom: 0;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 13px;
+  font-weight: normal;
+  line-height: 1;
+  -webkit-border-radius: 15px;
+     -moz-border-radius: 15px;
+          border-radius: 15px;
+}
+
+.navbar-static-top {
+  position: static;
+  margin-bottom: 0;
+}
+
+.navbar-static-top .navbar-inner {
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  position: fixed;
+  right: 0;
+  left: 0;
+  z-index: 1030;
+  margin-bottom: 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+  border-width: 0 0 1px;
+}
+
+.navbar-fixed-bottom .navbar-inner {
+  border-width: 1px 0 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-fixed-bottom .navbar-inner {
+  padding-right: 0;
+  padding-left: 0;
+  -webkit-border-radius: 0;
+     -moz-border-radius: 0;
+          border-radius: 0;
+}
+
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+  width: 940px;
+}
+
+.navbar-fixed-top {
+  top: 0;
+}
+
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+  -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
+     -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
+          box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
+}
+
+.navbar-fixed-bottom {
+  bottom: 0;
+}
+
+.navbar-fixed-bottom .navbar-inner {
+  -webkit-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
+     -moz-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
+          box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
+}
+
+.navbar .nav {
+  position: relative;
+  left: 0;
+  display: block;
+  float: left;
+  margin: 0 10px 0 0;
+}
+
+.navbar .nav.pull-right {
+  float: right;
+  margin-right: 0;
+}
+
+.navbar .nav > li {
+  float: left;
+}
+
+.navbar .nav > li > a {
+  float: none;
+  padding: 10px 15px 10px;
+  color: #777777;
+  text-decoration: none;
+  text-shadow: 0 1px 0 #ffffff;
+}
+
+.navbar .nav .dropdown-toggle .caret {
+  margin-top: 8px;
+}
+
+.navbar .nav > li > a:focus,
+.navbar .nav > li > a:hover {
+  color: #333333;
+  text-decoration: none;
+  background-color: transparent;
+}
+
+.navbar .nav > .active > a,
+.navbar .nav > .active > a:hover,
+.navbar .nav > .active > a:focus {
+  color: #555555;
+  text-decoration: none;
+  background-color: #e5e5e5;
+  -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+     -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+          box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+}
+
+.navbar .btn-navbar {
+  display: none;
+  float: right;
+  padding: 7px 10px;
+  margin-right: 5px;
+  margin-left: 5px;
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #ededed;
+  *background-color: #e5e5e5;
+  background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));
+  background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5);
+  background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5);
+  background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5);
+  background-repeat: repeat-x;
+  border-color: #e5e5e5 #e5e5e5 #bfbfbf;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+     -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
+}
+
+.navbar .btn-navbar:hover,
+.navbar .btn-navbar:focus,
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active,
+.navbar .btn-navbar.disabled,
+.navbar .btn-navbar[disabled] {
+  color: #ffffff;
+  background-color: #e5e5e5;
+  *background-color: #d9d9d9;
+}
+
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active {
+  background-color: #cccccc \9;
+}
+
+.navbar .btn-navbar .icon-bar {
+  display: block;
+  width: 18px;
+  height: 2px;
+  background-color: #f5f5f5;
+  -webkit-border-radius: 1px;
+     -moz-border-radius: 1px;
+          border-radius: 1px;
+  -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+     -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+          box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.btn-navbar .icon-bar + .icon-bar {
+  margin-top: 3px;
+}
+
+.navbar .nav > li > .dropdown-menu:before {
+  position: absolute;
+  top: -7px;
+  left: 9px;
+  display: inline-block;
+  border-right: 7px solid transparent;
+  border-bottom: 7px solid #ccc;
+  border-left: 7px solid transparent;
+  border-bottom-color: rgba(0, 0, 0, 0.2);
+  content: '';
+}
+
+.navbar .nav > li > .dropdown-menu:after {
+  position: absolute;
+  top: -6px;
+  left: 10px;
+  display: inline-block;
+  border-right: 6px solid transparent;
+  border-bottom: 6px solid #ffffff;
+  border-left: 6px solid transparent;
+  content: '';
+}
+
+.navbar-fixed-bottom .nav > li > .dropdown-menu:before {
+  top: auto;
+  bottom: -7px;
+  border-top: 7px solid #ccc;
+  border-bottom: 0;
+  border-top-color: rgba(0, 0, 0, 0.2);
+}
+
+.navbar-fixed-bottom .nav > li > .dropdown-menu:after {
+  top: auto;
+  bottom: -6px;
+  border-top: 6px solid #ffffff;
+  border-bottom: 0;
+}
+
+.navbar .nav li.dropdown > a:hover .caret,
+.navbar .nav li.dropdown > a:focus .caret {
+  border-top-color: #333333;
+  border-bottom-color: #333333;
+}
+
+.navbar .nav li.dropdown.open > .dropdown-toggle,
+.navbar .nav li.dropdown.active > .dropdown-toggle,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle {
+  color: #555555;
+  background-color: #e5e5e5;
+}
+
+.navbar .nav li.dropdown > .dropdown-toggle .caret {
+  border-top-color: #777777;
+  border-bottom-color: #777777;
+}
+
+.navbar .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret {
+  border-top-color: #555555;
+  border-bottom-color: #555555;
+}
+
+.navbar .pull-right > li > .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu:before,
+.navbar .nav > li > .dropdown-menu.pull-right:before {
+  right: 12px;
+  left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu:after,
+.navbar .nav > li > .dropdown-menu.pull-right:after {
+  right: 13px;
+  left: auto;
+}
+
+.navbar .pull-right > li > .dropdown-menu .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu {
+  right: 100%;
+  left: auto;
+  margin-right: -1px;
+  margin-left: 0;
+  -webkit-border-radius: 6px 0 6px 6px;
+     -moz-border-radius: 6px 0 6px 6px;
+          border-radius: 6px 0 6px 6px;
+}
+
+.navbar-inverse .navbar-inner {
+  background-color: #1b1b1b;
+  background-image: -moz-linear-gradient(top, #222222, #111111);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));
+  background-image: -webkit-linear-gradient(top, #222222, #111111);
+  background-image: -o-linear-gradient(top, #222222, #111111);
+  background-image: linear-gradient(to bottom, #222222, #111111);
+  background-repeat: repeat-x;
+  border-color: #252525;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);
+}
+
+.navbar-inverse .brand,
+.navbar-inverse .nav > li > a {
+  color: #999999;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.navbar-inverse .brand:hover,
+.navbar-inverse .nav > li > a:hover,
+.navbar-inverse .brand:focus,
+.navbar-inverse .nav > li > a:focus {
+  color: #ffffff;
+}
+
+.navbar-inverse .brand {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-text {
+  color: #999999;
+}
+
+.navbar-inverse .nav > li > a:focus,
+.navbar-inverse .nav > li > a:hover {
+  color: #ffffff;
+  background-color: transparent;
+}
+
+.navbar-inverse .nav .active > a,
+.navbar-inverse .nav .active > a:hover,
+.navbar-inverse .nav .active > a:focus {
+  color: #ffffff;
+  background-color: #111111;
+}
+
+.navbar-inverse .navbar-link {
+  color: #999999;
+}
+
+.navbar-inverse .navbar-link:hover,
+.navbar-inverse .navbar-link:focus {
+  color: #ffffff;
+}
+
+.navbar-inverse .divider-vertical {
+  border-right-color: #222222;
+  border-left-color: #111111;
+}
+
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle {
+  color: #ffffff;
+  background-color: #111111;
+}
+
+.navbar-inverse .nav li.dropdown > a:hover .caret,
+.navbar-inverse .nav li.dropdown > a:focus .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret {
+  border-top-color: #999999;
+  border-bottom-color: #999999;
+}
+
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .navbar-search .search-query {
+  color: #ffffff;
+  background-color: #515151;
+  border-color: #111111;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+     -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
+  -webkit-transition: none;
+     -moz-transition: none;
+       -o-transition: none;
+          transition: none;
+}
+
+.navbar-inverse .navbar-search .search-query:-moz-placeholder {
+  color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query:-ms-input-placeholder {
+  color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder {
+  color: #cccccc;
+}
+
+.navbar-inverse .navbar-search .search-query:focus,
+.navbar-inverse .navbar-search .search-query.focused {
+  padding: 5px 15px;
+  color: #333333;
+  text-shadow: 0 1px 0 #ffffff;
+  background-color: #ffffff;
+  border: 0;
+  outline: 0;
+  -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+     -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+          box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+}
+
+.navbar-inverse .btn-navbar {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #0e0e0e;
+  *background-color: #040404;
+  background-image: -moz-linear-gradient(top, #151515, #040404);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));
+  background-image: -webkit-linear-gradient(top, #151515, #040404);
+  background-image: -o-linear-gradient(top, #151515, #040404);
+  background-image: linear-gradient(to bottom, #151515, #040404);
+  background-repeat: repeat-x;
+  border-color: #040404 #040404 #000000;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.navbar-inverse .btn-navbar:hover,
+.navbar-inverse .btn-navbar:focus,
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active,
+.navbar-inverse .btn-navbar.disabled,
+.navbar-inverse .btn-navbar[disabled] {
+  color: #ffffff;
+  background-color: #040404;
+  *background-color: #000000;
+}
+
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active {
+  background-color: #000000 \9;
+}
+
+.breadcrumb {
+  padding: 8px 15px;
+  margin: 0 0 20px;
+  list-style: none;
+  background-color: #f5f5f5;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+.breadcrumb > li {
+  display: inline-block;
+  *display: inline;
+  text-shadow: 0 1px 0 #ffffff;
+  *zoom: 1;
+}
+
+.breadcrumb > li > .divider {
+  padding: 0 5px;
+  color: #ccc;
+}
+
+.breadcrumb > .active {
+  color: #999999;
+}
+
+.pagination {
+  margin: 20px 0;
+}
+
+.pagination ul {
+  display: inline-block;
+  *display: inline;
+  margin-bottom: 0;
+  margin-left: 0;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+  *zoom: 1;
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+     -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.pagination ul > li {
+  display: inline;
+}
+
+.pagination ul > li > a,
+.pagination ul > li > span {
+  float: left;
+  padding: 4px 12px;
+  line-height: 20px;
+  text-decoration: none;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-left-width: 0;
+}
+
+.pagination ul > li > a:hover,
+.pagination ul > li > a:focus,
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+  background-color: #f5f5f5;
+}
+
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+  color: #999999;
+  cursor: default;
+}
+
+.pagination ul > .disabled > span,
+.pagination ul > .disabled > a,
+.pagination ul > .disabled > a:hover,
+.pagination ul > .disabled > a:focus {
+  color: #999999;
+  cursor: default;
+  background-color: transparent;
+}
+
+.pagination ul > li:first-child > a,
+.pagination ul > li:first-child > span {
+  border-left-width: 1px;
+  -webkit-border-bottom-left-radius: 4px;
+          border-bottom-left-radius: 4px;
+  -webkit-border-top-left-radius: 4px;
+          border-top-left-radius: 4px;
+  -moz-border-radius-bottomleft: 4px;
+  -moz-border-radius-topleft: 4px;
+}
+
+.pagination ul > li:last-child > a,
+.pagination ul > li:last-child > span {
+  -webkit-border-top-right-radius: 4px;
+          border-top-right-radius: 4px;
+  -webkit-border-bottom-right-radius: 4px;
+          border-bottom-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+  -moz-border-radius-bottomright: 4px;
+}
+
+.pagination-centered {
+  text-align: center;
+}
+
+.pagination-right {
+  text-align: right;
+}
+
+.pagination-large ul > li > a,
+.pagination-large ul > li > span {
+  padding: 11px 19px;
+  font-size: 17.5px;
+}
+
+.pagination-large ul > li:first-child > a,
+.pagination-large ul > li:first-child > span {
+  -webkit-border-bottom-left-radius: 6px;
+          border-bottom-left-radius: 6px;
+  -webkit-border-top-left-radius: 6px;
+          border-top-left-radius: 6px;
+  -moz-border-radius-bottomleft: 6px;
+  -moz-border-radius-topleft: 6px;
+}
+
+.pagination-large ul > li:last-child > a,
+.pagination-large ul > li:last-child > span {
+  -webkit-border-top-right-radius: 6px;
+          border-top-right-radius: 6px;
+  -webkit-border-bottom-right-radius: 6px;
+          border-bottom-right-radius: 6px;
+  -moz-border-radius-topright: 6px;
+  -moz-border-radius-bottomright: 6px;
+}
+
+.pagination-mini ul > li:first-child > a,
+.pagination-small ul > li:first-child > a,
+.pagination-mini ul > li:first-child > span,
+.pagination-small ul > li:first-child > span {
+  -webkit-border-bottom-left-radius: 3px;
+          border-bottom-left-radius: 3px;
+  -webkit-border-top-left-radius: 3px;
+          border-top-left-radius: 3px;
+  -moz-border-radius-bottomleft: 3px;
+  -moz-border-radius-topleft: 3px;
+}
+
+.pagination-mini ul > li:last-child > a,
+.pagination-small ul > li:last-child > a,
+.pagination-mini ul > li:last-child > span,
+.pagination-small ul > li:last-child > span {
+  -webkit-border-top-right-radius: 3px;
+          border-top-right-radius: 3px;
+  -webkit-border-bottom-right-radius: 3px;
+          border-bottom-right-radius: 3px;
+  -moz-border-radius-topright: 3px;
+  -moz-border-radius-bottomright: 3px;
+}
+
+.pagination-small ul > li > a,
+.pagination-small ul > li > span {
+  padding: 2px 10px;
+  font-size: 11.9px;
+}
+
+.pagination-mini ul > li > a,
+.pagination-mini ul > li > span {
+  padding: 0 6px;
+  font-size: 10.5px;
+}
+
+.pager {
+  margin: 20px 0;
+  text-align: center;
+  list-style: none;
+  *zoom: 1;
+}
+
+.pager:before,
+.pager:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.pager:after {
+  clear: both;
+}
+
+.pager li {
+  display: inline;
+}
+
+.pager li > a,
+.pager li > span {
+  display: inline-block;
+  padding: 5px 14px;
+  background-color: #fff;
+  border: 1px solid #ddd;
+  -webkit-border-radius: 15px;
+     -moz-border-radius: 15px;
+          border-radius: 15px;
+}
+
+.pager li > a:hover,
+.pager li > a:focus {
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+
+.pager .next > a,
+.pager .next > span {
+  float: right;
+}
+
+.pager .previous > a,
+.pager .previous > span {
+  float: left;
+}
+
+.pager .disabled > a,
+.pager .disabled > a:hover,
+.pager .disabled > a:focus,
+.pager .disabled > span {
+  color: #999999;
+  cursor: default;
+  background-color: #fff;
+}
+
+.modal-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1040;
+  background-color: #000000;
+}
+
+.modal-backdrop.fade {
+  opacity: 0;
+}
+
+.modal-backdrop,
+.modal-backdrop.fade.in {
+  opacity: 0.8;
+  filter: alpha(opacity=80);
+}
+
+.modal {
+  position: fixed;
+  top: 10%;
+  left: 50%;
+  z-index: 1050;
+  width: 560px;
+  margin-left: -280px;
+  background-color: #ffffff;
+  border: 1px solid #999;
+  border: 1px solid rgba(0, 0, 0, 0.3);
+  *border: 1px solid #999;
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+  outline: none;
+  -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+     -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+          box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+  -webkit-background-clip: padding-box;
+     -moz-background-clip: padding-box;
+          background-clip: padding-box;
+}
+
+.modal.fade {
+  top: -25%;
+  -webkit-transition: opacity 0.3s linear, top 0.3s ease-out;
+     -moz-transition: opacity 0.3s linear, top 0.3s ease-out;
+       -o-transition: opacity 0.3s linear, top 0.3s ease-out;
+          transition: opacity 0.3s linear, top 0.3s ease-out;
+}
+
+.modal.fade.in {
+  top: 10%;
+}
+
+.modal-header {
+  padding: 9px 15px;
+  border-bottom: 1px solid #eee;
+}
+
+.modal-header .close {
+  margin-top: 2px;
+}
+
+.modal-header h3 {
+  margin: 0;
+  line-height: 30px;
+}
+
+.modal-body {
+  position: relative;
+  max-height: 400px;
+  padding: 15px;
+  overflow-y: auto;
+}
+
+.modal-form {
+  margin-bottom: 0;
+}
+
+.modal-footer {
+  padding: 14px 15px 15px;
+  margin-bottom: 0;
+  text-align: right;
+  background-color: #f5f5f5;
+  border-top: 1px solid #ddd;
+  -webkit-border-radius: 0 0 6px 6px;
+     -moz-border-radius: 0 0 6px 6px;
+          border-radius: 0 0 6px 6px;
+  *zoom: 1;
+  -webkit-box-shadow: inset 0 1px 0 #ffffff;
+     -moz-box-shadow: inset 0 1px 0 #ffffff;
+          box-shadow: inset 0 1px 0 #ffffff;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.modal-footer:after {
+  clear: both;
+}
+
+.modal-footer .btn + .btn {
+  margin-bottom: 0;
+  margin-left: 5px;
+}
+
+.modal-footer .btn-group .btn + .btn {
+  margin-left: -1px;
+}
+
+.modal-footer .btn-block + .btn-block {
+  margin-left: 0;
+}
+
+.tooltip {
+  position: absolute;
+  z-index: 1030;
+  display: block;
+  font-size: 11px;
+  line-height: 1.4;
+  opacity: 0;
+  filter: alpha(opacity=0);
+  visibility: visible;
+}
+
+.tooltip.in {
+  opacity: 0.8;
+  filter: alpha(opacity=80);
+}
+
+.tooltip.top {
+  padding: 5px 0;
+  margin-top: -3px;
+}
+
+.tooltip.right {
+  padding: 0 5px;
+  margin-left: 3px;
+}
+
+.tooltip.bottom {
+  padding: 5px 0;
+  margin-top: 3px;
+}
+
+.tooltip.left {
+  padding: 0 5px;
+  margin-left: -3px;
+}
+
+.tooltip-inner {
+  max-width: 200px;
+  padding: 8px;
+  color: #ffffff;
+  text-align: center;
+  text-decoration: none;
+  background-color: #000000;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+.tooltip-arrow {
+  position: absolute;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+
+.tooltip.top .tooltip-arrow {
+  bottom: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-top-color: #000000;
+  border-width: 5px 5px 0;
+}
+
+.tooltip.right .tooltip-arrow {
+  top: 50%;
+  left: 0;
+  margin-top: -5px;
+  border-right-color: #000000;
+  border-width: 5px 5px 5px 0;
+}
+
+.tooltip.left .tooltip-arrow {
+  top: 50%;
+  right: 0;
+  margin-top: -5px;
+  border-left-color: #000000;
+  border-width: 5px 0 5px 5px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+  top: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-bottom-color: #000000;
+  border-width: 0 5px 5px;
+}
+
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1010;
+  display: none;
+  max-width: 276px;
+  padding: 1px;
+  text-align: left;
+  white-space: normal;
+  background-color: #ffffff;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+     -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+          box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  -webkit-background-clip: padding-box;
+     -moz-background-clip: padding;
+          background-clip: padding-box;
+}
+
+.popover.top {
+  margin-top: -10px;
+}
+
+.popover.right {
+  margin-left: 10px;
+}
+
+.popover.bottom {
+  margin-top: 10px;
+}
+
+.popover.left {
+  margin-left: -10px;
+}
+
+.popover-title {
+  padding: 8px 14px;
+  margin: 0;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 18px;
+  background-color: #f7f7f7;
+  border-bottom: 1px solid #ebebeb;
+  -webkit-border-radius: 5px 5px 0 0;
+     -moz-border-radius: 5px 5px 0 0;
+          border-radius: 5px 5px 0 0;
+}
+
+.popover-title:empty {
+  display: none;
+}
+
+.popover-content {
+  padding: 9px 14px;
+}
+
+.popover .arrow,
+.popover .arrow:after {
+  position: absolute;
+  display: block;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+
+.popover .arrow {
+  border-width: 11px;
+}
+
+.popover .arrow:after {
+  border-width: 10px;
+  content: "";
+}
+
+.popover.top .arrow {
+  bottom: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-top-color: #999;
+  border-top-color: rgba(0, 0, 0, 0.25);
+  border-bottom-width: 0;
+}
+
+.popover.top .arrow:after {
+  bottom: 1px;
+  margin-left: -10px;
+  border-top-color: #ffffff;
+  border-bottom-width: 0;
+}
+
+.popover.right .arrow {
+  top: 50%;
+  left: -11px;
+  margin-top: -11px;
+  border-right-color: #999;
+  border-right-color: rgba(0, 0, 0, 0.25);
+  border-left-width: 0;
+}
+
+.popover.right .arrow:after {
+  bottom: -10px;
+  left: 1px;
+  border-right-color: #ffffff;
+  border-left-width: 0;
+}
+
+.popover.bottom .arrow {
+  top: -11px;
+  left: 50%;
+  margin-left: -11px;
+  border-bottom-color: #999;
+  border-bottom-color: rgba(0, 0, 0, 0.25);
+  border-top-width: 0;
+}
+
+.popover.bottom .arrow:after {
+  top: 1px;
+  margin-left: -10px;
+  border-bottom-color: #ffffff;
+  border-top-width: 0;
+}
+
+.popover.left .arrow {
+  top: 50%;
+  right: -11px;
+  margin-top: -11px;
+  border-left-color: #999;
+  border-left-color: rgba(0, 0, 0, 0.25);
+  border-right-width: 0;
+}
+
+.popover.left .arrow:after {
+  right: 1px;
+  bottom: -10px;
+  border-left-color: #ffffff;
+  border-right-width: 0;
+}
+
+.thumbnails {
+  margin-left: -20px;
+  list-style: none;
+  *zoom: 1;
+}
+
+.thumbnails:before,
+.thumbnails:after {
+  display: table;
+  line-height: 0;
+  content: "";
+}
+
+.thumbnails:after {
+  clear: both;
+}
+
+.row-fluid .thumbnails {
+  margin-left: 0;
+}
+
+.thumbnails > li {
+  float: left;
+  margin-bottom: 20px;
+  margin-left: 20px;
+}
+
+.thumbnail {
+  display: block;
+  padding: 4px;
+  line-height: 20px;
+  border: 1px solid #ddd;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+     -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+          box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+  -webkit-transition: all 0.2s ease-in-out;
+     -moz-transition: all 0.2s ease-in-out;
+       -o-transition: all 0.2s ease-in-out;
+          transition: all 0.2s ease-in-out;
+}
+
+a.thumbnail:hover,
+a.thumbnail:focus {
+  border-color: #0088cc;
+  -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+     -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+          box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+}
+
+.thumbnail > img {
+  display: block;
+  max-width: 100%;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+.thumbnail .caption {
+  padding: 9px;
+  color: #555555;
+}
+
+.media,
+.media-body {
+  overflow: hidden;
+  *overflow: visible;
+  zoom: 1;
+}
+
+.media,
+.media .media {
+  margin-top: 15px;
+}
+
+.media:first-child {
+  margin-top: 0;
+}
+
+.media-object {
+  display: block;
+}
+
+.media-heading {
+  margin: 0 0 5px;
+}
+
+.media > .pull-left {
+  margin-right: 10px;
+}
+
+.media > .pull-right {
+  margin-left: 10px;
+}
+
+.media-list {
+  margin-left: 0;
+  list-style: none;
+}
+
+.label,
+.badge {
+  display: inline-block;
+  padding: 2px 4px;
+  font-size: 11.844px;
+  font-weight: bold;
+  line-height: 14px;
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  white-space: nowrap;
+  vertical-align: baseline;
+  background-color: #999999;
+}
+
+.label {
+  -webkit-border-radius: 3px;
+     -moz-border-radius: 3px;
+          border-radius: 3px;
+}
+
+.badge {
+  padding-right: 9px;
+  padding-left: 9px;
+  -webkit-border-radius: 9px;
+     -moz-border-radius: 9px;
+          border-radius: 9px;
+}
+
+.label:empty,
+.badge:empty {
+  display: none;
+}
+
+a.label:hover,
+a.label:focus,
+a.badge:hover,
+a.badge:focus {
+  color: #ffffff;
+  text-decoration: none;
+  cursor: pointer;
+}
+
+.label-important,
+.badge-important {
+  background-color: #b94a48;
+}
+
+.label-important[href],
+.badge-important[href] {
+  background-color: #953b39;
+}
+
+.label-warning,
+.badge-warning {
+  background-color: #f89406;
+}
+
+.label-warning[href],
+.badge-warning[href] {
+  background-color: #c67605;
+}
+
+.label-success,
+.badge-success {
+  background-color: #468847;
+}
+
+.label-success[href],
+.badge-success[href] {
+  background-color: #356635;
+}
+
+.label-info,
+.badge-info {
+  background-color: #3a87ad;
+}
+
+.label-info[href],
+.badge-info[href] {
+  background-color: #2d6987;
+}
+
+.label-inverse,
+.badge-inverse {
+  background-color: #333333;
+}
+
+.label-inverse[href],
+.badge-inverse[href] {
+  background-color: #1a1a1a;
+}
+
+.btn .label,
+.btn .badge {
+  position: relative;
+  top: -1px;
+}
+
+.btn-mini .label,
+.btn-mini .badge {
+  top: 0;
+}
+
+ at -webkit-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+ at -moz-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+ at -ms-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+ at -o-keyframes progress-bar-stripes {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: 40px 0;
+  }
+}
+
+ at keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+.progress {
+  height: 20px;
+  margin-bottom: 20px;
+  overflow: hidden;
+  background-color: #f7f7f7;
+  background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
+  background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
+  background-repeat: repeat-x;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+     -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.progress .bar {
+  float: left;
+  width: 0;
+  height: 100%;
+  font-size: 12px;
+  color: #ffffff;
+  text-align: center;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #0e90d2;
+  background-image: -moz-linear-gradient(top, #149bdf, #0480be);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
+  background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
+  background-image: -o-linear-gradient(top, #149bdf, #0480be);
+  background-image: linear-gradient(to bottom, #149bdf, #0480be);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+     -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+  -webkit-transition: width 0.6s ease;
+     -moz-transition: width 0.6s ease;
+       -o-transition: width 0.6s ease;
+          transition: width 0.6s ease;
+}
+
+.progress .bar + .bar {
+  -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+     -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+          box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+}
+
+.progress-striped .bar {
+  background-color: #149bdf;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  -webkit-background-size: 40px 40px;
+     -moz-background-size: 40px 40px;
+       -o-background-size: 40px 40px;
+          background-size: 40px 40px;
+}
+
+.progress.active .bar {
+  -webkit-animation: progress-bar-stripes 2s linear infinite;
+     -moz-animation: progress-bar-stripes 2s linear infinite;
+      -ms-animation: progress-bar-stripes 2s linear infinite;
+       -o-animation: progress-bar-stripes 2s linear infinite;
+          animation: progress-bar-stripes 2s linear infinite;
+}
+
+.progress-danger .bar,
+.progress .bar-danger {
+  background-color: #dd514c;
+  background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));
+  background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
+  background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
+  background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);
+}
+
+.progress-danger.progress-striped .bar,
+.progress-striped .bar-danger {
+  background-color: #ee5f5b;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-success .bar,
+.progress .bar-success {
+  background-color: #5eb95e;
+  background-image: -moz-linear-gradient(top, #62c462, #57a957);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));
+  background-image: -webkit-linear-gradient(top, #62c462, #57a957);
+  background-image: -o-linear-gradient(top, #62c462, #57a957);
+  background-image: linear-gradient(to bottom, #62c462, #57a957);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);
+}
+
+.progress-success.progress-striped .bar,
+.progress-striped .bar-success {
+  background-color: #62c462;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-info .bar,
+.progress .bar-info {
+  background-color: #4bb1cf;
+  background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));
+  background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
+  background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
+  background-image: linear-gradient(to bottom, #5bc0de, #339bb9);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);
+}
+
+.progress-info.progress-striped .bar,
+.progress-striped .bar-info {
+  background-color: #5bc0de;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-warning .bar,
+.progress .bar-warning {
+  background-color: #faa732;
+  background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+  background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+  background-image: -o-linear-gradient(top, #fbb450, #f89406);
+  background-image: linear-gradient(to bottom, #fbb450, #f89406);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+}
+
+.progress-warning.progress-striped .bar,
+.progress-striped .bar-warning {
+  background-color: #fbb450;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.accordion {
+  margin-bottom: 20px;
+}
+
+.accordion-group {
+  margin-bottom: 2px;
+  border: 1px solid #e5e5e5;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+}
+
+.accordion-heading {
+  border-bottom: 0;
+}
+
+.accordion-heading .accordion-toggle {
+  display: block;
+  padding: 8px 15px;
+}
+
+.accordion-toggle {
+  cursor: pointer;
+}
+
+.accordion-inner {
+  padding: 9px 15px;
+  border-top: 1px solid #e5e5e5;
+}
+
+.carousel {
+  position: relative;
+  margin-bottom: 20px;
+  line-height: 1;
+}
+
+.carousel-inner {
+  position: relative;
+  width: 100%;
+  overflow: hidden;
+}
+
+.carousel-inner > .item {
+  position: relative;
+  display: none;
+  -webkit-transition: 0.6s ease-in-out left;
+     -moz-transition: 0.6s ease-in-out left;
+       -o-transition: 0.6s ease-in-out left;
+          transition: 0.6s ease-in-out left;
+}
+
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+  display: block;
+  line-height: 1;
+}
+
+.carousel-inner > .active,
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  display: block;
+}
+
+.carousel-inner > .active {
+  left: 0;
+}
+
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+
+.carousel-inner > .next {
+  left: 100%;
+}
+
+.carousel-inner > .prev {
+  left: -100%;
+}
+
+.carousel-inner > .next.left,
+.carousel-inner > .prev.right {
+  left: 0;
+}
+
+.carousel-inner > .active.left {
+  left: -100%;
+}
+
+.carousel-inner > .active.right {
+  left: 100%;
+}
+
+.carousel-control {
+  position: absolute;
+  top: 40%;
+  left: 15px;
+  width: 40px;
+  height: 40px;
+  margin-top: -20px;
+  font-size: 60px;
+  font-weight: 100;
+  line-height: 30px;
+  color: #ffffff;
+  text-align: center;
+  background: #222222;
+  border: 3px solid #ffffff;
+  -webkit-border-radius: 23px;
+     -moz-border-radius: 23px;
+          border-radius: 23px;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+
+.carousel-control.right {
+  right: 15px;
+  left: auto;
+}
+
+.carousel-control:hover,
+.carousel-control:focus {
+  color: #ffffff;
+  text-decoration: none;
+  opacity: 0.9;
+  filter: alpha(opacity=90);
+}
+
+.carousel-indicators {
+  position: absolute;
+  top: 15px;
+  right: 15px;
+  z-index: 5;
+  margin: 0;
+  list-style: none;
+}
+
+.carousel-indicators li {
+  display: block;
+  float: left;
+  width: 10px;
+  height: 10px;
+  margin-left: 5px;
+  text-indent: -999px;
+  background-color: #ccc;
+  background-color: rgba(255, 255, 255, 0.25);
+  border-radius: 5px;
+}
+
+.carousel-indicators .active {
+  background-color: #fff;
+}
+
+.carousel-caption {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  padding: 15px;
+  background: #333333;
+  background: rgba(0, 0, 0, 0.75);
+}
+
+.carousel-caption h4,
+.carousel-caption p {
+  line-height: 20px;
+  color: #ffffff;
+}
+
+.carousel-caption h4 {
+  margin: 0 0 5px;
+}
+
+.carousel-caption p {
+  margin-bottom: 0;
+}
+
+.hero-unit {
+  padding: 60px;
+  margin-bottom: 30px;
+  font-size: 18px;
+  font-weight: 200;
+  line-height: 30px;
+  color: inherit;
+  background-color: #eeeeee;
+  -webkit-border-radius: 6px;
+     -moz-border-radius: 6px;
+          border-radius: 6px;
+}
+
+.hero-unit h1 {
+  margin-bottom: 0;
+  font-size: 60px;
+  line-height: 1;
+  letter-spacing: -1px;
+  color: inherit;
+}
+
+.hero-unit li {
+  line-height: 30px;
+}
+
+.pull-right {
+  float: right;
+}
+
+.pull-left {
+  float: left;
+}
+
+.hide {
+  display: none;
+}
+
+.show {
+  display: block;
+}
+
+.invisible {
+  visibility: hidden;
+}
+
+.affix {
+  position: fixed;
+}
diff --git a/doc/etc/bootstrap/css/bootstrap.min.css b/doc/etc/bootstrap/css/bootstrap.min.css
new file mode 100644
index 0000000..df96c86
--- /dev/null
+++ b/doc/etc/bootstrap/css/bootstrap.min.css
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap v2.3.2
+ *
+ * Copyright 2013 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:in [...]
diff --git a/doc/etc/bootstrap/img/glyphicons-halflings-white.png b/doc/etc/bootstrap/img/glyphicons-halflings-white.png
new file mode 100644
index 0000000..3bf6484
Binary files /dev/null and b/doc/etc/bootstrap/img/glyphicons-halflings-white.png differ
diff --git a/doc/etc/bootstrap/img/glyphicons-halflings.png b/doc/etc/bootstrap/img/glyphicons-halflings.png
new file mode 100644
index 0000000..a996999
Binary files /dev/null and b/doc/etc/bootstrap/img/glyphicons-halflings.png differ
diff --git a/doc/etc/bootstrap/js/bootstrap.js b/doc/etc/bootstrap/js/bootstrap.js
new file mode 100644
index 0000000..44109f6
--- /dev/null
+++ b/doc/etc/bootstrap/js/bootstrap.js
@@ -0,0 +1,2280 @@
+/* ===================================================
+ * bootstrap-transition.js v2.3.2
+ * http://getbootstrap.com/2.3.2/javascript.html#transitions
+ * ===================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+  /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
+   * ======================================================= */
+
+  $(function () {
+
+    $.support.transition = (function () {
+
+      var transitionEnd = (function () {
+
+        var el = document.createElement('bootstrap')
+          , transEndEventNames = {
+               'WebkitTransition' : 'webkitTransitionEnd'
+            ,  'MozTransition'    : 'transitionend'
+            ,  'OTransition'      : 'oTransitionEnd otransitionend'
+            ,  'transition'       : 'transitionend'
+            }
+          , name
+
+        for (name in transEndEventNames){
+          if (el.style[name] !== undefined) {
+            return transEndEventNames[name]
+          }
+        }
+
+      }())
+
+      return transitionEnd && {
+        end: transitionEnd
+      }
+
+    })()
+
+  })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-alert.js v2.3.2
+ * http://getbootstrap.com/2.3.2/javascript.html#alerts
+ * ==========================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* ALERT CLASS DEFINITION
+  * ====================== */
+
+  var dismiss = '[data-dismiss="alert"]'
+    , Alert = function (el) {
+        $(el).on('click', dismiss, this.close)
+      }
+
+  Alert.prototype.close = function (e) {
+    var $this = $(this)
+      , selector = $this.attr('data-target')
+      , $parent
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    $parent = $(selector)
+
+    e && e.preventDefault()
+
+    $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
+
+    $parent.trigger(e = $.Event('close'))
+
+    if (e.isDefaultPrevented()) return
+
+    $parent.removeClass('in')
+
+    function removeElement() {
+      $parent
+        .trigger('closed')
+        .remove()
+    }
+
+    $.support.transition && $parent.hasClass('fade') ?
+      $parent.on($.support.transition.end, removeElement) :
+      removeElement()
+  }
+
+
+ /* ALERT PLUGIN DEFINITION
+  * ======================= */
+
+  var old = $.fn.alert
+
+  $.fn.alert = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('alert')
+      if (!data) $this.data('alert', (data = new Alert(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.alert.Constructor = Alert
+
+
+ /* ALERT NO CONFLICT
+  * ================= */
+
+  $.fn.alert.noConflict = function () {
+    $.fn.alert = old
+    return this
+  }
+
+
+ /* ALERT DATA-API
+  * ============== */
+
+  $(document).on('click.alert.data-api', dismiss, Alert.prototype.close)
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-button.js v2.3.2
+ * http://getbootstrap.com/2.3.2/javascript.html#buttons
+ * ============================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* BUTTON PUBLIC CLASS DEFINITION
+  * ============================== */
+
+  var Button = function (element, options) {
+    this.$element = $(element)
+    this.options = $.extend({}, $.fn.button.defaults, options)
+  }
+
+  Button.prototype.setState = function (state) {
+    var d = 'disabled'
+      , $el = this.$element
+      , data = $el.data()
+      , val = $el.is('input') ? 'val' : 'html'
+
+    state = state + 'Text'
+    data.resetText || $el.data('resetText', $el[val]())
+
+    $el[val](data[state] || this.options[state])
+
+    // push to event loop to allow forms to submit
+    setTimeout(function () {
+      state == 'loadingText' ?
+        $el.addClass(d).attr(d, d) :
+        $el.removeClass(d).removeAttr(d)
+    }, 0)
+  }
+
+  Button.prototype.toggle = function () {
+    var $parent = this.$element.closest('[data-toggle="buttons-radio"]')
+
+    $parent && $parent
+      .find('.active')
+      .removeClass('active')
+
+    this.$element.toggleClass('active')
+  }
+
+
+ /* BUTTON PLUGIN DEFINITION
+  * ======================== */
+
+  var old = $.fn.button
+
+  $.fn.button = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('button')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('button', (data = new Button(this, options)))
+      if (option == 'toggle') data.toggle()
+      else if (option) data.setState(option)
+    })
+  }
+
+  $.fn.button.defaults = {
+    loadingText: 'loading...'
+  }
+
+  $.fn.button.Constructor = Button
+
+
+ /* BUTTON NO CONFLICT
+  * ================== */
+
+  $.fn.button.noConflict = function () {
+    $.fn.button = old
+    return this
+  }
+
+
+ /* BUTTON DATA-API
+  * =============== */
+
+  $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) {
+    var $btn = $(e.target)
+    if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+    $btn.button('toggle')
+  })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-carousel.js v2.3.2
+ * http://getbootstrap.com/2.3.2/javascript.html#carousel
+ * ==========================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* CAROUSEL CLASS DEFINITION
+  * ========================= */
+
+  var Carousel = function (element, options) {
+    this.$element = $(element)
+    this.$indicators = this.$element.find('.carousel-indicators')
+    this.options = options
+    this.options.pause == 'hover' && this.$element
+      .on('mouseenter', $.proxy(this.pause, this))
+      .on('mouseleave', $.proxy(this.cycle, this))
+  }
+
+  Carousel.prototype = {
+
+    cycle: function (e) {
+      if (!e) this.paused = false
+      if (this.interval) clearInterval(this.interval);
+      this.options.interval
+        && !this.paused
+        && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+      return this
+    }
+
+  , getActiveIndex: function () {
+      this.$active = this.$element.find('.item.active')
+      this.$items = this.$active.parent().children()
+      return this.$items.index(this.$active)
+    }
+
+  , to: function (pos) {
+      var activeIndex = this.getActiveIndex()
+        , that = this
+
+      if (pos > (this.$items.length - 1) || pos < 0) return
+
+      if (this.sliding) {
+        return this.$element.one('slid', function () {
+          that.to(pos)
+        })
+      }
+
+      if (activeIndex == pos) {
+        return this.pause().cycle()
+      }
+
+      return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))
+    }
+
+  , pause: function (e) {
+      if (!e) this.paused = true
+      if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+        this.$element.trigger($.support.transition.end)
+        this.cycle(true)
+      }
+      clearInterval(this.interval)
+      this.interval = null
+      return this
+    }
+
+  , next: function () {
+      if (this.sliding) return
+      return this.slide('next')
+    }
+
+  , prev: function () {
+      if (this.sliding) return
+      return this.slide('prev')
+    }
+
+  , slide: function (type, next) {
+      var $active = this.$element.find('.item.active')
+        , $next = next || $active[type]()
+        , isCycling = this.interval
+        , direction = type == 'next' ? 'left' : 'right'
+        , fallback  = type == 'next' ? 'first' : 'last'
+        , that = this
+        , e
+
+      this.sliding = true
+
+      isCycling && this.pause()
+
+      $next = $next.length ? $next : this.$element.find('.item')[fallback]()
+
+      e = $.Event('slide', {
+        relatedTarget: $next[0]
+      , direction: direction
+      })
+
+      if ($next.hasClass('active')) return
+
+      if (this.$indicators.length) {
+        this.$indicators.find('.active').removeClass('active')
+        this.$element.one('slid', function () {
+          var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])
+          $nextIndicator && $nextIndicator.addClass('active')
+        })
+      }
+
+      if ($.support.transition && this.$element.hasClass('slide')) {
+        this.$element.trigger(e)
+        if (e.isDefaultPrevented()) return
+        $next.addClass(type)
+        $next[0].offsetWidth // force reflow
+        $active.addClass(direction)
+        $next.addClass(direction)
+        this.$element.one($.support.transition.end, function () {
+          $next.removeClass([type, direction].join(' ')).addClass('active')
+          $active.removeClass(['active', direction].join(' '))
+          that.sliding = false
+          setTimeout(function () { that.$element.trigger('slid') }, 0)
+        })
+      } else {
+        this.$element.trigger(e)
+        if (e.isDefaultPrevented()) return
+        $active.removeClass('active')
+        $next.addClass('active')
+        this.sliding = false
+        this.$element.trigger('slid')
+      }
+
+      isCycling && this.cycle()
+
+      return this
+    }
+
+  }
+
+
+ /* CAROUSEL PLUGIN DEFINITION
+  * ========================== */
+
+  var old = $.fn.carousel
+
+  $.fn.carousel = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('carousel')
+        , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)
+        , action = typeof option == 'string' ? option : options.slide
+      if (!data) $this.data('carousel', (data = new Carousel(this, options)))
+      if (typeof option == 'number') data.to(option)
+      else if (action) data[action]()
+      else if (options.interval) data.pause().cycle()
+    })
+  }
+
+  $.fn.carousel.defaults = {
+    interval: 5000
+  , pause: 'hover'
+  }
+
+  $.fn.carousel.Constructor = Carousel
+
+
+ /* CAROUSEL NO CONFLICT
+  * ==================== */
+
+  $.fn.carousel.noConflict = function () {
+    $.fn.carousel = old
+    return this
+  }
+
+ /* CAROUSEL DATA-API
+  * ================= */
+
+  $(document).on('click.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {
+    var $this = $(this), href
+      , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+      , options = $.extend({}, $target.data(), $this.data())
+      , slideIndex
+
+    $target.carousel(options)
+
+    if (slideIndex = $this.attr('data-slide-to')) {
+      $target.data('carousel').pause().to(slideIndex).cycle()
+    }
+
+    e.preventDefault()
+  })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-collapse.js v2.3.2
+ * http://getbootstrap.com/2.3.2/javascript.html#collapse
+ * =============================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* COLLAPSE PUBLIC CLASS DEFINITION
+  * ================================ */
+
+  var Collapse = function (element, options) {
+    this.$element = $(element)
+    this.options = $.extend({}, $.fn.collapse.defaults, options)
+
+    if (this.options.parent) {
+      this.$parent = $(this.options.parent)
+    }
+
+    this.options.toggle && this.toggle()
+  }
+
+  Collapse.prototype = {
+
+    constructor: Collapse
+
+  , dimension: function () {
+      var hasWidth = this.$element.hasClass('width')
+      return hasWidth ? 'width' : 'height'
+    }
+
+  , show: function () {
+      var dimension
+        , scroll
+        , actives
+        , hasData
+
+      if (this.transitioning || this.$element.hasClass('in')) return
+
+      dimension = this.dimension()
+      scroll = $.camelCase(['scroll', dimension].join('-'))
+      actives = this.$parent && this.$parent.find('> .accordion-group > .in')
+
+      if (actives && actives.length) {
+        hasData = actives.data('collapse')
+        if (hasData && hasData.transitioning) return
+        actives.collapse('hide')
+        hasData || actives.data('collapse', null)
+      }
+
+      this.$element[dimension](0)
+      this.transition('addClass', $.Event('show'), 'shown')
+      $.support.transition && this.$element[dimension](this.$element[0][scroll])
+    }
+
+  , hide: function () {
+      var dimension
+      if (this.transitioning || !this.$element.hasClass('in')) return
+      dimension = this.dimension()
+      this.reset(this.$element[dimension]())
+      this.transition('removeClass', $.Event('hide'), 'hidden')
+      this.$element[dimension](0)
+    }
+
+  , reset: function (size) {
+      var dimension = this.dimension()
+
+      this.$element
+        .removeClass('collapse')
+        [dimension](size || 'auto')
+        [0].offsetWidth
+
+      this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')
+
+      return this
+    }
+
+  , transition: function (method, startEvent, completeEvent) {
+      var that = this
+        , complete = function () {
+            if (startEvent.type == 'show') that.reset()
+            that.transitioning = 0
+            that.$element.trigger(completeEvent)
+          }
+
+      this.$element.trigger(startEvent)
+
+      if (startEvent.isDefaultPrevented()) return
+
+      this.transitioning = 1
+
+      this.$element[method]('in')
+
+      $.support.transition && this.$element.hasClass('collapse') ?
+        this.$element.one($.support.transition.end, complete) :
+        complete()
+    }
+
+  , toggle: function () {
+      this[this.$element.hasClass('in') ? 'hide' : 'show']()
+    }
+
+  }
+
+
+ /* COLLAPSE PLUGIN DEFINITION
+  * ========================== */
+
+  var old = $.fn.collapse
+
+  $.fn.collapse = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('collapse')
+        , options = $.extend({}, $.fn.collapse.defaults, $this.data(), typeof option == 'object' && option)
+      if (!data) $this.data('collapse', (data = new Collapse(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.collapse.defaults = {
+    toggle: true
+  }
+
+  $.fn.collapse.Constructor = Collapse
+
+
+ /* COLLAPSE NO CONFLICT
+  * ==================== */
+
+  $.fn.collapse.noConflict = function () {
+    $.fn.collapse = old
+    return this
+  }
+
+
+ /* COLLAPSE DATA-API
+  * ================= */
+
+  $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {
+    var $this = $(this), href
+      , target = $this.attr('data-target')
+        || e.preventDefault()
+        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+      , option = $(target).data('collapse') ? 'toggle' : $this.data()
+    $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+    $(target).collapse(option)
+  })
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-dropdown.js v2.3.2
+ * http://getbootstrap.com/2.3.2/javascript.html#dropdowns
+ * ============================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* DROPDOWN CLASS DEFINITION
+  * ========================= */
+
+  var toggle = '[data-toggle=dropdown]'
+    , Dropdown = function (element) {
+        var $el = $(element).on('click.dropdown.data-api', this.toggle)
+        $('html').on('click.dropdown.data-api', function () {
+          $el.parent().removeClass('open')
+        })
+      }
+
+  Dropdown.prototype = {
+
+    constructor: Dropdown
+
+  , toggle: function (e) {
+      var $this = $(this)
+        , $parent
+        , isActive
+
+      if ($this.is('.disabled, :disabled')) return
+
+      $parent = getParent($this)
+
+      isActive = $parent.hasClass('open')
+
+      clearMenus()
+
+      if (!isActive) {
+        if ('ontouchstart' in document.documentElement) {
+          // if mobile we we use a backdrop because click events don't delegate
+          $('<div class="dropdown-backdrop"/>').insertBefore($(this)).on('click', clearMenus)
+        }
+        $parent.toggleClass('open')
+      }
+
+      $this.focus()
+
+      return false
+    }
+
+  , keydown: function (e) {
+      var $this
+        , $items
+        , $active
+        , $parent
+        , isActive
+        , index
+
+      if (!/(38|40|27)/.test(e.keyCode)) return
+
+      $this = $(this)
+
+      e.preventDefault()
+      e.stopPropagation()
+
+      if ($this.is('.disabled, :disabled')) return
+
+      $parent = getParent($this)
+
+      isActive = $parent.hasClass('open')
+
+      if (!isActive || (isActive && e.keyCode == 27)) {
+        if (e.which == 27) $parent.find(toggle).focus()
+        return $this.click()
+      }
+
+      $items = $('[role=menu] li:not(.divider):visible a', $parent)
+
+      if (!$items.length) return
+
+      index = $items.index($items.filter(':focus'))
+
+      if (e.keyCode == 38 && index > 0) index--                                        // up
+      if (e.keyCode == 40 && index < $items.length - 1) index++                        // down
+      if (!~index) index = 0
+
+      $items
+        .eq(index)
+        .focus()
+    }
+
+  }
+
+  function clearMenus() {
+    $('.dropdown-backdrop').remove()
+    $(toggle).each(function () {
+      getParent($(this)).removeClass('open')
+    })
+  }
+
+  function getParent($this) {
+    var selector = $this.attr('data-target')
+      , $parent
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    $parent = selector && $(selector)
+
+    if (!$parent || !$parent.length) $parent = $this.parent()
+
+    return $parent
+  }
+
+
+  /* DROPDOWN PLUGIN DEFINITION
+   * ========================== */
+
+  var old = $.fn.dropdown
+
+  $.fn.dropdown = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('dropdown')
+      if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.dropdown.Constructor = Dropdown
+
+
+ /* DROPDOWN NO CONFLICT
+  * ==================== */
+
+  $.fn.dropdown.noConflict = function () {
+    $.fn.dropdown = old
+    return this
+  }
+
+
+  /* APPLY TO STANDARD DROPDOWN ELEMENTS
+   * =================================== */
+
+  $(document)
+    .on('click.dropdown.data-api', clearMenus)
+    .on('click.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+    .on('click.dropdown.data-api'  , toggle, Dropdown.prototype.toggle)
+    .on('keydown.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+
+}(window.jQuery);
+/* =========================================================
+ * bootstrap-modal.js v2.3.2
+ * http://getbootstrap.com/2.3.2/javascript.html#modals
+ * =========================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* MODAL CLASS DEFINITION
+  * ====================== */
+
+  var Modal = function (element, options) {
+    this.options = options
+    this.$element = $(element)
+      .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
+    this.options.remote && this.$element.find('.modal-body').load(this.options.remote)
+  }
+
+  Modal.prototype = {
+
+      constructor: Modal
+
+    , toggle: function () {
+        return this[!this.isShown ? 'show' : 'hide']()
+      }
+
+    , show: function () {
+        var that = this
+          , e = $.Event('show')
+
+        this.$element.trigger(e)
+
+        if (this.isShown || e.isDefaultPrevented()) return
+
+        this.isShown = true
+
+        this.escape()
+
+        this.backdrop(function () {
+          var transition = $.support.transition && that.$element.hasClass('fade')
+
+          if (!that.$element.parent().length) {
+            that.$element.appendTo(document.body) //don't move modals dom position
+          }
+
+          that.$element.show()
+
+          if (transition) {
+            that.$element[0].offsetWidth // force reflow
+          }
+
+          that.$element
+            .addClass('in')
+            .attr('aria-hidden', false)
+
+          that.enforceFocus()
+
+          transition ?
+            that.$element.one($.support.transition.end, function () { that.$element.focus().trigger('shown') }) :
+            that.$element.focus().trigger('shown')
+
+        })
+      }
+
+    , hide: function (e) {
+        e && e.preventDefault()
+
+        var that = this
+
+        e = $.Event('hide')
+
+        this.$element.trigger(e)
+
+        if (!this.isShown || e.isDefaultPrevented()) return
+
+        this.isShown = false
+
+        this.escape()
+
+        $(document).off('focusin.modal')
+
+        this.$element
+          .removeClass('in')
+          .attr('aria-hidden', true)
+
+        $.support.transition && this.$element.hasClass('fade') ?
+          this.hideWithTransition() :
+          this.hideModal()
+      }
+
+    , enforceFocus: function () {
+        var that = this
+        $(document).on('focusin.modal', function (e) {
+          if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
+            that.$element.focus()
+          }
+        })
+      }
+
+    , escape: function () {
+        var that = this
+        if (this.isShown && this.options.keyboard) {
+          this.$element.on('keyup.dismiss.modal', function ( e ) {
+            e.which == 27 && that.hide()
+          })
+        } else if (!this.isShown) {
+          this.$element.off('keyup.dismiss.modal')
+        }
+      }
+
+    , hideWithTransition: function () {
+        var that = this
+          , timeout = setTimeout(function () {
+              that.$element.off($.support.transition.end)
+              that.hideModal()
+            }, 500)
+
+        this.$element.one($.support.transition.end, function () {
+          clearTimeout(timeout)
+          that.hideModal()
+        })
+      }
+
+    , hideModal: function () {
+        var that = this
+        this.$element.hide()
+        this.backdrop(function () {
+          that.removeBackdrop()
+          that.$element.trigger('hidden')
+        })
+      }
+
+    , removeBackdrop: function () {
+        this.$backdrop && this.$backdrop.remove()
+        this.$backdrop = null
+      }
+
+    , backdrop: function (callback) {
+        var that = this
+          , animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+        if (this.isShown && this.options.backdrop) {
+          var doAnimate = $.support.transition && animate
+
+          this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+            .appendTo(document.body)
+
+          this.$backdrop.click(
+            this.options.backdrop == 'static' ?
+              $.proxy(this.$element[0].focus, this.$element[0])
+            : $.proxy(this.hide, this)
+          )
+
+          if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+          this.$backdrop.addClass('in')
+
+          if (!callback) return
+
+          doAnimate ?
+            this.$backdrop.one($.support.transition.end, callback) :
+            callback()
+
+        } else if (!this.isShown && this.$backdrop) {
+          this.$backdrop.removeClass('in')
+
+          $.support.transition && this.$element.hasClass('fade')?
+            this.$backdrop.one($.support.transition.end, callback) :
+            callback()
+
+        } else if (callback) {
+          callback()
+        }
+      }
+  }
+
+
+ /* MODAL PLUGIN DEFINITION
+  * ======================= */
+
+  var old = $.fn.modal
+
+  $.fn.modal = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('modal')
+        , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
+      if (!data) $this.data('modal', (data = new Modal(this, options)))
+      if (typeof option == 'string') data[option]()
+      else if (options.show) data.show()
+    })
+  }
+
+  $.fn.modal.defaults = {
+      backdrop: true
+    , keyboard: true
+    , show: true
+  }
+
+  $.fn.modal.Constructor = Modal
+
+
+ /* MODAL NO CONFLICT
+  * ================= */
+
+  $.fn.modal.noConflict = function () {
+    $.fn.modal = old
+    return this
+  }
+
+
+ /* MODAL DATA-API
+  * ============== */
+
+  $(document).on('click.modal.data-api', '[data-toggle="modal"]', function (e) {
+    var $this = $(this)
+      , href = $this.attr('href')
+      , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+      , option = $target.data('modal') ? 'toggle' : $.extend({ remote:!/#/.test(href) && href }, $target.data(), $this.data())
+
+    e.preventDefault()
+
+    $target
+      .modal(option)
+      .one('hide', function () {
+        $this.focus()
+      })
+  })
+
+}(window.jQuery);
+/* ===========================================================
+ * bootstrap-tooltip.js v2.3.2
+ * http://getbootstrap.com/2.3.2/javascript.html#tooltips
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ===========================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* TOOLTIP PUBLIC CLASS DEFINITION
+  * =============================== */
+
+  var Tooltip = function (element, options) {
+    this.init('tooltip', element, options)
+  }
+
+  Tooltip.prototype = {
+
+    constructor: Tooltip
+
+  , init: function (type, element, options) {
+      var eventIn
+        , eventOut
+        , triggers
+        , trigger
+        , i
+
+      this.type = type
+      this.$element = $(element)
+      this.options = this.getOptions(options)
+      this.enabled = true
+
+      triggers = this.options.trigger.split(' ')
+
+      for (i = triggers.length; i--;) {
+        trigger = triggers[i]
+        if (trigger == 'click') {
+          this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+        } else if (trigger != 'manual') {
+          eventIn = trigger == 'hover' ? 'mouseenter' : 'focus'
+          eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
+          this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+          this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+        }
+      }
+
+      this.options.selector ?
+        (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+        this.fixTitle()
+    }
+
+  , getOptions: function (options) {
+      options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options)
+
+      if (options.delay && typeof options.delay == 'number') {
+        options.delay = {
+          show: options.delay
+        , hide: options.delay
+        }
+      }
+
+      return options
+    }
+
+  , enter: function (e) {
+      var defaults = $.fn[this.type].defaults
+        , options = {}
+        , self
+
+      this._options && $.each(this._options, function (key, value) {
+        if (defaults[key] != value) options[key] = value
+      }, this)
+
+      self = $(e.currentTarget)[this.type](options).data(this.type)
+
+      if (!self.options.delay || !self.options.delay.show) return self.show()
+
+      clearTimeout(this.timeout)
+      self.hoverState = 'in'
+      this.timeout = setTimeout(function() {
+        if (self.hoverState == 'in') self.show()
+      }, self.options.delay.show)
+    }
+
+  , leave: function (e) {
+      var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+      if (this.timeout) clearTimeout(this.timeout)
+      if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+      self.hoverState = 'out'
+      this.timeout = setTimeout(function() {
+        if (self.hoverState == 'out') self.hide()
+      }, self.options.delay.hide)
+    }
+
+  , show: function () {
+      var $tip
+        , pos
+        , actualWidth
+        , actualHeight
+        , placement
+        , tp
+        , e = $.Event('show')
+
+      if (this.hasContent() && this.enabled) {
+        this.$element.trigger(e)
+        if (e.isDefaultPrevented()) return
+        $tip = this.tip()
+        this.setContent()
+
+        if (this.options.animation) {
+          $tip.addClass('fade')
+        }
+
+        placement = typeof this.options.placement == 'function' ?
+          this.options.placement.call(this, $tip[0], this.$element[0]) :
+          this.options.placement
+
+        $tip
+          .detach()
+          .css({ top: 0, left: 0, display: 'block' })
+
+        this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+
+        pos = this.getPosition()
+
+        actualWidth = $tip[0].offsetWidth
+        actualHeight = $tip[0].offsetHeight
+
+        switch (placement) {
+          case 'bottom':
+            tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
+            break
+          case 'top':
+            tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
+            break
+          case 'left':
+            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
+            break
+          case 'right':
+            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
+            break
+        }
+
+        this.applyPlacement(tp, placement)
+        this.$element.trigger('shown')
+      }
+    }
+
+  , applyPlacement: function(offset, placement){
+      var $tip = this.tip()
+        , width = $tip[0].offsetWidth
+        , height = $tip[0].offsetHeight
+        , actualWidth
+        , actualHeight
+        , delta
+        , replace
+
+      $tip
+        .offset(offset)
+        .addClass(placement)
+        .addClass('in')
+
+      actualWidth = $tip[0].offsetWidth
+      actualHeight = $tip[0].offsetHeight
+
+      if (placement == 'top' && actualHeight != height) {
+        offset.top = offset.top + height - actualHeight
+        replace = true
+      }
+
+      if (placement == 'bottom' || placement == 'top') {
+        delta = 0
+
+        if (offset.left < 0){
+          delta = offset.left * -2
+          offset.left = 0
+          $tip.offset(offset)
+          actualWidth = $tip[0].offsetWidth
+          actualHeight = $tip[0].offsetHeight
+        }
+
+        this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
+      } else {
+        this.replaceArrow(actualHeight - height, actualHeight, 'top')
+      }
+
+      if (replace) $tip.offset(offset)
+    }
+
+  , replaceArrow: function(delta, dimension, position){
+      this
+        .arrow()
+        .css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
+    }
+
+  , setContent: function () {
+      var $tip = this.tip()
+        , title = this.getTitle()
+
+      $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+      $tip.removeClass('fade in top bottom left right')
+    }
+
+  , hide: function () {
+      var that = this
+        , $tip = this.tip()
+        , e = $.Event('hide')
+
+      this.$element.trigger(e)
+      if (e.isDefaultPrevented()) return
+
+      $tip.removeClass('in')
+
+      function removeWithAnimation() {
+        var timeout = setTimeout(function () {
+          $tip.off($.support.transition.end).detach()
+        }, 500)
+
+        $tip.one($.support.transition.end, function () {
+          clearTimeout(timeout)
+          $tip.detach()
+        })
+      }
+
+      $.support.transition && this.$tip.hasClass('fade') ?
+        removeWithAnimation() :
+        $tip.detach()
+
+      this.$element.trigger('hidden')
+
+      return this
+    }
+
+  , fixTitle: function () {
+      var $e = this.$element
+      if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+        $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+      }
+    }
+
+  , hasContent: function () {
+      return this.getTitle()
+    }
+
+  , getPosition: function () {
+      var el = this.$element[0]
+      return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
+        width: el.offsetWidth
+      , height: el.offsetHeight
+      }, this.$element.offset())
+    }
+
+  , getTitle: function () {
+      var title
+        , $e = this.$element
+        , o = this.options
+
+      title = $e.attr('data-original-title')
+        || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
+
+      return title
+    }
+
+  , tip: function () {
+      return this.$tip = this.$tip || $(this.options.template)
+    }
+
+  , arrow: function(){
+      return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow")
+    }
+
+  , validate: function () {
+      if (!this.$element[0].parentNode) {
+        this.hide()
+        this.$element = null
+        this.options = null
+      }
+    }
+
+  , enable: function () {
+      this.enabled = true
+    }
+
+  , disable: function () {
+      this.enabled = false
+    }
+
+  , toggleEnabled: function () {
+      this.enabled = !this.enabled
+    }
+
+  , toggle: function (e) {
+      var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this
+      self.tip().hasClass('in') ? self.hide() : self.show()
+    }
+
+  , destroy: function () {
+      this.hide().$element.off('.' + this.type).removeData(this.type)
+    }
+
+  }
+
+
+ /* TOOLTIP PLUGIN DEFINITION
+  * ========================= */
+
+  var old = $.fn.tooltip
+
+  $.fn.tooltip = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('tooltip')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tooltip.Constructor = Tooltip
+
+  $.fn.tooltip.defaults = {
+    animation: true
+  , placement: 'top'
+  , selector: false
+  , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+  , trigger: 'hover focus'
+  , title: ''
+  , delay: 0
+  , html: false
+  , container: false
+  }
+
+
+ /* TOOLTIP NO CONFLICT
+  * =================== */
+
+  $.fn.tooltip.noConflict = function () {
+    $.fn.tooltip = old
+    return this
+  }
+
+}(window.jQuery);
+/* ===========================================================
+ * bootstrap-popover.js v2.3.2
+ * http://getbootstrap.com/2.3.2/javascript.html#popovers
+ * ===========================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* POPOVER PUBLIC CLASS DEFINITION
+  * =============================== */
+
+  var Popover = function (element, options) {
+    this.init('popover', element, options)
+  }
+
+
+  /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
+     ========================================== */
+
+  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
+
+    constructor: Popover
+
+  , setContent: function () {
+      var $tip = this.tip()
+        , title = this.getTitle()
+        , content = this.getContent()
+
+      $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+      $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)
+
+      $tip.removeClass('fade top bottom left right in')
+    }
+
+  , hasContent: function () {
+      return this.getTitle() || this.getContent()
+    }
+
+  , getContent: function () {
+      var content
+        , $e = this.$element
+        , o = this.options
+
+      content = (typeof o.content == 'function' ? o.content.call($e[0]) :  o.content)
+        || $e.attr('data-content')
+
+      return content
+    }
+
+  , tip: function () {
+      if (!this.$tip) {
+        this.$tip = $(this.options.template)
+      }
+      return this.$tip
+    }
+
+  , destroy: function () {
+      this.hide().$element.off('.' + this.type).removeData(this.type)
+    }
+
+  })
+
+
+ /* POPOVER PLUGIN DEFINITION
+  * ======================= */
+
+  var old = $.fn.popover
+
+  $.fn.popover = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('popover')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('popover', (data = new Popover(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.popover.Constructor = Popover
+
+  $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
+    placement: 'right'
+  , trigger: 'click'
+  , content: ''
+  , template: '<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+  })
+
+
+ /* POPOVER NO CONFLICT
+  * =================== */
+
+  $.fn.popover.noConflict = function () {
+    $.fn.popover = old
+    return this
+  }
+
+}(window.jQuery);
+/* =============================================================
+ * bootstrap-scrollspy.js v2.3.2
+ * http://getbootstrap.com/2.3.2/javascript.html#scrollspy
+ * =============================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* SCROLLSPY CLASS DEFINITION
+  * ========================== */
+
+  function ScrollSpy(element, options) {
+    var process = $.proxy(this.process, this)
+      , $element = $(element).is('body') ? $(window) : $(element)
+      , href
+    this.options = $.extend({}, $.fn.scrollspy.defaults, options)
+    this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process)
+    this.selector = (this.options.target
+      || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+      || '') + ' .nav li > a'
+    this.$body = $('body')
+    this.refresh()
+    this.process()
+  }
+
+  ScrollSpy.prototype = {
+
+      constructor: ScrollSpy
+
+    , refresh: function () {
+        var self = this
+          , $targets
+
+        this.offsets = $([])
+        this.targets = $([])
+
+        $targets = this.$body
+          .find(this.selector)
+          .map(function () {
+            var $el = $(this)
+              , href = $el.data('target') || $el.attr('href')
+              , $href = /^#\w/.test(href) && $(href)
+            return ( $href
+              && $href.length
+              && [[ $href.position().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href ]] ) || null
+          })
+          .sort(function (a, b) { return a[0] - b[0] })
+          .each(function () {
+            self.offsets.push(this[0])
+            self.targets.push(this[1])
+          })
+      }
+
+    , process: function () {
+        var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+          , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+          , maxScroll = scrollHeight - this.$scrollElement.height()
+          , offsets = this.offsets
+          , targets = this.targets
+          , activeTarget = this.activeTarget
+          , i
+
+        if (scrollTop >= maxScroll) {
+          return activeTarget != (i = targets.last()[0])
+            && this.activate ( i )
+        }
+
+        for (i = offsets.length; i--;) {
+          activeTarget != targets[i]
+            && scrollTop >= offsets[i]
+            && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+            && this.activate( targets[i] )
+        }
+      }
+
+    , activate: function (target) {
+        var active
+          , selector
+
+        this.activeTarget = target
+
+        $(this.selector)
+          .parent('.active')
+          .removeClass('active')
+
+        selector = this.selector
+          + '[data-target="' + target + '"],'
+          + this.selector + '[href="' + target + '"]'
+
+        active = $(selector)
+          .parent('li')
+          .addClass('active')
+
+        if (active.parent('.dropdown-menu').length)  {
+          active = active.closest('li.dropdown').addClass('active')
+        }
+
+        active.trigger('activate')
+      }
+
+  }
+
+
+ /* SCROLLSPY PLUGIN DEFINITION
+  * =========================== */
+
+  var old = $.fn.scrollspy
+
+  $.fn.scrollspy = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('scrollspy')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.scrollspy.Constructor = ScrollSpy
+
+  $.fn.scrollspy.defaults = {
+    offset: 10
+  }
+
+
+ /* SCROLLSPY NO CONFLICT
+  * ===================== */
+
+  $.fn.scrollspy.noConflict = function () {
+    $.fn.scrollspy = old
+    return this
+  }
+
+
+ /* SCROLLSPY DATA-API
+  * ================== */
+
+  $(window).on('load', function () {
+    $('[data-spy="scroll"]').each(function () {
+      var $spy = $(this)
+      $spy.scrollspy($spy.data())
+    })
+  })
+
+}(window.jQuery);/* ========================================================
+ * bootstrap-tab.js v2.3.2
+ * http://getbootstrap.com/2.3.2/javascript.html#tabs
+ * ========================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* TAB CLASS DEFINITION
+  * ==================== */
+
+  var Tab = function (element) {
+    this.element = $(element)
+  }
+
+  Tab.prototype = {
+
+    constructor: Tab
+
+  , show: function () {
+      var $this = this.element
+        , $ul = $this.closest('ul:not(.dropdown-menu)')
+        , selector = $this.attr('data-target')
+        , previous
+        , $target
+        , e
+
+      if (!selector) {
+        selector = $this.attr('href')
+        selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+      }
+
+      if ( $this.parent('li').hasClass('active') ) return
+
+      previous = $ul.find('.active:last a')[0]
+
+      e = $.Event('show', {
+        relatedTarget: previous
+      })
+
+      $this.trigger(e)
+
+      if (e.isDefaultPrevented()) return
+
+      $target = $(selector)
+
+      this.activate($this.parent('li'), $ul)
+      this.activate($target, $target.parent(), function () {
+        $this.trigger({
+          type: 'shown'
+        , relatedTarget: previous
+        })
+      })
+    }
+
+  , activate: function ( element, container, callback) {
+      var $active = container.find('> .active')
+        , transition = callback
+            && $.support.transition
+            && $active.hasClass('fade')
+
+      function next() {
+        $active
+          .removeClass('active')
+          .find('> .dropdown-menu > .active')
+          .removeClass('active')
+
+        element.addClass('active')
+
+        if (transition) {
+          element[0].offsetWidth // reflow for transition
+          element.addClass('in')
+        } else {
+          element.removeClass('fade')
+        }
+
+        if ( element.parent('.dropdown-menu') ) {
+          element.closest('li.dropdown').addClass('active')
+        }
+
+        callback && callback()
+      }
+
+      transition ?
+        $active.one($.support.transition.end, next) :
+        next()
+
+      $active.removeClass('in')
+    }
+  }
+
+
+ /* TAB PLUGIN DEFINITION
+  * ===================== */
+
+  var old = $.fn.tab
+
+  $.fn.tab = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('tab')
+      if (!data) $this.data('tab', (data = new Tab(this)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tab.Constructor = Tab
+
+
+ /* TAB NO CONFLICT
+  * =============== */
+
+  $.fn.tab.noConflict = function () {
+    $.fn.tab = old
+    return this
+  }
+
+
+ /* TAB DATA-API
+  * ============ */
+
+  $(document).on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+    e.preventDefault()
+    $(this).tab('show')
+  })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-typeahead.js v2.3.2
+ * http://getbootstrap.com/2.3.2/javascript.html#typeahead
+ * =============================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function($){
+
+  "use strict"; // jshint ;_;
+
+
+ /* TYPEAHEAD PUBLIC CLASS DEFINITION
+  * ================================= */
+
+  var Typeahead = function (element, options) {
+    this.$element = $(element)
+    this.options = $.extend({}, $.fn.typeahead.defaults, options)
+    this.matcher = this.options.matcher || this.matcher
+    this.sorter = this.options.sorter || this.sorter
+    this.highlighter = this.options.highlighter || this.highlighter
+    this.updater = this.options.updater || this.updater
+    this.source = this.options.source
+    this.$menu = $(this.options.menu)
+    this.shown = false
+    this.listen()
+  }
+
+  Typeahead.prototype = {
+
+    constructor: Typeahead
+
+  , select: function () {
+      var val = this.$menu.find('.active').attr('data-value')
+      this.$element
+        .val(this.updater(val))
+        .change()
+      return this.hide()
+    }
+
+  , updater: function (item) {
+      return item
+    }
+
+  , show: function () {
+      var pos = $.extend({}, this.$element.position(), {
+        height: this.$element[0].offsetHeight
+      })
+
+      this.$menu
+        .insertAfter(this.$element)
+        .css({
+          top: pos.top + pos.height
+        , left: pos.left
+        })
+        .show()
+
+      this.shown = true
+      return this
+    }
+
+  , hide: function () {
+      this.$menu.hide()
+      this.shown = false
+      return this
+    }
+
+  , lookup: function (event) {
+      var items
+
+      this.query = this.$element.val()
+
+      if (!this.query || this.query.length < this.options.minLength) {
+        return this.shown ? this.hide() : this
+      }
+
+      items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
+
+      return items ? this.process(items) : this
+    }
+
+  , process: function (items) {
+      var that = this
+
+      items = $.grep(items, function (item) {
+        return that.matcher(item)
+      })
+
+      items = this.sorter(items)
+
+      if (!items.length) {
+        return this.shown ? this.hide() : this
+      }
+
+      return this.render(items.slice(0, this.options.items)).show()
+    }
+
+  , matcher: function (item) {
+      return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+    }
+
+  , sorter: function (items) {
+      var beginswith = []
+        , caseSensitive = []
+        , caseInsensitive = []
+        , item
+
+      while (item = items.shift()) {
+        if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
+        else if (~item.indexOf(this.query)) caseSensitive.push(item)
+        else caseInsensitive.push(item)
+      }
+
+      return beginswith.concat(caseSensitive, caseInsensitive)
+    }
+
+  , highlighter: function (item) {
+      var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
+      return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
+        return '<strong>' + match + '</strong>'
+      })
+    }
+
+  , render: function (items) {
+      var that = this
+
+      items = $(items).map(function (i, item) {
+        i = $(that.options.item).attr('data-value', item)
+        i.find('a').html(that.highlighter(item))
+        return i[0]
+      })
+
+      items.first().addClass('active')
+      this.$menu.html(items)
+      return this
+    }
+
+  , next: function (event) {
+      var active = this.$menu.find('.active').removeClass('active')
+        , next = active.next()
+
+      if (!next.length) {
+        next = $(this.$menu.find('li')[0])
+      }
+
+      next.addClass('active')
+    }
+
+  , prev: function (event) {
+      var active = this.$menu.find('.active').removeClass('active')
+        , prev = active.prev()
+
+      if (!prev.length) {
+        prev = this.$menu.find('li').last()
+      }
+
+      prev.addClass('active')
+    }
+
+  , listen: function () {
+      this.$element
+        .on('focus',    $.proxy(this.focus, this))
+        .on('blur',     $.proxy(this.blur, this))
+        .on('keypress', $.proxy(this.keypress, this))
+        .on('keyup',    $.proxy(this.keyup, this))
+
+      if (this.eventSupported('keydown')) {
+        this.$element.on('keydown', $.proxy(this.keydown, this))
+      }
+
+      this.$menu
+        .on('click', $.proxy(this.click, this))
+        .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+        .on('mouseleave', 'li', $.proxy(this.mouseleave, this))
+    }
+
+  , eventSupported: function(eventName) {
+      var isSupported = eventName in this.$element
+      if (!isSupported) {
+        this.$element.setAttribute(eventName, 'return;')
+        isSupported = typeof this.$element[eventName] === 'function'
+      }
+      return isSupported
+    }
+
+  , move: function (e) {
+      if (!this.shown) return
+
+      switch(e.keyCode) {
+        case 9: // tab
+        case 13: // enter
+        case 27: // escape
+          e.preventDefault()
+          break
+
+        case 38: // up arrow
+          e.preventDefault()
+          this.prev()
+          break
+
+        case 40: // down arrow
+          e.preventDefault()
+          this.next()
+          break
+      }
+
+      e.stopPropagation()
+    }
+
+  , keydown: function (e) {
+      this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
+      this.move(e)
+    }
+
+  , keypress: function (e) {
+      if (this.suppressKeyPressRepeat) return
+      this.move(e)
+    }
+
+  , keyup: function (e) {
+      switch(e.keyCode) {
+        case 40: // down arrow
+        case 38: // up arrow
+        case 16: // shift
+        case 17: // ctrl
+        case 18: // alt
+          break
+
+        case 9: // tab
+        case 13: // enter
+          if (!this.shown) return
+          this.select()
+          break
+
+        case 27: // escape
+          if (!this.shown) return
+          this.hide()
+          break
+
+        default:
+          this.lookup()
+      }
+
+      e.stopPropagation()
+      e.preventDefault()
+  }
+
+  , focus: function (e) {
+      this.focused = true
+    }
+
+  , blur: function (e) {
+      this.focused = false
+      if (!this.mousedover && this.shown) this.hide()
+    }
+
+  , click: function (e) {
+      e.stopPropagation()
+      e.preventDefault()
+      this.select()
+      this.$element.focus()
+    }
+
+  , mouseenter: function (e) {
+      this.mousedover = true
+      this.$menu.find('.active').removeClass('active')
+      $(e.currentTarget).addClass('active')
+    }
+
+  , mouseleave: function (e) {
+      this.mousedover = false
+      if (!this.focused && this.shown) this.hide()
+    }
+
+  }
+
+
+  /* TYPEAHEAD PLUGIN DEFINITION
+   * =========================== */
+
+  var old = $.fn.typeahead
+
+  $.fn.typeahead = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('typeahead')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.typeahead.defaults = {
+    source: []
+  , items: 8
+  , menu: '<ul class="typeahead dropdown-menu"></ul>'
+  , item: '<li><a href="#"></a></li>'
+  , minLength: 1
+  }
+
+  $.fn.typeahead.Constructor = Typeahead
+
+
+ /* TYPEAHEAD NO CONFLICT
+  * =================== */
+
+  $.fn.typeahead.noConflict = function () {
+    $.fn.typeahead = old
+    return this
+  }
+
+
+ /* TYPEAHEAD DATA-API
+  * ================== */
+
+  $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
+    var $this = $(this)
+    if ($this.data('typeahead')) return
+    $this.typeahead($this.data())
+  })
+
+}(window.jQuery);
+/* ==========================================================
+ * bootstrap-affix.js v2.3.2
+ * http://getbootstrap.com/2.3.2/javascript.html#affix
+ * ==========================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* AFFIX CLASS DEFINITION
+  * ====================== */
+
+  var Affix = function (element, options) {
+    this.options = $.extend({}, $.fn.affix.defaults, options)
+    this.$window = $(window)
+      .on('scroll.affix.data-api', $.proxy(this.checkPosition, this))
+      .on('click.affix.data-api',  $.proxy(function () { setTimeout($.proxy(this.checkPosition, this), 1) }, this))
+    this.$element = $(element)
+    this.checkPosition()
+  }
+
+  Affix.prototype.checkPosition = function () {
+    if (!this.$element.is(':visible')) return
+
+    var scrollHeight = $(document).height()
+      , scrollTop = this.$window.scrollTop()
+      , position = this.$element.offset()
+      , offset = this.options.offset
+      , offsetBottom = offset.bottom
+      , offsetTop = offset.top
+      , reset = 'affix affix-top affix-bottom'
+      , affix
+
+    if (typeof offset != 'object') offsetBottom = offsetTop = offset
+    if (typeof offsetTop == 'function') offsetTop = offset.top()
+    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+    affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ?
+      false    : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ?
+      'bottom' : offsetTop != null && scrollTop <= offsetTop ?
+      'top'    : false
+
+    if (this.affixed === affix) return
+
+    this.affixed = affix
+    this.unpin = affix == 'bottom' ? position.top - scrollTop : null
+
+    this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''))
+  }
+
+
+ /* AFFIX PLUGIN DEFINITION
+  * ======================= */
+
+  var old = $.fn.affix
+
+  $.fn.affix = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('affix')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('affix', (data = new Affix(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.affix.Constructor = Affix
+
+  $.fn.affix.defaults = {
+    offset: 0
+  }
+
+
+ /* AFFIX NO CONFLICT
+  * ================= */
+
+  $.fn.affix.noConflict = function () {
+    $.fn.affix = old
+    return this
+  }
+
+
+ /* AFFIX DATA-API
+  * ============== */
+
+  $(window).on('load', function () {
+    $('[data-spy="affix"]').each(function () {
+      var $spy = $(this)
+        , data = $spy.data()
+
+      data.offset = data.offset || {}
+
+      data.offsetBottom && (data.offset.bottom = data.offsetBottom)
+      data.offsetTop && (data.offset.top = data.offsetTop)
+
+      $spy.affix(data)
+    })
+  })
+
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/doc/etc/bootstrap/js/bootstrap.min.js b/doc/etc/bootstrap/js/bootstrap.min.js
new file mode 100644
index 0000000..848258d
--- /dev/null
+++ b/doc/etc/bootstrap/js/bootstrap.min.js
@@ -0,0 +1,6 @@
+/*!
+* Bootstrap.js by @fat & @mdo
+* Copyright 2013 Twitter, Inc.
+* http://www.apache.org/licenses/LICENSE-2.0.txt
+*/
+!function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=funct [...]
\ No newline at end of file
diff --git a/doc/etc/fcm-icon.png b/doc/etc/fcm-icon.png
new file mode 100644
index 0000000..bdfa2bf
Binary files /dev/null and b/doc/etc/fcm-icon.png differ
diff --git a/doc/etc/fcm-terms-of-use.html b/doc/etc/fcm-terms-of-use.html
new file mode 100644
index 0000000..a7316bd
--- /dev/null
+++ b/doc/etc/fcm-terms-of-use.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: Terms of Use</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: Terms of Use</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <h2 id="software.licence">FCM Software: Licence</h2>
+
+  <p>FCM is free software: you can redistribute it and/or modify it under the
+  terms of the <a href="http://www.gnu.org/licenses/gpl.html" rel="license">GNU
+  General Public License</a> as published by the Free Software Foundation,
+  either version 3 of the License, or (at your option) any later version.</p>
+
+  <p>FCM is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+  details.</p>
+
+  <p>You should have received a copy of the GNU General Public License along
+  with FCM. If not, see
+  <a href="http://www.gnu.org/licenses/"
+  rel="license">http://www.gnu.org/licenses/</a>.</p>
+
+  <h2 id="doc.licence">FCM Documentation: Licence</h2>
+
+  <p>You may use and re-use Crown copyright information from any part of the
+  <a href="..">FCM Documentation</a> (not including logos) free of charge in
+  any format or medium, under the terms and conditions of the <a href=
+  "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+  "license">Open Government Licence</a>, provided it is reproduced accurately
+  and not used in a misleading context. Where any of the Crown copyright
+  items on this website are being republished or copied to others, the source
+  of the material must be identified and the copyright status
+  acknowledged.</p>
+
+  <p>See the <a href=
+  "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+  "license">Open Government Licence</a> for the full conditions of this
+  licence and for information on how to attribute the source of material.</p>
+
+  <h2 id="team">FCM: Maintenance</h2>
+
+  <p>The FCM software and <a href="..">FCM Documentation</a> are maintained
+  by the <a href="mailto:fcm-team at metoffice.gov.uk">FCM Team</a>, Met Office,
+  FitzRoy Road, Exeter, EX1 3PB, UK.</p>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/etc/fcm-version.js b/doc/etc/fcm-version.js
new file mode 100644
index 0000000..ca077f8
--- /dev/null
+++ b/doc/etc/fcm-version.js
@@ -0,0 +1 @@
+FCM.VERSION="2014.09.0";
diff --git a/doc/etc/fcm.css b/doc/etc/fcm.css
new file mode 100644
index 0000000..c25e6c7
--- /dev/null
+++ b/doc/etc/fcm.css
@@ -0,0 +1,28 @@
+/**********************************************************************
+ * (C) British Crown Copyright 2006-14 Met Office.
+ *
+ * This file is part of FCM.
+ *
+ * FCM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FCM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FCM. If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************/
+
+:hover > .sectionlink,
+.sectionlink:active,
+.sectionlink:hover {
+    display: inline;
+}
+.sectionlink {
+    display: none;
+}
diff --git a/doc/etc/fcm.js b/doc/etc/fcm.js
new file mode 100644
index 0000000..1a6ccef
--- /dev/null
+++ b/doc/etc/fcm.js
@@ -0,0 +1,166 @@
+/**********************************************************************
+ * (C) British Crown Copyright 2006-14 Met Office.
+ *
+ * This file is part of FCM.
+ *
+ * FCM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FCM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FCM. If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************/
+
+FCM = {};
+$(function() {
+    var TITLE_COLLAPSE = "collapse";
+    var TITLE_EXPAND = "expand";
+    var IS_MAIN = true;
+
+    // Toggle a collapse/expand image.
+    function collapse_expand_icon_toggle(anchor) {
+        var icon = $("i", anchor);
+        if (icon.attr("title") == TITLE_EXPAND) {
+            icon.attr("title", TITLE_COLLAPSE);
+            icon.removeClass("icon-chevron-right");
+            icon.addClass("icon-chevron-down");
+            anchor.siblings().filter("ul").show();
+        }
+        else { // if (icon.attr("title") == "collapse")
+            icon.attr("title", TITLE_EXPAND);
+            icon.removeClass("icon-chevron-down");
+            icon.addClass("icon-chevron-right");
+            anchor.siblings().filter("ul").hide();
+        }
+    }
+
+    // Add collapse/expand anchor to a ul tree.
+    function ul_collapse_expand(ul, is_main) {
+        var nodes = $("li", ul);
+        nodes.each(function(i) {
+            var li = $(this);
+            var li_anchor = li.children().first();
+            if (!li_anchor.is("a")) {
+                return;
+            }
+            var li_ul = $("> ul", li);
+            li_ul.hide();
+            var icon = $("<i/>", {"title": TITLE_EXPAND});
+            icon.addClass("icon-chevron-right");
+            icon.css("opacity", 0.5);
+            var anchor = $("<a/>").append(icon);
+            li.prepend(anchor);
+            if (is_main) {
+                anchor.click(function() {
+                    var href = li_anchor.attr("href");
+                    $.get(
+                        href,
+                        function(data) {
+                            collapse_expand_icon_toggle(anchor);
+                            anchor.unbind("click");
+                            if (content_gen(li, data, href)) {
+                                ul_collapse_expand(li.children().filter("ul"));
+                                anchor.click(function() {
+                                    collapse_expand_icon_toggle(anchor);
+                                });
+                            }
+                            else {
+                                icon.css("opacity", 0.1);
+                            }
+                        },
+                        "html"
+                    )
+                    .error(function(b) {
+                        alert(b);
+                        anchor.unbind("click");
+                        icon.css("opacity", 0.1);
+                    });
+                });
+            }
+            else if (li_ul.length) {
+                anchor.click(function() {
+                    collapse_expand_icon_toggle(anchor);
+                });
+            }
+            else {
+                icon.css("opacity", 0.1);
+            }
+        });
+    }
+
+    // Generate table of content of a document.
+    function content_gen(root, d, d_href) {
+        if (d == null) {
+            d = document;
+        }
+        var CONTENT_INDEX_OF = {"h2": 1, "h3": 2, "h4": 3, "h5": 4, "h6": 5};
+        var stack = [];
+        var done_something = false;
+        var headings = $("h2, h3, h4, h5, h6", $(d));
+        headings.each(function(i) {
+            if (this.id == null || this.id == "") {
+                return;
+            }
+            var tag_name = this.tagName.toLowerCase();
+            // Add to table of content
+            while (CONTENT_INDEX_OF[tag_name] < stack.length) {
+                stack.shift();
+            }
+            while (stack.length < CONTENT_INDEX_OF[tag_name]) {
+                var node = stack.length == 0 ? root : $("> :last-child", stack[0]);
+                stack.unshift($("<ul/>").appendTo(node).addClass("unstyled"));
+            }
+            var href = "#" + this.id;
+            if (d_href) {
+                href = d_href + href;
+            }
+            var padding = "";
+            for (var i = 0; i < stack.length; i++) {
+                padding += "    ";
+            }
+            stack[0].append($("<li/>").html(padding).append(
+                $("<a/>", {"href": href}).html($(this).text())
+            ));
+
+            // Add a section link as well
+            if (d == document) {
+                var section_link_anchor = $("<a/>", {"href": "#" + this.id});
+                section_link_anchor.addClass("sectionlink");
+                section_link_anchor.append("\xb6");
+                $(this).append(section_link_anchor);
+            }
+
+            done_something = true;
+        });
+        return done_something;
+    }
+
+    var NODE;
+
+    // Top page table of content
+    NODE = $(".fcm-top-content");
+    if (NODE) {
+        ul_collapse_expand(NODE, IS_MAIN);
+    }
+
+    // Table of content
+    NODE = $(".fcm-page-content");
+    if (NODE) {
+        if (content_gen(NODE)) {
+            ul_collapse_expand(NODE);
+        }
+    }
+
+    // Display version information
+    NODE = $(".fcm-version");
+    if (NODE) {
+        NODE.text("FCM " + FCM.VERSION);
+    }
+});
diff --git a/doc/etc/fcm.png b/doc/etc/fcm.png
new file mode 100644
index 0000000..9677679
Binary files /dev/null and b/doc/etc/fcm.png differ
diff --git a/doc/etc/jquery.min.js b/doc/etc/jquery.min.js
new file mode 100644
index 0000000..198b3ff
--- /dev/null
+++ b/doc/etc/jquery.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.7.1 jquery.com | jquery.org/license */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement( [...]
+f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]| [...]
+{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replac [...]
\ No newline at end of file
diff --git a/doc/etc/moment.min.js b/doc/etc/moment.min.js
new file mode 100644
index 0000000..62b1697
--- /dev/null
+++ b/doc/etc/moment.min.js
@@ -0,0 +1,6 @@
+// moment.js
+// version : 2.1.0
+// author : Tim Wood
+// license : MIT
+// momentjs.com
+!function(t){function e(t,e){return function(n){return u(t.call(this,n),e)}}function n(t,e){return function(n){return this.lang().ordinal(t.call(this,n),e)}}function s(){}function i(t){a(this,t)}function r(t){var e=t.years||t.year||t.y||0,n=t.months||t.month||t.M||0,s=t.weeks||t.week||t.w||0,i=t.days||t.day||t.d||0,r=t.hours||t.hour||t.h||0,a=t.minutes||t.minute||t.m||0,o=t.seconds||t.second||t.s||0,u=t.milliseconds||t.millisecond||t.ms||0;this._input=t,this._milliseconds=u+1e3*o+6e4*a+3 [...]
\ No newline at end of file
diff --git a/doc/index.html b/doc/index.html
new file mode 100644
index 0000000..6d73ef7
--- /dev/null
+++ b/doc/index.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM Documentation</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="etc/fcm-icon.png" type="image/png" />
+  <link href="etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="active brand" href="#"><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="installation/">Installation</a></li>
+
+        <li><a href="user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+  <div class="hero-unit">
+    <div class="container-fluid">
+      <div class="row-fluid">
+        <div class="span12">
+          <h1>FCM</h1>
+        </div>
+      </div>
+    
+      <div class="row-fluid">
+        <div class="span6 well">
+          <h2>Build</h2>
+
+          <p>A powerful build system for modern Fortran software
+          applications.</p>
+        </div>
+
+        <div class="span6 well">
+          <h2>Version Control</h2>
+
+          <p>Wrappers to the Subversion version control system,
+          usage conventions and processes for scientific software
+          development.</p>
+        </div>
+      </div>
+
+      <div class="row-fluid text-center">
+        <div class="span4">
+          <p><a class="btn btn-primary btn-block" href=
+          "https://github.com/metomi/fcm/releases/">
+          <i class="icon-download icon-white"></i>
+          Get FCM from Github</a></p>
+        </div>
+        <div class="span4">
+          <p><a class="btn btn-primary btn-block" href="installation/">
+          <i class="icon-wrench icon-white"></i>
+          Read the FCM Installation Guide</a></p>
+        </div>
+        <div class="span4">
+          <p><a class="btn btn-primary btn-block" href="user_guide/">
+          <i class="icon-user icon-white"></i>
+          Read the FCM User Guide</a></p>
+        </div>
+      </div>
+    </div>
+  </div>
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="etc/jquery.min.js"></script>
+  <script type="text/javascript" src="etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="etc/fcm.js"></script>
+  <script type="text/javascript" src="etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/installation/index.html b/doc/installation/index.html
new file mode 100644
index 0000000..2b25aef
--- /dev/null
+++ b/doc/installation/index.html
@@ -0,0 +1,354 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: Installation</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" type="text/css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" type="text/css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li class="active"><a href="#">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: Installation</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+  <h2 id="requirements">System Requirements</h2>
+
+  <p>FCM is intended to run on a Unix/Linux system. It is known to work on
+  RHEL-6 and some part of it on AIX-7.</p>
+
+  <p>FCM releases can be downloaded from <a href=
+  "https://github.com/metomi/fcm/releases">Github</a>. Download the tar.gz file
+  and un-pack it into an appropriate location on your system. Add the
+  <samp>bin/</samp> directory into your <var>PATH</var> environment variable.
+  Enable the configuration files in <samp>etc/fcm/</samp> directory and edit
+  them to meet the your requirements. Once you have done this you should now
+  have access to the FCM user utilities, assuming that you have met the
+  requirements described below:</p>
+
+  <dl class="well">
+    <dt><a href="http://www.perl.org/">Perl</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> <code>fcm</code>.</p>
+
+      <p><dfn>versions known to work:</dfn> 5.10.1.</p>
+
+      <p><dfn>remark:</dfn> We assume that all <em>core</em> Perl modules (as
+      documented by <a href="http://perldoc.perl.org/">perldoc.perl.org</a>) of
+      the <em>known to work versions</em> are installed on your system. (N.B. On
+      platforms based on RHEL, you may need the <em>perl-core</em> RPM instead
+      of just <em>perl</em>, see <a href=
+      "http://www.nntp.perl.org/group/perl.perl5.porters/2009/08/msg149891.html">this
+      discussion</a>.)</p>
+    </dd>
+
+    <dt>Perl module <a href=
+    "http://search.cpan.org/dist/Config-IniFiles/">Config::IniFiles</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the admin commands.</p>
+
+      <p><dfn>versions known to work:</dfn> RHEL-6: 2.72.</p>
+    </dd>
+
+    <dt>Perl module <a href=
+    "http://search.cpan.org/dist/DBD-SQLite/">DBD::SQLite</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the admin commands.</p>
+
+      <p><dfn>versions known to work:</dfn> RHEL-6: 1.29.</p>
+    </dd>
+
+    <dt>Perl module <a href=
+    "http://search.cpan.org/dist/Alien-SVN/">SVN::Client</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the admin commands.</p>
+
+      <p><dfn>versions known to work:</dfn> RHEL-6: 1.8.5 (RPM from <a href=
+      "http://opensource.wandisco.com/rhel/6/svn-1.8/RPMS/x86_64/">http://opensource.wandisco.com/</a>).</p>
+    </dd>
+
+    <dt>Perl module <a href=
+    "http://search.cpan.org/dist/XML-Parser/">XML::Parser</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the code management commands.</p>
+
+      <p><dfn>versions known to work:</dfn> RHEL-6: 2.36.</p>
+    </dd>
+
+    <dt>Perl module <a href=
+    "http://search.cpan.org/~srezic/Tk-804.028/">Tk</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> <code>fcm gui</code>.</p>
+
+      <p><dfn>versions known to work:</dfn> RHEL-6: 804.028.</p>
+    </dd>
+
+    <dt><a href="http://subversion.apache.org/">Subversion</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the code management commands, the extract system
+      of <code>fcm make</code>, the deprecated <code>fcm extract</code>.</p>
+
+      <p><dfn>versions known to work:</dfn> AIX-7: 1.6.18, RHEL-6: 1.6.17,
+      RHEL-6: 1.8.8.</p>
+
+      <p><dfn>remark:</dfn> you can use the extract system to mirror code to a
+      remote platform for building. Therefore it is only necessary to have
+      Subversion installed on the platform where you do your code development.
+      If you use other platforms purely for building and running then you do
+      not need to have Subversion installed on these platforms.</p>
+    </dd>
+
+    <dt><a href="http://trac.edgewall.org/">Trac</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> (optional, but highly recommended as a companion
+      to Subversion)</p>
+
+      <p><dfn>versions known to work:</dfn> 0.11.7.</p>
+    </dd>
+
+    <dt><a href="http://furius.ca/xxdiff/">xxdiff</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> <code>fcm branch-diff --graphical</code>,
+      <code>fcm conflicts</code>, <code>fcm diff --graphical</code>.</p>
+
+      <p><dfn>versions known to work:</dfn> RHEL-6: 3.2.</p>
+
+      <p><dfn>remark:</dfn> The <code>fcm branch-diff --graphical</code> and
+      <code>fcm diff --graphical</code> commands use xxdiff by default but can
+      also use other graphical diff tools.</p>
+    </dd>
+
+    <dt><a href="http://www.gnu.org/software/diffutils/">GNU diffutils</a>:
+    diff3</dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the extract system of <code>fcm make</code>, the
+      deprecated <code>fcm extract</code>.</p>
+
+      <p><dfn>versions known to work:</dfn> RHEL-6: 2.8.1.</p>
+
+      <p><dfn>remark:</dfn>: used to merge changes to source files modified by
+      2+ diff source trees (compared with the base).</p>
+    </dd>
+
+    <dt><a href="http://rsync.samba.org/">rsync</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the mirror system of <code>fcm make</code>, the
+      deprecated <code>fcm extract</code>.</p>
+
+      <p><dfn>versions known to work:</dfn> AIX-7: 3.0.9, RHEL-6: 3.0.6.</p>
+
+      <p><dfn>remark:</dfn> used to mirror source file to another
+      <var>USER at HOST</var>.</p>
+    </dd>
+
+    <dt><a href="http://www.gnu.org/software/make/make.html">GNU make</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the deprecated <code>fcm build</code>.</p>
+
+      <p><dfn>versions known to work:</dfn> AIX-7: 3.80, RHEL-6: 3.81.</p>
+    </dd>
+  </dl>
+  </div>
+  </div>
+
+  <div class="row">
+  <div class="span12">
+  <h2 id="distro">Content in the Distribution</h2>
+
+  <dl class="well">
+    <dt>ACKNOWLEDGEMENT.md<br />
+    CONTRIBUTING.md<br />
+    README.md<br />
+    COPYING</dt>
+
+    <dd>Terms of use and project information.</dd>
+
+    <dt>CHANGES.md</dt>
+
+    <dd>Contains highlight and noteworthy changes since release 2-3-1.</dd>
+
+    <dt>bin/</dt>
+
+    <dd>Contains the <code>fcm</code> user utilities.</dd>
+
+    <dt>doc/</dt>
+
+    <dd>FCM documentation.</dd>
+
+    <dt>doc/installation/</dt>
+
+    <dd>Contains this document.</dd>
+
+    <dt>doc/release_notes/</dt>
+
+    <dd>Contains release notes prior to and including release 2-3-1.</dd>
+
+    <dt>doc/user_guide/</dt>
+
+    <dd>Contains the <a href="../user_guide/">FCM User Guide</a>.</dd>
+
+    <dt>etc/</dt>
+
+    <dd>Miscellaneous items and example site configurations, including the
+    <samp>fcm/keyword.cfg.example</samp> file. If you wish to define keywords
+    for your site you will need to create the <samp>etc/fcm/keyword.cfg</samp>
+    file. An example file, <samp>fcm/keyword.cfg.example</samp>, is provided
+    which is a copy of the file currently used at the Met Office. For further
+    details please refer to the section <a
+    href="../user_guide/system_admin.html#fcm-keywords">FCM keywords</a> in the
+    System Admin chapter of the User Guide.</dd>
+
+    <dt>lib/</dt>
+
+    <dd>Contains the Perl library of FCM.</dd>
+
+    <dt>man/</dt>
+
+    <dd>Contains a basic manual page for <code>fcm</code>.</dd>
+
+    <dt>sbin/</dt>
+
+    <dd>Contains a selection of useful admin utility commands.</dd>
+
+    <dt>sbin/my-regular-update.example</dt>
+
+    <dd>An example of how you might set up a cron job to make use of the
+    <samp><repos>.latest</samp> file (see
+    <code>svn-hooks/post-commit-background</code>).</dd>
+
+    <dt>svn-hooks/</dt>
+
+    <dd>Contains a selection of useful hook scripts for Subversion.</dd>
+
+    <dt>svn-hooks/pre-commit</dt>
+
+    <dd>
+      This script restricts write-access to the repository by checking the
+      following:
+
+      <ul>
+        <li>It executes the Subversion utility <code>svnperms.py</code> if it,
+        and the associated <samp>svnperms.conf</samp> file, exist. This utility
+        checks whether the author of the current transaction has enough
+        permission to write to particular paths in the repository.</li>
+
+        <li>It checks the disk space required by the current transaction. It
+        fails the commit if it requires more than 10MB of disk space (or
+        whatever is specified in the
+        <code>pre-commit-size-threshold.conf</code> file.</li>
+      </ul>
+    </dd>
+
+    <dt>svn-hooks/post-commit</dt>
+
+    <dd>This script runs <code>post-commit-background</code> in the
+    background.</dd>
+
+    <dt>svn-hooks/post-commit-background</dt>
+
+    <dd>
+      This script runs in the background after each commit.
+
+      <ul>
+        <li>It updates a <samp><repos>.latest</samp> file with the latest
+        revision number.</li>
+
+        <li>It creates a dump of the new revision.</li>
+
+        <li>It calls <code>post-commit-background-custom</code> if it
+        exists.</li>
+      </ul>
+    </dd>
+
+    <dt>svn-hooks/pre-revprop-change</dt>
+
+    <dd>This script only allows the modification of <var>svn:log</var>.</dd>
+
+    <dt>svn-hooks/post-revprop-change</dt>
+
+    <dd>This script runs <code>post-revprop-change-background</code> in the
+    background.</dd>
+
+    <dt>svn-hooks/post-revprop-change-background</dt>
+
+    <dd>This script invokes the <code>trac-admin</code> command to
+    <code>resync</code> the revision property cache stored in the corresponding
+    Trac environment. If a user modifies the log message of a changeset and
+    he/she is not the original author of the changeset, this script will e-mail
+    the original author. If the file
+    <code>post-revprop-change-background-cc.list</code> exits, the script will
+    also e-mail those in the list.</dd>
+
+    <dt>t/</dt>
+
+    <dd>Contains functional test for FCM.</dd>
+
+    <dt>test/</dt>
+
+    <dd>Contains regression tests for FCM.</dd>
+
+    <dt>test/test_include/</dt>
+
+    <dd>Contains simple test code to check how your chosen compilers handle
+    include files.</dd>
+
+    <dt>tutorial/</dt>
+
+    <dd>Contains the files necessary to set up a Subversion repository for the
+    FCM tutorial. This will allow you to follow the <a href=
+    "../user_guide/getting_started.html#tutorial">tutorial section</a> in the
+    User Guide. See <samp>tutorial/README</samp> on how to set it up.</dd>
+  </dl>
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/release_notes/1-1.html b/doc/release_notes/1-1.html
new file mode 100644
index 0000000..a66ab78
--- /dev/null
+++ b/doc/release_notes/1-1.html
@@ -0,0 +1,475 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM 1.1 Release Notes</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM 1.1 Release Notes <small>06 November 2006</small></h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>These are the release notes for FCM release 1.1. You can use this release
+  of FCM freely under the terms of the FCM LICENSE, which you should receive
+  with the distribution of this release. Release 1.1 is the first external
+  release of FCM.  (Release 1.0, 30 November 2005, was an internal Met Office
+  release which marked the start of the main migration of systems into FCM.)</p>
+
+  <p>FCM is maintained by the FCM team at the Met Office. Please feedback any
+  bug reports or feature requests to us by <a href=
+  "mailto:fcm-team at metoffice.gov.uk">e-mail</a>.</p>
+
+  <h2 id="new">What's New?</h2>
+
+  <p>Build system:</p>
+
+  <ul>
+    <li>Support building of Fortran <code>BLOCKDATA</code> program units.</li>
+
+    <li>Option to generate Fortran interface files in lower case names after
+    their program units.</li>
+
+    <li>Allow renaming of main program targets using the build configuration
+    file.</li>
+
+    <li>The <samp>fcm_env.ksh</samp> file now provides an environment variable
+    for the <samp>etc/</samp> sub-directory of the build which can be used if
+    you are <em>building</em> data files.</li>
+
+    <li>The build root directory is now locked while a build is running. This
+    prevents multiple instances of build running in the same directory.
+    However, you can bypass the lock if you specify the new
+    <code>--ignore-lock</code> option with <code>fcm build</code>.</li>
+  </ul>
+
+  <p>Extract system:</p>
+
+  <ul>
+    <li>The destination root directory is now locked while an extract is
+    running. This prevents multiple instances of extract running in the same
+    directory. However, you can bypass the lock if you specify the new
+    <code>--ignore-lock</code> option with <code>fcm extract</code>.</li>
+  </ul>
+
+  <p>Code management commands:</p>
+
+  <ul>
+    <li><code>fcm merge</code> now supports custom and reverse modes.</li>
+
+    <li><code>fcm merge</code> now allows automatic merges in sub-trees if it
+    is safe.</li>
+
+    <li><code>fcm merge</code> now handles automatic merges from sibling
+    branches that are created at different revisions of the parent.</li>
+
+    <li><code>fcm branch, fcm diff --branch</code> and <code>fcm merge</code>
+    can now handle creation, diff and automatic merge of a branch of a
+    branch.</li>
+
+    <li><code>fcm switch</code> is improved to allow safer switches of your
+    working copy to point to different branches in your project.</li>
+
+    <li><code>fcm commit</code> now displays your location in the branch, and
+    extra warning when you are committing to the trunk of a project.</li>
+
+    <li>New <code>fcm mkpatch</code> command.</li>
+  </ul>
+
+  <p>General:</p>
+
+  <ul>
+    <li>Error handling is improved.</li>
+
+    <li>The User Guide is now complete, with a much improved tutorial.</li>
+
+    <li>New document: Fortran coding standard for FCM.</li>
+  </ul>
+
+  <h2 id="fix">Minor Enhancements & Bug Fixes</h2>
+
+  <p>Build system:</p>
+
+  <ul>
+    <li>Ignore empty Fortran source files, so that alternate sections can be
+    handled correctly.</li>
+
+    <li>Handle recursive header file dependency correctly in pre-processing
+    stage.</li>
+
+    <li>Identify changes in pre-processed source file, the pre-processor
+    options and keys correctly in incremental builds.</li>
+
+    <li>Improve speed of pre-processing in incremental builds by not performing
+    any unnecessary null-action pre-processing.</li>
+
+    <li>Load archiver no longer includes main program objects.</li>
+
+    <li>It is now possible for a sub-package to exclude particular types of
+    dependencies.</li>
+  </ul>
+
+  <p>Extract system:</p>
+
+  <ul>
+    <li>Sub-directories are now extracted with the non-recursive mode of
+    <code>svn export</code>.</li>
+
+    <li>Peg revisions should now be handled correctly in <var>INC</var> extract
+    declarations.</li>
+
+    <li>The command should now fail if a declared source directory does not
+    exist or if the update of an extract destination fails.</li>
+  </ul>
+
+  <p>Code management commands:</p>
+
+  <ul>
+    <li><code>fcm branch --info</code> and <code>fcm diff</code> can now take a
+    <code>PATH</code> as an argument.</li>
+
+    <li>The <code>--ticket</code> option of <code>fcm branch --create</code>
+    can now accept multiple tickets.</li>
+
+    <li><code>fcm branch --info</code> and <code>fcm diff --branch</code>
+    should now work correctly in a sub-tree of a branch.</li>
+
+    <li><code>fcm branch --create</code> no longer offers to checkout the
+    branch.</li>
+
+    <li><code>fcm commit</code> no longer fails when adding a new symbolic
+    link.</li>
+
+    <li>The <code>--password</code> option is now supported by the <code>fcm
+    branch --create</code>, <code>fcm branch --delete</code>, <code>fcm
+    commit</code> and <code>fcm delete</code> commands.</li>
+
+    <li>Empty arguments to code management commands are now parsed
+    correctly.</li>
+
+    <li>The <code>fcm diff --graphical</code> option no longer fails with
+    binary files.</li>
+
+    <li>FCM will always set the environment variable <var>LANG=en_GB</var>
+    before running Subversion commands. This prevents failure of FCM when it
+    attempts to parse output from Subversion commands when a different
+    <var>LANG</var> setting is used.</li>
+  </ul>
+
+  <p>General:</p>
+
+  <ul>
+    <li>Various other very minor enhancements and bug fixes.</li>
+  </ul>
+
+  <h2 id="req">System Requirements</h2>
+
+  <h3 id="req_perl">Perl</h3>
+
+  <p>The core part of FCM is a set of Perl scripts and modules. For the build
+  system to work, you need the following modules installed:</p>
+
+  <ul>
+    <li>Carp</li>
+
+    <li>Cwd</li>
+
+    <li>File::Basename</li>
+
+    <li>File::Compare</li>
+
+    <li>File::Find</li>
+
+    <li>File::Path</li>
+
+    <li>File::Spec::Functions</li>
+
+    <li>File::Spec</li>
+
+    <li>FindBin</li>
+
+    <li>Getopt::Long</li>
+
+    <li>POSIX</li>
+  </ul>
+
+  <p>The code management commands and extract system need the following
+  additional modules installed:</p>
+
+  <ul>
+    <li>File::Temp</li>
+
+    <li>Getopt::Long</li>
+
+    <li>HTTP::Date</li>
+
+    <li>XML::DOM</li>
+  </ul>
+
+  <p>To use the simple GUI for some of the code management commands, you also
+  need the following modules:</p>
+
+  <ul>
+    <li>Tk::ROText</li>
+
+    <li>Tk</li>
+  </ul>
+
+  <p>At the Met Office we are currently using the complete FCM system with Perl
+  5.8.x. In addition the build system is being used with Perl 5.6.x.</p>
+
+  <h3 id="req_svn">Subversion</h3>
+
+  <p>To use the code management commands (and relevant parts of the extract
+  system) you need to have <a href=
+  "http://subversion.tigris.org/">Subversion</a> installed.</p>
+
+  <ul>
+    <li>FCM makes extensive use of peg revisions in both the code management
+    and extract systems. This requires Subversion 1.2.0.</li>
+
+    <li>At the Met Office we are currently using Subversion 1.3.2 (although
+    1.2.3 was used until very recently).</li>
+  </ul>
+
+  <p>Note that the extract system can mirror extracted code to a remote
+  platform for building. Therefore it is only necessary to have Subversion
+  installed on the platform where you do your code development. If you use
+  other platforms purely for building and running then you do not need to have
+  Subversion installed on these platforms.</p>
+
+  <h3 id="req_trac">Trac</h3>
+
+  <p>The use of <a href="http://trac.edgewall.org/">Trac</a> is entirely
+  optional (although highly recommended if you are using Subversion). At the
+  Met Office we are currently using version 0.9.6.</p>
+
+  <h3 id="req_other">Other Requirements</h3>
+
+  <p>The <code>fcm diff --graphical</code> and <code>fcm conflicts</code>
+  commands require <a href="http://furius.ca/xxdiff/">xxdiff</a>. At the Met
+  Office we are currently using version 3.1.</p>
+
+  <p>The build system requires <a href=
+  "http://www.gnu.org/software/make/make.html">GNU make</a>. At the Met Office
+  we are currently using version 3.79.x and 3.80.</p>
+
+  <p>Optionally, the build system can use <a href=
+  "http://www.ifremer.fr/ditigo/molagnon/fortran90/">f90aib</a> to generate
+  interface files. However, there is also a built in Perl based interface file
+  generator which is quicker and better in most cases so you are unlikely to
+  need f90aib unless you hit a problem with some particular code.</p>
+
+  <p>FCM is intended to run on a Unix/Linux system. It is currently used at the
+  Met Office on Linux (Red Hat 9 and Red Hat Enterprise 2.1 and 4.4) and HP-UX
+  11.00.</p>
+
+  <h2 id="ins">Installation</h2>
+
+  <p>FCM is distributed in the form of a compressed tar file. Un-pack the tar
+  file into an appropriate location on your system. Then add the
+  <samp>bin/</samp> directory into your <var>PATH</var>. Once you have done this
+  you should now have full access to the FCM system, assuming that you have met
+  the requirements described in the previous section.</p>
+
+  <p>If you wish to define keywords for your systems you will need to create a
+  file <samp>etc/fcm.cfg</samp>. An example file, <samp>fcm.cfg.eg</samp>, is
+  provided which is a copy of the file currently used at the Met Office. For
+  further details please refer to the section <a href=
+  "../user_guide/system_admin.html#fcm-keywords">FCM keywords</a> in the System
+  Admin chapter of the User Guide.</p>
+
+  <p>The <samp>doc/</samp> directory contains all the system documentation.</p>
+
+  <ul>
+    <li><samp>doc/release_notes/</samp> contains these release notes. It also
+    contains the release notes for all previous versions which may be useful if
+    you have skipped any versions.</li>
+
+    <li><samp>doc/user_guide/</samp> contains the FCM User Guide in both
+    <a href="../user_guide/">HTML</a> and <a href=
+    "../user_guide/fcm-user-guide.pdf">PDF</a> form.</li>
+
+    <li><samp>doc/design/</samp> contains the <a href="../design/">FCM Detailed
+    Design</a> document (currently in draft form).</li>
+
+    <li><samp>doc/standards/</samp> contains the FCM <a href=
+    "../standards/perl_standard.html">Perl</a> and <a href=
+    "../standards/fortran_standard.html">Fortran</a> coding standards. The Perl
+    standard describes the standards followed by the FCM code. The Fortran
+    standard contains some <a href=
+    "../standards/fortran_standard.html#fcm">specific advice</a> on the best
+    way of writing Fortran code for use with FCM as well as more general advice
+    on good practise.</li>
+  </ul>
+
+  <p>The <samp>tutorial/</samp> directory contains the files necessary to set
+  up a tutorial repository. This will allow you to follow the <a href=
+  "../user_guide/getting_started.html#tutorial">tutorial section</a> in the
+  User Guide.</p>
+
+  <ul>
+    <li>The file <samp>tutorial/repos/tutorial.dump</samp> should be loaded
+    into an empty repository using the <code>svnadmin load</code> command.</li>
+
+    <li>The hook scripts in <samp>tutorial/hook/</samp> should then be
+    installed in this repository in order to prevent any commits to the trunk.
+    Note that the configuration file <samp>svnperms.conf</samp> assumes that
+    the tutorial repository is called <samp>tutorial_svn</samp>. Please edit
+    this file if you use a different name.</li>
+
+    <li>The repository should be configured to allow users write access. You
+    may find it easiest to simply allow anonymous access.</li>
+
+    <li>A Trac system should be configured associated with the Tutorial
+    repository. You then need to allow users write access. You may find it
+    easiest to set up a number of guest accounts for this purpose.</li>
+  </ul>
+
+  <p>The <samp>templates/</samp> directory contains various example scripts
+  which you may find useful. Note that these scripts are all specific to the
+  Met Office and may contain hard coded paths and email addresses. They are
+  provided in the hope that you may find them useful as templates for setting
+  up similar scripts of your own. However, they should only be used after
+  careful review to adapt them to your environment. The contents are as
+  follows:</p>
+
+  <dl>
+    <dt>templates/hook/pre-commit</dt>
+
+    <dd>
+      This script restricts write-access to the repository by checking the
+      following:
+
+      <ul>
+        <li>It executes the Subversion utility <code>svnperms.py</code> if it
+        exists. This utility checks whether the author of the current
+        transaction has enough permission to write to particular paths in the
+        repository.</li>
+
+        <li>It checks the disk space required by the current transaction. It
+        fails the commit if it requires more than 5Mb of disk space.</li>
+      </ul>
+    </dd>
+
+    <dt>templates/hook/post-commit</dt>
+
+    <dd>A simple post-commit hook script which runs the script
+    <code>post-commit-background</code> in the background.</dd>
+
+    <dt>templates/hook/post-commit-background</dt>
+
+    <dd>
+      This script runs in the background after each commit.
+
+      <ul>
+        <li>It updates a <samp><repos>.latest</samp> file with the latest
+        revision number.</li>
+
+        <li>It creates a dump of the new revision.</li>
+
+        <li>It calls the script <code>background_updates.pl</code> if it
+        exists.</li>
+      </ul>This script is installed as standard in all our repositories.
+    </dd>
+
+    <dt>templates/hook/background_updates.pl</dt>
+
+    <dd>An example of how you may want to set up a
+    <code>background_updates.pl</code> script to perform post-commit tasks for
+    a specific repository. This script uses a lock file to prevent multiple
+    commits in quick succession from causing problems.</dd>
+
+    <dt>templates/hook/pre-revprop-change</dt>
+
+    <dd>A simple pre-revprop-change hook script which runs the script
+    <code>pre-revprop-change.pl</code>.</dd>
+
+    <dt>templates/hook/pre-revprop-change.pl</dt>
+
+    <dd>If a user attempts to modify the log message of a changeset and he/she
+    is not the original author of the changeset, this script will e-mail the
+    original author. You can also set up a watch facility to monitor changes of
+    log messages that affect particular paths in the repository. For further
+    details please refer to the section <a href=
+    "../user_guide/system_admin.html#svn_watch">Watching changes in log
+    messages</a> in the System Admin chapter of the User Guide.</dd>
+
+    <dt>templates/hook/post-revprop-change</dt>
+
+    <dd>A simple post-revprop-change hook script which runs the script
+    <code>post-revprop-change.py</code>.</dd>
+
+    <dt>templates/hook/post-revprop-change.py</dt>
+
+    <dd>This hook script updates the Trac SQLite database following a
+    successful change in the log message.</dd>
+
+    <dt>templates/utils/cron_template.ksh</dt>
+
+    <dd>An example of how you might set up a cron job to make use of the
+    <samp><repos>.latest</samp> file.</dd>
+
+    <dt>templates/utils/daily_cron</dt>
+
+    <dd>The cron job which we run each night. It verifies and backs up each of
+    our repositories, housekeeps the revision dumps created by
+    <code>post-commit-background</code> and backs up each of our Trac systems.
+    It also handles the distribution of FCM to various platforms at the Met
+    Office.</dd>
+
+    <dt>templates/utils/fcm_add_trac.pl</dt>
+
+    <dd>This script sets up a new Trac system and applies some configuration
+    options which we use by default at the Met Office.</dd>
+
+    <dt>templates/utils/recover_svn.pl</dt>
+
+    <dd>This script allows us to recover all of our Subversion repositories by
+    using the nightly backups and the repository dumps.</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/release_notes/1-2.html b/doc/release_notes/1-2.html
new file mode 100644
index 0000000..e8824d7
--- /dev/null
+++ b/doc/release_notes/1-2.html
@@ -0,0 +1,400 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM 1.2 Release Notes</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM 1.2 Release Notes <small>22 March 2007</small></h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>These are the release notes for FCM release 1.2. You can use this release
+  of FCM freely under the terms of the FCM LICENSE, which you should receive
+  with the distribution of this release.</p>
+
+  <p>FCM is maintained by the FCM team at the Met Office. Please feedback any
+  bug reports or feature requests to us by <a href=
+  "mailto:fcm-team at metoffice.gov.uk">e-mail</a>.</p>
+
+  <h2 id="new">What's New?</h2>
+
+  <p>Code management commands:</p>
+
+  <ul>
+    <li>New options <code>--trac</code> and <code>--wiki</code> for <code>fcm
+    diff --branch</code>.</li>
+
+    <li>Allow other graphical diff tools to be used in place of
+    <code>xxdiff</code>.</li>
+  </ul>
+
+  <p>General:</p>
+
+  <ul>
+    <li>New document <cite>External Distribution & Collaboration for FCM
+    Projects</cite>.</li>
+  </ul>
+
+  <h2 id="fix">Minor enhancements & Bug Fixes</h2>
+
+  <p>Build system:</p>
+
+  <ul>
+    <li>Extra warnings when multiple targets are detected in the source
+    tree.</li>
+
+    <li>Improved the patterns for detecting <code>recursive</code>,
+    <code>pure</code> and <code>elemental</code> Fortran subroutines and
+    functions.</li>
+  </ul>
+
+  <p>Code management commands:</p>
+
+  <ul>
+    <li><code>fcm branch --list</code> now prints the branches using FCM URL
+    keywords by default. Use the <code>--verbose</code> option to print
+    branches in full Subversion URLs.</li>
+  </ul>
+
+  <p>General:</p>
+
+  <ul>
+    <li>Enhanced <code>fcm cmp-ext-cfg</code> to link to tickets.</li>
+
+    <li>Improved handling of FCM URL keywords. The <code>SET::REPOS</code>
+    declaration in the central/user configuration file is deprecated in favour
+    of <code>SET::URL</code>. The keyword of the project with the standard
+    suffices (<code>_tr</code> or <code>-tr</code> for <em>trunk</em>,
+    <code>_br</code> or <code>-br</code> for <em>branches</em>, and
+    <code>_tg</code> or <code>-tg</code> for <em>tags</em>) are recognised
+    automatically.</li>
+
+    <li>Fix: full extract/build should no longer delete one another's cache if
+    they are run in the same directory.</li>
+
+    <li>Various other very minor enhancements and bug fixes.</li>
+  </ul>
+
+  <h2 id="req">System Requirements</h2>
+
+  <h3 id="req_perl">Perl</h3>
+
+  <p>The core part of FCM is a set of Perl scripts and modules. For the build
+  system to work, you need the following modules installed:</p>
+
+  <ul>
+    <li>Carp</li>
+
+    <li>Cwd</li>
+
+    <li>File::Basename</li>
+
+    <li>File::Compare</li>
+
+    <li>File::Find</li>
+
+    <li>File::Path</li>
+
+    <li>File::Spec::Functions</li>
+
+    <li>File::Spec</li>
+
+    <li>FindBin</li>
+
+    <li>Getopt::Long</li>
+
+    <li>POSIX</li>
+  </ul>
+
+  <p>The code management commands and extract system need the following
+  additional modules installed:</p>
+
+  <ul>
+    <li>File::Temp</li>
+
+    <li>Getopt::Long</li>
+
+    <li>HTTP::Date</li>
+
+    <li>XML::DOM</li>
+  </ul>
+
+  <p>To use the simple GUI for some of the code management commands, you also
+  need the following modules:</p>
+
+  <ul>
+    <li>Tk::ROText</li>
+
+    <li>Tk</li>
+  </ul>
+
+  <p>At the Met Office we are currently using the complete FCM system with Perl
+  5.8.x. In addition the build system is being used with Perl 5.6.x.</p>
+
+  <h3 id="req_svn">Subversion</h3>
+
+  <p>To use the code management commands (and relevant parts of the extract
+  system) you need to have <a href=
+  "http://subversion.tigris.org/">Subversion</a> installed.</p>
+
+  <ul>
+    <li>FCM makes extensive use of peg revisions in both the code management
+    and extract systems. This requires Subversion 1.2.0.</li>
+
+    <li>At the Met Office we are currently using Subversion 1.3.2.</li>
+  </ul>
+
+  <p>Note that the extract system can mirror extracted code to a remote
+  platform for building. Therefore it is only necessary to have Subversion
+  installed on the platform where you do your code development. If you use
+  other platforms purely for building and running then you do not need to have
+  Subversion installed on these platforms.</p>
+
+  <h3 id="req_trac">Trac</h3>
+
+  <p>The use of <a href="http://trac.edgewall.org/">Trac</a> is entirely
+  optional (although highly recommended if you are using Subversion).</p>
+
+  <ul>
+    <li>The <code>--trac</code> and <code>--wiki</code> options to the
+    <code>fcm diff --branch</code> command allow you to view branch differences
+    using Trac. This requires Trac 0.10.</li>
+
+    <li>At the Met Office we are currently using Trac 0.10.3.</li>
+  </ul>
+
+  <h3 id="req_other">Other Requirements</h3>
+
+  <p>The <code>fcm conflicts</code> command requires <a href=
+  "http://furius.ca/xxdiff/">xxdiff</a>. At the Met Office we are currently
+  using version 3.1. The <code>fcm diff --graphical</code> command also uses
+  xxdiff by default although other graphical diff tools can also be used.</p>
+
+  <p>The build system requires <a href=
+  "http://www.gnu.org/software/make/make.html">GNU make</a>. At the Met Office
+  we are currently using version 3.79.x and 3.80.</p>
+
+  <p>Optionally, the build system can use <a href=
+  "http://www.ifremer.fr/ditigo/molagnon/fortran90/">f90aib</a> to generate
+  interface files. However, there is also a built in Perl based interface file
+  generator which is quicker and better in most cases so you are unlikely to
+  need f90aib unless you hit a problem with some particular code.</p>
+
+  <p>FCM is intended to run on a Unix/Linux system. It is currently used at the
+  Met Office on Linux (Red Hat Enterprise 2.1 and 4.4) and HP-UX 11.00.</p>
+
+  <h2 id="ins">Installation</h2>
+
+  <p>FCM is distributed in the form of a compressed tar file. Un-pack the tar
+  file into an appropriate location on your system. Then add the
+  <samp>bin/</samp> directory into your <var>PATH</var>. Once you have done
+  this you should now have full access to the FCM system, assuming that you
+  have met the requirements described in the previous section.</p>
+
+  <p>If you wish to define keywords for your systems you will need to create a
+  file <samp>etc/fcm.cfg</samp>. An example file, <samp>fcm.cfg.eg</samp>, is
+  provided which is a copy of the file currently used at the Met Office. For
+  further details please refer to the section <a href=
+  "../user_guide/system_admin.html#fcm-keywords">FCM keywords</a> in the System
+  Admin chapter of the User Guide.</p>
+
+  <p>The <samp>doc/</samp> directory contains all the system documentation.</p>
+
+  <ul>
+    <li><samp>doc/release_notes/</samp> contains these release notes. It also
+    contains the release notes for all previous versions which may be useful if
+    you have skipped any versions.</li>
+
+    <li><samp>doc/user_guide/</samp> contains the <a href="../user_guide/">FCM
+    User Guide</a>.</li>
+
+    <li><samp>doc/design/</samp> contains the <a href="../design/">FCM Detailed
+    Design</a> document (currently in draft form).</li>
+
+    <li><samp>doc/standards/</samp> contains the FCM <a href=
+    "../standards/perl_standard.html">Perl</a> and <a href=
+    "../standards/fortran_standard.html">Fortran</a> coding standards. The Perl
+    standard describes the standards followed by the FCM code. The Fortran
+    standard contains some <a href=
+    "../standards/fortran_standard.html#fcm">specific advice</a> on the best
+    way of writing Fortran code for use with FCM as well as more general advice
+    on good practise.</li>
+
+    <li><samp>doc/collaboration/</samp> contains the <a href=
+    "../collaboration/index.html">External Distribution & Collaboration for
+    FCM Projects</a> document which discusses how projects configured under FCM
+    can be distributed externally.</li>
+  </ul>
+
+  <p>The <samp>tutorial/</samp> directory contains the files necessary to set
+  up a tutorial repository. This will allow you to follow the <a href=
+  "../user_guide/getting_started.html#tutorial">tutorial section</a> in the
+  User Guide.</p>
+
+  <ul>
+    <li>The file <samp>tutorial/repos/tutorial.dump</samp> should be loaded
+    into an empty repository using the <code>svnadmin load</code> command.</li>
+
+    <li>The hook scripts in <samp>tutorial/hook/</samp> should then be
+    installed in this repository in order to prevent any commits to the trunk.
+    Note that the configuration file <samp>svnperms.conf</samp> assumes that
+    the tutorial repository is called <samp>tutorial_svn</samp>. Please edit
+    this file if you use a different name.</li>
+
+    <li>The repository should be configured to allow users write access. You
+    may find it easiest to simply allow anonymous access.</li>
+
+    <li>A Trac system should be configured associated with the Tutorial
+    repository. You then need to allow users write access. You may find it
+    easiest to set up a number of guest accounts for this purpose.</li>
+  </ul>
+
+  <p>The <samp>templates/</samp> directory contains various example scripts
+  which you may find useful. Note that these scripts are all specific to the
+  Met Office and may contain hard coded paths and email addresses. They are
+  provided in the hope that you may find them useful as templates for setting
+  up similar scripts of your own. However, they should only be used after
+  careful review to adapt them to your environment. The contents are as
+  follows:</p>
+
+  <dl>
+    <dt>templates/hook/pre-commit</dt>
+
+    <dd>
+      This script restricts write-access to the repository by checking the
+      following:
+
+      <ul>
+        <li>It executes the Subversion utility <code>svnperms.py</code> if it
+        exists. This utility checks whether the author of the current
+        transaction has enough permission to write to particular paths in the
+        repository.</li>
+
+        <li>It checks the disk space required by the current transaction. It
+        fails the commit if it requires more than 5Mb of disk space.</li>
+      </ul>
+    </dd>
+
+    <dt>templates/hook/post-commit</dt>
+
+    <dd>A simple post-commit hook script which runs the script
+    <code>post-commit-background</code> in the background.</dd>
+
+    <dt>templates/hook/post-commit-background</dt>
+
+    <dd>
+      This script runs in the background after each commit
+
+      <ul>
+        <li>It updates a <samp><repos>.latest</samp> file with the latest
+        revision number.</li>
+
+        <li>It creates a dump of the new revision.</li>
+
+        <li>It calls the script <code>background_updates.pl</code> if it
+        exists.</li>
+      </ul>This script is installed as standard in all our repositories.
+    </dd>
+
+    <dt>templates/hook/background_updates.pl</dt>
+
+    <dd>An example of how you may want to set up a
+    <code>background_updates.pl</code> script to perform post-commit tasks for
+    a specific repository. This script uses a lock file to prevent multiple
+    commits in quick succession from causing problems.</dd>
+
+    <dt>templates/hook/pre-revprop-change</dt>
+
+    <dd>A simple pre-revprop-change hook script which runs the script
+    <code>pre-revprop-change.pl</code>.</dd>
+
+    <dt>templates/hook/pre-revprop-change.pl</dt>
+
+    <dd>If a user attempts to modify the log message of a changeset and he/she
+    is not the original author of the changeset, this script will e-mail the
+    original author. You can also set up a watch facility to monitor changes of
+    log messages that affect particular paths in the repository. For further
+    details please refer to the section <a href=
+    "../user_guide/system_admin.html#svn_watch">Watching changes in log
+    messages</a> in the System Admin chapter of the User Guide.</dd>
+
+    <dt>templates/hook/post-revprop-change</dt>
+
+    <dd>A simple post-revprop-change hook script which runs the script
+    <code>post-revprop-change.py</code>.</dd>
+
+    <dt>templates/hook/post-revprop-change.py</dt>
+
+    <dd>This hook script updates the Trac SQLite database following a
+    successful change in the log message.</dd>
+
+    <dt>templates/utils/cron_template.ksh</dt>
+
+    <dd>An example of how you might set up a cron job to make use of the
+    <samp><repos>.latest</samp> file.</dd>
+
+    <dt>templates/utils/daily_cron</dt>
+
+    <dd>The cron job which we run each night. It verifies and backs up each of
+    our repositories, housekeeps the revision dumps created by
+    <code>post-commit-background</code> and backs up each of our Trac systems.
+    It also handles the distribution of FCM to various platforms at the Met
+    Office.</dd>
+
+    <dt>templates/utils/fcm_add_trac.pl</dt>
+
+    <dd>This script sets up a new Trac system and applies some configuration
+    options which we use by default at the Met Office.</dd>
+
+    <dt>templates/utils/recover_svn.pl</dt>
+
+    <dd>This script allows us to recover all of our Subversion repositories by
+    using the nightly backups and the repository dumps.</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/release_notes/1-3.html b/doc/release_notes/1-3.html
new file mode 100644
index 0000000..f9dc901
--- /dev/null
+++ b/doc/release_notes/1-3.html
@@ -0,0 +1,585 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM 1.3 Release Notes</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM 1.3 Release Notes <small>30 January 2008</small></h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>These are the release notes for FCM release 1.3. You can use this release
+  of FCM freely under the terms of the FCM LICENSE, which you should receive
+  with the distribution of this release.</p>
+
+  <p>Note that FCM now requires Subversion 1.4.0 or later (previous releases
+  only required Subversion 1.2.0).</p>
+
+  <p>FCM is maintained by the FCM team at the Met Office. Please feedback any
+  bug reports or feature requests to us by <a href=
+  "mailto:fcm-team at metoffice.gov.uk">e-mail</a>.</p>
+
+  <h2 id="new">What's New?</h2>
+
+  <p>Build and extract:</p>
+
+  <ul>
+    <li>The syntax for declaring the extract/build destinations are unified.
+    The <code>DEST</code> (or <code>DEST::ROOTDIR</code>) declaration should be
+    used to declare the root of the extract/build destination. The
+    <code>DIR::ROOT</code> declaration is deprecated.</li>
+
+    <li>Users can no longer declare sub-directories in a destination.
+    Declarations such as <code>DEST::CFGDIR</code>, <code>DEST::SRCDIR</code>,
+    etc are no longer supported.</li>
+
+    <li>In configuration files, words or fields in a <em>label</em> can now be
+    delimited by a slash (<code>/</code>) as well as a double colon
+    (<code>::</code>). To improve readability, the convention is to only use
+    slash as the delimiter when referring to package names.</li>
+
+    <li>In a <code>true</code> or <code>false</code> declaration in a
+    configuration file, the system will now accept the following values as
+    false: <samp>0</samp>, <samp><empty string></samp>,
+    <samp>false</samp>, <samp>no</samp> and <samp>off</samp>. (It used to
+    accept <samp><any non empty string></samp> for <code>true</code> and
+    <samp>0</samp>, or <samp><empty string></samp> for
+    <code>false</code>.)</li>
+
+    <li>The build and extract caches are now located separately in
+    <samp>.cache/.bld/</samp> and <samp>.cache/.ext/</samp> respectively. In
+    most cases they will be upgraded automatically when you perform the next
+    incremental build/extract. However, if you use inherited build/extract, you
+    must upgrade the inherited build/extract before they can be used. To
+    upgrade an inherited extract, issue the following commands:
+      <pre>
+cd /path/to/inherited/extract/
+cd .cache/
+mkdir .ext/
+cp -r -p .config * .ext/
+</pre>
+
+      <p>To upgrade an inherited build, issue the following commands:</p>
+      <pre>
+cd /path/to/inherited/build/
+fcm bld
+</pre>
+    </li>
+
+    <li>Extra/common stages in the build/extract processes: <em>parse
+    configuration</em> and <em>set up destination</em>.</li>
+
+    <li>The build/extract destination (and the remote destination for extract)
+    will now be printed in verbose mode 1 or above.</li>
+
+    <li>A new command line option <code>--clean</code> for removing files
+    generated by previous extracts/builds.</li>
+
+    <li>An as-parsed configuration file will be generated for each run (if the
+    file differs from the previous run).</li>
+  </ul>
+
+  <p>Build:</p>
+
+  <ul>
+    <li>It is now more efficient to make <code>SRC</code> declarations for
+    files instead of the container directories. Container directories can still
+    be declared, but they will be expanded by the configuration file parser
+    into a list of source files within it. In addition, <code>SRC</code>
+    declarations no longer require the specification of package names - if it
+    is a relative path of the src/ sub-directory. If a relative path is
+    specified, it will be assumed a relative path of the src/
+    sub-directory.</li>
+
+    <li>For declarations such as <code>EXCL_DEP, PP, TOOL</code>, if a package
+    name is associated with the declaration, the system will fail if the
+    package is not declared or defined.</li>
+
+    <li>The system will now detect changes in <code>EXCL_DEP, INFILE_EXT and
+    OUTFILE_EXT</code> declarations in an incremental build.</li>
+
+    <li>It is now possible to make PP declarations down to the file level.
+    (Previously, it could only be done down to the level of the container
+    directories.)</li>
+
+    <li>The package name for a file used to be its root name (i.e. its basename
+    without the extension). It is now its basename, (although the system will
+    continue to support declarations down to the file's root name).</li>
+
+    <li>The declarations <code>TYPE</code> and <code>DEP</code> can now be used
+    to define the type and dependencies of a source file. (This replaces most
+    functionalities of the package configuration file.)</li>
+
+    <li>The build package configuration file is no longer supported.</li>
+
+    <li>The system now generates a single <samp>Makefile</samp> in the root
+    location of the destination. Hard coded directories should appear only once
+    at the top of the Makefile, provided that source files are only located in
+    the <samp>src/</samp> sub-directory of the build destination. (A build used
+    to have a top level <samp>Makefile</samp>, which included a
+    <samp>*.mk</samp> file for each source directory in the <samp>bld/</samp>
+    sub-directory of the destination.)</li>
+
+    <li>The <samp>fcm_env.ksh</samp> file is renamed <samp>fcm_env.sh</samp>,
+    which should work with the Bourne shell. (<samp>fcm_env.ksh</samp> is still
+    available via a symbolic link but is deprecated.)</li>
+
+    <li>The <code>--archive</code> or <code>-a</code> option will now archive
+    your build directories using the command <code>tar -czf FILE DIR</code>.
+    Consequently, the system will dearchive them using <code>tar -xzf
+    FILE</code>. If you have been using this option in your previous builds,
+    you should extract the archives manually using <code>tar -xf FILE</code>
+    before running <code>fcm build</code> in incremental mode. Remove the old
+    TAR files on success.</li>
+
+    <li>The linker command now defaults to the compiler of the language of the
+    program source file. (The default used to be <code>ld</code>.)</li>
+  </ul>
+
+  <p>Code management commands:</p>
+
+  <ul>
+    <li>Allow other graphical merge tools to be used in place of
+    <em>xxdiff</em> by modifying <code>bin/fcm_graphic_merge</code>.</li>
+
+    <li><code>fcm commit</code> will issue extra warning when a user attempts
+    to commit to or remove a branch belonging to another user.</li>
+
+    <li>The system has been modified to account for the improved support for
+    peg revisions using the <code>svn log</code> command in Subversion 1.4.
+    Unfortunately this means that Subversion 1.4.x clients are now
+    required.</li>
+
+    <li><code>fcm diff</code> now supports the <code>--summarize</code> option
+    which was introduced in Subversion 1.4.</li>
+
+    <li>Added alternate branching strategy in the <a href=
+    "../collaboration/">External Distribution & Collaboration for FCM
+    Projects</a> document.</li>
+
+    <li>A number of limitations with the <code>fcm mkpatch</code> command have
+    been fixed. It will also use unified diffs where possible in order to
+    reduce the size of the patch and to make it more readable.</li>
+  </ul>
+
+  <p>Extract:</p>
+
+  <ul>
+    <li>The <code>ROOTDIR</code> part of the <code>RDEST::ROOTDIR</code>
+    declaration is now optional.</li>
+
+    <li>The <code>MIRROR</code> declaration is deprecated, and replaced by the
+    <code>RDEST::MIRROR_CMD</code> declaration.</li>
+
+    <li>The build configuration file generated by extract should no longer
+    contain hard coded paths - except for <code>USE</code> declarations and
+    those protected by the <code>BLD</code> prefix.</li>
+
+    <li>The <code>VERSION</code> declaration is deprecated, and replaced by the
+    <code>REVISION</code> declaration.</li>
+
+    <li>Added a new <code>REVMATCH</code> declaration for the extract
+    configuration file. If you specify a revision (other than HEAD) for a
+    branch, and this revision is not associated with a changeset for this
+    branch, the system will normally inform you of this discrepancy. By setting
+    <code>REVMATCH</code> to "true", however, the discrepancy will cause
+    extract to fail.</li>
+
+    <li>Extract used to fail if the same file is modified by two different
+    branches (compared with the base branch). It now attempts to merge the
+    changes using <code>diff3 -E -m</code>. It only fails if there are
+    unresolved conflicts.</li>
+
+    <li>Consequently, the <code>OVERRIDE</code> declaration is deprecated, and
+    replaced by the <code>CONFLICT</code> declaration, which can be set to
+    <samp>fail</samp>, <samp>merge</samp> (default) or
+    <samp>override</samp>.</li>
+  </ul>
+
+  <h2 id="fix">Minor enhancements & Bug Fixes</h2>
+
+  <p>Build:</p>
+
+  <ul>
+    <li>If there is no source file to build, report an error at the beginning
+    of the build process.</li>
+  </ul>
+
+  <p>Code management commands:</p>
+
+  <ul>
+    <li>Fixed: FCM URL keyword not expanded when it is specified with an equal
+    sign in an option.</li>
+
+    <li>Fixed: typo in the output of <code>fcm branch --info</code>.</li>
+  </ul>
+
+  <p>Extract:</p>
+
+  <ul>
+    <li>Improved logic for better performance.</li>
+
+    <li>Allow mirroring to use <code>rsync</code> with an alternate remote
+    shell.</li>
+
+    <li><code>fcm cmp-ext-cfg</code>: Improved support for <a href=
+    "http://trac.edgewall.org/wiki/InterTrac">InterTrac</a> links.</li>
+  </ul>
+
+  <p>General:</p>
+
+  <ul>
+    <li>Fixed: problems parsing Subversion peg revision with the FCM URL
+    keywords.</li>
+
+    <li>The general shell used by FCM is changed from <code>/usr/bin/ksh</code>
+    to <code>/bin/sh</code> to improve portability.</li>
+
+    <li>Various other very minor enhancements and bug fixes.</li>
+  </ul>
+
+  <h2 id="issues">Known Issues</h2>
+
+  <p>The following are known issues with this release of FCM which we plan to
+  address in later releases:</p>
+
+  <ul>
+    <li>FCM build does not handle changes in an include file correctly in an
+    inherited build if the include file resides in the same directory as the
+    source file including it, and the source file remains unchanged. This is
+    due to the fact that most pre-processor/compiler commands search the
+    directory containing the source file for include files first before they
+    search elsewhere. We are hoping to find a solution to this problem before
+    the next release.</li>
+  </ul>
+
+  <h2 id="req">System Requirements</h2>
+
+  <h3 id="req_perl">Perl</h3>
+
+  <p>The core part of FCM is a set of Perl scripts and modules. For the build
+  system to work, you need the following modules installed:</p>
+
+  <ul>
+    <li>Carp</li>
+
+    <li>Cwd</li>
+
+    <li>File::Basename</li>
+
+    <li>File::Compare</li>
+
+    <li>File::Find</li>
+
+    <li>File::Path</li>
+
+    <li>File::Spec::Functions</li>
+
+    <li>File::Spec</li>
+
+    <li>FindBin</li>
+
+    <li>Getopt::Long</li>
+
+    <li>POSIX</li>
+  </ul>
+
+  <p>The code management commands and extract system need the following
+  additional modules installed:</p>
+
+  <ul>
+    <li>File::Temp</li>
+
+    <li>Getopt::Long</li>
+
+    <li>HTTP::Date</li>
+
+    <li>XML::DOM</li>
+  </ul>
+
+  <p>To use the simple GUI for some of the code management commands, you also
+  need the following modules:</p>
+
+  <ul>
+    <li>Tk::ROText</li>
+
+    <li>Tk</li>
+  </ul>
+
+  <p>At the Met Office we are currently using the complete FCM system with Perl
+  5.8.x. In addition the build system is being used with Perl 5.6.x.</p>
+
+  <h3 id="req_svn">Subversion</h3>
+
+  <p>To use the code management commands (and relevant parts of the extract
+  system) you need to have <a href=
+  "http://subversion.tigris.org/">Subversion</a> installed.</p>
+
+  <ul>
+    <li>FCM makes extensive use of peg revisions in both the code management
+    and extract systems. This requires Subversion 1.4.0.</li>
+
+    <li>At the Met Office we are currently using Subversion 1.4.3.</li>
+  </ul>
+
+  <p>Note that the extract system can mirror extracted code to a remote
+  platform for building. Therefore it is only necessary to have Subversion
+  installed on the platform where you do your code development. If you use
+  other platforms purely for building and running then you do not need to have
+  Subversion installed on these platforms.</p>
+
+  <h3 id="req_trac">Trac</h3>
+
+  <p>The use of <a href="http://trac.edgewall.org/">Trac</a> is entirely
+  optional (although highly recommended if you are using Subversion).</p>
+
+  <ul>
+    <li>The <code>--trac</code> and <code>--wiki</code> options to the
+    <code>fcm diff --branch</code> command allow you to view branch differences
+    using Trac. This requires Trac 0.10.</li>
+
+    <li>At the Met Office we are currently using Trac 0.10.3.</li>
+  </ul>
+
+  <h3 id="req_other">Other Requirements</h3>
+
+  <p>The <code>fcm conflicts</code> command requires <a href=
+  "http://furius.ca/xxdiff/">xxdiff</a>. At the Met Office we are currently
+  using version 3.1. The <code>fcm diff --graphical</code> command also uses
+  xxdiff by default although other graphical diff tools can also be used.</p>
+
+  <p>The extract system can use diff3, which is part of <a href=
+  "http://www.gnu.org/software/diffutils/">GNU diffutils</a>, to merge together
+  changes where the same file is modified by two different branches (compared
+  with the base branch). At the Met Office we are currently using version
+  2.8.1.</p>
+
+  <p>The build system requires <a href=
+  "http://www.gnu.org/software/make/make.html">GNU make</a>. At the Met Office
+  we are currently using version 3.79.x and 3.80.</p>
+
+  <p>Optionally, the build system can use <a href=
+  "http://www.ifremer.fr/ditigo/molagnon/fortran90/">f90aib</a> to generate
+  interface files. However, there is also a built in Perl based interface file
+  generator which is quicker and better in most cases so you are unlikely to
+  need f90aib unless you hit a problem with some particular code.</p>
+
+  <p>FCM is intended to run on a Unix/Linux system. It is currently used at the
+  Met Office on Linux (Red Hat Enterprise 2.1 and 4.5) and HP-UX 11.00.</p>
+
+  <h2 id="ins">Installation</h2>
+
+  <p>FCM is distributed in the form of a compressed tar file. Un-pack the tar
+  file into an appropriate location on your system. Then add the
+  <samp>bin/</samp> directory into your <var>PATH</var>. Once you have done
+  this you should now have full access to the FCM system, assuming that you
+  have met the requirements described in the previous section.</p>
+
+  <p>If you wish to define keywords for your systems you will need to create a
+  file <samp>etc/fcm.cfg</samp>. An example file, <samp>fcm.cfg.eg</samp>, is
+  provided which is a copy of the file currently used at the Met Office. For
+  further details please refer to the section <a href=
+  "../user_guide/system_admin.html#fcm-keywords">FCM keywords</a> in the System
+  Admin chapter of the User Guide.</p>
+
+  <p>The <samp>doc/</samp> directory contains all the system documentation.</p>
+
+  <ul>
+    <li><samp>doc/release_notes/</samp> contains these release notes. It also
+    contains the release notes for all previous versions which may be useful if
+    you have skipped any versions.</li>
+
+    <li><samp>doc/user_guide/</samp> contains the <a href="../user_guide/">FCM
+    User Guide</a>.</li>
+
+    <li><samp>doc/design/</samp> contains the <a href="../design/">FCM Detailed
+    Design</a> document (currently in draft form).</li>
+
+    <li><samp>doc/standards/</samp> contains the FCM <a href=
+    "../standards/perl_standard.html">Perl</a> and <a href=
+    "../standards/fortran_standard.html">Fortran</a> coding standards. The Perl
+    standard describes the standards followed by the FCM code. The Fortran
+    standard contains some <a href=
+    "../standards/fortran_standard.html#fcm">specific advice</a> on the best
+    way of writing Fortran code for use with FCM as well as more general advice
+    on good practise.</li>
+
+    <li><samp>doc/collaboration/</samp> contains the <a href=
+    "../collaboration/index.html">External Distribution & Collaboration for
+    FCM Projects</a> document which discusses how projects configured under FCM
+    can be distributed externally.</li>
+  </ul>
+
+  <p>The <samp>tutorial/</samp> directory contains the files necessary to set
+  up a tutorial repository. This will allow you to follow the <a href=
+  "../user_guide/getting_started.html#tutorial">tutorial section</a> in the
+  User Guide.</p>
+
+  <ul>
+    <li>The file <samp>tutorial/repos/tutorial.dump</samp> should be loaded
+    into an empty repository using the <code>svnadmin load</code> command.</li>
+
+    <li>The hook scripts in <samp>tutorial/hook/</samp> should then be
+    installed in this repository in order to prevent any commits to the trunk.
+    Note that the configuration file <samp>svnperms.conf</samp> assumes that
+    the tutorial repository is called <samp>tutorial_svn</samp>. Please edit
+    this file if you use a different name. You also need to install the
+    Subversion utility <code>svnperms.py</code> in order for this to work.</li>
+
+    <li>The repository should be configured to allow users write access. You
+    may find it easiest to simply allow anonymous access.</li>
+
+    <li>A Trac system should be configured associated with the Tutorial
+    repository. You then need to allow users write access. You may find it
+    easiest to set up a number of guest accounts for this purpose.</li>
+  </ul>
+
+  <p>The <samp>templates/</samp> directory contains various example scripts
+  which you may find useful. Note that these scripts are all specific to the
+  Met Office and may contain hard coded paths and email addresses. They are
+  provided in the hope that you may find them useful as templates for setting
+  up similar scripts of your own. However, they should only be used after
+  careful review to adapt them to your environment. The contents are as
+  follows:</p>
+
+  <dl>
+    <dt>templates/hook/pre-commit</dt>
+
+    <dd>
+      This script restricts write-access to the repository by checking the
+      following:
+
+      <ul>
+        <li>It executes the Subversion utility <code>svnperms.py</code> if it,
+        and the associated <code>svnperms.conf</code> file, exist. This utility
+        checks whether the author of the current transaction has enough
+        permission to write to particular paths in the repository.</li>
+
+        <li>It checks the disk space required by the current transaction. It
+        fails the commit if it requires more than 5Mb of disk space.</li>
+      </ul>
+    </dd>
+
+    <dt>templates/hook/post-commit</dt>
+
+    <dd>A simple post-commit hook script which runs the script
+    <code>post-commit-background</code> in the background.</dd>
+
+    <dt>templates/hook/post-commit-background</dt>
+
+    <dd>
+      This script runs in the background after each commit
+
+      <ul>
+        <li>It updates a <code><repos>.latest</code> file with the latest
+        revision number.</li>
+
+        <li>It creates a dump of the new revision.</li>
+
+        <li>It calls the script <code>background_updates.pl</code> if it
+        exists.</li>
+      </ul>This script is installed as standard in all our repositories.
+    </dd>
+
+    <dt>templates/hook/background_updates.pl</dt>
+
+    <dd>An example of how you may want to set up a
+    <code>background_updates.pl</code> script to perform post-commit tasks for
+    a specific repository. This script uses a lock file to prevent multiple
+    commits in quick succession from causing problems.</dd>
+
+    <dt>templates/hook/pre-revprop-change</dt>
+
+    <dd>A simple pre-revprop-change hook script which runs the script
+    <code>pre-revprop-change.pl</code>.</dd>
+
+    <dt>templates/hook/pre-revprop-change.pl</dt>
+
+    <dd>If a user attempts to modify the log message of a changeset and he/she
+    is not the original author of the changeset, this script will e-mail the
+    original author. You can also set up a watch facility to monitor changes of
+    log messages that affect particular paths in the repository. For further
+    details please refer to the section <a href=
+    "../user_guide/system_admin.html#svn_watch">Watching changes in log
+    messages</a> in the System Admin chapter of the User Guide.</dd>
+
+    <dt>templates/hook/post-revprop-change</dt>
+
+    <dd>A simple post-revprop-change hook script which runs the script
+    <code>post-revprop-change.py</code>.</dd>
+
+    <dt>templates/hook/post-revprop-change.py</dt>
+
+    <dd>This hook script updates the Trac SQLite database following a
+    successful change in the log message.</dd>
+
+    <dt>templates/utils/cron_template.sh</dt>
+
+    <dd>An example of how you might set up a cron job to make use of the
+    <samp><repos>.latest</samp> file.</dd>
+
+    <dt>templates/utils/daily_cron</dt>
+
+    <dd>The cron job which we run each night. It verifies and backs up each of
+    our repositories, housekeeps the revision dumps created by
+    <code>post-commit-background</code> and backs up each of our Trac systems.
+    It also handles the distribution of FCM to various platforms at the Met
+    Office.</dd>
+
+    <dt>templates/utils/fcm_add_trac.pl</dt>
+
+    <dd>This script sets up a new Trac system and applies some configuration
+    options which we use by default at the Met Office.</dd>
+
+    <dt>templates/utils/recover_svn.pl</dt>
+
+    <dd>This script allows us to recover all of our Subversion repositories by
+    using the nightly backups and the repository dumps.</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/release_notes/1-4.html b/doc/release_notes/1-4.html
new file mode 100644
index 0000000..7616f76
--- /dev/null
+++ b/doc/release_notes/1-4.html
@@ -0,0 +1,395 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM 1.4 Release Notes</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM 1.4 Release Notes <small>12 February 2009</small></h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>These are the release notes for FCM release 1.4. You can use this release
+  of FCM freely under the terms of the FCM LICENSE, which you should receive
+  with the distribution of this release.</p>
+
+  <p>Note that FCM 1.4 requires Subversion 1.4.x (but it has not been tested on
+  Subversion 1.5.x or above).</p>
+
+  <p>FCM is maintained by the FCM team at the Met Office. Please feedback any
+  bug reports or feature requests to us by <a href=
+  "mailto:fcm-team at metoffice.gov.uk">e-mail</a>.</p>
+
+  <h2 id="fix">Minor enhancements & Bug Fixes</h2>
+
+  <p>Build:</p>
+
+  <ul>
+    <li>Fixed: ensure consistent behaviour for specifying <code>-D</code>,
+    <code>-I</code>, etc options for a preprocessor/compiler.</li>
+  </ul>
+
+  <p>Code management commands:</p>
+
+  <ul>
+    <li>Fixed: <code>fcm mkpatch</code>: Fix how property changes are
+    handled.</li>
+
+    <li>Fixed: <code>fcm mkpatch</code>: Fix how copied files and directories
+    are handled.</li>
+
+    <li>Fixed: <code>fcm mkpatch</code>: Prevent failures caused by the use of
+    Subversion keywords.</li>
+
+    <li>Fixed: <code>fcm mkpatch</code>: Prevent failures when used with
+    branches which do not follow the FCM naming convention.</li>
+  </ul>
+
+  <p>Extract:</p>
+
+  <ul>
+    <li>Allow extract config to define an alternate remote shell for
+    <code>rsync</code>.</li>
+  </ul>
+
+  <p>General:</p>
+
+  <ul>
+    <li>Some of the example scripts in the <samp>templates/</samp> directory
+    have been rewritten.</li>
+
+    <li>Various other very minor enhancements and bug fixes.</li>
+  </ul>
+
+  <h2 id="issues">Known Issues</h2>
+
+  <p>The following are known issues with this release of FCM which we plan to
+  address in later releases:</p>
+
+  <ul>
+    <li>FCM build does not handle changes in an include file correctly in an
+    inherited build if the include file resides in the same directory as the
+    source file including it, and the source file remains unchanged. This is
+    due to the fact that most pre-processor/compiler commands search the
+    directory containing the source file for include files first before they
+    search elsewhere.</li>
+  </ul>
+
+  <h2 id="req">System Requirements</h2>
+
+  <h3 id="req_perl">Perl</h3>
+
+  <p>The core part of FCM is a set of Perl scripts and modules. For the build
+  system to work, you need the following modules installed:</p>
+
+  <ul>
+    <li>Carp</li>
+
+    <li>Cwd</li>
+
+    <li>File::Basename</li>
+
+    <li>File::Compare</li>
+
+    <li>File::Find</li>
+
+    <li>File::Path</li>
+
+    <li>File::Spec::Functions</li>
+
+    <li>File::Spec</li>
+
+    <li>FindBin</li>
+
+    <li>Getopt::Long</li>
+
+    <li>POSIX</li>
+  </ul>
+
+  <p>The code management commands and extract system need the following
+  additional modules installed:</p>
+
+  <ul>
+    <li>File::Temp</li>
+
+    <li>Getopt::Long</li>
+
+    <li>HTTP::Date</li>
+
+    <li>XML::DOM</li>
+  </ul>
+
+  <p>To use the simple GUI for some of the code management commands, you also
+  need the following modules:</p>
+
+  <ul>
+    <li>Tk::ROText</li>
+
+    <li>Tk</li>
+  </ul>
+
+  <p>At the Met Office we are currently using the complete FCM system with Perl
+  5.8.x. In addition the build system is being used with Perl 5.6.x.</p>
+
+  <h3 id="req_svn">Subversion</h3>
+
+  <p>To use the code management commands (and relevant parts of the extract
+  system) you need to have <a href=
+  "http://subversion.tigris.org/">Subversion</a> installed.</p>
+
+  <ul>
+    <li>FCM makes extensive use of peg revisions in both the code management
+    and extract systems. This requires Subversion 1.4.0.</li>
+
+    <li>At the Met Office we are currently using Subversion 1.4.3.</li>
+  </ul>
+
+  <p>Note that the extract system can mirror extracted code to a remote
+  platform for building. Therefore it is only necessary to have Subversion
+  installed on the platform where you do your code development. If you use
+  other platforms purely for building and running then you do not need to have
+  Subversion installed on these platforms.</p>
+
+  <h3 id="req_trac">Trac</h3>
+
+  <p>The use of <a href="http://trac.edgewall.org/">Trac</a> is entirely
+  optional (although highly recommended if you are using Subversion).</p>
+
+  <ul>
+    <li>The <code>--trac</code> and <code>--wiki</code> options to the
+    <code>fcm diff --branch</code> command allow you to view branch differences
+    using Trac. This requires Trac 0.10.</li>
+
+    <li>Some of the example scripts in the <samp>templates/</samp> directory
+    require Trac 0.11.</li>
+
+    <li>At the Met Office we are currently using Trac 0.11.2.1.</li>
+  </ul>
+
+  <h3 id="req_other">Other Requirements</h3>
+
+  <p>The <code>fcm conflicts</code> command requires <a href=
+  "http://furius.ca/xxdiff/">xxdiff</a>. At the Met Office we are currently
+  using version 3.1. The <code>fcm diff --graphical</code> command also uses
+  xxdiff by default although other graphical diff tools can also be used.</p>
+
+  <p>The extract system can use diff3, which is part of <a href=
+  "http://www.gnu.org/software/diffutils/">GNU diffutils</a>, to merge together
+  changes where the same file is modified by two different branches (compared
+  with the base branch). At the Met Office we are currently using version
+  2.8.1.</p>
+
+  <p>The build system requires <a href=
+  "http://www.gnu.org/software/make/make.html">GNU make</a>. At the Met Office
+  we are currently using version 3.79.x and 3.80.</p>
+
+  <p>Optionally, the build system can use <a href=
+  "http://www.ifremer.fr/ditigo/molagnon/fortran90/">f90aib</a> to generate
+  interface files. However, there is also a built in Perl based interface file
+  generator which is quicker and better in most cases so you are unlikely to
+  need f90aib unless you hit a problem with some particular code.</p>
+
+  <p>FCM is intended to run on a Unix/Linux system. It is currently used at the
+  Met Office on Linux (Red Hat Enterprise 2.1 and 4.6) and HP-UX 11.00.</p>
+
+  <h2 id="ins">Installation</h2>
+
+  <p>FCM is distributed in the form of a compressed tar file. Un-pack the tar
+  file into an appropriate location on your system. Then add the
+  <samp>bin/</samp> directory into your <var>PATH</var>. Once you have done
+  this you should now have full access to the FCM system, assuming that you
+  have met the requirements described in the previous section.</p>
+
+  <p>If you wish to define keywords for your systems you will need to create a
+  file <samp>etc/fcm.cfg</samp>. An example file, <samp>fcm.cfg.eg</samp>, is
+  provided which is a copy of the file currently used at the Met Office. For
+  further details please refer to the section <a href=
+  "../user_guide/system_admin.html#fcm-keywords">FCM keywords</a> in the System
+  Admin chapter of the User Guide.</p>
+
+  <p>The <samp>doc/</samp> directory contains all the system documentation.</p>
+
+  <ul>
+    <li><samp>doc/release_notes/</samp> contains these release notes. It also
+    contains the release notes for all previous versions which may be useful if
+    you have skipped any versions.</li>
+
+    <li><samp>doc/user_guide/</samp> contains the <a href="../user_guide/">FCM
+    User Guide</a>.</li>
+
+    <li><samp>doc/design/</samp> contains the <a href="../design/">FCM Detailed
+    Design</a> document (currently in draft form).</li>
+
+    <li><samp>doc/standards/</samp> contains the FCM <a href=
+    "../standards/perl_standard.html">Perl</a> and <a href=
+    "../standards/fortran_standard.html">Fortran</a> coding standards. The Perl
+    standard describes the standards followed by the FCM code. The Fortran
+    standard contains some <a href=
+    "../standards/fortran_standard.html#fcm">specific advice</a> on the best
+    way of writing Fortran code for use with FCM as well as more general advice
+    on good practise.</li>
+
+    <li><samp>doc/collaboration/</samp> contains the <a href=
+    "../collaboration/index.html">External Distribution & Collaboration for
+    FCM Projects</a> document which discusses how projects configured under FCM
+    can be distributed externally.</li>
+  </ul>
+
+  <p>The <samp>tutorial/</samp> directory contains the files necessary to set
+  up a tutorial repository. This will allow you to follow the <a href=
+  "../user_guide/getting_started.html#tutorial">tutorial section</a> in the
+  User Guide.</p>
+
+  <ul>
+    <li>The file <samp>tutorial/svn.dump</samp> should be loaded into an empty
+    repository using the <code>svnadmin load</code> command.</li>
+
+    <li>The hook scripts in <samp>tutorial/hooks/</samp> should then be
+    installed in this repository in order to prevent any commits to the trunk.
+    Note that the configuration file <code>svnperms.conf</code> assumes that
+    the tutorial repository is called <samp>tutorial_svn</samp>. Please edit
+    this file if you use a different name.</li>
+
+    <li>The repository should be configured to allow users write access. You
+    may find it easiest to simply allow anonymous access.</li>
+
+    <li>A Trac system should be configured associated with the Tutorial
+    repository. You then need to allow users write access. You may find it
+    easiest to set up a number of guest accounts for this purpose.</li>
+  </ul>
+
+  <p>The <samp>templates/</samp> directory contains various example scripts
+  which you may find useful. Note that these scripts are all specific to the
+  Met Office and may contain hard coded paths and email addresses. They are
+  provided in the hope that you may find them useful as templates for setting
+  up similar scripts of your own. However, they should only be used after
+  careful review to adapt them to your environment. The contents are as
+  follows:</p>
+
+  <dl>
+    <dt>templates/hooks/pre-commit</dt>
+
+    <dd>
+      This script restricts write-access to the repository by checking the
+      following:
+
+      <ul>
+        <li>It executes the Subversion utility <code>svnperms.py</code> if it,
+        and the associated <samp>svnperms.conf</samp> file, exist. This utility
+        checks whether the author of the current transaction has enough
+        permission to write to particular paths in the repository.</li>
+
+        <li>It checks the disk space required by the current transaction. It
+        fails the commit if it requires more than 5Mb of disk space.</li>
+      </ul>
+    </dd>
+
+    <dt>templates/hooks/post-commit</dt>
+
+    <dd>A simple post-commit hook script which runs the script
+    <code>post-commit-background</code> in the background.</dd>
+
+    <dt>templates/hooks/post-commit-background</dt>
+
+    <dd>
+      This script runs in the background after each commit
+
+      <ul>
+        <li>It updates a <samp><repos>.latest</samp> file with the latest
+        revision number.</li>
+
+        <li>It creates a dump of the new revision.</li>
+
+        <li>It calls the script <code>background_updates.pl</code> if it
+        exists.</li>
+      </ul>This script is installed as standard in all our repositories.
+    </dd>
+
+    <dt>templates/hooks/background_updates.pl</dt>
+
+    <dd>An example of how you may want to set up a
+    <code>background_updates.pl</code> script to perform post-commit tasks for
+    a specific repository. This script uses a lock file to prevent multiple
+    commits in quick succession from causing problems.</dd>
+
+    <dt>templates/hooks/pre-revprop-change</dt>
+
+    <dd>A simple pre-revprop-change hook script which runs the script
+    <code>pre-revprop-change.pl</code>.</dd>
+
+    <dt>templates/hooks/pre-revprop-change.pl</dt>
+
+    <dd>If a user attempts to modify the log message of a changeset and he/she
+    is not the original author of the changeset, this script will e-mail the
+    original author. You can also set up a watch facility to monitor changes of
+    log messages that affect particular paths in the repository. For further
+    details please refer to the section <a href=
+    "../user_guide/system_admin.html#svn_watch">Watching changes in log
+    messages</a> in the System Admin chapter of the User Guide.</dd>
+
+    <dt>templates/hooks/post-revprop-change</dt>
+
+    <dd>A simple post-revprop-change hook script which invokes the
+    <code>trac-admin</code> command to <code>resync</code> the revision
+    property cache stored in the corresponding Trac environment.</dd>
+
+    <dt>templates/utils/cron_template.sh</dt>
+
+    <dd>An example of how you might set up a cron job to make use of the
+    <samp><repos>.latest</samp> file.</dd>
+
+    <dt>templates/utils/FCM/Admin/</dt>
+
+    <dd>A Perl library in the <code>FCM::Admin::*</code> name space, which
+    implements the functionalities of the FCM admin utility commands.</dd>
+
+    <dt>templates/utils/fcm-*</dt>
+
+    <dd>A selection of useful FCM admin utility commands.</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/release_notes/1-5.html b/doc/release_notes/1-5.html
new file mode 100644
index 0000000..b7de61b
--- /dev/null
+++ b/doc/release_notes/1-5.html
@@ -0,0 +1,483 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM 1.5 Release Notes</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM 1.5 Release Notes <small>22 January 2010</small></h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>These are the release notes for FCM release 1.5. You can use this release
+  of FCM freely under the terms of the FCM LICENSE, which you should receive
+  with the distribution of this release.</p>
+
+  <p>FCM is maintained by the FCM team at the Met Office. Please feedback any
+  bug reports or feature requests to us by <a href=
+  "mailto:fcm-team at metoffice.gov.uk">e-mail</a>.</p>
+
+  <h2 id="new">What's New?</h2>
+
+  <dl>
+    <dt><code>fcm branch --list --show-all</code></dt>
+
+    <dd>New option to list all branches in a project.</dd>
+
+    <dt><code>fcm keyword-print</code>: new command</dt>
+
+    <dd>A command to print registered FCM keywords.</dd>
+
+    <dt>New method to manage revision keywords and other keywords related
+    settings.</dt>
+
+    <dd>See <a href="../user_guide/system_admin.html#fcm-keywords">FCM User
+    Guide > System Administration > FCM keywords</a> for detail. Note:
+    the <code>SET::TRAC</code> declaration is no longer supported.</dd>
+
+    <dt><code>fcm update</code>: improvement</dt>
+
+    <dd>The <code>fcm update</code> command applies to a whole working copy. If
+    the working copy contains local changes, the command will prompt the user
+    for confirmation.</dd>
+  </dl>
+
+  <h2 id="fix">Minor Enhancements & Bug Fixes</h2>
+
+  <dl>
+    <dt><code>fcm build</code>: new <code>NO_DEP</code> declaration to switch
+    off dependency checking</dt>
+
+    <dd>A new declaration to switch off dependency checking for a given
+    name-space. See the <a href=
+    "../user_guide/build.html#advanced_dependency">FCM User Guide > The
+    Build System > Further dependency features</a> for details.</dd>
+
+    <dt><code>fcm build</code>: incorrect behaviour when dealing with the
+    removal of TOOL declarations in incremental mode</dt>
+
+    <dd><code>fcm build</code> did not always handle the removal of TOOL
+    declarations from the build configuration correctly in incremental mode.
+    This has been fixed.</dd>
+
+    <dt><code>fcm build</code>: new <code>TOOL::FC_MODSEARCH</code>
+    declaration</dt>
+
+    <dd>While most Fortran compilers search for the compiled module definition
+    files (i.e. <samp>*.mod</samp> files) using the same option as the include
+    search path (i.e. <samp>-I</samp>), some require a special option such as
+    <samp>-M</samp>. The new <code>TOOL::FC_MODSEARCH</code> declaration allows
+    such an option to be specified in the build configuration file.</dd>
+
+    <dt><code>fcm build</code>: incorrect logic for handling
+    <code>INHERIT::SRC</code> declarations</dt>
+
+    <dd>The logic for handling this declaration was incorrect. This led to
+    deleted files being incorrectly inherited. This has been fixed.</dd>
+
+    <dt><code>fcm build</code>: incorrect logic for generating exclude
+    dependency files for directory-based libraries</dt>
+
+    <dd>These files were not being generated corectly. This has been fixed.</dd>
+
+    <dt><code>fcm build</code>: incorrect logic for handling
+    <code>SRC_TYPE</code></dt>
+
+    <dd>The system was unable to search for an include file whose type was
+    declared via a <code>SRC_TYPE</code> declaration. This has been fixed.</dd>
+
+    <dt><code>fcm build</code>: improvement to the Fortran interface file
+    generator</dt>
+
+    <dd>The logic to extract the calling interfaces of top level subroutines
+    and functions from Fortran source files has been rewritten, based on the
+    original logic developed by the <a href="http://www.ecmwf.int">European
+    Centre for Medium-Range Weather Forecasts (ECMWF)</a>. In particular, the
+    new logic will correctly handle 1) pre-processor directives with
+    continuation lines, 2) continuation and comment markers in quotes, 3)
+    BLOCKDATA program units in the source file, 4) TYPE components in variable
+    identifiers, and 5) multiple program units in the source file. There are
+    also improvements in the new logic to reduce the number of useless
+    declarations and module imports in the generated interface block.</dd>
+
+    <dt><code>fcm commit</code>: improvement to the commit message
+    delimiter</dt>
+
+    <dd>Some users found the old delimiter line confusing. This has been
+    improved.</dd>
+
+    <dt><code>fcm commit</code>: <code>svn:special</code> and
+    <code>svn:executable</code></dt>
+
+    <dd>A symbolic link pointing to an executable target can cause a subsequent
+    <code>svn checkout</code> to fail if the target is removed. To avoid the
+    potential problem, <code>fcm commit</code> has been altered to remove the
+    <code>svn:executable</code> property if a path is a symbolic link.</dd>
+
+    <dt><code>fcm extract</code>: handling of file permission changes in
+    incremental mode</dt>
+
+    <dd><code>fcm extract</code> did not handle file permission changes in
+    incremental mode. This has been fixed.</dd>
+
+    <dt><code>fcm extract</code>: handling of symbolic links</dt>
+
+    <dd>Symbolic links cannot be handled safely by <code>fcm extract</code>.
+    They are now removed from the extract.</dd>
+
+    <dt><code>fcm extract</code> and <code>fcm build</code>: machine
+    hostname</dt>
+
+    <dd>The machine hostname will now be printed with the destination in the
+    diagnostic output of these commands.</dd>
+
+    <dt><code>fcm extract</code>: missing <code>RDEST</code> in the on-success
+    configuration file</dt>
+
+    <dd>Some <code>RDEST</code> declarations were missing from the on-success
+    generated configuration file. This has been fixed.</dd>
+
+    <dt><code>fcm extract</code>: improved options for the mirror
+    sub-system</dt>
+
+    <dd>It is now possible to specify the options of the <code>rsync</code>
+    command in the extract configuration file. In addition, <code>ssh</code> is
+    now the default remote shell command.</dd>
+
+    <dt><code>fcm cmp-ext-cfg</code>: changed verbose option</dt>
+
+    <dd>The <code>--verbose</code> option now requires an argument.</dd>
+  </dl>
+
+  <h2 id="issues">Known Issues</h2>
+
+  <dl>
+    <dt>Build inheritance limitation: handling of include files</dt>
+
+    <dd>See the <a href="../user_guide/build.html#advanced_inherit">FCM User
+    Guide > The Build System > Inherit from a previous build</a> for
+    detail.</dd>
+  </dl>
+
+  <h2 id="req">System Requirements</h2>
+
+  <h3 id="req_perl">Perl</h3>
+
+  <p>The core part of FCM is a set of Perl scripts and modules. The following
+  core/CPAN Perl modules are required to invoke the <code>fcm</code>
+  command:</p>
+  <pre>
+Carp
+Cwd
+File::Basename
+File::Compare
+File::Copy
+File::Find
+File::Path
+File::Spec
+File::Spec::Functions
+File::Temp
+FindBin
+Getopt::Long
+HTTP::Date
+IO::File
+List::Util
+POSIX
+Pod::Usage
+Scalar::Util
+Sys::Hostname
+Text::ParseWords
+URI
+XML::DOM
+</pre>
+
+  <p>The following Perl modules are also required if you want to use the
+  <code>fcm gui</code> command:</p>
+  <pre>
+Tk
+Tk::ROText
+</pre>
+
+  <p>At the Met Office we are currently using FCM with Perl 5.8.2 on AIX 5.3
+  and Perl 5.8.5 on RHEL 4.</p>
+
+  <h3 id="req_svn">Subversion</h3>
+
+  <p>To use the code management commands (and relevant parts of the extract
+  system) you need to have <a href=
+  "http://subversion.tigris.org/">Subversion</a> installed.</p>
+
+  <p>At the Met Office we are currently using Subversion 1.4.3. Note: FCM 1.5
+  requires Subversion 1.4.x (but it has not been tested on Subversion 1.5.x or
+  above).</p>
+
+  <p>Note: you can use the extract system to mirror code to a remote platform
+  for building. Therefore it is only necessary to have Subversion installed on
+  the platform where you do your code development. If you use other platforms
+  purely for building and running then you do not need to have Subversion
+  installed on these platforms.</p>
+
+  <h3 id="req_trac">Trac</h3>
+
+  <p>The use of <a href="http://trac.edgewall.org/">Trac</a> is entirely
+  optional (although highly recommended if you are using Subversion).</p>
+
+  <p>At the Met Office we are currently using Trac 0.11.2.1. Note:</p>
+
+  <ul>
+    <li>The <code>--trac</code> and <code>--wiki</code> options to the
+    <code>fcm diff --branch</code> command allow you to view branch differences
+    using Trac. This requires Trac 0.10 or above.</li>
+
+    <li>Some of the example scripts in the <samp>examples/</samp> directory
+    require Trac 0.11.</li>
+  </ul>
+
+  <h3 id="req_other">Other Requirements</h3>
+
+  <p>The <code>fcm conflicts</code> command requires <a href=
+  "http://furius.ca/xxdiff/">xxdiff</a>. At the Met Office we are currently
+  using version 3.1. The <code>fcm diff --graphical</code> command also uses
+  xxdiff by default although other graphical diff tools can also be used.</p>
+
+  <p>The extract system uses <code>diff3</code>, (which is part of <a href=
+  "http://www.gnu.org/software/diffutils/">GNU diffutils</a>), to merge
+  together changes where the same file is modified by two different branches
+  (compared with the base branch). At the Met Office we are currently using
+  version 2.8.1.</p>
+
+  <p>The extract system uses <a href="http://rsync.samba.org/">rsync</a> to
+  mirror source file to another machine. At the Met Office we are currently
+  using version 2.6.3</p>
+
+  <p>The build system requires <a href=
+  "http://www.gnu.org/software/make/make.html">GNU make</a>. At the Met Office
+  we are currently using version 3.80.</p>
+
+  <p>FCM is intended to run on a Unix/Linux system. It is currently used at the
+  Met Office on Linux (RHEL 4.8) and AIX 5.3.</p>
+
+  <h2 id="ins">Installation</h2>
+
+  <p>FCM is distributed in the form of a compressed tar file. Un-pack the tar
+  file into an appropriate location on your system. Add the <samp>bin/</samp>
+  directory into your <var>PATH</var> environment variable. Once you have done
+  this you should now have full access to the FCM system, assuming that you
+  have met the requirements described in the previous section.</p>
+
+  <p>You should find the following contents in the distribution:</p>
+
+  <dl>
+    <dt>README</dt>
+
+    <dd>The README file contains the internal revision number of the release.</dd>
+
+    <dt>COPYRIGHT.txt<br />
+    LICENSE.html</dt>
+
+    <dd>The FCM license and other copyright information.</dd>
+
+    <dt>bin/</dt>
+
+    <dd>Contains the <code>fcm</code> command and other utilities.</dd>
+
+    <dt>doc/</dt>
+
+    <dd>System documentation.</dd>
+
+    <dt>doc/release_notes/</dt>
+
+    <dd>Contains these release notes. It also contains the release notes for
+    all previous versions which may be useful if you have skipped any
+    versions.</dd>
+
+    <dt>doc/user_guide/</dt>
+
+    <dd>Contains the <a href="../user_guide/">FCM User Guide</a>.</dd>
+
+    <dt>doc/standards/</dt>
+
+    <dd>Contains the FCM <a href="../standards/perl_standard.html">Perl</a> and
+    <a href="../standards/fortran_standard.html">Fortran</a> coding standards.
+    The Perl standard describes the standards followed by the FCM code. The
+    Fortran standard contains some <a href=
+    "../standards/fortran_standard.html#fcm">specific advice</a> on the best
+    way of writing Fortran code for use with FCM as well as more general advice
+    on good practise.</dd>
+
+    <dt>doc/collaboration/</dt>
+
+    <dd>Contains the <a href="../collaboration/index.html">External
+    Distribution & Collaboration for FCM Projects</a> document which
+    discusses how projects configured under FCM can be distributed
+    externally.</dd>
+
+    <dt>etc/</dt>
+
+    <dd>Miscellaneous items, including the <samp>fcm.cfg.eg</samp> file. If you
+    wish to define keywords for your systems you will need to create the
+    <samp>etc/fcm.cfg</samp> file. An example file, <samp>fcm.cfg.eg</samp>, is
+    provided which is a copy of the file currently used at the Met Office. For
+    further details please refer to the section <a href=
+    "../user_guide/system_admin.html#fcm-keywords">FCM keywords</a> in the
+    System Admin chapter of the User Guide.</dd>
+
+    <dt>examples/</dt>
+
+    <dd>Contains various example scripts which you may find useful. Note that
+    these scripts are all specific to the Met Office and may contain hard coded
+    paths and email addresses. They are provided in the hope that you may find
+    them useful as examples for setting up similar scripts of your own.
+    However, they should only be used after careful review to adapt them to
+    your environment.</dd>
+
+    <dt>examples/etc/regular-update.eg</dt>
+
+    <dd>An example of how you might set up a cron job to make use of the
+    <samp><repos>.latest</samp> file (see
+    <code>examples/svn-hooks/post-commit-background</code>).</dd>
+
+    <dt>examples/lib/</dt>
+
+    <dd>Contains the <code>FCM::Admin::*</code> Perl library, which implements
+    the functionalities of the FCM admin utility commands.</dd>
+
+    <dt>examples/sbin/</dt>
+
+    <dd>Contains a selection of useful admin utility commands.</dd>
+
+    <dt>examples/svn-hooks/pre-commit</dt>
+
+    <dd>
+      This script restricts write-access to the repository by checking the
+      following:
+
+      <ul>
+        <li>It executes the Subversion utility <code>svnperms.py</code> if it,
+        and the associated <samp>svnperms.conf</samp> file, exist. This utility
+        checks whether the author of the current transaction has enough
+        permission to write to particular paths in the repository.</li>
+
+        <li>It checks the disk space required by the current transaction. It
+        fails the commit if it requires more than 5Mb of disk space.</li>
+      </ul>
+    </dd>
+
+    <dt>examples/svn-hooks/post-commit</dt>
+
+    <dd>A simple post-commit hook script which runs the script
+    <code>post-commit-background</code> in the background.</dd>
+
+    <dt>examples/svn-hooks/post-commit-background</dt>
+
+    <dd>
+      This script runs in the background after each commit
+
+      <ul>
+        <li>It updates a <samp><repos>.latest</samp> file with the latest
+        revision number.</li>
+
+        <li>It creates a dump of the new revision.</li>
+
+        <li>It calls the script <code>background_updates.pl</code> if it
+        exists.</li>
+      </ul>This script is installed as standard in all our repositories.
+    </dd>
+
+    <dt>examples/svn-hooks/background_updates.pl</dt>
+
+    <dd>An example of how you may want to set up a
+    <code>background_updates.pl</code> script to perform post-commit tasks for
+    a specific repository. This script uses a lock file to prevent multiple
+    commits in quick succession from causing problems.</dd>
+
+    <dt>examples/svn-hooks/pre-revprop-change</dt>
+
+    <dd>A simple pre-revprop-change hook script which runs the script
+    <code>pre-revprop-change.pl</code>.</dd>
+
+    <dt>examples/svn-hooks/pre-revprop-change.pl</dt>
+
+    <dd>If a user attempts to modify the log message of a changeset and he/she
+    is not the original author of the changeset, this script will e-mail the
+    original author. You can also set up a watch facility to monitor changes of
+    log messages that affect particular paths in the repository. For further
+    details please refer to the section <a href=
+    "../user_guide/system_admin.html#svn_watch">Watching changes in log
+    messages</a> in the System Admin chapter of the User Guide.</dd>
+
+    <dt>examples/svn-hooks/post-revprop-change</dt>
+
+    <dd>A simple post-revprop-change hook script which invokes the
+    <code>trac-admin</code> command to <code>resync</code> the revision
+    property cache stored in the corresponding Trac environment.</dd>
+
+    <dt>lib/</dt>
+
+    <dd>Contains the Perl library of FCM.</dd>
+
+    <dt>man/</dt>
+
+    <dd>Contains a basic manual page for <code>fcm</code>.</dd>
+
+    <dt>t/</dt>
+
+    <dd>Contains unit test for FCM.</dd>
+
+    <dt>test/</dt>
+
+    <dd>Contains regression tests for FCM.</dd>
+
+    <dt>tutorial/</dt>
+
+    <dd>Contains the files necessary to set up a Subversion repository for the
+    FCM tutorial. This will allow you to follow the <a href=
+    "../user_guide/getting_started.html#tutorial">tutorial section</a> in the
+    User Guide. See <samp>tutorial/README</samp> on how to set it up.</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/release_notes/2-0.html b/doc/release_notes/2-0.html
new file mode 100644
index 0000000..976d4e5
--- /dev/null
+++ b/doc/release_notes/2-0.html
@@ -0,0 +1,754 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM 2-0 Release Notes</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM 2-0 Release Notes <small>11 March 2011</small></h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>These are the release notes for FCM 2-0. You can use this release of FCM
+  freely under the terms of the FCM LICENSE, which you should receive with the
+  distribution of this release.</p>
+
+  <p>FCM is maintained by the FCM team at the Met Office. Please feedback any
+  bug reports or feature requests to us by <a href=
+  "mailto:fcm-team at metoffice.gov.uk">e-mail</a>.</p>
+
+  <h2 id="new">What's New?</h2>
+
+  <dl>
+    <dt><code><a href="../user_guide/command_ref.html#fcm-make">fcm
+    make</a></code>: new command</dt>
+
+    <dd>New extract/build system. Deprecates <code>fcm extract</code> and
+    <code>fcm build</code>. See <a href="../user_guide/make.html">User Guide
+    > FCM Make</a> for detail on how to use the new system. See also
+    <a href="#new.make">fcm make: key differences compared with fcm
+    extract/build</a>.</dd>
+
+    <dt><code><a href="../user_guide/command_ref.html#fcm-branch-create">fcm
+    branch-create</a></code>: new command</dt>
+
+    <dd>Deprecates <code>fcm branch --create</code>.</dd>
+
+    <dt><code><a href="../user_guide/command_ref.html#fcm-branch-delete">fcm
+    branch-delete</a></code>: new command</dt>
+
+    <dd>Deprecates <code>fcm branch --delete</code>.</dd>
+
+    <dt><code><a href="../user_guide/command_ref.html#fcm-branch-diff">fcm
+    branch-diff</a></code>: new command</dt>
+
+    <dd>Deprecates <code>fcm diff --branch</code>.</dd>
+
+    <dt><code><a href="../user_guide/command_ref.html#fcm-branch-info">fcm
+    branch-info</a></code>: new command</dt>
+
+    <dd>Deprecates <code>fcm branch --info</code>.</dd>
+
+    <dt><code><a href="../user_guide/command_ref.html#fcm-branch-list">fcm
+    branch-list</a></code>: new command</dt>
+
+    <dd>Deprecates <code>fcm branch --list</code>.</dd>
+
+    <dt><code><a href="../user_guide/command_ref.html#fcm-browse">fcm
+    browse</a></code></dt>
+
+    <dd>This is now the preferred name of <code>fcm trac</code> or <code>fcm
+    www</code>.</dd>
+
+    <dt><code><a href="../user_guide/command_ref.html#fcm-cfg-print">fcm
+    cfg-print</a></code></dt>
+
+    <dd>This is now the preferred name of <code>fcm cfg</code>.</dd>
+
+    <dt><code><a href="../user_guide/command_ref.html#fcm-export-items">fcm
+    export-items</a></code>: new command</dt>
+
+    <dd>Deprecates <code>fcm_update_version_dir.pl</code>.</dd>
+
+    <dt>Configuration for keywords</dt>
+
+    <dd>New syntax and location. See <a href=
+    "../user_guide/annex_cfg.html#keyword">User Guide > Annex: FCM
+    Configuration File > FCM Keyword Configuration</a> for detail.
+    Equivalent settings in <samp>$FCM/etc/fcm.cfg</samp> will no longer work.
+    Those in <samp>$HOME/.fcm</samp> should continue to work. We would
+    encourage users to migrate to the new syntax and location.</dd>
+
+    <dt>Configuration for external commands</dt>
+
+    <dd>New syntax and location. See <a href=
+    "../user_guide/annex_cfg.html#external">User Guide > Annex: FCM
+    Configuration File > FCM External Configuration</a> for detail.
+    Equivalent settings in <samp>$FCM/etc/fcm.cfg</samp> and
+    <samp>$HOME/.fcm</samp> will no longer work.</dd>
+  </dl>
+
+  <h3 id="new.make">fcm make: key differences compared with fcm
+  extract/build</h3>
+
+  <p>Single command and framework of a configurable chain of <dfn>steps</dfn>,
+  e.g. extract, mirror, preprocess, build, etc.</p>
+
+  <ul>
+    <li>as opposed to 2 separate commands with fixed steps.</li>
+
+    <li>possible to set up multiple builds with different configurations from
+    the same extract.</li>
+  </ul>
+
+  <p>New configuration file format, with more powerful syntax and declarations.
+  E.g.:</p>
+
+  <ul>
+    <li>Improved support for specifying <dfn>name-spaces</dfn> (previously
+    <dfn>package</dfn> or <dfn>sub-package</dfn>) for a declaration.</li>
+
+    <li>Improved support for declaring and referencing variables.</li>
+
+    <li>Improved support for space and other meta-characters.</li>
+  </ul>
+
+  <p>extract: automatically associates location keywords to source tree
+  locations. E.g.:</p>
+
+  <ul>
+    <li>
+      <code>fcm extract</code> configuration requires repeated declarations:
+      <pre>
+cfg::type ext
+repos::foo::base fcm:foo/trunk
+expsrc::foo::base
+repos::bar::base fcm:bar/trunk
+expsrc::bar::base
+</pre>
+    </li>
+
+    <li>
+      <code>fcm make</code> configuration is much simpler:
+      <pre>
+steps = extract
+extract.ns = foo bar
+</pre>
+    </li>
+  </ul>
+
+  <p>extract: clearly distinguishes a base source tree from the diff source
+  trees for each project. E.g.:</p>
+
+  <ul>
+    <li>
+      <code>fcm extract</code> configuration requires arbitrary IDs for each
+      source tree, separate revision declarations, and assumes that the first
+      declared tree for a project is the <dfn>base</dfn>:
+      <pre>
+cfg::type ext
+repos::foo::base fcm:foo/trunk
+revision::foo::base 1234
+expsrc::foo::base
+repos::foo::b1 fcm:foo/branches/dev/fred/r1234_b1
+revision::foo::b1 2345
+repos::foo::b2 fcm:foo/branches/dev/bob/r1234_b2
+repos::foo::b3 fcm:foo/branches/dev/alice/r1234_b3
+</pre>
+    </li>
+
+    <li>
+      <code>fcm make</code> configuration uses different declarations for the
+      location of the <dfn>base</dfn> source tree and the locations of the
+      <dfn>diff</dfn> source trees:
+      <pre>
+steps = extract
+extract.ns = foo
+extract.location[foo] = trunk at 1234
+extract.location{diff}[foo] = \
+    branches/dev/fred/r1234_b1 at 2345 \
+    branches/dev/bob/r1234_b2 \
+    branches/dev/alice/r1234_b3
+</pre>
+    </li>
+  </ul>
+
+  <p>extract: can easily filter parts of a project source tree, and/or change
+  the root of the extract tree. E.g.:</p>
+
+  <ul>
+    <li>
+      <code>fcm extract</code> configuration can cause confusion:
+      <pre>
+cfg::type ext
+repos::um::base fcm:um/trunk/src
+revision::foo::base vn7.7
+expsrc::um::base
+repos::um::b1 fcm:um/branches/dev/fred/vn7.7_b1/src
+repos::um::b2 fcm:um/branches/dev/bob/vn7.7_b2/src
+repos::um::b3 fcm:um/branches/dev/alice/vn7.7_b3/src
+</pre>
+    </li>
+
+    <li>
+      <code>fcm make</code> configuration is clearer and has more features:
+      <pre>
+steps = extract
+extract.ns = um
+extract.path-root[um] = src
+extract.path-excl[um] = configs scm
+extract.location[um] = trunk at vn7.7
+extract.location{diff}[um] = \
+    branches/dev/fred/vn7.7_b1 \
+    branches/dev/bob/vn7.7_b2 \
+    branches/dev/alice/vn7.7_b3
+</pre>
+    </li>
+  </ul>
+
+  <p>extract: works with project source trees as opposed to individual source
+  directories.</p>
+
+  <ul>
+    <li>fewer calls to the version control system servers.</li>
+
+    <li>deleted directories are now handled correctly.</li>
+  </ul>
+
+  <p>extract: can use multiple processes to retrieve source trees information
+  and to export source tree files from the version control system.</p>
+
+  <ul>
+    <li>extract of multiple projects and/or with multiple source trees can be
+    much faster.</li>
+  </ul>
+
+  <p>mirror: is now an independent step.</p>
+
+  <ul>
+    <li>can set up multiple mirror steps to mirror an extract to alternate
+    destinations.</li>
+  </ul>
+
+  <p>build: can use multiple processes to analyse the source files for
+  dependencies and other information.</p>
+
+  <ul>
+    <li>multi-process build is much faster.</li>
+  </ul>
+
+  <p>build: uses an internal task manager and runner - more efficient logic
+  possible:</p>
+
+  <ul>
+    <li>no longer requires GNU make.</li>
+
+    <li>no longer requires dummy files such as <samp>*.done
+    *.flags</samp>.</li>
+
+    <li>uses MD5 checksums to determine whether sources and targets are out of
+    date - as opposed to time stamps.</li>
+
+    <li>fails the build if duplicated targets are detected if those targets are
+    required by the build.</li>
+  </ul>
+
+  <p>build: has improved the logic for building Fortran program units.</p>
+
+  <ul>
+    <li>detects correctly multiple top program units in the same source
+    file.</li>
+
+    <li>sets up a module usage as an include dependency on the
+    <samp>*.mod</samp> file instead of the <samp>*.o</samp> file - reduces the
+    chance of module compile cascades in incremental mode.</li>
+
+    <li>only generates interface files on demand.</li>
+  </ul>
+
+  <p>build: has improved facilities for sources and targets selection.</p>
+
+  <ul>
+    <li>can now select targets by name-space, category and task.</li>
+
+    <li>has better documentation on the relationship between source files and
+    build targets.</li>
+
+    <li>note that target declarations are not cumulative and that targets are
+    inherited by default (unlike with <code>fcm build</code>).</li>
+  </ul>
+
+  <p>build: automatically uses the Fortran compiler to link Fortran executables
+  and the C compiler to link C executables.</p>
+
+  <p>build: has more diagnostics, e.g. on source dependencies, target build
+  tree, etc.</p>
+
+  <p>preprocess: is now an independent step, but shares all the logic of the
+  build system, e.g.:</p>
+
+  <ul>
+    <li>preprocessing dependency analysis and target update can be performed in
+    multiple processes.</li>
+
+    <li>note that file extensions are not modified by the preprocess step
+    unlike with <code>fcm build</code> which changed, for example
+    <samp>.F90</samp> extensions to <samp>.f90</samp>.</li>
+  </ul>
+
+  <p>Other notable changes:</p>
+
+  <ul>
+    <li>By default, <code>fcm make</code> will always rebuild link targets and
+    re-install scripts in inherited builds. Therefore, to use the executables
+    from a build all you need to do is set your <var>PATH</var> environment
+    variable to point to <samp>$DEST/build/bin/</samp> (where <var>$DEST</var>
+    is the destination of the make). Note that there is no
+    <samp>fcm_env.sh</samp> file produced by <code>fcm make</code>.</li>
+
+    <li><code>fcm extract</code> has the ability to fail if the declared
+    revision of a branch does not correspond to a changeset of that branch.
+    Furthermore, it can output the latest revision of a branch if the declared
+    revision is not the latest. <code>fcm make</code> does not support
+    this.</li>
+
+    <li>There is no equivalent of <code><a href=
+    "../user_guide/command_ref.html#fcm-cmp-ext-cfg">fcm cmp-ext-cfg</a></code>
+    for FCM make configurations.</li>
+
+    <li><code>fcm make</code> does not support defining a separate linker - it
+    always uses the compiler of the source file containing the main program
+    (which is the default with <code>fcm build</code>).</li>
+
+    <li><code>fcm make</code> does not recognise existing binaries as install
+    targets (unlike <code>fcm build</code>). This feature is currently used to
+    allow the Met Office's Suite Control System (SCS) to "build" suites but is
+    no longer considered the best method. SCS will continue to use <code>fcm
+    build</code> until such time as a better method is adopted or the system is
+    retired.</li>
+
+    <li><code>fcm make</code> recognises data files as install targets in a
+    similar way to <code>fcm build</code>. However, the default destination of
+    such targets is now the full name-space under the <samp>etc/</samp>
+    sub-directory.</li>
+
+    <li><code>fcm make</code> supports the building of libraries but does not
+    generate the relevant exclude dependency configuration as is done by
+    <code>fcm build</code>.</li>
+
+    <li><code>fcm make</code> has no equivalent of the <code>--archive</code>
+    nor the <code>--targets</code> options provided by <code>fcm
+    build</code>.</li>
+
+    <li><code>fcm build</code> recognises a file name without its extension as
+    a sub-package name. This is not true with <code>fcm make</code> which only
+    recognises the full file name as a namespace.</li>
+  </ul>
+
+  <p>In addition to the differences noted above, <code>fcm make</code> fixes
+  various subtle problems which can occur with <code>fcm extract</code> and
+  <code>fcm build</code> as a result of limitations in the internal design.
+  Overall, <code>fcm make</code> is much better and we recommend that all users
+  migrate to it. <code>fcm extract</code> and <code>fcm build</code> will
+  continue to be maintained for legacy systems but will not be developed
+  further.</p>
+
+  <h2 id="fix">Minor Changes and Bug Fixes</h2>
+
+  <dl>
+    <dt><code>fcm build</code></dt>
+
+    <dd>
+      <p>Handle directory names with a dot extension.</p>
+
+      <p>Correct search path for inherited configuration file.</p>
+
+      <p>Always export <var>OBJECTS</var> in generated
+      <samp>Makefile</samp>.</p>
+    </dd>
+
+    <dt><code>fcm cfg</code></dt>
+
+    <dd>
+      <p>Now an alias of <code>fcm cfg-print</code>.</p>
+
+      <p>The default behaviour is to parse FCM 2 configuration files. To parse
+      FCM 1 configuration files, use the <code>--fcm1</code> option.</p>
+
+      <p>The values in the output will no longer be lined up.</p>
+    </dd>
+
+    <dt><code>fcm extract</code></dt>
+
+    <dd>
+      <p>Fix double slashes in cache of extract with project root level
+      files.</p>
+
+      <p>Correct search path for inherited configuration file.</p>
+
+      <p>Fix incremental mode behaviour of targets with <dfn>deleted,
+      overriding inherited</dfn> status.</p>
+    </dd>
+
+    <dt><code>fcm keyword-print</code></dt>
+
+    <dd>Change in output format to match the new configuration file
+    format.</dd>
+
+    <dt><code>fcm mkpatch</code></dt>
+
+    <dd>
+      <p>Don't use patch file if PDF file detected.</p>
+
+      <p>Handle property changes to directories.</p>
+
+      <p>Handle copies within new directories.</p>
+
+      <p>Handle replaced directories.</p>
+
+      <p>Fix handling of symbolic links.</p>
+
+      <p>Fix pattern match used when checking for excluded or copied paths.</p>
+
+      <p>Use <code>--no-backup-if-mismatch</code> option to patch command to
+      ensure backup files not created if patch does not match exactly.</p>
+    </dd>
+
+    <dt><code>fcm</code> direct wrappers to <code>svn</code> commands</dt>
+
+    <dd>No longer prints <samp>=> svn ...</samp> on STDOUT.</dd>
+
+    <dt>Misc fixes</dt>
+
+    <dd>Misc fixes related to changes in Perl 5.10 and Subversion 1.6.</dd>
+  </dl>
+
+  <h2 id="issues">Known Issues</h2>
+
+  <dl>
+    <dt>Build inheritance limitation: handling of include files</dt>
+
+    <dd>See the <a href="../user_guide/make.html#build.inherit">User Guide >
+    FCM Make > Build > Build Inheritance</a> for detail.</dd>
+  </dl>
+
+  <h2 id="req">System Requirements</h2>
+
+  <h3 id="req.perl">Perl</h3>
+
+  <p>The core part of FCM is a set of Perl scripts and modules. At the Met
+  Office, FCM runs on:</p>
+
+  <dl>
+    <dt>Perl 5.8.2 on AIX 5.3</dt>
+
+    <dd>
+      <p><code>Text::ParseWords</code> (core Perl module) is upgraded to
+      version 3.22.</p>
+
+      <p>Met Office users do not use the code management commands and the
+      extract system on this platform.</p>
+    </dd>
+
+    <dt>Perl 5.8.5 on RHEL 4</dt>
+
+    <dd>
+      <p><a href=
+      "http://search.cpan.org/~gaas/libwww-perl/lib/HTTP/Date.pm">HTTP::Date</a>
+      in <a href="http://search.cpan.org/~gaas/libwww-perl/">libwww-perl</a> is
+      required by <code>fcm extract</code> and the extract system in <code>fcm
+      make</code>. (libwww-perl 5.79 installed.)</p>
+
+      <p><a href=
+      "http://search.cpan.org/~enno/libxml-enno/lib/XML/DOM.pm">XML::DOM</a> in
+      <a href="http://search.cpan.org/~enno/libxml-enno/">libxml-enno</a> is
+      required by the code management commands. (libxml-enno 1.02
+      installed.)</p>
+
+      <p><a href="http://search.cpan.org/~srezic/Tk/">Tk</a> is required by the
+      <code>fcm gui</code> command. (Tk 804.027 installed.)</p>
+    </dd>
+  </dl>
+
+  <h3 id="req.svn">Subversion</h3>
+
+  <p>To use the code management commands (and relevant parts of the extract
+  system) you need to have <a href=
+  "http://subversion.tigris.org/">Subversion</a> installed.</p>
+
+  <p>FCM requires Subversion 1.4.x or above. At the Met Office we are currently
+  using Subversion 1.4.3.</p>
+
+  <p>Note: you can use the extract system to mirror code to a remote platform
+  for building. Therefore it is only necessary to have Subversion installed on
+  the platform where you do your code development. If you use other platforms
+  purely for building and running then you do not need to have Subversion
+  installed on these platforms.</p>
+
+  <h3 id="req.trac">Trac</h3>
+
+  <p>The use of <a href="http://trac.edgewall.org/">Trac</a> is entirely
+  optional (although highly recommended if you are using Subversion).</p>
+
+  <p>At the Met Office we are currently using Trac 0.11.7. Note:</p>
+
+  <ul>
+    <li>The <code>--trac</code> and <code>--wiki</code> options to the
+    <code>fcm diff --branch</code> command allow you to view branch differences
+    using Trac. This requires Trac 0.10 or above.</li>
+
+    <li>Some of the example scripts in the <samp>examples/</samp> directory
+    require Trac 0.11.</li>
+  </ul>
+
+  <h3 id="req.other">Other Requirements</h3>
+
+  <p>The <code>fcm conflicts</code> command requires <a href=
+  "http://furius.ca/xxdiff/">xxdiff</a>. At the Met Office we are currently
+  using version 3.1. The <code>fcm diff --graphical</code> command also uses
+  xxdiff by default although other graphical diff tools can also be used.</p>
+
+  <p>The <code>fcm make</code> command uses <code>gzip</code>. At the Met
+  Office we are currently using gzip 1.2.4 on AIX 5.3 and gzip 1.3.3 on RHEL
+  4.</p>
+
+  <p>The extract system uses <code>diff3</code>, (which is part of <a href=
+  "http://www.gnu.org/software/diffutils/">GNU diffutils</a>), to merge
+  together changes where the same file is modified by two different branches
+  (compared with the base branch). At the Met Office we are currently using
+  version 2.8.1.</p>
+
+  <p>The mirror system uses <a href="http://rsync.samba.org/">rsync</a> to
+  mirror source file to another machine. At the Met Office we are currently
+  using version 2.6.3</p>
+
+  <p>The deprecated <code>fcm build</code> requires <a href=
+  "http://www.gnu.org/software/make/make.html">GNU make</a>. At the Met Office
+  we are currently using version 3.80.</p>
+
+  <p>FCM is intended to run on a Unix/Linux system. It is currently used at the
+  Met Office on Linux (RHEL 4.8) and AIX 5.3.</p>
+
+  <h2 id="ins">Installation</h2>
+
+  <p>FCM is distributed in the form of a compressed tar file. Un-pack the tar
+  file into an appropriate location on your system. Add the <samp>bin/</samp>
+  directory into your <var>PATH</var> environment variable. Once you have done
+  this you should now have full access to the FCM system, assuming that you
+  have met the requirements described in the previous section.</p>
+
+  <p>You should find the following contents in the distribution:</p>
+
+  <dl>
+    <dt>README</dt>
+
+    <dd>The README file contains the internal revision number of the
+    release.</dd>
+
+    <dt>COPYRIGHT.txt<br />
+    LICENSE.html</dt>
+
+    <dd>The FCM license and other copyright information.</dd>
+
+    <dt>bin/</dt>
+
+    <dd>Contains the <code>fcm</code> command and other utilities.</dd>
+
+    <dt>doc/</dt>
+
+    <dd>System documentation.</dd>
+
+    <dt>doc/release_notes/</dt>
+
+    <dd>Contains these release notes. It also contains the release notes for
+    all previous versions which may be useful if you have skipped any
+    versions.</dd>
+
+    <dt>doc/user_guide/</dt>
+
+    <dd>Contains the <a href="../user_guide/">FCM User Guide</a>.</dd>
+
+    <dt>doc/standards/</dt>
+
+    <dd>Contains the FCM <a href="../standards/perl_standard.html">Perl</a> and
+    <a href="../standards/fortran_standard.html">Fortran</a> coding standards.
+    The Perl standard describes the standards followed by the FCM code. The
+    Fortran standard contains some <a href=
+    "../standards/fortran_standard.html#fcm">specific advice</a> on the best
+    way of writing Fortran code for use with FCM as well as more general advice
+    on good practice.</dd>
+
+    <dt>doc/collaboration/</dt>
+
+    <dd>Contains the <a href="../collaboration/index.html">External
+    Distribution & Collaboration for FCM Projects</a> document which
+    discusses how projects configured under FCM can be distributed
+    externally.</dd>
+
+    <dt>etc/</dt>
+
+    <dd>Miscellaneous items, including the <samp>fcm/keyword.cfg.eg</samp> file.
+    If you wish to define keywords for your site you will need to create the
+    <samp>etc/fcm/keyword.cfg</samp> file. An example file,
+    <samp>fcm/keyword.cfg.eg</samp>, is provided which is a copy of the file
+    currently used at the Met Office. For further details please refer to the
+    section <a href="../user_guide/system_admin.html#fcm-keywords">FCM
+    keywords</a> in the System Admin chapter of the User Guide.</dd>
+
+    <dt>examples/</dt>
+
+    <dd>Contains various example scripts which you may find useful. Note that
+    these scripts are all specific to the Met Office and may contain hard coded
+    paths and email addresses. They are provided in the hope that you may find
+    them useful as examples for setting up similar scripts of your own.
+    However, they should only be used after careful review to adapt them to
+    your environment.</dd>
+
+    <dt>examples/etc/regular-update.eg</dt>
+
+    <dd>An example of how you might set up a cron job to make use of the
+    <samp><repos>.latest</samp> file (see
+    <code>examples/svn-hooks/post-commit-background</code>).</dd>
+
+    <dt>examples/lib/</dt>
+
+    <dd>Contains the <code>FCM::Admin::*</code> Perl library, which implements
+    the functionalities of the FCM admin utility commands.</dd>
+
+    <dt>examples/sbin/</dt>
+
+    <dd>Contains a selection of useful admin utility commands.</dd>
+
+    <dt>examples/svn-hooks/pre-commit</dt>
+
+    <dd>
+      This script restricts write-access to the repository by checking the
+      following:
+
+      <ul>
+        <li>It executes the Subversion utility <code>svnperms.py</code> if it,
+        and the associated <samp>svnperms.conf</samp> file, exist. This utility
+        checks whether the author of the current transaction has enough
+        permission to write to particular paths in the repository.</li>
+
+        <li>It checks the disk space required by the current transaction. It
+        fails the commit if it requires more than 5Mb of disk space.</li>
+      </ul>
+    </dd>
+
+    <dt>examples/svn-hooks/post-commit</dt>
+
+    <dd>A simple post-commit hook script which runs the script
+    <code>post-commit-background</code> in the background.</dd>
+
+    <dt>examples/svn-hooks/post-commit-background</dt>
+
+    <dd>
+      This script runs in the background after each commit.
+
+      <ul>
+        <li>It updates a <samp><repos>.latest</samp> file with the latest
+        revision number.</li>
+
+        <li>It creates a dump of the new revision.</li>
+
+        <li>It calls <code>post-commit-background-custom</code> if it
+        exists.</li>
+      </ul>
+    </dd>
+
+    <dt>examples/svn-hooks/pre-revprop-change</dt>
+
+    <dd>A simple pre-revprop-change hook script which runs the script
+    <code>pre-revprop-change.pl</code>.</dd>
+
+    <dt>examples/svn-hooks/pre-revprop-change.pl</dt>
+
+    <dd>If a user attempts to modify the log message of a changeset and he/she
+    is not the original author of the changeset, this script will e-mail the
+    original author. You can also set up a watch facility to monitor changes of
+    log messages that affect particular paths in the repository. For further
+    details please refer to the section <a href=
+    "../user_guide/system_admin.html#svn_watch">Watching changes in log
+    messages</a> in the System Admin chapter of the User Guide.</dd>
+
+    <dt>examples/svn-hooks/post-revprop-change</dt>
+
+    <dd>A simple post-revprop-change hook script which invokes the
+    <code>trac-admin</code> command to <code>resync</code> the revision
+    property cache stored in the corresponding Trac environment.</dd>
+
+    <dt>lib/</dt>
+
+    <dd>Contains the Perl library of FCM.</dd>
+
+    <dt>man/</dt>
+
+    <dd>Contains a basic manual page for <code>fcm</code>.</dd>
+
+    <dt>t/</dt>
+
+    <dd>Contains unit test for FCM.</dd>
+
+    <dt>test/</dt>
+
+    <dd>Contains regression tests for FCM.</dd>
+
+    <dt>test/test_include/</dt>
+
+    <dd>Contains simple test code to check how your chosen compilers handle
+    include files (see <a href="#issues">Known Issues</a>).</dd>
+
+    <dt>tutorial/</dt>
+
+    <dd>Contains the files necessary to set up a Subversion repository for the
+    FCM tutorial. This will allow you to follow the <a href=
+    "../user_guide/getting_started.html#tutorial">tutorial section</a> in the
+    User Guide. See <samp>tutorial/README</samp> on how to set it up.</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/release_notes/2-1.html b/doc/release_notes/2-1.html
new file mode 100644
index 0000000..c33bf7d
--- /dev/null
+++ b/doc/release_notes/2-1.html
@@ -0,0 +1,457 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM 2-1 Release Notes</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM 2-1 Release Notes <small>22 July 2011</small></h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>These are the release notes for FCM 2-1. You can use this release of FCM
+  freely under the terms of the FCM LICENSE, which you should receive with the
+  distribution of this release.</p>
+
+  <p>FCM is maintained by the FCM team at the Met Office. Please feedback any
+  bug reports or feature requests to us by <a href=
+  "mailto:fcm-team at metoffice.gov.uk">e-mail</a>.</p>
+
+  <h2 id="new">What's New?</h2>
+
+  <p>No major new features in this release.</p>
+
+  <h2 id="fix">Minor Changes and Bug Fixes</h2>
+
+  <dl>
+    <dt><code>fcm branch-create</code></dt>
+
+    <dd>
+      <p>if the <code>--ticket=N</code> option is not specified and
+      <var>NAME</var> contains only a list of positive integers separated by
+      <code>[_-]</code> (an underscore or a hyphen), the command will now assume
+      that <var>NAME</var> also specifies the related ticket numbers.</p>
+
+      <p>ticket numbers will now prefix the 1st line of the automatic commit log
+      message.</p>
+    </dd>
+
+    <dt><code>fcm cmp-ext-cfg</code></dt>
+
+    <dd>
+      <p>fixed <code>--wiki-format=TARGET</code> option, broken at <a href=
+      "2-0.html">2-0</a>.</p>
+    </dd>
+
+    <dt><code>fcm export-items</code></dt>
+
+    <dd>
+      <p>fixed <code>--new</code> option, broken at <a href=
+      "2-0.html">2-0</a>.</p>
+
+      <p>now generate real <samp>v*</samp> directories and revision directories
+      as symbolic links.</p>
+
+      <p><code>fcm_update_version_dir.pl</code> removed.</p>
+    </dd>
+
+    <dt><code>fcm make</code></dt>
+
+    <dd>
+      <p>build: allow space between <code>#</code> and <code>include</code> for
+      <code>#include</code> syntax in C or Fortran source files requiring
+      pre-processing.</p>
+
+      <p>build: will now fail when an invalid target key is specified.</p>
+
+      <p>build: correctly support compilers and linkers that require a space
+      between an option flag and its argument.</p>
+
+      <p>build: will no longer call <code>ar</code> for single file link.</p>
+
+      <p>build: improve error message if a target does not exist in the expected
+      location following an update.</p>
+
+      <p>build: <code>target-rename</code> declarations are now non-cumulative,
+      as documented.</p>
+
+      <p>build: <code>build.prop{file-name-option.f90-mod} = case=upper</code>
+      can now be specified for compilers that generate <samp>MODULE.mod</samp>
+      files for Fortran modules (as opposed to the more common
+      <samp>module.mod</samp> convention).</p>
+
+      <p>extract: <code>path-incl</code>, <code>path-excl</code>,
+      <code>path-root</code> declarations no longer require a name-space.</p>
+
+      <p>extract: fixed error handling when multiple processing.</p>
+    </dd>
+
+    <dt><code>fcm mkpatch</code></dt>
+
+    <dd>
+      <p>fixed CLI prompt to confirm removal of old output directory.</p>
+
+      <p>ensure initial branch creation is ignored.</p>
+    </dd>
+
+    <dt>commit message text editor</dt>
+
+    <dd>
+      <p><code>fcm branch-create</code>, <code>fcm
+      branch-delete</code> and <code>fcm commit</code> now support the
+      <var>[helpers] editor-cmd</var> option defined in
+      <samp>$HOME/.subversion/config</samp>.</p>
+
+      <p><code>nedit</code> is no longer the default external text editor.
+      <code>vi</code> is the default external text editor for the command line
+      interface. <code>gedit</code> is the default external text editor for
+      <code>fcm gui</code>.</p>
+    </dd>
+
+    <dt>configuration file syntax</dt>
+
+    <dd>
+      <p>the <code>inc=LOCATION</code> is no longer supported for configuration
+      files in the new part of the system.</p>
+
+      <p>incorrect variable assignment syntax will now trigger an exception.</p>
+
+      <p>leading and trailing spaces in modifiers and name-space are now
+      ignored without triggering Perl warnings.</p>
+    </dd>
+
+    <dt>installation dependency</dt>
+
+    <dd>
+      <p><code>fcm</code> no longer requires the <code>XML::DOM</code> Perl
+      module. It now uses <code>XML::Parser</code> instead. The latter is
+      normally installed by default on most Unix/Linux platforms.</p>
+    </dd>
+  </dl>
+
+  <h2 id="issues">Known Issues</h2>
+
+  <dl>
+    <dt>Build inheritance limitation: handling of include files</dt>
+
+    <dd>See the <a href="../user_guide/make.html#build.inherit">User Guide >
+    FCM Make > Build > Build Inheritance</a> for detail.</dd>
+  </dl>
+
+  <h2 id="req">System Requirements</h2>
+
+  <h3 id="req.perl">Perl</h3>
+
+  <p>The core part of FCM is a set of Perl scripts and modules. At the Met
+  Office, FCM runs on:</p>
+
+  <dl>
+    <dt>Perl 5.8.2 on AIX 5.3</dt>
+
+    <dd>
+      <p><code>Text::ParseWords</code> (core Perl module) is upgraded to
+      version 3.22.</p>
+
+      <p>Met Office users do not use the code management commands and the
+      extract system on this platform.</p>
+    </dd>
+
+    <dt>Perl 5.8.5 on RHEL 4</dt>
+
+    <dd>
+      <p><a href=
+      "http://search.cpan.org/~gaas/libwww-perl/lib/HTTP/Date.pm">HTTP::Date</a>
+      in <a href="http://search.cpan.org/~gaas/libwww-perl/">libwww-perl</a> is
+      required by <code>fcm extract</code> and the extract system in <code>fcm
+      make</code>. (libwww-perl 5.79 installed.)</p>
+
+      <p><a href="http://search.cpan.org/dist/XML-Parser/">XML::Parser</a> is
+      required by the code management commands. (2.34 installed.)</p>
+
+      <p><a href="http://search.cpan.org/~srezic/Tk/">Tk</a> is required by the
+      <code>fcm gui</code> command. (Tk 804.027 installed.)</p>
+    </dd>
+  </dl>
+
+  <h3 id="req.svn">Subversion</h3>
+
+  <p>To use the code management commands (and relevant parts of the extract
+  system) you need to have <a href=
+  "http://subversion.tigris.org/">Subversion</a> installed.</p>
+
+  <p>FCM requires Subversion 1.4.x or above. At the Met Office we are currently
+  using Subversion 1.4.3.</p>
+
+  <p>Note: you can use the extract system to mirror code to a remote platform
+  for building. Therefore it is only necessary to have Subversion installed on
+  the platform where you do your code development. If you use other platforms
+  purely for building and running then you do not need to have Subversion
+  installed on these platforms.</p>
+
+  <h3 id="req.trac">Trac</h3>
+
+  <p>The use of <a href="http://trac.edgewall.org/">Trac</a> is entirely
+  optional (although highly recommended if you are using Subversion).</p>
+
+  <p>At the Met Office we are currently using Trac 0.11.7. Note:</p>
+
+  <ul>
+    <li>The <code>--trac</code> and <code>--wiki</code> options to the
+    <code>fcm diff --branch</code> command allow you to view branch differences
+    using Trac. This requires Trac 0.10 or above.</li>
+
+    <li>Some of the example scripts in the <samp>examples/</samp> directory
+    require Trac 0.11.</li>
+  </ul>
+
+  <h3 id="req.other">Other Requirements</h3>
+
+  <p>The <code>fcm conflicts</code> command requires <a href=
+  "http://furius.ca/xxdiff/">xxdiff</a>. At the Met Office we are currently
+  using version 3.1. The <code>fcm diff --graphical</code> command also uses
+  xxdiff by default although other graphical diff tools can also be used.</p>
+
+  <p>The <code>fcm make</code> command uses <code>gzip</code>. At the Met
+  Office we are currently using gzip 1.2.4 on AIX 5.3 and gzip 1.3.3 on RHEL
+  4.</p>
+
+  <p>The extract system uses <code>diff3</code>, (which is part of <a href=
+  "http://www.gnu.org/software/diffutils/">GNU diffutils</a>), to merge
+  together changes where the same file is modified by two different branches
+  (compared with the base branch). At the Met Office we are currently using
+  version 2.8.1.</p>
+
+  <p>The mirror system uses <a href="http://rsync.samba.org/">rsync</a> to
+  mirror source file to another machine. At the Met Office we are currently
+  using version 2.6.3</p>
+
+  <p>The deprecated <code>fcm build</code> requires <a href=
+  "http://www.gnu.org/software/make/make.html">GNU make</a>. At the Met Office
+  we are currently using version 3.80.</p>
+
+  <p>FCM is intended to run on a Unix/Linux system. It is currently used at the
+  Met Office on Linux (RHEL 4.8) and AIX 5.3.</p>
+
+  <h2 id="ins">Installation</h2>
+
+  <p>FCM is distributed in the form of a compressed tar file. Un-pack the tar
+  file into an appropriate location on your system. Add the <samp>bin/</samp>
+  directory into your <var>PATH</var> environment variable. Once you have done
+  this you should now have full access to the FCM system, assuming that you
+  have met the requirements described in the previous section.</p>
+
+  <p>You should find the following contents in the distribution:</p>
+
+  <dl>
+    <dt>README</dt>
+
+    <dd>The README file contains the internal revision number of the
+    release.</dd>
+
+    <dt>COPYRIGHT.txt<br />
+    LICENSE.html</dt>
+
+    <dd>The FCM license and other copyright information.</dd>
+
+    <dt>bin/</dt>
+
+    <dd>Contains the <code>fcm</code> command and other utilities.</dd>
+
+    <dt>doc/</dt>
+
+    <dd>System documentation.</dd>
+
+    <dt>doc/release_notes/</dt>
+
+    <dd>Contains these release notes. It also contains the release notes for
+    all previous versions which may be useful if you have skipped any
+    versions.</dd>
+
+    <dt>doc/user_guide/</dt>
+
+    <dd>Contains the <a href="../user_guide/">FCM User Guide</a>.</dd>
+
+    <dt>doc/standards/</dt>
+
+    <dd>Contains the FCM <a href="../standards/perl_standard.html">Perl</a> and
+    <a href="../standards/fortran_standard.html">Fortran</a> coding standards.
+    The Perl standard describes the standards followed by the FCM code. The
+    Fortran standard contains some <a href=
+    "../standards/fortran_standard.html#fcm">specific advice</a> on the best
+    way of writing Fortran code for use with FCM as well as more general advice
+    on good practice.</dd>
+
+    <dt>doc/collaboration/</dt>
+
+    <dd>Contains the <a href="../collaboration/index.html">External
+    Distribution & Collaboration for FCM Projects</a> document which
+    discusses how projects configured under FCM can be distributed
+    externally.</dd>
+
+    <dt>etc/</dt>
+
+    <dd>Miscellaneous items, including the <samp>fcm/keyword.cfg.eg</samp> file.
+    If you wish to define keywords for your site you will need to create the
+    <samp>etc/fcm/keyword.cfg</samp> file. An example file,
+    <samp>fcm/keyword.cfg.eg</samp>, is provided which is a copy of the file
+    currently used at the Met Office. For further details please refer to the
+    section <a href="../user_guide/system_admin.html#fcm-keywords">FCM
+    keywords</a> in the System Admin chapter of the User Guide.</dd>
+
+    <dt>examples/</dt>
+
+    <dd>Contains various example scripts which you may find useful. Note that
+    these scripts are all specific to the Met Office and may contain hard coded
+    paths and email addresses. They are provided in the hope that you may find
+    them useful as examples for setting up similar scripts of your own.
+    However, they should only be used after careful review to adapt them to
+    your environment.</dd>
+
+    <dt>examples/etc/regular-update.eg</dt>
+
+    <dd>An example of how you might set up a cron job to make use of the
+    <samp><repos>.latest</samp> file (see
+    <code>examples/svn-hooks/post-commit-background</code>).</dd>
+
+    <dt>examples/lib/</dt>
+
+    <dd>Contains the <code>FCM::Admin::*</code> Perl library, which implements
+    the functionalities of the FCM admin utility commands.</dd>
+
+    <dt>examples/sbin/</dt>
+
+    <dd>Contains a selection of useful admin utility commands.</dd>
+
+    <dt>examples/svn-hooks/pre-commit</dt>
+
+    <dd>
+      This script restricts write-access to the repository by checking the
+      following:
+
+      <ul>
+        <li>It executes the Subversion utility <code>svnperms.py</code> if it,
+        and the associated <samp>svnperms.conf</samp> file, exist. This utility
+        checks whether the author of the current transaction has enough
+        permission to write to particular paths in the repository.</li>
+
+        <li>It checks the disk space required by the current transaction. It
+        fails the commit if it requires more than 5Mb of disk space.</li>
+      </ul>
+    </dd>
+
+    <dt>examples/svn-hooks/post-commit</dt>
+
+    <dd>A simple post-commit hook script which runs the script
+    <code>post-commit-background</code> in the background.</dd>
+
+    <dt>examples/svn-hooks/post-commit-background</dt>
+
+    <dd>
+      This script runs in the background after each commit.
+
+      <ul>
+        <li>It updates a <samp><repos>.latest</samp> file with the latest
+        revision number.</li>
+
+        <li>It creates a dump of the new revision.</li>
+
+        <li>It calls <code>post-commit-background-custom</code> if it
+        exists.</li>
+      </ul>
+    </dd>
+
+    <dt>examples/svn-hooks/pre-revprop-change</dt>
+
+    <dd>A simple pre-revprop-change hook script which runs the script
+    <code>pre-revprop-change.pl</code>.</dd>
+
+    <dt>examples/svn-hooks/pre-revprop-change.pl</dt>
+
+    <dd>If a user attempts to modify the log message of a changeset and he/she
+    is not the original author of the changeset, this script will e-mail the
+    original author. You can also set up a watch facility to monitor changes of
+    log messages that affect particular paths in the repository. For further
+    details please refer to the section <a href=
+    "../user_guide/system_admin.html#svn_watch">Watching changes in log
+    messages</a> in the System Admin chapter of the User Guide.</dd>
+
+    <dt>examples/svn-hooks/post-revprop-change</dt>
+
+    <dd>A simple post-revprop-change hook script which invokes the
+    <code>trac-admin</code> command to <code>resync</code> the revision
+    property cache stored in the corresponding Trac environment.</dd>
+
+    <dt>lib/</dt>
+
+    <dd>Contains the Perl library of FCM.</dd>
+
+    <dt>man/</dt>
+
+    <dd>Contains a basic manual page for <code>fcm</code>.</dd>
+
+    <dt>t/</dt>
+
+    <dd>Contains unit test for FCM.</dd>
+
+    <dt>test/</dt>
+
+    <dd>Contains regression tests for FCM.</dd>
+
+    <dt>test/test_include/</dt>
+
+    <dd>Contains simple test code to check how your chosen compilers handle
+    include files (see <a href="#issues">Known Issues</a>).</dd>
+
+    <dt>tutorial/</dt>
+
+    <dd>Contains the files necessary to set up a Subversion repository for the
+    FCM tutorial. This will allow you to follow the <a href=
+    "../user_guide/getting_started.html#tutorial">tutorial section</a> in the
+    User Guide. See <samp>tutorial/README</samp> on how to set it up.</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/release_notes/2-2.html b/doc/release_notes/2-2.html
new file mode 100644
index 0000000..6198d6b
--- /dev/null
+++ b/doc/release_notes/2-2.html
@@ -0,0 +1,600 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM 2-2 Release Notes</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM 2-2 Release Notes <small>15 June 2012</small></h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>These are the release notes for FCM 2-2. You can use this release of FCM
+  freely under the terms of the FCM LICENSE, which you should receive with the
+  distribution of this release.</p>
+
+  <p>FCM is maintained by the FCM team at the Met Office. Please feedback any
+  bug reports or feature requests to us by <a href=
+  "mailto:fcm-team at metoffice.gov.uk">e-mail</a>.</p>
+
+  <h2>Contents</h2>
+
+  <div id="fcm-content"></div>
+
+  <h2 id="changes-highlight">Highlight Changes</h2>
+
+  <dl>
+    <dt><code>Subversion 1.6</code></dt>
+
+    <dd>
+      <p>Code management commands and extract system tested with Subversion
+      1.6. Note that Subversion 1.4 is no longer supported and Subversion 1.5
+      has not been tested.</p>
+
+      <p>We experienced some non-repeatable problems using Subversion 1.6 where
+      we got incorrect results from a merge. Running <code>svn cleanup</code>
+      appears to avoid the problem. Therefore this is now run prior to any
+      update, switch or merge command.</p>
+    </dd>
+
+    <dt><code>fcm branch-create</code></dt>
+
+    <dd>
+      <p><code>-rREV</code> option no longer supported - add a peg revision to
+      the source if necessary.</p>
+    </dd>
+
+    <dt><code>fcm branch-list</code></dt>
+
+    <dd>
+      <p>significant improvement to speed.</p>
+
+      <p>multiple projects can be specified as arguments.</p>
+
+      <p>more options for advanced listing.</p>
+
+      <p><code>-rREV</code> option no longer supported - can use peg revision
+      for each project in the argument list.</p>
+
+      <p>no longer returns 1 if 0 branch is found - non-zero value reserved for
+      fatal errors.</p>
+
+      <p>new <code>--quiet</code> option to print names of matched branches
+      only.</p>
+
+      <p><code>-v</code> option no longer supported - use new
+      <code>--url</code> option to print branch names as full URLs instead of
+      FCM location keywords.</p>
+
+      <p>minor change to output format.</p>
+    </dd>
+
+    <dt><code>fcm conflicts</code></dt>
+
+    <dd>
+      <p>handle resolution of common cases of tree conflicts.</p>
+    </dd>
+
+    <dt><code>fcm make</code></dt>
+
+    <dd>
+      <p>build: change in how the dependency tree is created. This fixes the
+      behaviour where the system incorrectly reports cyclic dependency. An
+      example situation is where a recursive <code>subroutine a</code> calls
+      <code>subroutine b</code> which calls <code>subroutine a</code>. The
+      object file target <samp>a.o</samp> has a dependency on
+      <samp>b.interface</samp> and the object file target <samp>b.o</samp> has a
+      dependency on <samp>a.interface</samp>. Previously,
+      <samp>b.interface</samp> would also depend on <samp>b.o</samp> which would
+      result in a cyclic dependency. This fix changes the internal data
+      structure for representing a build target. <strong>Use of <code>fcm
+      make</code> at release 2-2 to build incrementally or to inherit a build
+      created by <code>fcm make</code> at release 2-1 or before will result in an
+      incorrect behaviour or failure. Use the <code>--new</code> option to start
+      a new build if the build destination is known to contain an existing build
+      created by <code>fcm make</code> at release 2-1 or before.</strong></p>
+    </dd>
+
+    <dt>Flexible repository layout</dt>
+
+    <dd>
+      <p>FCM code management commands no longer insist on a rigid layout of its
+      Subversion repositories. Behaviours can now be configured per repository.
+      In its default setting, FCM code management commands no longer insist on
+      <samp>branches/</samp> and <samp>tags/</samp> being present in a
+      <em>project</em>.</p>
+    </dd>
+  </dl>
+
+  <h2 id="changes-minor">Minor Changes and Bug Fixes</h2>
+
+  <dl>
+    <dt><code>fcm branch-create</code></dt>
+
+    <dd>
+      <p>new <code>--switch</code> option to switch the current working copy to
+      point to the newly created branch.</p>
+    </dd>
+
+    <dt><code>fcm branch-delete</code></dt>
+
+    <dd>
+      <p>new <code>--switch</code> option to switch the current working copy to
+      point to the <em>trunk</em> after the branch deletion.</p>
+    </dd>
+
+    <dt><code>fcm branch-diff</code></dt>
+
+    <dd>
+      <p>fixed: the command will no longer fail when called with an unmodified
+      sub-tree of a branch.</p>
+    </dd>
+
+    <dt><code>fcm build/extract</code></dt>
+
+    <dd>
+      <p>fixed multi-level inheritance destination search path.</p>
+    </dd>
+
+    <dt><code>fcm commit</code></dt>
+
+    <dd>
+      <p>fixed incorrect behaviour on <code>svn commit</code> failure.</p>
+    </dd>
+
+    <dt><code>fcm make</code></dt>
+
+    <dd>
+      <p>build: fixed multi-level inheritance destination search path.</p>
+
+      <p>build: do not use relative path for inherited include paths and object
+      file paths in command lines.</p>
+
+      <p>build: fixed duplicated dependent targets being reported as missing
+      dependencies.</p>
+
+      <p>build: fixed incorrect behaviour with Fortran <samp>*.mod</samp>
+      target in incremental mode where only a build property (e.g. a compiler
+      flag) associated with the source file of the <samp>*.mod</samp> target is
+      modified.</p>
+
+      <p>extract: fixed handling of add/delete of files in file system
+      locations during incremental extract.</p>
+
+      <p>extract: fixed Perl warning when a file system location contains a
+      symbolic link with a non-existent target.</p>
+
+      <p>extract: fixed Perl warning when 2 diff locations both add a file of
+      the same name but with different contents.</p>
+
+      <p>extract: improved merge conflict diagnostics.</p>
+
+      <p>extract: allow unmodified location in the configuration to pass in an
+      inherited mode.</p>
+
+      <p>mirror: fixed: create configuration file in target even if there is no
+      source to mirror.</p>
+
+      <p>mirror: always expand a relative path in a mirror target to a full path
+      to allow it to be inherited. Some infrequently used modifiers in the
+      <code>mirror.prop</code> declarations are added/removed for this fix.</p>
+    </dd>
+
+    <dt><code>fcm merge</code></dt>
+
+    <dd>
+      <p>simplified prompts and diagnostic outputs.</p>
+
+      <p>new <code>--auto-log</code> option for automatic merge. If the option
+      is specified, the command will include the logs of the merged revisions in
+      the commit log message automatically.</p>
+    </dd>
+
+    <dt><code>fcm mkpatch</code></dt>
+
+    <dd>
+      <p>no longer uses patch files if they include a carrriage return in the
+      middle of a line.</p>
+    </dd>
+
+    <dt><code>fcm project-create</code></dt>
+
+    <dd>
+      <p>new command to create a new project and its trunk directory in a
+      repository.</p>
+    </dd>
+
+    <dt>code management command line</dt>
+
+    <dd>
+      <p>fixed logic for parsing <var>URL at REV</var> where <var>URL</var> is not
+      a location keyword but <var>REV</var> is a revision keyword.</p>
+    </dd>
+
+    <dt>revision keyword</dt>
+
+    <dd>
+      <p>fixed: <var>fcm:revision</var> property setting: trailing spaces in
+      each line will now be ignored.</p>
+    </dd>
+
+    <dt>user guide</dt>
+
+    <dd>
+      <p>tutorial: added section on tree conflict resolution.</p>
+
+      <p>tutorial: removed references to <code>fcm gui</code>.</p>
+
+      <p>added new annex: quick reference: tree conflict resolution.</p>
+    </dd>
+
+    <dt>Perl 5.12</dt>
+
+    <dd>
+      <p>fixed: <a href=
+      "http://search.cpan.org/~jesse/perl-5.12.0/pod/perl5120delta.pod#REGEXPs_are_now_first_class">
+      incompatibility problem</a>.</p>
+    </dd>
+  </dl>
+
+  <h2 id="issues">Known Issues</h2>
+
+  <dl>
+    <dt>Build inheritance limitation: handling of include files</dt>
+
+    <dd>See the <a href="../user_guide/make.html#build.inherit">User Guide >
+    FCM Make > Build > Build Inheritance</a> for detail.</dd>
+  </dl>
+
+  <h2 id="requirements">System Requirements</h2>
+
+  <p>FCM is intended to run on a Unix/Linux system. It is currently used at the
+  Met Office on AIX-5.3 and RHEL-6.1.</p>
+
+  <dl>
+    <dt><a href="http://www.perl.org/">Perl</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> <code>fcm</code>.</p>
+
+      <p><dfn>versions at Met Office:</dfn> AIX-5.3: 5.8.2 (see remark),
+      RHEL-6.1: 5.10.1.</p>
+
+      <p><dfn>remark:</dfn> 5.8.2 on AIX-5.3: code management commands, the
+      extract system of <code>fcm make</code> and the deprecated <code>fcm
+      extract</code> are not used by Met Office users on this platform.</p>
+
+      <p><dfn>remark:</dfn> 5.8.2 on AIX-5.3: <code>Text::ParseWords</code>
+      (core Perl module) is upgraded to version 3.22.</p>
+    </dd>
+
+    <dt>Perl module <a href=
+    "http://search.cpan.org/~gaas/libwww-perl-5.834/lib/HTTP/Date.pm">HTTP::Date</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the extract system in <code>fcm make</code> and
+      the deprecated <code>fcm extract</code>.</p>
+
+      <p><dfn>versions at Met Office:</dfn> RHEL-6.1: 5.833.</p>
+    </dd>
+
+    <dt>Perl module <a href=
+    "http://search.cpan.org/~msergeant/XML-Parser-2.36/">XML::Parser</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the code management commands.</p>
+
+      <p><dfn>versions at Met Office:</dfn> RHEL-6.1: 2.36.</p>
+    </dd>
+
+    <dt>Perl module <a href=
+    "http://search.cpan.org/~srezic/Tk-804.028/">Tk</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> <code>fcm gui</code>.</p>
+
+      <p><dfn>versions at Met Office:</dfn> RHEL-6.1: 804.028.</p>
+    </dd>
+
+    <dt><a href="http://subversion.apache.org/">Subversion</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the code management commands, the extract system
+      of <code>fcm make</code>, the deprecated <code>fcm extract</code>.</p>
+
+      <p><dfn>versions at Met Office:</dfn> RHEL-6.1: 1.6.17.</p>
+
+      <p><dfn>remark:</dfn> you can use the extract system to mirror code to a
+      remote platform for building. Therefore it is only necessary to have
+      Subversion installed on the platform where you do your code development.
+      If you use other platforms purely for building and running then you do
+      not need to have Subversion installed on these platforms.</p>
+    </dd>
+
+    <dt><a href="http://trac.edgewall.org/">Trac</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> (optional, but highly recommended as a companion
+      to Subversion)</p>
+
+      <p><dfn>versions at Met Office:</dfn> 0.11.7.</p>
+    </dd>
+
+    <dt><a href="http://furius.ca/xxdiff/">xxdiff</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> <code>fcm branch-diff --graphical</code>,
+      <code>fcm conflicts</code>, <code>fcm diff --graphical</code>.</p>
+
+      <p><dfn>versions at Met Office:</dfn> RHEL-6.1: 3.2.</p>
+
+      <p><dfn>remark:</dfn> The <code>fcm branch-diff --graphical</code> and
+      <code>fcm diff --graphical</code> commands use xxdiff by default but can
+      also use other graphical diff tools.</p>
+    </dd>
+
+    <dt><a href="http://www.gzip.org/">gzip</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> <code>fcm make</code>.</p>
+
+      <p><dfn>versions at Met Office:</dfn> AIX-5.3: 1.2.4, RHEL-6.1: 1.3.12.</p>
+    </dd>
+
+    <dt><a href="http://www.gnu.org/software/diffutils/">GNU diffutils</a>:
+    diff3</dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the extract system of <code>fcm make</code>, the
+      deprecated <code>fcm extract</code>.</p>
+
+      <p><dfn>versions at Met Office:</dfn> RHEL-6.1: 2.8.1.</p>
+
+      <p><dfn>remark:</dfn>: used to merge changes to source files modified by
+      2+ diff source trees (compared with the base).</p>
+    </dd>
+
+    <dt><a href="http://rsync.samba.org/">rsync</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the mirror system of <code>fcm make</code>, the
+      deprecated <code>fcm extract</code>.</p>
+
+      <p><dfn>versions at Met Office:</dfn> AIX-5.3: 2.6.2, RHEL-6.1: 3.0.6.</p>
+
+      <p><dfn>remark:</dfn> used to mirror source file to another
+      <var>USER at HOST</var>.</p>
+    </dd>
+
+    <dt><a href="http://www.gnu.org/software/make/make.html">GNU make</a></dt>
+
+    <dd>
+      <p><dfn>used by:</dfn> the deprecated <code>fcm build</code>.</p>
+
+      <p><dfn>versions at Met Office:</dfn> AIX-5.3: 3.80, RHEL-6.1: 3.81.</p>
+    </dd>
+  </dl>
+
+  <h2 id="installation">Installation</h2>
+
+  <p>FCM is distributed in the form of a compressed tar file. Un-pack the tar
+  file into an appropriate location on your system. Add the <samp>bin/</samp>
+  directory into your <var>PATH</var> environment variable. Once you have done
+  this you should now have full access to the FCM system, assuming that you
+  have met the requirements described in the previous section.</p>
+
+  <p>You should find the following contents in the distribution:</p>
+
+  <dl>
+    <dt>README</dt>
+
+    <dd>The README file contains the internal revision number of the
+    release.</dd>
+
+    <dt>COPYRIGHT.txt<br />
+    LICENSE.html</dt>
+
+    <dd>The FCM license and other copyright information.</dd>
+
+    <dt>bin/</dt>
+
+    <dd>Contains the <code>fcm</code> command and other utilities.</dd>
+
+    <dt>doc/</dt>
+
+    <dd>System documentation.</dd>
+
+    <dt>doc/release_notes/</dt>
+
+    <dd>Contains these release notes. It also contains the release notes for
+    all previous versions which may be useful if you have skipped any
+    versions.</dd>
+
+    <dt>doc/user_guide/</dt>
+
+    <dd>Contains the <a href="../user_guide/">FCM User Guide</a>.</dd>
+
+    <dt>doc/standards/</dt>
+
+    <dd>Contains the FCM <a href="../standards/perl_standard.html">Perl</a> and
+    <a href="../standards/fortran_standard.html">Fortran</a> coding standards.
+    The Perl standard describes the standards followed by the FCM code. The
+    Fortran standard contains some <a href=
+    "../standards/fortran_standard.html#fcm">specific advice</a> on the best
+    way of writing Fortran code for use with FCM as well as more general advice
+    on good practice.</dd>
+
+    <dt>doc/collaboration/</dt>
+
+    <dd>Contains the <a href="../collaboration/index.html">External
+    Distribution & Collaboration for FCM Projects</a> document which
+    discusses how projects configured under FCM can be distributed
+    externally.</dd>
+
+    <dt>etc/</dt>
+
+    <dd>Miscellaneous items, including the <samp>fcm/keyword.cfg.eg</samp>
+    file. If you wish to define keywords for your site you will need to create
+    the <samp>etc/fcm/keyword.cfg</samp> file. An example file,
+    <samp>fcm/keyword.cfg.eg</samp>, is provided which is a copy of the file
+    currently used at the Met Office. For further details please refer to the
+    section <a href="../user_guide/system_admin.html#fcm-keywords">FCM
+    keywords</a> in the System Admin chapter of the User Guide.</dd>
+
+    <dt>examples/</dt>
+
+    <dd>Contains various example scripts which you may find useful. Note that
+    these scripts are all specific to the Met Office and may contain hard coded
+    paths and email addresses. They are provided in the hope that you may find
+    them useful as examples for setting up similar scripts of your own.
+    However, they should only be used after careful review to adapt them to
+    your environment.</dd>
+
+    <dt>examples/etc/regular-update.eg</dt>
+
+    <dd>An example of how you might set up a cron job to make use of the
+    <samp><repos>.latest</samp> file (see
+    <code>examples/svn-hooks/post-commit-background</code>).</dd>
+
+    <dt>examples/lib/</dt>
+
+    <dd>Contains the <code>FCM::Admin::*</code> Perl library, which implements
+    the functionalities of the FCM admin utility commands.</dd>
+
+    <dt>examples/sbin/</dt>
+
+    <dd>Contains a selection of useful admin utility commands.</dd>
+
+    <dt>examples/svn-hooks/pre-commit</dt>
+
+    <dd>
+      This script restricts write-access to the repository by checking the
+      following:
+
+      <ul>
+        <li>It executes the Subversion utility <code>svnperms.py</code> if it,
+        and the associated <samp>svnperms.conf</samp> file, exist. This utility
+        checks whether the author of the current transaction has enough
+        permission to write to particular paths in the repository.</li>
+
+        <li>It checks the disk space required by the current transaction. It
+        fails the commit if it requires more than 10MB of disk space (or
+        whatever is specified in the
+        <code>pre-commit-size-threshold.conf</code> file.</li>
+      </ul>
+    </dd>
+
+    <dt>examples/svn-hooks/post-commit</dt>
+
+    <dd>This script runs <code>post-commit-background</code> in the
+    background.</dd>
+
+    <dt>examples/svn-hooks/post-commit-background</dt>
+
+    <dd>
+      This script runs in the background after each commit.
+
+      <ul>
+        <li>It updates a <samp><repos>.latest</samp> file with the latest
+        revision number.</li>
+
+        <li>It creates a dump of the new revision.</li>
+
+        <li>It calls <code>post-commit-background-custom</code> if it
+        exists.</li>
+      </ul>
+    </dd>
+
+    <dt>examples/svn-hooks/pre-revprop-change</dt>
+
+    <dd>This script only allows the modification of <var>svn:log</var>.</dd>
+
+    <dt>examples/svn-hooks/post-revprop-change</dt>
+
+    <dd>This script runs <code>post-revprop-change-background</code> in the
+    background.</dd>
+
+    <dt>examples/svn-hooks/post-revprop-change-background</dt>
+
+    <dd>This script invokes the <code>trac-admin</code> command to
+    <code>resync</code> the revision property cache stored in the corresponding
+    Trac environment. If a user modifies the log message of a changeset and
+    he/she is not the original author of the changeset, this script will e-mail
+    the original author. If the file
+    <code>post-revprop-change-background-cc.list</code> exits, the script will
+    also e-mail those in the list.</dd>
+
+    <dt>lib/</dt>
+
+    <dd>Contains the Perl library of FCM.</dd>
+
+    <dt>man/</dt>
+
+    <dd>Contains a basic manual page for <code>fcm</code>.</dd>
+
+    <dt>t/</dt>
+
+    <dd>Contains unit test for FCM.</dd>
+
+    <dt>test/</dt>
+
+    <dd>Contains regression tests for FCM.</dd>
+
+    <dt>test/test_include/</dt>
+
+    <dd>Contains simple test code to check how your chosen compilers handle
+    include files (see <a href="#issues">Known Issues</a>).</dd>
+
+    <dt>tutorial/</dt>
+
+    <dd>Contains the files necessary to set up a Subversion repository for the
+    FCM tutorial. This will allow you to follow the <a href=
+    "../user_guide/getting_started.html#tutorial">tutorial section</a> in the
+    User Guide. See <samp>tutorial/README</samp> on how to set it up.</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/release_notes/2-3-1.html b/doc/release_notes/2-3-1.html
new file mode 100644
index 0000000..8b6e92c
--- /dev/null
+++ b/doc/release_notes/2-3-1.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM 2-3-1 Release Notes</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM 2-3-1 Release Notes <small>2013-04-05</small></h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>These are the release notes for FCM 2-3-1. This release of FCM is distributed
+  under the terms of the <a href="http://www.gnu.org/licenses/gpl.html"
+  rel="license">GNU General Public License</a>. See <a href=
+  "../etc/fcm-terms-of-use.html">Terms of Use</a> for detail.</p>
+
+  <p>If FCM is not yet installed at your site, please refer to <a href=
+  "../installation/">FCM: Installation</a> for detail.</p>
+
+  <h2>Contents</h2>
+
+  <div id="fcm-content"></div>
+
+  <h2 id="changes-highlight">Highlight Changes</h2>
+
+  <p>This is a bug fix release.</p>
+
+  <h2 id="changes-minor">Minor Changes and Bug Fixes</h2>
+
+  <dl>
+    <dt>Configuration file syntax</dt>
+
+    <dd>If a setting accepts a space delimited list of locations, it is now
+    possible to use <var>$HERE</var> in front of any of the locations in the
+    list.</dd>
+
+    <dt>User configuration file location</dt>
+
+    <dd>The user configuration file should now be placed at
+    <var>$HOME/.metomi/fcm/</var>. The old location
+    <var>$HOME/.met-um/fcm/</var> is still supported but is deprecated.</dd>
+
+    <dt><code>fcm make</code></dt>
+
+    <dd>
+      <p>Reduced run time memory footprint.</p>
+
+      <p>Build: Create the <var>include/</var> sub-directory for <var>*.o</var>
+      targets of Fortran source files containing modules. Some compilers will
+      put the <var>*.mod</var> files directly into the <var>include/</var>
+      sub-directory.</p>
+
+      <p>Build: <var>ns-dep.o</var> property improved. Its value will now accept
+      namespaces at the source file level. It will also accept <kbd>/</kbd> as
+      the root namespace. (Previously, it required a pair of quotes
+      <kbd>""</kbd>.) A bad namespace in the value will now trigger a missing
+      dependency error.</p>
+
+      <p>Build: handle <var>.</var> and <var>..</var> in value of
+      <var>source</var> declaration.</p>
+    </dd>
+  </dl>
+
+  <h2 id="issues">Known Issues</h2>
+
+  <dl>
+    <dt>Build inheritance limitation: handling of include files</dt>
+
+    <dd>See the <a href="../user_guide/make.html#build.inherit">User Guide >
+    FCM Make > Build > Build Inheritance</a> for detail.</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/release_notes/2-3.html b/doc/release_notes/2-3.html
new file mode 100644
index 0000000..da5e9b2
--- /dev/null
+++ b/doc/release_notes/2-3.html
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM 2-3 Release Notes</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM 2-3 Release Notes <small>26 October 2012</small></h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>These are the release notes for FCM 2-3. This release of FCM is distributed
+  under the terms of the <a href="http://www.gnu.org/licenses/gpl.html"
+  rel="license">GNU General Public License</a>. See <a href=
+  "../etc/fcm-terms-of-use.html">Terms of Use</a> for detail.</p>
+
+  <p>If FCM is not yet installed at your site, please refer to <a href=
+  "../installation/">FCM: Installation</a> for detail.</p>
+
+  <h2>Contents</h2>
+
+  <div id="fcm-content"></div>
+
+  <h2 id="changes-highlight">Highlight Changes</h2>
+
+  <dl>
+    <dt><a href="http://www.gnu.org/licenses/gpl.html" rel="license">GNU General
+    Public License</a></dt>
+
+    <dd>
+      <p>From this release onwards, FCM will be released under the terms of the
+      GNU General Public License.</p>
+    </dd>
+
+    <dt><code>fcm make</code></dt>
+
+    <dd>
+      <p>Build system now recognises all text files starting with a
+      <code>#!</code> line as scripts.</p>
+
+      <p>Build system target selection logic has been improved to work more
+      efficiently, and to work correctly with a complex case of non-fatal cyclic
+      dependency.</p>
+    </dd>
+  </dl>
+
+  <h2 id="changes-minor">Minor Changes and Bug Fixes</h2>
+
+  <dl>
+    <dt><code>fcm branch-create</code></dt>
+
+    <dd>
+      <p>Fixed: no longer issue an incorrect error when creating a branch for a
+      project that resides at the root level of a repository.</p>
+    </dd>
+
+    <dt><code>fcm make</code></dt>
+
+    <dd>
+      <p>The behaviour of the <code>--new</code> option is modified to remove
+      only step directories defined in the <code>steps</code> declaration
+      instead of all known steps in the configuration.</p>
+
+      <p>Mirror: write to the mirror target a <samp>fcm-make.cfg.orig</samp> file
+      containing the configuration of the original <code>fcm make</code>
+      invocation.</p>
+    </dd>
+
+    <dt><code>fcm loc-layout</code></dt>
+
+    <dd>
+      <p>New command to parse a Subversion target and print the FCM layout
+      information of its URL.</p>
+
+    <dt><code>fcm mkpatch</code></dt>
+
+    <dd>
+      <p>Import scripts now uses <code>/bin/bash</code> instead of
+      <code>/bin/sh</code>.</p>
+    </dd>
+  </dl>
+
+  <h2 id="issues">Known Issues</h2>
+
+  <dl>
+    <dt>Build inheritance limitation: handling of include files</dt>
+
+    <dd>See the <a href="../user_guide/make.html#build.inherit">User Guide >
+    FCM Make > Build > Build Inheritance</a> for detail.</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/release_notes/index.html b/doc/release_notes/index.html
new file mode 100644
index 0000000..a1ec0b9
--- /dev/null
+++ b/doc/release_notes/index.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM Release Notes (Old)</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li><a href="../user_guide/">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <!--div class="fcm-page-content pull-right well well-small"></div-->
+    <h1>FCM Release Notes (Old)</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>Current release notes can be found at
+  <a href="https://github.com/metomi/fcm/blob/master/CHANGES.md">FCM Changes</a>
+  or the <var>CHANGES.md</var> file of your local installation of FCM.</p>
+
+  <p>Old release notes:</p>
+
+  <dl>
+    <dt><a href="2-3-1.html">2-3-1</a></dt>
+
+    <dd>Released on 2013-04-05. Bug fix release.</dd>
+
+    <dt><a href="2-3.html">2-3</a></dt>
+
+    <dd>Released on 2012-10-26. GPL release.</dd>
+
+    <dt><a href="2-2.html">2-2</a></dt>
+
+    <dd>Released on 2012-06-15. Subversion 1.6 upgrade.</dd>
+
+    <dt><a href="2-1.html">2-1</a></dt>
+
+    <dd>Released on 2011-07-22. Bug fix release.</dd>
+
+    <dt><a href="2-0.html">2-0</a></dt>
+
+    <dd>Released on 2011-03-11. New extract and build system.</dd>
+
+    <dt><a href="1-5.html">1-5</a></dt>
+
+    <dd>Released on 2010-01-22. New features and bug fixes.</dd>
+
+    <dt><a href="1-4.html">1-4</a></dt>
+
+    <dd>Released on 2009-02-12. Bug fix release.</dd>
+
+    <dt><a href="1-3.html">1-3</a></dt>
+
+    <dd>Released on 2008-01-30. Major update to the extract and build
+    systems.</dd>
+
+    <dt><a href="1-2.html">1-2</a></dt>
+
+    <dd>Released on 2007-03-22. Bug fix release.</dd>
+
+    <dt><a href="1-1.html">1-1</a></dt>
+
+    <dd>Released on 2006-11-06. First external release of FCM.</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/annex_bld_cfg.html b/doc/user_guide/annex_bld_cfg.html
new file mode 100644
index 0000000..9e15477
--- /dev/null
+++ b/doc/user_guide/annex_bld_cfg.html
@@ -0,0 +1,931 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: Annex: Declarations in FCM 1 build configuration
+  file</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: Annex: Declarations in FCM 1 build configuration
+    file</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p><em>The FCM 1 build system is deprecated. The documentation for the current
+  build system can be found at <a href="make.html">FCM Make</a>.</em></p>
+
+  <p>The following is a list of supported declarations for the configuration
+  file used by the FCM build system. Unless otherwise stated, the fields in all
+  declaration labels are not case sensitive. Build declarations can be made
+  either in a build configuration file or in an extract configuration file. In
+  the latter case, the prefix <code>BLD::</code> must be added at the beginning
+  of each label to inform the extract system that the declaration is a build
+  system declaration. (In a build configuration file, the prefix
+  <code>BLD::</code> is optional.)</p>
+
+  <dl>
+    <dt>CFG::TYPE</dt>
+
+    <dd>
+      <p>The configuration file type, the value should always be
+      <samp>bld</samp> for a build configuration file. This declaration is
+      compulsory for all build configuration files. (This declaration is
+      automatic when the extract system creates a build configuration
+      file.)</p>
+
+      <p>Example:</p>
+      <pre>
+cfg::type  bld
+</pre>
+    </dd>
+
+    <dt>CFG::VERSION</dt>
+
+    <dd>
+      <p>The file format version, currently <samp>1.0</samp> - a version is
+      included so that we shall be able to read the configuration file
+      correctly should we decide to change its format in the future. (This
+      declaration is automatic when the extract system creates a build
+      configuration file.)</p>
+
+      <p>Example:</p>
+      <pre>
+cfg::version  1.0
+</pre>
+    </dd>
+
+    <dt>%<name></dt>
+
+    <dd>
+      <p><code>%<name></code> declares an internal variable
+      <var><name></var> that can later be re-used.</p>
+
+      <p>Example:</p>
+      <pre>
+%my_variable  -foo -bar
+tool::fflags  %my_variable
+tool::cflags  %my_variable
+</pre>
+    </dd>
+
+    <dt>INC</dt>
+
+    <dd>
+      <p>This declares the name of a file containing build configuration. The
+      lines in the declared file will be included inline to the current
+      configuration file.</p>
+
+      <p>Example:</p>
+      <pre>
+inc  ~frva/var_stable_22.0/cfg/bld.cfg
+# ... and then your changes ...
+</pre>
+    </dd>
+
+    <dt>
+      DEST[::ROOTDIR]<br />
+      <del>DIR::ROOT</del>
+    </dt>
+
+    <dd>
+      <p>The destination of the build. It must be declared for each build.
+      (This declaration is automatic when the extract system creates a build
+      configuration file. The value is normally the path of the extract
+      destination.)</p>
+
+      <p>Example:</p>
+      <pre>
+dest  $HOME/my_build
+</pre>
+    </dd>
+
+    <dt>USE</dt>
+
+    <dd>
+      <p>This inherits settings from a previous build. The value must be must
+      be either the configuration file or the root directory of a successful
+      build. Output of the build, the tools, the exclude dependency
+      declarations, the file type registers declarations are automatically
+      inherited from the declared build. Source directories and build targets
+      declarations may be inherited depending on the INHERIT declarations. (If
+      you have a USE declaration in an extract, the resulting build
+      configuration file will contain an automatic USE declaration, which
+      expects an inherited build at the extract destination.)</p>
+
+      <p>Example:</p>
+      <pre>
+# Use VAR build 22.0
+USE  ~frva/var_22.0
+</pre>
+    </dd>
+
+    <dt>INHERIT::<name>[::<pcks>]</dt>
+
+    <dd>
+      <p>This declares whether build targets (<name> =
+      <samp>target</samp>) or source directories (<name> =
+      <samp>src</samp>) can be inherited using the USE statement. By default,
+      source directories are inherited, while build targets are not. Use the
+      value <samp>true</samp> to switch on inheritance, or <samp>false</samp>
+      to switch off. For source directories declarations, the name of a
+      sub-package <pcks> can be specified. If a sub-package pcks is
+      specified, the declaration applies only to the files and directories
+      under the sub-package. Otherwise, the declaration applies globally.</p>
+
+      <p>Example:</p>
+      <pre>
+inherit::target   true
+inherit::src      false
+</pre>
+    </dd>
+
+    <dt>SRC[::<pcks>]</dt>
+
+    <dd>
+      <p>This declares a source file/directory. You must specify the
+      sub-package <pcks> if the source file/directory is located outside
+      of the <samp>src/</samp> sub-directory of the build destination or if you
+      want to redefine the sub-package name of the source file/directory. The
+      name of the sub-package <pcks> must be unique. Package names are
+      delimited by double colons <code>::</code> or double underscores
+      <code>__</code>. If you declare a relative path, it is assumed to be
+      relative to the <samp>src/</samp> sub-directory of the build destination.
+      (This declaration is automatic when the extract system creates the build
+      configuration file. The list of declared source directories will be the
+      list of extracted source directories.)</p>
+
+      <p>Example:</p>
+      <pre>
+src::var/code/VarMod_PF  $HOME/var/src/code/VarMod_PF
+</pre>
+    </dd>
+
+    <dt>SEARCH_SRC</dt>
+
+    <dd>
+      <p>This declares a flag to determine whether the build system should
+      search the <samp>src/</samp> sub-directory of the build root for a list
+      of source files. The automatic search is useful if the build system is
+      invoked standalone and the <samp>src/</samp> sub-directory contains the
+      full source tree of the build. The default is to search
+      (<samp>true</samp>). Set the flag to <samp>false</samp> to switch off the
+      behaviour. (When the extract system creates a build configuration file,
+      it declares all source files. Searching of the source sub-directory
+      should not be required, and so this flag is automatically set to
+      <samp>false</samp>.)</p>
+
+      <p>Example:</p>
+      <pre>
+search_src  false
+</pre>
+    </dd>
+
+    <dt>TARGET</dt>
+
+    <dd>
+      <p>Specify the targets for the build. Multiple targets can be declared in
+      one or more declarations. These targets become the dependencies of the
+      default <samp>all</samp> target in the <em>Makefile</em>. It is worth
+      noting that <code>TARGET</code> declarations are cumulative. A later
+      declaration does not override an earlier one - it simply adds more targets
+      to the list.</p>
+
+      <p>Example:</p>
+      <pre>
+target  VarScr_AnalysePF VarScr_CovAccStats
+target  VarScr_CovPFstats
+</pre>
+    </dd>
+
+    <dt>TOOL::<label>[::<pcks>]</dt>
+
+    <dd>
+      <p>This declaration is used to specify a build tool such as the Fortran
+      compiler or its flags. The <label> determines the tool you are
+      declaring. A TOOL declaration normally applies globally. However, where
+      it is sensible to do so, a sub-package <pcks> can be specified. In
+      which case, the declaration applies only to the files and directories
+      under the sub-package. A list of <label> fields is available
+      <a href="#tools-list">later in this annex</a>.</p>
+
+      <p>Example:</p>
+      <pre>
+tool::fc      sxmpif90
+tool::fflags  -Chopt -Pstack
+
+tool::cc      sxmpic++
+tool::cflags  -O nomsg -pvctl nomsg
+
+tool::ar      sxar
+</pre>
+    </dd>
+
+    <dt>EXE_DEP[::<target>]</dt>
+
+    <dd>
+      <p>This declares an extra dependency for either all main program targets
+      or only <target> if it is specified. If <target> is
+      specified, it must be the name of a main program target. The value of the
+      declaration is a space delimited list. Each item in the list can either
+      be a valid name of a sub-package or the name of a valid object target. If
+      a sub-package name is used, the <em>make</em> rule for the main program
+      will be set to depend on all (non-program) object files within the
+      sub-package.</p>
+
+      <p>Example:</p>
+      <pre>
+# Only foo.exe to depend on the package foo::bar and egg.o
+exe_dep::foo.exe  foo::bar egg.o
+
+# All executables to depend on the package foo::bar and egg.o
+exe_dep  foo::bar egg.o
+
+# Only foo.exe to depend on all objects
+exe_dep::foo.exe
+
+# All executables to depend on all objects
+exe_dep
+</pre>
+    </dd>
+
+    <dt>BLOCKDATA[::<target>]</dt>
+
+    <dd>
+      <p>This declares a BLOCKDATA dependency for either all main program
+      targets or only <target> if it is specified. If <target> is
+      specified, it must be the name of a main program target. The value of the
+      declaration is a space delimited list. Each item in the list must be the
+      name of a valid object target containing a Fortran BLOCKDATA program
+      unit.</p>
+
+      <p>Example:</p>
+      <pre>
+# Only foo.exe to depend on blkdata.o
+blockdata::foo.exe  blkdata.o
+
+# All executables to depend on fbd.o
+blockdata  fbd.o
+</pre>
+    </dd>
+
+    <dt>EXCL_DEP[::<pcks>]</dt>
+
+    <dd>
+      <p>This declaration is used to specify whether a particular dependency
+      should be ignored during the automatic dependency scan. If a sub-package
+      <pcks> is specified, the declaration applies only to the files and
+      directories under the sub-package. Otherwise, the declaration applies
+      globally. The value of this declaration must contain one or two fields
+      (separated by the double colon <code>::</code>). The first field denotes
+      the dependency type, and the second field is the dependency target. If
+      the second field is specified, it will only exclude the dependency to the
+      specified target. Otherwise, it will exclude all dependency to the
+      specified type. The following dependency types are supported:</p>
+
+      <dl id="dependency-types">
+        <dt>USE</dt>
+
+        <dd>The dependency target is a Fortran module.</dd>
+
+        <dt>INTERFACE</dt>
+
+        <dd>The dependency target is a Fortran 9X interface block file.</dd>
+
+        <dt>INC</dt>
+
+        <dd>The dependency target is a Fortran INCLUDE file.</dd>
+
+        <dt>H</dt>
+
+        <dd>The dependency target is a pre-processor #include header file.</dd>
+
+        <dt>OBJ</dt>
+
+        <dd>The dependency target is a compiled binary object file.</dd>
+
+        <dt>EXE</dt>
+
+        <dd>The dependency target is an executable binary or script.</dd>
+      </dl>
+
+      <p>N.B. The following dependency targets are in the default list of
+      excluded dependencies:</p>
+
+      <dl>
+        <dt>Intrinsic Fortran modules:</dt>
+
+        <dd>
+          <ul>
+            <li>USE::ISO_C_BINDING</li>
+
+            <li>USE::IEEE_EXCEPTIONS</li>
+
+            <li>USE::IEEE_ARITHMETIC</li>
+
+            <li>USE::IEEE_FEATURES</li>
+          </ul>
+        </dd>
+
+        <dt>Intrinsic Fortran subroutines:</dt>
+
+        <dd>
+          <ul>
+            <li>OBJ::CPU_TIME</li>
+
+            <li>OBJ::GET_COMMAND</li>
+
+            <li>OBJ::GET_COMMAND_ARGUMENT</li>
+
+            <li>OBJ::GET_ENVIRONMENT_VARIABLE</li>
+
+            <li>OBJ::MOVE_ALLOC</li>
+
+            <li>OBJ::MVBITS</li>
+
+            <li>OBJ::RANDOM_NUMBER</li>
+
+            <li>OBJ::RANDOM_SEED</li>
+
+            <li>OBJ::SYSTEM_CLOCK</li>
+          </ul>
+        </dd>
+
+        <dt>Dummy declarations:</dt>
+
+        <dd>
+          <ul>
+            <li>OBJ::NONE</li>
+
+            <li>EXE::NONE</li>
+          </ul>
+        </dd>
+      </dl>
+
+      <p>Example:</p>
+      <pre>
+excl_dep  USE::YourFortranMod
+excl_dep  INTERFACE::HerFortran.interface
+excl_dep  INC::HisFortranInc.inc
+excl_dep  H::TheirHeader.h
+excl_dep  OBJ
+excl_dep  EXE
+</pre>
+    </dd>
+
+    <dt>DEP::<pcks></dt>
+
+    <dd>
+      <p>This declaration is used to specify a dependency for a source file in
+      <pcks>. The value of this declaration must contain two fields
+      (separated by the double colon <code>::</code>). The first field denotes
+      the dependency type, and the second field is the dependency target. The
+      dependency types are the same as those for EXCL_DEP described <a href=
+      "#dependency-types">above</a>.</p>
+
+      <p>Example:</p>
+      <pre>
+dep::foo/bar.f  USE::your_fortran_mod
+dep::foo/bar.f  INTERFACE::her_fortran.interface
+dep::foo/bar.f  INC::his_fortran_inc.inc
+dep::foo/bar.f  H::their_header.h
+dep::foo/bar.f  OBJ::its_object.o
+dep::foo/egg    EXE::ham
+</pre>
+    </dd>
+
+    <dt>NO_DEP::<pcks></dt>
+
+    <dd>
+      <p>This declaration is used to switch off/on dependency checking. If
+      <pcks> is specified in the label, the declaration applies to the
+      specified sub-package only.</p>
+
+      <p>Example:</p>
+      <pre>
+# Switch on dependency checking only for "foo"
+no_dep      true
+no_dep::foo false
+</pre>
+    </dd>
+
+    <dt>EXE_NAME::<name></dt>
+
+    <dd>
+      <p>This renames the executable target of a main program source file
+      <name> to the specified value.</p>
+
+      <p>Example:</p>
+      <pre>
+# Rename executable target of foo.f90 from "foo.exe" to "bar"
+exe_name::foo  bar
+</pre>
+    </dd>
+
+    <dt>LIB[::<pcks>]</dt>
+
+    <dd>
+      <p>This declares the name of a library archive target. If <pcks> is
+      specified in the label, the declaration applies to the library archive
+      target for that sub-package only. If set, the name of the library archive
+      target will be named <samp>lib<value>.a</samp>, where <value>
+      is the value of the declaration. If not specified, the default is to name
+      the global library <samp>libfcm_default.a</samp>. For a library archive
+      of a sub-package, the default is to name its library after the name of
+      the sub-package.</p>
+
+      <p>Example:</p>
+      <pre>
+# Rename the top level library "libfoo.a"
+lib  foo
+
+# Rename the library for the sub-package "egg::ham"
+# from "libegg__ham.a" to "libegg-ham.a"
+lib::egg/ham  egg-ham
+</pre>
+    </dd>
+
+    <dt>PP[::<pcks>]</dt>
+
+    <dd>
+      <p>This declares whether a pre-processing stage is required. To switch on
+      pre-processing, set the value to <samp>true</samp>. If <pcks> is
+      specified in the label, the flag applies to the files within that
+      sub-package only. Otherwise, the flag affects source directories in all
+      packages. The pre-processing stage is useful if the pre-processor changes
+      the dependency and/or the argument list of the source files. The default
+      behaviour is skip the pre-processing stage for all source.</p>
+
+      <p>Example:</p>
+      <pre>
+pp::gen true  # switch on pre-processing for "gen" only
+pp      true  # switch on pre-processing globally
+</pre>
+    </dd>
+
+    <dt>SRC_TYPE::<pcks></dt>
+
+    <dd>
+      <p>This declaration is used to (re-)register the file type of the
+      sub-package <pcks> to associate with different file types. The
+      value of the declaration is a list of type flags delimited by the double
+      colon <code>::</code>. Each type flag is used internally to describe the
+      nature of the file. For example, a Fortran free source form containing a
+      main program is registered as
+      <code>FORTRAN::FORTRAN9X::SOURCE::PROGRAM</code>. A list of type flags is
+      available <a href="#infile-ext-types">later in this annex</a>.</p>
+
+      <p>Example:</p>
+      <pre>
+src_type::foo/bar.f  FORTRAN::FORTRAN9X::SOURCE::PROGRAM
+</pre>
+    </dd>
+
+    <dt>INFILE_EXT::<ext></dt>
+
+    <dd>
+      <p>This declaration is used to re-register particular file name
+      extensions <ext> to associate with different file types. The value
+      of the declaration has a similar format to that of SRC_TYPE declaration
+      described above. A list of type flags is available <a href=
+      "#infile-ext-types">later in this annex</a>.</p>
+
+      <p>Example:</p>
+      <pre>
+infile_ext::h90  CPP::INCLUDE
+infile_ext::inc  FORTRAN::FORTRAN9X::INCLUDE
+</pre>
+    </dd>
+
+    <dt>OUTFILE_EXT::<type></dt>
+
+    <dd>
+      <p>This declaration is used to re-register the output file extension for
+      a particular <type> of output files. The value must be a valid file
+      extension. The following is a list of output file types in-use by the
+      build system:</p>
+
+      <dl id="outfile-ext-types">
+        <dt>OBJ</dt>
+
+        <dd>compiled object files<br />
+        [default = .o]</dd>
+
+        <dt>MOD</dt>
+
+        <dd>compiled Fortran module information files<br />
+        [default = .mod]</dd>
+
+        <dt>EXE</dt>
+
+        <dd>binary executables<br />
+        [default = .exe]</dd>
+
+        <dt>DONE</dt>
+
+        <dd><em>done</em> files for compiled source<br />
+        [default = .done]</dd>
+
+        <dt>IDONE</dt>
+
+        <dd><em>done</em> files for included source<br />
+        [default = .idone]</dd>
+
+        <dt>FLAGS</dt>
+
+        <dd><em>flags</em> files, compiler flags config<br />
+        [default = .flags]</dd>
+
+        <dt>INTERFACE</dt>
+
+        <dd>interface files for F9X standalone subroutines/functions<br />
+        [default = .interface]</dd>
+
+        <dt>LIB</dt>
+
+        <dd>archive object library<br />
+        [default = .a]</dd>
+
+        <dt>TAR</dt>
+
+        <dd>TAR archive<br />
+        [default = .tar]</dd>
+      </dl>
+
+      <p>Example:</p>
+      <pre>
+# Output F9X interface files will now have ".foo" extension
+outfile_ext::interface  .foo
+</pre>
+    </dd>
+  </dl>
+
+  <p id="tools-list">The following is a list of <label> fields that can
+  be used with a <code>TOOL</code> declaration. Those marked with an asterisk
+  (*) accept declarations at sub-package levels.</p>
+
+  <dl>
+    <dt>FC</dt>
+
+    <dd>The Fortran compiler.<br />
+    [default = <samp>f90</samp>]</dd>
+
+    <dt>FFLAGS *</dt>
+
+    <dd>Options used by the Fortran compiler.<br />
+    [default = ""]</dd>
+
+    <dt>FC_COMPILE</dt>
+
+    <dd>The option used by the Fortran compiler to suppress the linking
+    stage.<br />
+    [default = <samp>-c</samp>]</dd>
+
+    <dt>FC_INCLUDE</dt>
+
+    <dd>The option used by the Fortran compiler to specify the include search
+    path.<br />
+    [default = <samp>-I</samp>]</dd>
+
+    <dt>FC_MODSEARCH</dt>
+
+    <dd>The option used by the Fortran compiler to specify the search
+    path for the compiled module definition files. This option is often
+    unnecessary as it is normally covered by the include search path.<br />
+    [default = ""]</dd>
+
+    <dt>FC_DEFINE</dt>
+
+    <dd>The option used by the Fortran compiler to define a pre-processor
+    definition macro.<br />
+    [default = <samp>-D</samp>]</dd>
+
+    <dt>FC_OUTPUT</dt>
+
+    <dd>The option used by the Fortran compiler to specify the output file
+    name.<br />
+    [default = <samp>-o</samp>]</dd>
+
+    <dt>CC</dt>
+
+    <dd>The C compiler.<br />
+    [default = <samp>cc</samp>]</dd>
+
+    <dt>CFLAGS *</dt>
+
+    <dd>Options used by the C compiler.<br />
+    [default = ""]</dd>
+
+    <dt>CC_COMPILE</dt>
+
+    <dd>The option used by the C compiler to suppress the linking stage.<br />
+    [default = <samp>-c</samp>]</dd>
+
+    <dt>CC_INCLUDE</dt>
+
+    <dd>The option used by the C compiler to specify the include search
+    path.<br />
+    [default = <samp>-I</samp>]</dd>
+
+    <dt>CC_DEFINE</dt>
+
+    <dd>The option used by the C compiler to define a pre-processor definition
+    macro.<br />
+    [default = <samp>-D</samp>]</dd>
+
+    <dt>CC_OUTPUT</dt>
+
+    <dd>The option used by the C compiler to specify the output file
+    name.<br />
+    [default = <samp>-o</samp>]</dd>
+
+    <dt>LD *</dt>
+
+    <dd>Name of the linker or loader for linking object files into an
+    executable. If not set, use the compiler of the source file containing the
+    main program.<br />
+    [default = ""]</dd>
+
+    <dt>LDFLAGS *</dt>
+
+    <dd>The flags used by the linker or loader.<br />
+    [default = ""]</dd>
+
+    <dt>LD_OUTPUT</dt>
+
+    <dd>The option used by the linker or loader for the output file name (other
+    than the default <samp>a.out</samp>).<br />
+    [default = <samp>-o</samp>]</dd>
+
+    <dt>LD_LIBSEARCH</dt>
+
+    <dd>The option used by the linker or loader for specifying the search path
+    for link libraries.<br />
+    [default = <samp>-L</samp>]</dd>
+
+    <dt>LD_LIBLINK</dt>
+
+    <dd>The option used by the linker or loader command for linking with a
+    library.<br />
+    [default = <samp>-l</samp>]</dd>
+
+    <dt>AR</dt>
+
+    <dd>The archive command.<br />
+    [default = <samp>ar</samp>]</dd>
+
+    <dt>ARFLAGS</dt>
+
+    <dd>The options used for the archive command to create a library.<br />
+    [default = <samp>rs</samp>]</dd>
+
+    <dt>FPP</dt>
+
+    <dd>The Fortran pre-processor command.<br />
+    [default = <samp>cpp</samp>]</dd>
+
+    <dt>FPPKEYS *</dt>
+
+    <dd>The Fortran pre-processor will pre-define each word in this setting as
+    a macro.<br />
+    [default = ""]</dd>
+
+    <dt>FPPFLAGS *</dt>
+
+    <dd>The options used by the Fortran pre-processor.<br />
+    [default = <samp>-P -traditional</samp>]</dd>
+
+    <dt>FPP_DEFINE</dt>
+
+    <dd>The option used by the Fortran pre-processor to define a macro.<br />
+    [default = <samp>-D</samp>]</dd>
+
+    <dt>FPP_INCLUDE</dt>
+
+    <dd>The option used by the Fortran pre-processor to specify the include
+    search path.<br />
+    [default = <samp>-I</samp>]</dd>
+
+    <dt>CPP</dt>
+
+    <dd>The C pre-processor command.<br />
+    [default = <samp>cpp</samp>]</dd>
+
+    <dt>CPPKEYS *</dt>
+
+    <dd>The C pre-processor will pre-define each word in this setting as a
+    macro.<br />
+    [default = ""]</dd>
+
+    <dt>CPPFLAGS *</dt>
+
+    <dd>The options used by the C pre-processor.<br />
+    [default = <samp>-C</samp>]</dd>
+
+    <dt>CPP_DEFINE</dt>
+
+    <dd>The option used by the C pre-processor to define a macro.<br />
+    [default = <samp>-D</samp>]</dd>
+
+    <dt>CPP_INCLUDE</dt>
+
+    <dd>The option used by the C pre-processor to specify the include search
+    path.<br />
+    [default = <samp>-I</samp>]</dd>
+
+    <dt>MAKE</dt>
+
+    <dd>The <code>make</code> command.<br />
+    [default = <samp>make</samp>]</dd>
+
+    <dt>MAKEFLAGS</dt>
+
+    <dd>The options used by the <code>make</code> command.<br />
+    [default = ""]</dd>
+
+    <dt>MAKE_SILENT</dt>
+
+    <dd>The option used by the <code>make</code> command to specify silent
+    operation.<br />
+    [default = <samp>-s</samp>]</dd>
+
+    <dt>MAKE_JOB</dt>
+
+    <dd>The option used by the <code>make</code> command to specify the number
+    jobs to run simultaneously.<br />
+    [default = <samp>-j</samp>]</dd>
+
+    <dt>GENINTERFACE *</dt>
+
+    <dd>The command/method to extract the calling interfaces of top level
+    subroutines and functions in a Fortran 9X source. Supported values are
+    <samp>f90aib</samp> and <samp>none</samp> (to switch off interface
+    generation). If not specified, the system will use its own internal logic.
+    <br />
+    [default = (not specified)]</dd>
+
+    <dt>INTERFACE *</dt>
+
+    <dd>Generate Fortran 9X interface files with root names according to either
+    the root name of the source <samp>file</samp> or the name of the
+    <samp>program</samp> unit.<br />
+    [default = <samp>file</samp>]</dd>
+  </dl>
+
+  <p id="infile-ext-types">The following is a list of type flags that are
+  currently in-use (or <dfn>* reserved</dfn>) by the build system for TYPE and
+  INFILE_EXT declarations:</p>
+
+  <dl>
+    <dt>SOURCE</dt>
+
+    <dd>a source file containing program code of a supported language
+    (currently Fortran, FPP, C and CPP).</dd>
+
+    <dt>INCLUDE</dt>
+
+    <dd>an include file containing program code of a supported language
+    (currently Fortran, FPP, C and CPP).</dd>
+
+    <dt>FORTRAN</dt>
+
+    <dd>a file containing Fortran code.</dd>
+
+    <dt>FORTRAN9X</dt>
+
+    <dd>a file containing the Fortran free source form. This word must be used
+    in conjunction with the word <code>FORTRAN</code>.</dd>
+
+    <dt>FPP</dt>
+
+    <dd>a file containing Fortran code requiring pre-processing.</dd>
+
+    <dt>FPP9X</dt>
+
+    <dd>a file containing Fortran free source form requiring pre-processing.
+    This word must be used in conjunction with the word <code>FPP</code>.</dd>
+
+    <dt>C</dt>
+
+    <dd>a file containing C code.</dd>
+
+    <dt>CPP</dt>
+
+    <dd>a file containing CPP include header.</dd>
+
+    <dt>INTERFACE</dt>
+
+    <dd>a file containing a Fortran 9X interface block.</dd>
+
+    <dt>PROGRAM</dt>
+
+    <dd>a file containing a main program.</dd>
+
+    <dt>MODULE</dt>
+
+    <dd>a file containing a Fortran 9X module.</dd>
+
+    <dt>BINARY</dt>
+
+    <dd>a binary file.</dd>
+
+    <dt>EXE</dt>
+
+    <dd>an executable file. This word must be used in conjunction with the word
+    <code>BINARY</code>.</dd>
+
+    <dt>LIB</dt>
+
+    <dd>an archive library. This word must be used in conjunction with the word
+    <code>BINARY</code>.</dd>
+
+    <dt>SCRIPT</dt>
+
+    <dd>a file containing source code of a scripting language.</dd>
+
+    <dt>PVWAVE</dt>
+
+    <dd>a file containing executable PVWAVE scripts. This word must be used in
+    conjunction with the word <code>SCRIPT</code>.</dd>
+
+    <dt>SQL</dt>
+
+    <dd>a file containing SQL scripts. This word must be used in conjunction
+    with the word <code>SCRIPT</code>.</dd>
+
+    <dt>GENLIST</dt>
+
+    <dd>a GEN List file.</dd>
+
+    <dt>OBJ</dt>
+
+    <dd><dfn>(* reserved)</dfn> an object file. This word must be used in
+    conjunction with the word <code>BINARY</code>.</dd>
+
+    <dt>SHELL</dt>
+
+    <dd><dfn>(* reserved)</dfn> a file containing executable shell scripts.
+    This word must be used in conjunction with the word
+    <code>SCRIPT</code>.</dd>
+
+    <dt>PERL</dt>
+
+    <dd><dfn>(* reserved)</dfn> a file containing executable Perl scripts. This
+    word must be used in conjunction with the word <code>SCRIPT</code>.</dd>
+
+    <dt>PYTHON</dt>
+
+    <dd><dfn>(* reserved)</dfn> a file containing executable Python scripts.
+    This word must be used in conjunction with the word
+    <code>SCRIPT</code>.</dd>
+
+    <dt>TCL</dt>
+
+    <dd><dfn>(* reserved)</dfn> a file containing executable TCL scripts. This
+    word must be used in conjunction with the word <code>SCRIPT</code>.</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/annex_cfg.html b/doc/user_guide/annex_cfg.html
new file mode 100644
index 0000000..8f6fb9b
--- /dev/null
+++ b/doc/user_guide/annex_cfg.html
@@ -0,0 +1,1433 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: Annex: FCM Configuration File</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: Annex: FCM Configuration File</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <h2 id="syntax">Syntax</h2>
+
+  <h3 id="syntax.comment">Syntax: comment</h3>
+
+  <p>An empty line, a line with only space characters and a line beginning with
+  a <kbd>#</kbd> character is a comment line. For example:</p>
+  <pre>
+# This is a comment.
+    # This is also a comment.
+</pre>
+
+  <p>Also, if a line contains a space character followed by a <kbd>#</kbd>
+  character then this, and any following characters, are treated as an end of
+  line comment and are ignored.</p>
+
+  <h3 id="syntax.declaration">Syntax: declaration</h3>
+
+  <p>The general syntax of a declaration consists of a label, a set of
+  modifiers, a list of name-spaces, and a value:</p>
+  <pre>
+label{mod:mod-value}[ns] = value
+label{mod:mod-value} = value
+label[ns] = value
+label = value
+label = value\
+    value # "value    value"
+label = value\
+    \value # "valuevalue"
+</pre>
+
+  <p>The label is compulsory. It defines the declaration, and may have one or
+  more characters in the alpha-numeric, the underscore, the minus sign and the
+  full stop. The label cannot be substituted with a variable.</p>
+
+  <p>The modifier is an optional part of the syntax, but may be a compulsory
+  part of some declarations. The modifiers are embedded between a pair of curly
+  braces and must come after the label, but before the name-spaces and the
+  equal sign. It should be a comma-separated list of colon-separated key-value
+  pairs, (i.e. <kbd>{key1: value1, key2: value2, ...}</kbd>). The value in each
+  modifier is optional - if a key is set without a value, its value is assumed
+  to be 1. The contents of the modifiers can be substituted using a
+  variable.</p>
+
+  <p>The name-space is an optional part of the syntax, but may be a compulsory
+  part of some declarations. The name-spaces are embedded between a pair of
+  square braces and must come after the label and/or modifiers but before the
+  equal sign. It should be a space-separated list of names. If a name contains
+  space, the space character can be escaped using a backslash character or the
+  name can be quoted. The contents of the name-spaces can be substituted using
+  a variable.</p>
+
+  <p>The first non-space character after the equal sign begins the value of the
+  declaration. If there is nothing after the equal sign, the value is an empty
+  string. The contents of the value can be substituted using a variable.
+  Trailing space characters are ignored. The end of line can be escaped using a
+  backslash, and the value of the declaration will continue from the next
+  non-comment line. If the first non-space character on that line is a
+  backslash then the line contents up to and including that backslash are
+  ignored.</p>
+
+  <h3 id="syntax.variable">Syntax: variable</h3>
+
+  <p>A variable is used to substitute commonly used values in a declaration. It
+  can refer to an environment variable or can be defined locally using the
+  syntax:</p>
+  <pre>
+$var = value    # Sets "var" variable to "value"
+$var{?} = value # Sets "var" variable to "value" only if "var" is not defined
+# E.g.:
+$projects = foo bar baz
+$long_path = src/path/is/very/very/very/long
+$UMDIR{?} = /projects/um1
+# time passes...
+extract.ns = $projects
+extract.path-excl[$projects egg ham] = doc man ${long_path}_longer
+build.prop{cc.defs} = foo=\$foo # escape substitution
+</pre>
+
+  <p>The name of a variable must begin with an alphabet or an underscore and
+  followed by 0 or more alpha-numeric or underscore characters, (i.e. it must
+  match this regular expression: <var>^[A-Za-z_][A-Za-z0-9_]*$</var>).</p>
+
+  <p>Locally defined variables only apply in the context of the configuration
+  file and are not passed down, for instance, to the compiler. If there is an
+  environment variable with the same identifier, its value is normally
+  overridden (within the context of the configuration file) by any local
+  setting. However, if a <kbd>?</kbd> modifier is given, it assigns the value
+  to the variable only if the variable is not already defined (either as an
+  environment variable or locally). Substitution can be escaped using a
+  backslash character in front of the dollar sign. Any attempt to reference an
+  undefined variable will trigger an exception.</p>
+
+  <p>Note: <var>$HERE</var> is a reserved variable to indicate the parent
+  directory of the current configuration file. (The system ignores the $HERE
+  environment variable, if there is one.) Any attempt to assign a value to
+  <var>$HERE</var> will trigger an exception.</p>
+
+  <h3 id="syntax.include">Syntax: include</h3>
+
+  <p>An <code>include</code> declaration specifies the logical locations of a
+  list of configuration files, where contents are to be included inline:</p>
+  <pre>
+include{type:type} = location ...
+# E.g.:
+include = fcm:foo/path/to/config/file
+include = svn://server/path/to/config/file@1234
+include{type:svn} = http://server/path/to/config/file
+include = /path/to/config/file $HERE/another-config-file
+include = $HERE/another-config-file
+include = ~/path/to/config/file ~fred/path/to/config/file
+</pre>
+
+  <p>If the value of an <code>include</code> declaration is a relative path,
+  the system will search the directory containing the current configuration
+  file for the include file. More include search paths can be specified using
+  the <code>include-path</code> declaration. E.g.:</p>
+
+  <pre>
+# Define or replace include search paths
+include-path=/path/to/some/cfg /path/to/more/cfg
+# Append to include search paths
+include-path{+}=host2:/path/to/cfg
+include-path{type:svn,+}=https://host1/path/to/cfg
+# ... Include files can now be relative paths
+include=foo.cfg bar.cfg
+</pre>
+
+  <p>Some commands, e.g. <code>fcm make</code>, accept one or more
+  <code>--config-file-path=PATH</code> command line option, which can be used
+  to pre-define the include search paths when they read their configuration
+  files.</p>
+
+  <p>Currently, the location can be a path in the file system, a Subversion URL,
+  a FCM keyword pointing to a Subversion URL, or a location in the file system
+  of a remote host accessible via passphrase-less SSH. The system will attempt
+  to make an intelligent guess of the location type. To allow for future
+  expansion, both the <code>include</code> and the <code>include-path</code>
+  declarations accept a <var>type:TYPE</var> modifier (where TYPE can currently
+  be <samp>fs</samp> for a file system path or <samp>svn</samp> for a Subversion
+  location) to allow the location type to be defined where it is ambiguous.</p>
+
+  <h2 id="keyword">FCM Keyword Configuration</h2>
+
+  <p>The keyword configuration files are mainly used to define FCM location
+  keywords and related settings. The <code>fcm</code> command searches for
+  keyword configuration files from the following locations:</p>
+
+  <ol>
+    <li><dfn>(Deprecated)</dfn> <samp>$HOME/.fcm</samp> - expects FCM 1
+    configuration file format. See <a href="annex_fcm_cfg.html">Annex:
+    Declarations in FCM 1 central/user configuration file</a> for detail.</li>
+
+    <li><samp>$FCM/etc/fcm/keyword.cfg</samp> where <var>$FCM/bin/</var> is the
+    path at which <code>fcm</code> is installed.</li>
+
+    <li><samp>$HOME/.metomi/fcm/keyword.cfg</samp>.</li>
+  </ol>
+
+  <p>The following are declarations recognised by the keyword configuration
+  files:</p>
+
+  <dl>
+    <dt id="keyword.location">location</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Associates a namespace with a location, and
+      allows the use of <samp>fcm:namespace</samp> as a substitute of the
+      location.</p>
+
+      <p><dfn>modifier</dfn>: <var>primary</var>: Optional. If specified, the
+      location is a primary location, i.e. the system will create the
+      <samp>_tr</samp>, <samp>_br</samp>, <samp>_tg</samp>, <samp>-tr</samp>,
+      <samp>-br</samp> and <samp>-tg</samp> keywords for this location.</p>
+
+      <p><dfn>modifier</dfn>: <var>type</var>: Optional. Specifies the type of
+      the location. The system currently supports <samp>svn</samp> for a
+      Subversion location and <samp>fs</samp> for a file system location. If
+      not specified, the system will make an intelligent guess based on the
+      given value.</p>
+
+      <p><dfn>namespace</dfn>: The namespace to be associated with the
+      location.</p>
+
+      <p><dfn>value</dfn>: A valid location of a type supported by the
+      system.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+location{primary}[var] = svn://fcm5/VAR_svn/VAR
+location{primary, type:svn}[egg] = http://chicken/egg
+</pre>
+    </dd>
+
+    <dt id="keyword.revision">revision</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Associates a keyword with a revision of a
+      location in a version control system.</p>
+
+      <p><dfn>namespace</dfn>: The namespace of a location (already defined
+      using the <code>location</code> declaration), followed by a colon and the
+      namespace of the revision.</p>
+
+      <p><dfn>value</dfn>: A valid revision of the location.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+revision[um:vn7.5] = 18479
+revision[egg:free-range] = 1
+</pre>
+    </dd>
+
+    <dt id="keyword.browser.comp-pat">browser.comp-pat</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies a regular expression to capture
+      components in the scheme-specific part of a version control system
+      location (already defined using the <code>location{primary}</code>
+      declaration). These components can then be used in the
+      <code>browser.loc-tmpl</code> template string.</p>
+
+      <p><dfn>namespace</dfn>: Optional. The namespace of a location. If not
+      specified, the declaration applies globally.</p>
+
+      <p><dfn>value</dfn>: A valid regular expression.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+browser.comp-pat = (?msx-i:\A // ([^/]+) /+ ([^/]+)_svn /*(.*) \z) # default
+browser.comp-pat[egg] = (?msx-i:\A//([^/]+)/(.*)\z)
+</pre>
+    </dd>
+
+    <dt id="keyword.browser.loc-tmpl">browser.loc-tmpl</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies a template string, in which the
+      components captured by the <code>browser.comp.pat</code> regular
+      expression are used to fill in the numbered fields. The template should
+      have one more field than the number of components captured by
+      <code>browser.comp-pat</code>. The final field is used to place the
+      revision, which is generated via the <code>browser.rev-tmpl</code>.</p>
+
+      <p><dfn>namespace</dfn>: Optional. The namespace of a location. If not
+      specified, the declaration applies globally.</p>
+
+      <p><dfn>value</dfn>: A valid template.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+browser.loc-tmpl = http://{1}/projects/{2}/intertrac/source:/{3}{4} # default
+browser.loc-tmpl[egg] = http://{1}/intertrac/source:/{3}{4}
+</pre>
+    </dd>
+
+    <dt id="keyword.browser.rev-tmpl">browser.rev-tmpl</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies a template string, which should have
+      a single numbered field for filling in the revision number.</p>
+
+      <p><dfn>namespace</dfn>: Optional. The namespace of a location. If not
+      specified, the declaration applies globally.</p>
+
+      <p><dfn>value</dfn>: A valid template.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+browser.rev-tmpl = @{1} # default
+</pre>
+    </dd>
+  </dl>
+
+  <h2 id="external">FCM External Configuration</h2>
+
+  <p>The external configuration files are used to define the name of external
+  commands used by the FCM system. The <code>fcm</code> command searches for
+  external configuration files from the following locations:</p>
+
+  <ol>
+    <li><samp>$FCM/etc/fcm/external.cfg</samp> where <var>$FCM/bin/</var> is
+    the path at which <code>fcm</code> is installed.</li>
+
+    <li><samp>$HOME/.metomi/fcm/external.cfg</samp>.</li>
+  </ol>
+
+  <p>The following are declarations recognised by the external configuration
+  files:</p>
+
+  <dl>
+    <dt id="external.browser">browser</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies the command to invoke the web
+      browser. (default=<samp>firefox</samp>)</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+browser = konqueror
+</pre>
+    </dd>
+
+    <dt id="external.diff3">diff3</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: The shell command used by the extract system of
+      FCM Make to perform a 3-way merge. (default=<samp>diff3</samp>)</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+diff3 = diff3
+</pre>
+    </dd>
+
+    <dt id="external.diff3.flags">diff3.flags</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: The options used by the 3-way merge shell
+      command. (default=<samp>-E -m</samp>)</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+diff3.flags = -E -m
+</pre>
+    </dd>
+
+    <dt id="external.graphic-diff">graphic-diff</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies the command to invoke the graphic
+      diff tool. (default=<samp>xxdiff</samp>)</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+graphic-diff = tkdiff
+</pre>
+    </dd>
+
+    <dt id="external.graphic-merge">graphic-merge</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies the command to invoke the graphic
+      merge tool. (default=<samp>xxdiff</samp>)</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+graphic-merge = xxdiff
+</pre>
+    </dd>
+
+    <dt id="external.ssh">ssh</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: The secure remote shell command to execute
+      commands on a remote host.  (default=<samp>ssh</samp>)</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+ssh = ssh
+</pre>
+    </dd>
+
+    <dt id="external.ssh.flags">ssh.flags</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: The options used by the secure shell command to
+      execute commands on a remote host. (default=<samp>-n
+      -oBatchMode=yes</samp>)</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+ssh.flags = -n -oBatchMode=yes
+</pre>
+    </dd>
+
+    <dt id="external.rsync">rsync</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: The <code>rsync</code> command.
+      (default=<samp>rsync</samp>)</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+rsync = rsync
+</pre>
+    </dd>
+
+    <dt id="external.rsync.flags">rsync.flags</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: The options used by the <code>rsync</code>
+      command. (default=<samp>-a --exclude=.* --delete-excluded --timeout=900
+      --rsh=ssh\ -oBatchMode=yes</samp>)</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+rsync.flags = -a --exclude=.* --delete-excluded --timeout=900 --rsh=ssh\ -oBatchMode=yes
+</pre>
+    </dd>
+  </dl>
+
+  <h2 id="make">FCM Make Configuration</h2>
+
+  <p>A typical FCM make configuration consists of some top level declarations
+  to define the make, and specific declarations for each step. The top level
+  declarations are described below, and the specific declarations for each type
+  of step will be described in the sub-sections to follow.</p>
+
+  <p>Note:</p>
+
+  <ul>
+    <li>Unless stated otherwise, FCM make configuration declarations are
+    non-cumulative, i.e. if more than one declarations apply to the
+    <dfn>same</dfn> configuration, the value of the last declaration overrides
+    those of the earlier ones. E.g.:
+      <pre>
+# Sets the Fortran compiler flags for the root name-space
+build.prop{fc.flags} = -O3
+# build.prop{fc.flags}[/] = -O3
+
+# Sets the C/Fortran compiler flags for the "foo/bar" name-space
+build.prop{cc.flags, fc.flags}[foo/bar] = -g -C
+# build.prop{cc.flags}[foo/bar] = -g -C
+# build.prop{fc.flags}[/] = -O3
+# build.prop{fc.flags}[foo/bar] = -g -C
+
+# Sets the Fortran compiler flags for the root and "foo/bar" name-spaces
+build.prop{fc.flags}[/ foo/bar] = -O2
+# build.prop{cc.flags}[foo/bar] = -g -C
+# build.prop{fc.flags}[/] = -O2
+# build.prop{fc.flags}[foo/bar] = -O2
+
+# Sets the Fortran compiler flags for the "foo/bar" name-spaces
+build.prop{fc.flags}[foo/bar] = -O1
+# build.prop{cc.flags}[foo/bar] = -g -C
+# build.prop{fc.flags}[/] = -O2
+# build.prop{fc.flags}[foo/bar] = -O1
+</pre>
+    </li>
+
+    <li>Unless stated otherwise, declarations are inherited.</li>
+
+    <li>The default values of the property settings of each step class (e.g.
+    <code>build.prop{fc}</code>) can be modified in:
+
+      <ol>
+        <li>The site configuration file <samp>$FCM/etc/fcm/make.cfg</samp>
+        where <var>$FCM/bin/</var> is the path at which <code>fcm</code> is
+        installed.</li>
+
+        <li>The user configuration file
+        <samp>$HOME/.metomi/fcm/make.cfg</samp>.</li>
+
+        <li><samp>*.prop{class,*}</samp> declarations in the FCM make
+        configuration file of the current session.</li>
+      </ol>
+
+      <p>The syntax is identical to a normal FCM make configuration file
+      declaration except that:</p>
+
+      <ul>
+        <li>Only <samp>*.prop{*}</samp> declarations are accepted.</li>
+
+        <li>The settings apply to a step class, not an individual step.</li>
+
+        <li>Name-space is not allowed.</li>
+      </ul>
+    </li>
+  </ul>
+
+  <p>For further details, please refer to the chapter on <a href=
+  "make.html">FCM Make</a>.</p>
+
+  <dl>
+    <dt id="make.dest">dest</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies the output destination of the make
+      system. If not specified, the system assumes the output destination to be
+      the current working directory. This setting is not inherited.</p>
+
+      <p><dfn>value</dfn>: A writable directory path.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+dest = path
+# E.g.:
+dest = $HERE
+</pre>
+    </dd>
+
+    <dt id="make.steps">steps</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies a list of steps for the make system
+      to invoke. Each specified step can either be the ID of a known
+      sub-system, i.e. <samp>extract</samp>, <samp>mirror</samp>,
+      <samp>preprocess</samp> and <samp>build</samp>, or an ID defined using
+      the <code>step.class</code> declaration. (See below.)</p>
+
+      <p><dfn>value</dfn>: A list of steps for the make system to invoke.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+steps = id-1 id-2 ...
+# E.g.:
+steps = extract mirror
+</pre>
+    </dd>
+
+    <dt id="make.step.class">step.class</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Defines custom step IDs. This allows built-in
+      steps to be renamed, or re-used in multiple steps.</p>
+
+      <p><dfn>namespace</dfn>: Specifies the step IDs.</p>
+
+      <p><dfn>value</dfn>: An ID of a known sub-system, i.e.
+      <samp>extract</samp>, <samp>mirror</samp>, <samp>preprocess</samp> and
+      <samp>build</samp>.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+step.class[id ...] = id-of-class
+# E.g.:
+step.class[build-qxrecon build-ieee] = build
+</pre>
+    </dd>
+
+    <dt id="make.use">use</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies inheritance locations. It is worth
+      noting that this declaration is cumulative, i.e. a <code>use</code>
+      declaration adds more inheritance locations rather than overrides the
+      inheritance locations of any previous <code>use</code> declarations.</p>
+
+      <p><dfn>value</dfn>: A list of paths where this make can inherit items
+      and properties from.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+use = path1 path2 ...
+E.g.:
+use = /path/to/inherit
+</pre>
+    </dd>
+  </dl>
+
+  <h3 id="make.extract">FCM Make Configuration: Extract</h3>
+
+  <p>All declarations are prefixed with <samp>extract.*</samp>. Where
+  appropriate, replace the leading <samp>extract</samp> with the appropriate
+  step ID. With the exception of the <code>extract.location{diff}</code>
+  declaration, any attempt to specify any extract declarations in an inherited
+  extract will result in an exception.</p>
+
+  <dl>
+    <dt id="make.extract.ns">extract.ns</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies a list of names of the projects to
+      extract.</p>
+
+      <p><dfn>value</dfn>: A space-delimited list of names of the projects to
+      extract.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+extract.ns = gen ops um var ver
+</pre>
+    </dd>
+
+    <dt id="make.extract.location">extract.location</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies the location of the base source tree
+      a project. If the base source tree is not specified for a Subversion
+      project, the system will try to use <samp>trunk at HEAD</samp>.</p>
+
+      <p><dfn>modifier</dfn>: <var>type</var>: Specifies the type of the
+      locations. The system currently supports <samp>svn</samp> for a Subversion
+      location, <samp>ssh</samp> for a file system on a remote host accessible
+      via SSH and RSYNC, and <samp>fs</samp> for a file system location.
+      (default=<samp>svn</samp> if the location is recognised as a Subversion
+      URL, <samp>ssh</samp> if the location has the form
+      <var>[USER@]HOST:PATH</var> and <var>HOST</var> is a valid remote host,
+      <samp>fs</samp> otherwise)</p>
+
+      <p><dfn>namespace</dfn>: A space-delimited list of project IDs.</p>
+
+      <p><dfn>value</dfn>: A location, which can be a full path in the file
+      system, or a relative location of the project root.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+extract.location[um] = trunk at vn7.5
+extract.location[gen ops var] = trunk at HEAD
+</pre>
+    </dd>
+
+    <dt id="make.extract.location.diff">extract.location{diff}</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies the location(s) of one or more diff
+      source trees of a project. It is OK to add more diff source trees in an
+      inheriting extract.</p>
+
+      <p><dfn>modifier</dfn>: <var>type</var>: -as above-</p>
+
+      <p><dfn>namespace</dfn>: A project ID.</p>
+
+      <p><dfn>value</dfn>: A space-delimited list of locations. A location can
+      be a full path in the file system, or a relative location of the project
+      root.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+extract.location{diff}[gen] = \
+    branches/dev/fred/r12345_678 \
+    branches/dev/free/r12345_678 at 123
+</pre>
+    </dd>
+
+    <dt id="make.extract.location.primary">extract.location{primary}</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies the project locations for an
+      extract, if they are not already defined in the keyword
+      configuration.</p>
+
+      <p><dfn>modifier</dfn>: <var>type</var>: -as above-</p>
+
+      <p><dfn>namespace</dfn>: A project ID.</p>
+
+      <p><dfn>value</dfn>: A project location. The location can be a path in
+      the file system, a Subversion URL or a FCM keyword pointing to a
+      Subversion URL.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+extract.location{primary}[egg] = svn://server2/egg
+extract.location{primary,type:svn}[foo] = http://server1/foo
+</pre>
+    </dd>
+
+    <dt id="make.extract.path-excl">extract.path-excl / <span id=
+    "make.extract.path-incl">extract.path-incl</span></dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies the paths in a project tree to
+      exclude/include. (A path-incl takes precedence over a path-excl
+      declaration.)</p>
+
+      <p><dfn>namespace</dfn>: A space-delimited list of project IDs.</p>
+
+      <p><dfn>value</dfn>: A space-delimited list of paths in a project
+      tree.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+extract.path-excl[foo] = / # everything
+extract.path-incl[foo] = bin lib src/code
+</pre>
+    </dd>
+
+    <dt id="make.extract.path-root">extract.path-root</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies the root paths in the source trees
+      of the projects to be extracted. By default, the root path is the
+      root of a source tree. If this setting is specified, the system will only
+      extract source files under the specified root path. The system will also
+      adjust the name-space of each source file to be relative to the specified
+      root path. The <code>extract.path-excl</code> and
+      <code>extract.path-incl</code> declarations will apply from the level of
+      the specified root path.</p>
+
+      <p><dfn>namespace</dfn>: A space-delimited list of project IDs.</p>
+
+      <p><dfn>value</dfn>: A relative path in the project source trees.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+extract.path-root[gen] = src/code
+</pre>
+    </dd>
+
+    <dt id="make.extract.prop">extract.prop</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Sets or modifies a property.</p>
+
+      <p><dfn>modifier</dfn>: <var>diff3</var>: The shell command to perform a
+      3-way merge. If not specified, use the <a href="#external.diff3">diff3</a>
+      setting of the FCM external configuration.</p>
+
+      <p><dfn>modifier</dfn>: <var>diff3.flags</var>: The options used by the
+      3-way merge shell command. If not specified, use the <a href=
+      "#external.diff3.flags">diff3.flags</a> setting of the FCM external
+      configuration.</p>
+
+      <p><dfn>value</dfn>: The value of the property.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+extract.prop{diff3} = diff3
+extract.prop{diff3.flags} = -E -m
+</pre>
+    </dd>
+  </dl>
+
+  <h3 id="make.mirror">FCM Make Configuration: Mirror</h3>
+
+  <p>All declarations are prefixed with <samp>mirror.*</samp>. Where
+  appropriate, replace the leading <samp>mirror</samp> with the appropriate
+  ID.</p>
+
+  <dl>
+    <dt id="make.mirror.target">mirror.target</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies the mirror target. This setting is
+      not inherited.</p>
+
+      <p><dfn>value</dfn>: The mirror target.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+mirror.target = fred at server:/path/of/dest
+# Or if the current user ID is "fred"
+mirror.target = server:/path/of/dest
+# Or if the current user ID is "fred" and local host is "server"
+mirror.target = /path/of/dest
+</pre>
+    </dd>
+
+    <dt id="make.mirror.prop">mirror.prop</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Sets or modifies a property.</p>
+
+      <p><dfn>modifier</dfn>: See below.</p>
+
+      <p><dfn>value</dfn>: The value of the property.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+mirror.prop{rsync} = cnysr
+</pre>
+    </dd>
+  </dl>
+
+  <p>The following is a list of properties accepted by the
+  <code>mirror.prop</code> declaration:</p>
+
+  <dl>
+    <dt id="make.mirror.prop.config-file.steps">config-file.steps</dt>
+
+    <dd>The steps to be included in the generated configuration file in the
+    mirror destination.</dd>
+
+    <dt id="make.mirror.prop.no-config-file">no-config-file</dt>
+
+    <dd>If specified, do not generate a <samp>fcm-make.cfg</samp> in the mirror
+    target.</dd>
+
+    <dt id="make.mirror.prop.ssh">ssh</dt>
+
+    <dd>The secure remote shell command to execute commands on a remote machine.
+    If not specified, use the <a href= "#external.ssh">ssh</a> setting of the
+    FCM external configuration.</dd>
+
+    <dt id="make.mirror.prop.ssh.flags">ssh.flags</dt>
+
+    <dd>The flags used by the remote shell command to execute commands on a
+    remote machine. If not specified, use the <a href=
+    "#external.ssh.flags">ssh.flags</a> setting of the FCM external
+    configuration.</dd>
+
+    <dt id="make.mirror.prop.rsync">rsync</dt>
+
+    <dd>The <code>rsync</code> command. If not specified, use the <a href=
+    "#external.rsync">rsync</a> setting of the FCM external configuration.</dd>
+
+    <dt id="make.mirror.prop.rsync.flags">rsync.flags</dt>
+
+    <dd>The options used by the <code>rsync</code> command. If not specified,
+    use the <a href= "#external.rsync.flags">rsync.flags</a> setting of the FCM
+    external configuration.</dd>
+  </dl>
+
+  <h3 id="make.build">FCM Make Configuration: Build</h3>
+
+  <p>All declarations are prefixed with <samp>build.*</samp>. Where
+  appropriate, replace the leading <samp>build</samp> with the appropriate
+  ID.</p>
+
+  <dl>
+    <dt id="make.build.source">build.source</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies one or more source files and/or
+      directories.</p>
+
+      <p><dfn>namespace</dfn>: Optional. Specifies the namespace of the
+      specified paths.</p>
+
+      <p><dfn>value</dfn>: A list of space-delimited file system paths.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+build.source[foo] = $HOME/foo $DATADIR/foo
+</pre>
+    </dd>
+
+    <dt id="make.build.ns-excl">build.ns-excl / <span id=
+    "make.build.ns-incl">build.ns-incl</span></dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Specifies the name-spaces in the source tree
+      to exclude/include. (A <code>build.ns-incl</code> takes precedence over a
+      <code>build.ns-excl</code> declaration.)</p>
+
+      <p><dfn>value</dfn>: A list of space-delimited items. Each item is a
+      name-space to exclude/include.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+build.ns-excl = / # exclude everything
+build.ns-incl = foo bar/baz # include items in these name-spaces
+</pre>
+    </dd>
+
+    <dt id="make.build.target">build.target</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Selects targets to build according to their
+      keys, categories, name-spaces and tasks.</p>
+
+      <p><dfn>modifier</dfn>: <samp>key</samp> (default if no modifier
+      specified), <samp>category</samp>, <samp>ns</samp> (i.e. name-space) or
+      <samp>task</samp></p>
+
+      <p><dfn>value</dfn>: A list of space-delimited items (according to the
+      modifier).</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+build.target{category} = bin lib
+build.target{ns} = egg/fried ham
+build.target{task} = archive install link
+build.target = egg.sh mince.py
+</pre>
+    </dd>
+
+    <dt id="make.build.target-rename">build.target-rename</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Rename targets.</p>
+
+      <p><dfn>value</dfn>: A list of space-delimited items. Each item should be
+      the (automatic) key of a target, followed by a colon and its preferred
+      key.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+build.target-rename = hello.exe:hello
+</pre>
+    </dd>
+
+    <dt id="make.build.prop">build.prop</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Sets or modifies a property.</p>
+
+      <p><dfn>modifier</dfn>: The name of one (or more) property. See
+      below.</p>
+
+      <p><dfn>namespace</dfn>: Optional. A list of space-delimited namespaces to
+      which this setting applies. The namespaces can be target names, when used
+      with target properties (dependency properties are regarded as source
+      properties), or the hierarchical namespaces of source files. If not
+      specified, the setting applies to the global/root namespace. N.B. Not all
+      build properties accept a namespace. See below for detail.</p>
+
+      <p><dfn>value</dfn>: The value of the property.</p>
+
+      <p><dfn>example</dfn>:</p>
+      <pre>
+build.prop{fc} = gfortran
+build.prop{cc.flags,fc.flags} = -C -g
+build.prop{fc.flags}[foo/bar] = -C -g -W
+build.prop{fc.flags}[egg ham bacon] = -C -W
+build.prop{fc.flag-define} = -D%s
+build.prop{cc.defs,fc.defs}[gen] = LOWERCASE UNDERSCORE
+build.prop{fc.libs}[myprog.exe] = netcdf grib
+</pre>
+    </dd>
+  </dl>
+
+  <p>The following is a list of properties accepted by the
+  <code>build.prop</code> declaration. The default value is an empty string
+  unless given otherwise. Properties that do not accept a namespace are marked
+  with an asterisk (*).</p>
+
+  <dl>
+    <dt id="make.build.prop.ar">ar</dt>
+
+    <dd>The archive command. (default=<samp>ar</samp>)</dd>
+
+    <dt id="make.build.prop.ar.flags">ar.flags</dt>
+
+    <dd>The options used by the archive command. (default=<samp>rs</samp>)</dd>
+
+    <dt id="make.build.prop.cc">cc</dt>
+
+    <dd>The C compiler and linker. (default=<samp>gcc</samp>)</dd>
+
+    <dt id="make.build.prop.cc.defs">cc.defs</dt>
+
+    <dd>The C compiler will pre-define each word in this setting as a
+    macro.</dd>
+
+    <dt id="make.build.prop.cc.flags">cc.flags</dt>
+
+    <dd>The options used by the C compiler at compile time.</dd>
+
+    <dt id="make.build.prop.cc.flags-ld">cc.flags-ld</dt>
+
+    <dd>The options used by the C compiler at link time.</dd>
+
+    <dt id="make.build.prop.cc.flag-compile">cc.flag-compile</dt>
+
+    <dd>The option used by the C compiler to suppress the linking stage.
+    (default=<samp>-c</samp>)</dd>
+
+    <dt id="make.build.prop.cc.flag-define">cc.flag-define</dt>
+
+    <dd>The option used by the C compiler to define a macro.
+    (default=<samp>-D%s</samp>)</dd>
+
+    <dt id="make.build.prop.cc.flag-include">cc.flag-include</dt>
+
+    <dd>The option used by the C compiler to specify an include search
+    path. (default=<samp>-I%s</samp>)</dd>
+
+    <dt id="make.build.prop.cc.flag-lib">cc.flag-lib</dt>
+
+    <dd>The option used by the C compiler at link time to specify a
+    library. (default=<samp>-l%s</samp>)</dd>
+
+    <dt id="make.build.prop.cc.flag-lib-path">cc.flag-lib-path</dt>
+
+    <dd>The option used by the C compiler at link time to specify a library
+    search path. (default=<samp>-L%s</samp>)</dd>
+
+    <dt id="make.build.prop.cc.flag-omp">cc.flag-omp</dt>
+
+    <dd>The option used by the C compiler to switch on OpenMP.</dd>
+
+    <dt id="make.build.prop.cc.flag-output">cc.flag-output</dt>
+
+    <dd>The option used by the C compiler to specify the output file name.
+    (default=<samp>-o%s</samp>)</dd>
+
+    <dt id="make.build.prop.cc.include-paths">cc.include-paths</dt>
+
+    <dd>The C compiler will add each directory in this setting as an include
+    search path.</dd>
+
+    <dt id="make.build.prop.cc.libs">cc.libs</dt>
+
+    <dd>The C linker will add each item in this setting as a link
+    library.</dd>
+
+    <dt id="make.build.prop.cc.lib-paths">cc.lib-paths</dt>
+
+    <dd>The C linker will add each directory in this setting as a library
+    search path.</dd>
+
+    <dt id="make.build.prop.cxx">cxx</dt>
+
+    <dd>The C++ compiler and linker. (default=<samp>g++</samp>)</dd>
+
+    <dt id="make.build.prop.cxx.defs">cxx.defs</dt>
+
+    <dd>The C++ compiler will pre-define each word in this setting as a
+    macro.</dd>
+
+    <dt id="make.build.prop.cxx.flags">cxx.flags</dt>
+
+    <dd>The options used by the C++ compiler at compile time.</dd>
+
+    <dt id="make.build.prop.cxx.flags-ld">cxx.flags-ld</dt>
+
+    <dd>The options used by the C++ compiler at link time.</dd>
+
+    <dt id="make.build.prop.cxx.flag-compile">cxx.flag-compile</dt>
+
+    <dd>The option used by the C++ compiler to suppress the linking stage.
+    (default=<samp>-c</samp>)</dd>
+
+    <dt id="make.build.prop.cxx.flag-define">cxx.flag-define</dt>
+
+    <dd>The option used by the C++ compiler to define a macro.
+    (default=<samp>-D%s</samp>)</dd>
+
+    <dt id="make.build.prop.cxx.flag-include">cxx.flag-include</dt>
+
+    <dd>The option used by the C++ compiler to specify an include search
+    path. (default=<samp>-I%s</samp>)</dd>
+
+    <dt id="make.build.prop.cxx.flag-lib">cxx.flag-lib</dt>
+
+    <dd>The option used by the C++ compiler at link time to specify a
+    library. (default=<samp>-l%s</samp>)</dd>
+
+    <dt id="make.build.prop.cxx.flag-lib-path">cxx.flag-lib-path</dt>
+
+    <dd>The option used by the C++ compiler at link time to specify a library
+    search path. (default=<samp>-L%s</samp>)</dd>
+
+    <dt id="make.build.prop.cxx.flag-omp">cxx.flag-omp</dt>
+
+    <dd>The option used by the C++ compiler to switch on OpenMP.</dd>
+
+    <dt id="make.build.prop.cxx.flag-output">cxx.flag-output</dt>
+
+    <dd>The option used by the C++ compiler to specify the output file name.
+    (default=<samp>-o%s</samp>)</dd>
+
+    <dt id="make.build.prop.cxx.include-paths">cxx.include-paths</dt>
+
+    <dd>The C++ compiler will add each directory in this setting as an include
+    search path.</dd>
+
+    <dt id="make.build.prop.cxx.libs">cxx.libs</dt>
+
+    <dd>The C++ linker will add each item in this setting as a link
+    library.</dd>
+
+    <dt id="make.build.prop.cxx.lib-paths">cxx.lib-paths</dt>
+
+    <dd>The C++ linker will add each directory in this setting as a library
+    search path.</dd>
+
+    <dt id="make.build.prop.dep.bin">dep.bin</dt>
+
+    <dd>Specifies a list of manual dependencies on external executable
+    commands.</dd>
+
+    <dt id="make.build.prop.dep.f.module">dep.f.module</dt>
+
+    <dd>Specifies a list of manual Fortran module dependencies.</dd>
+
+    <dt id="make.build.prop.dep.include">dep.include</dt>
+
+    <dd>Specifies a list of manual include dependencies.</dd>
+
+    <dt id="make.build.prop.dep.o">dep.o</dt>
+
+    <dd>Specifies a list of manual object dependencies.</dd>
+
+    <dt id="make.build.prop.dep.o.special">dep.o.special</dt>
+
+    <dd>Specifies a list of manual object dependencies, which must appear on
+    the command line of the linker, (e.g. an object containing a Fortran
+    blockdata program unit).</dd>
+
+    <dt id="make.build.prop.fc">fc</dt>
+
+    <dd>The Fortran compiler and linker. (default=<samp>gfortran</samp>)</dd>
+
+    <dt id="make.build.prop.fc.defs">fc.defs</dt>
+
+    <dd>The Fortran compiler will pre-define each word in this setting as a
+    macro.</dd>
+
+    <dt id="make.build.prop.fc.flags">fc.flags</dt>
+
+    <dd>The options used by the Fortran compiler at compile time.</dd>
+
+    <dt id="make.build.prop.fc.flags-ld">fc.flags-ld</dt>
+
+    <dd>The options used by the Fortran compiler at link time.</dd>
+
+    <dt id="make.build.prop.fc.flag-compile">fc.flag-compile</dt>
+
+    <dd>The option used by the Fortran compiler to suppress the linking stage.
+    (default=<samp>-c</samp>)</dd>
+
+    <dt id="make.build.prop.fc.flag-define">fc.flag-define</dt>
+
+    <dd>The option used by the Fortran compiler to define a macro.
+    (default=<samp>-D%s</samp>)</dd>
+
+    <dt id="make.build.prop.fc.flag-include">fc.flag-include</dt>
+
+    <dd>The option used by the Fortran compiler to specify an include search
+    path. (default=<samp>-I%s</samp>)</dd>
+
+    <dt id="make.build.prop.fc.flag-lib">fc.flag-lib</dt>
+
+    <dd>The option used by the Fortran compiler at link time to specify a
+    library. (default=<samp>-l%s</samp>)</dd>
+
+    <dt id="make.build.prop.fc.flag-lib-path">fc.flag-lib-path</dt>
+
+    <dd>The option used by the Fortran compiler at link time to specify a
+    library search path. (default=<samp>-L%s</samp>)</dd>
+
+    <dt id="make.build.prop.fc.flag-module">fc.flag-module</dt>
+
+    <dd>The option used by the Fortran compiler to specify a module search
+    path.</dd>
+
+    <dt id="make.build.prop.fc.flag-omp">fc.flag-omp</dt>
+
+    <dd>The option used by the Fortran compiler to switch on OpenMP. If
+    specified, the build system will treat <code>USE</code> and
+    <code>INCLUDE</code> statements that are protected by <code>!$ </code>
+    sentinels as normal dependency statements.</dd>
+
+    <dt id="make.build.prop.fc.flag-output">fc.flag-output</dt>
+
+    <dd>The option used by the Fortran compiler to specify the output file
+    name. (default=<samp>-o%s</samp>)</dd>
+
+    <dt id="make.build.prop.fc.include-paths">fc.include-paths</dt>
+
+    <dd>The Fortran compiler will add each directory in this setting as an
+    include search path.</dd>
+
+    <dt id="make.build.prop.fc.libs">fc.libs</dt>
+
+    <dd>The Fortran linker will add each item in this setting as a link
+    library.</dd>
+
+    <dt id="make.build.prop.fc.lib-paths">fc.lib-paths</dt>
+
+    <dd>The Fortran linker will add each directory in this setting as a library
+    search path.</dd>
+
+    <dt id="make.build.prop.file-ext.a">file-ext.a *</dt>
+
+    <dd>Specifies the extension of an object archive file.
+    (default=<samp>.a</samp>)</dd>
+
+    <dt id="make.build.prop.file-ext.bin">file-ext.bin *</dt>
+
+    <dd>Specifies the extension of a binary executable file.
+    (default=<samp>.exe</samp>)</dd>
+
+    <dt id="make.build.prop.file-ext.c">file-ext.c *</dt>
+
+    <dd>Specifies the extensions of a C source file.
+    (default=<samp>.c .i .m .mi</samp>)</dd>
+
+    <dt id="make.build.prop.file-ext.cxx">file-ext.cxx *</dt>
+
+    <dd>Specifies the extensions of a C++ source file.
+    (default=<samp>.cc .cp .cxx .cpp .CPP .c++ .C .mm .M .mii</samp>)</dd>
+
+    <dt id="make.build.prop.file-ext.fortran">file-ext.fortran *</dt>
+
+    <dd>Specifies the extensions of a Fortran source file. (default=<samp>.F
+    .FOR .FTN .F90 .F95 .f .for .ftn .f90 .f95 .inc</samp>)</dd>
+
+    <dt id="make.build.prop.file-ext.f90-interface">file-ext.f90-interface
+    *</dt>
+
+    <dd>Specifies the extension of a Fortran interface file.
+    (default=<samp>.interface</samp>)</dd>
+
+    <dt id="make.build.prop.file-ext.f90-mod">file-ext.f90-mod *</dt>
+
+    <dd>Specifies the extension of a compiled Fortran module file.
+    (default=<samp>.mod</samp>)</dd>
+
+    <dt id="make.build.prop.file-ext.h">file-ext.h *</dt>
+
+    <dd>Specifies the extensions of a C/C++ header file.
+    (default=<samp>.h</samp>)</dd>
+
+    <dt id="make.build.prop.file-ext.o">file-ext.o *</dt>
+
+    <dd>Specifies the extension of a compiled object file.
+    (default=<samp>.o</samp>)</dd>
+
+    <dt id="make.build.prop.file-ext.script">file-ext.script *</dt>
+
+    <dd>Specifies the extensions of script files.</dd>
+
+    <dt id="make.build.prop.file-name-option.f90-mod">file-name-option.f90-mod
+    *</dt>
+
+    <dd>Specifies other options for naming a compiled Fortran module file.
+    Accepts <samp>case=upper</samp> or <samp>case=lower</samp> (default).</dd>
+
+    <dt id="make.build.prop.file-pat.script">file-pat.script *</dt>
+
+    <dd>Specifies a regular expression for matching the name of script
+    files.</dd>
+
+    <dt id="make.build.prop.ignore-missing-dep-ns">ignore-missing-dep-ns *</dt>
+
+    <dd>Specifies a list of source name-spaces, in which targets can ignore
+    missing dependencies.</dd>
+
+    <dt id="make.build.prop.keep-lib-o">keep-lib-o</dt>
+
+    <dd>Relevant when linking a binary executable. If <kbd>true</kbd>, create
+    and keep the dependent object library as <samp>lib/libNAME.a</samp>, where
+    <var>NAME</var> is the root name of the executable. The normal behaviour is
+    to create the dependent object library in a temporary directory, which is
+    removed after the linker command is completed.</dd>
+
+    <dt id="make.build.prop.ld">ld</dt>
+
+    <dd>The linker command. If not specified, use the compiler of the source
+    file.</dd>
+
+    <dt id="make.build.prop.no-dep.bin">no-dep.bin</dt>
+
+    <dd>Switches off a list of automatic dependencies on external executable.
+    If the value is a <samp>*</samp>, switches off all automatic external
+    executable dependencies.</dd>
+
+    <dt id="make.build.prop.no-dep.f.module">no-dep.f.module</dt>
+
+    <dd>Switches off a list of automatic Fortran module dependencies. If the
+    value is a <samp>*</samp>, switches off all automatic Fortran module
+    dependencies.</dd>
+
+    <dt id="make.build.prop.no-dep.include">no-dep.include</dt>
+
+    <dd>Switches off a list of automatic include dependencies. If the value is
+    a <samp>*</samp>, switches off all automatic include dependencies.</dd>
+
+    <dt id="make.build.prop.no-dep.o">no-dep.o</dt>
+
+    <dd>Switches off a list of automatic object dependencies. If the value is a
+    <samp>*</samp>, switches off all automatic object dependencies.</dd>
+
+    <dt id="make.build.prop.no-inherit-source">no-inherit-source *</dt>
+
+    <dd>If a list of values is specified, the system will not inherit sources
+    with the name-spaces matching the specified values. If the value is a
+    <samp>*</samp>, the system will not inherit any sources.</dd>
+
+    <dt id="make.build.prop.no-inherit-target-category">
+    no-inherit-target-category *</dt>
+
+    <dd>If a list of values is specified, the system will not inherit a target
+    in a category matching a specified value. (default=<samp>bin etc
+    lib</samp>)</dd>
+
+    <dt id="make.build.prop.no-step-source">no-step-source *</dt>
+
+    <dd>If a list of make steps is specified, the system will not search for
+    source files from the specified make steps.</dd>
+
+    <dt id="make.build.prop.ns-dep.o">ns-dep.o</dt>
+
+    <dd>Specifies a list of link-time object dependencies on all objects in the
+    list of name-spaces in the value.</dd>
+  </dl>
+
+  <h3 id="make.preprocess">FCM Make Configuration: Preprocess</h3>
+
+  <p>The preprocess system uses the same declarations as the build system (see
+  <a href="#make.build">FCM Make Configuration: Build</a>), although their
+  prefixes should be replaced with <samp>preprocess.*</samp> or the appropriate
+  step ID. The following is a list of modifiers accepted by the
+  <code>preprocess.prop</code> declaration. The default value of a property is
+  an empty string unless given otherwise. Properties that do not accept a
+  namespace are marked with an asterisk (*).</p>
+
+  <dl>
+    <dt id="make.preprocess.prop.cpp">cpp</dt>
+
+    <dd>The command of the C/C++ pre-processor. (default=<samp>cpp</samp>)</dd>
+
+    <dt id="make.preprocess.prop.cpp.defs">cpp.defs</dt>
+
+    <dd>The C/C++ pre-processor will pre-define each word in this setting as a
+    macro.</dd>
+
+    <dt id="make.preprocess.prop.cpp.flags">cpp.flags</dt>
+
+    <dd>The options used by the C/C++ pre-processor.</dd>
+
+    <dt id="make.preprocess.prop.cpp.flag-define">cpp.flag-define</dt>
+
+    <dd>The option used by the C/C++ pre-processor to define a macro.
+    (default=<samp>-D%s</samp>)</dd>
+
+    <dt id="make.preprocess.prop.cpp.flag-include">cpp.flag-include</dt>
+
+    <dd>The option used by the C/C++ pre-processor to specify the include
+    search path. (default=<samp>-I%s</samp>)</dd>
+
+    <dt id="make.preprocess.prop.cpp.include-paths">cpp.include-paths</dt>
+
+    <dd>The C/C++ preprocessor will add each directory in this setting as an
+    include search path.</dd>
+
+    <dt id="make.preprocess.prop.dep.include">dep.include</dt>
+
+    <dd>Specifies a list of manual include dependencies.</dd>
+
+    <dt id="make.preprocess.prop.file-ext.cpp">file-ext.cpp *</dt>
+
+    <dd>Specifies the extensions of C/C++ source file.
+    (default=<samp>.c .m .cc .cp .cxx .cpp .CPP .c++ .C .mm .M</samp>)</dd>
+
+    <dt id="make.preprocess.prop.file-ext.fpp">file-ext.fpp *</dt>
+
+    <dd>Specifies the extensions of Fortran source file requiring
+    preprocessing. (default=<samp>.F .FOR .FTN .F90 .F95</samp>)</dd>
+
+    <dt id="make.preprocess.prop.file-ext.h">file-ext.h *</dt>
+
+    <dd>Specifies the extensions of a C/C++ header file.
+    (default=<samp>.h</samp>)</dd>
+
+    <dt id="make.preprocess.prop.fpp">fpp</dt>
+
+    <dd>The command of the Fortran pre-processor.
+    (default=<samp>cpp</samp>)</dd>
+
+    <dt id="make.preprocess.prop.fpp.defs">fpp.defs</dt>
+
+    <dd>The Fortran pre-processor will pre-define each word in this setting as
+    a macro.</dd>
+
+    <dt id="make.preprocess.prop.fpp.flags">fpp.flags</dt>
+
+    <dd>The options used by the Fortran pre-processor. (default=<samp>-P
+    -traditional</samp>)</dd>
+
+    <dt id="make.preprocess.prop.fpp.flag-define">fpp.flag-define</dt>
+
+    <dd>The option used by the Fortran pre-processor to define a macro.
+    (default=<samp>-D%s</samp>)</dd>
+
+    <dt id="make.preprocess.prop.fpp.flag-include">fpp.flag-include</dt>
+
+    <dd>The option used by the Fortran pre-processor to specify the include
+    search path. (default=<samp>-I%s</samp>)</dd>
+
+    <dt id="make.preprocess.prop.fpp.include-paths">fpp.include-paths</dt>
+
+    <dd>The Fortran preprocessor will add each directory in this setting as an
+    include search path.</dd>
+
+    <dt id="make.preprocess.prop.no-dep.include">no-dep.include</dt>
+
+    <dd>Switches off a list of automatic include dependencies. If the value is
+    a <samp>*</samp>, it switches off all automatic include dependencies.</dd>
+
+    <dt id="make.preprocess.prop.no-inherit-source">no-inherit-source *</dt>
+
+    <dd>Same as the build property of the same name.</dd>
+
+    <dt id="make.preprocess.prop.no-inherit-target-category">
+    no-inherit-target-category *</dt>
+
+    <dd>Same as the build property of the same name.</dd>
+
+    <dt id="make.preprocess.prop.no-step-source">no-step-source *</dt>
+
+    <dd>Same as the build property of the same name.</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/annex_ext_cfg.html b/doc/user_guide/annex_ext_cfg.html
new file mode 100644
index 0000000..f56fca7
--- /dev/null
+++ b/doc/user_guide/annex_ext_cfg.html
@@ -0,0 +1,397 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: Annex: Declarations in FCM 1 extract configuration
+    file</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: Annex: Declarations in FCM 1 extract configuration
+    file</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p><em>The FCM 1 extract system is deprecated. The documentation for the
+  current extract system can be found at <a href="make.html">FCM
+  Make</a>.</em></p>
+
+  <p>The following is a list of supported declarations for the configuration
+  file used by the FCM extract system. Unless otherwise stated, the fields in
+  all declaration labels are not case sensitive.</p>
+
+  <dl>
+    <dt>CFG::TYPE</dt>
+
+    <dd>
+      <p>The configuration file type, the value should always be
+      <samp>ext</samp> for an extract configuration file. This declaration is
+      compulsory for all extract configuration files.</p>
+
+      <p>Example:</p>
+      <pre>
+cfg::type  ext
+</pre>
+    </dd>
+
+    <dt>CFG::VERSION</dt>
+
+    <dd>
+      <p>The file format version, currently <samp>1.0</samp> - a version is
+      included so that we shall be able to read the configuration file
+      correctly should we decide to change its format in the future.</p>
+
+      <p>Example:</p>
+      <pre>
+cfg::version  1.0
+</pre>
+    </dd>
+
+    <dt>%<name></dt>
+
+    <dd>
+      <p><code>%<name></code> declares an internal variable
+      <var><name></var> that can later be re-used.</p>
+
+      <p>Example:</p>
+      <pre>
+%my_variable   foo
+src::bar::base %my_variable
+src::egg::base %my_variable
+src::ham::base %my_variable
+</pre>
+    </dd>
+
+    <dt>INC</dt>
+
+    <dd>
+      <p>This declares the name of a file containing extract configuration. The
+      lines in the declared file will be included inline to the current
+      configuration file.</p>
+
+      <p>Example:</p>
+      <pre>
+inc  ~frva/var_stable_22.0/cfg/ext.cfg
+# ... and then your changes ...
+</pre>
+    </dd>
+
+    <dt>DEST[::ROOTDIR]</dt>
+
+    <dd>
+      <p>The <em>root</em> path of the destination of this extract. This
+      declaration is compulsory for all extract configuration files.</p>
+
+      <p>Example:</p>
+      <pre>
+dest  $HOME/project/my_project
+</pre>
+    </dd>
+
+    <dt>USE</dt>
+
+    <dd>
+      <p>This declares the location of a previous successful extract, which the
+      current extract will inherit from. If the previous extract is also a
+      build, the subsequent invocation of the build system on the current
+      extract will automatically trigger an inherited incremental build based
+      on that build.</p>
+
+      <p>Example:</p>
+      <pre>
+use  ~frva/var_stable_22.0
+# ... and then the settings for your current extract ...
+</pre>
+    </dd>
+
+    <dt>RDEST[::ROOTDIR]</dt>
+
+    <dd>
+      <p>The alternate destination of this extract. This declaration is
+      compulsory if this extract requires mirroring to an alternate
+      destination.</p>
+
+      <p>Example:</p>
+      <pre>
+rdest  /home/nwp/da/frva/project/my_project
+</pre>
+    </dd>
+
+    <dt>RDEST::LOGNAME</dt>
+
+    <dd>
+      <p>The login name of the user on the alternate destination machine. If
+      not specified, the current login name of the user on the local platform
+      is assumed.</p>
+
+      <p>Example:</p>
+      <pre>
+rdest::logname  frva
+</pre>
+    </dd>
+
+    <dt>RDEST::MACHINE</dt>
+
+    <dd>
+      <p>The destination machine for this extract. If not specified, the current
+      host name is assumed.</p>
+
+      <p>Example:</p>
+      <pre>
+rdest::machine  tx01
+</pre>
+    </dd>
+
+    <dt>
+      RDEST::MIRROR_CMD<br />
+      <del>MIRROR</del>
+    </dt>
+
+    <dd>
+      <p>The extract system can mirror the extracted source to an alternate
+      machine. Currently, it does this using either the <code>rdist</code> or
+      the <code>rsync</code> command. The default is <samp>rsync</samp>. This
+      declaration can be used to switch to using <samp>rdist</samp>.</p>
+
+      <p>Example:</p>
+      <pre>
+rdest::mirror_cmd  rdist
+</pre>
+    </dd>
+
+    <dt>
+      RDEST::RSH_MKDIR_RSH (<del>RDEST::REMOTE_SHELL</del>)<br />
+      RDEST::RSH_MKDIR_RSHFLAGS<br />
+      RDEST::RSH_MKDIR_MKDIR<br />
+      RDEST::RSH_MKDIR_MKDIRFLAGS
+    </dt>
+
+    <dd>
+      <p>If <code>rsync</code> is used to mirror an extract, the system needs to
+      issue a separate remote shell command to create the container directory of
+      the mirror destination. The default is to issue a shell command in the
+      form <samp>ssh -n -oBatchMode=yes LOGNAME at MACHINE mkdir -p DEST</samp>.
+      These declarations can be used to modify the command.</p>
+
+      <p>Example:</p>
+      <pre>
+# Examples using the default settings:
+rdest::rsh_mkdir_rsh         ssh
+rdest::rsh_mkdir_rshflags    -n -oBatchMode=yes
+rdest::rsh_mkdir_mkdir       mkdir
+rdest::rsh_mkdir_mkdirflags  -p
+</pre>
+    </dd>
+
+    <dt>
+      RDEST::RSYNC<br />
+      RDEST::RSYNCFLAGS
+    </dt>
+
+    <dd>
+      <p>These declarations are only useful if <code>rsync</code> is used to
+      mirror an extract. By default, the system issues the shell command
+      <samp>rsync -a --exclude='.*' --delete-excluded --timeout=900 --rsh='ssh
+      -oBatchMode=yes' SOURCE DEST</samp>. These declarations can be used to
+      modify the command.</p>
+
+      <p>Example:</p>
+      <pre>
+# Examples using the default settings:
+rdest::rsync       rsync
+rdest::rsyncflags  -a --exclude='.*' --delete-excluded --timeout=900 \
+                   --rsh='ssh -oBatchMode=yes'
+</pre>
+    </dd>
+
+    <dt>REPOS::<pck>::<branch></dt>
+
+    <dd>
+      <p>This declares a URL or a local file system path for the container
+      <em>repository</em> of a branch named <branch> in a package named
+      <pck>. The package name <pck> must be the name of a top-level
+      package (i.e. it must not contain the double colon <code>::</code>
+      delimiter). The name <branch> is used internally within the extract
+      system, and so is independent of the branch name of the code management
+      system. However, it is usually desirable to use the same name of the
+      actual branch in the code management system. For declaration of a local
+      file system path, the convention is to name the branch <samp>user</samp>.
+      Please note that both <pck> and <branch> fields are case
+      sensitive. The declared URL must be a valid Subversion URL or a valid FCM
+      URL keyword.</p>
+
+      <p>Example:</p>
+      <pre>
+repos::var::base    fcm:var_tr
+repos::var::branch1 fcm:var_br/frsn/r4790_foobar
+repos::var::user    $HOME/var
+</pre>
+    </dd>
+
+    <dt>
+      REVISION::<pck>::<branch><br />
+      <del>VERSION::<pck>::<branch></del>
+    </dt>
+
+    <dd>
+      <p>The revision to be used for the URL of <branch> in the package
+      <pck>. If specified, the revision must be a revision where the
+      branch exists. If not specified, the revision defaults to last changed
+      revision at the HEAD of the branch. Please note that if the declared
+      <em>branch</em> is in the local file system, this declaration must not be
+      used. The value of the declaration can be a FCM revision keyword or any
+      revision argument acceptable by Subversion. You can use a valid revision
+      number, a date between a pair of curly brackets (e.g. <samp>{"2005-05-01
+      12:00"}</samp>) or the keyword HEAD. However, please do not use the
+      keywords BASE, COMMITTED or PREV as these are reserved for working copies
+      only. Again, please note that both <pck> and <branch> fields
+      are case sensitive.</p>
+
+      <p>Example:</p>
+      <pre>
+# Declare the revision with the FCM revision keyword "vn22.0"
+revision::var::base     vn22.0
+# Declare the revision with a {date}
+revision::var::branch1  {2006-01-01}
+</pre>
+    </dd>
+
+    <dt>REVMATCH</dt>
+
+    <dd>
+      <p>If set to true, the declared revision of a branch must be a changed
+      revision of that branch, (unless the keyword HEAD is used).</p>
+
+      <p>Example:</p>
+      <pre>
+revmatch  true
+</pre>
+    </dd>
+
+    <dt>SRC::<pcks>::<branch></dt>
+
+    <dd>
+      <p>This declares a source directory for the sub-package <pcks> of
+      <branch>. If the repository is declared as a URL, the source
+      directory must be quoted as a relative path to the URL. If the repository
+      is declared as a path in the local file system, the source directory can
+      be declared as either a relative path to the <em>repository</em> or a
+      full path. If the source directory is a relative path and <pcks> is
+      a top-level package, the full name of the sub-package will be determined
+      automatically using the directory names of the relative path as the names
+      of the sub-packages. If the source directory is a full path, the full
+      sub-package name must be specified. The name of the sub-package
+      determines the destination path of the source directory in the
+      extract.</p>
+
+      <p>Example:</p>
+      <pre>
+src::var::base                    code/VarMod_PF
+src::var/code/VarMod_PF::user   $HOME/var/code/VarMod_PF 
+</pre>
+    </dd>
+
+    <dt>EXPSRC::<pcks>::<branch></dt>
+
+    <dd>
+      <p>This declares an expandable source directory for the sub-package
+      <pcks> of <branch>. This declaration is essentially the same
+      as the SRC declaration, except that the system will attempt to search
+      recursively for sub-directories within the declared source directory.</p>
+
+      <p>Example:</p>
+      <pre>
+expsrc::var::base  code
+expsrc::var::user  code
+</pre>
+    </dd>
+
+    <dt>
+      CONFLICT<br />
+      <del>OVERRIDE</del>
+    </dt>
+
+    <dd>
+      <p>This declaration can be used to specify the conflict mode, which is
+      relevant when a file is modified by two different branches (or more)
+      relative to the base branch. The conflict mode can be <samp>fail</samp>,
+      <samp>merge</samp> (default) or <samp>override</samp> (or 0, 1 and 2
+      respectively). If <samp>fail</samp> is specified, the extract fails when
+      a file is modified by two branches (or more) relative to the base branch.
+      If <samp>merge</samp> is specified, the system will attempt to merge the
+      changes. It will fail only on unresolved conflicts. If
+      <samp>override</samp> is specified, the changes in the last branch takes
+      precedence and the changes in the earlier branches will be ignored. Note:
+      the old <code>override true|false</code> declaration is deprecated. If
+      declared, <code>override true</code> will be equivalent to <code>conflict
+      override</code>, and <code>override false</code> will be equivalent to
+      <code>conflict fail</code>.</p>
+
+      <p>Example:</p>
+      <pre>
+conflict  override
+</pre>
+    </dd>
+
+    <dt>BLD::<fields></dt>
+
+    <dd>
+      <p>Declare a build configuration file declaration. The label
+      <fields> is the label of the declaration. On a successful extract,
+      <fields> will be added to the build configuration file. Please note
+      that some of the <fields> may be case sensitive.</p>
+
+      <p>Example:</p>
+      <pre>
+bld::target   VarScr_AnalysePF
+bld::tool::fc sxmpif90
+bld::tool::cc sxmpic++
+# ... and so on ...
+</pre>
+    </dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/annex_fcm_cfg.html b/doc/user_guide/annex_fcm_cfg.html
new file mode 100644
index 0000000..f5d25b9
--- /dev/null
+++ b/doc/user_guide/annex_fcm_cfg.html
@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: Annex: Declarations in FCM 1 central/user configuration
+  file</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: Annex: Declarations in FCM 1 central/user configuration
+    file</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>The deprecated FCM 1 commands, i.e. <code><a href=
+  "command_ref.html#fcm-build">fcm build</a></code>, <code><a href=
+  "command_ref.html#fcm-cmp-ext-cfg">fcm cmp-ext-cfg</a></code> and
+  <code><a href="command_ref.html#fcm-extract">fcm extract</a></code>, can
+  modify their settings using the central and user configuration files at:</p>
+
+  <ul>
+    <li><samp>$FCM/etc/fcm.cfg</samp> where <var>$FCM/bin/</var> is the path at
+    which <code>fcm</code> is installed.</li>
+
+    <li><samp>$HOME/.fcm</samp>.</li>
+  </ul>
+
+  <p>Note:</p>
+
+  <ul>
+    <li>The detail of the above settings will remain undocumented. For further
+    information, please refer to the Perl module <code>FCM1::Config</code>,
+    which should be located at <samp>lib/FCM1/Config.pm</samp> in your FCM
+    distribution.</li>
+
+    <li>Setting labels in both files are case insensitive.</li>
+  </ul>
+
+  <p>The following deprecated settings related to FCM keywords will no longer
+  work in <samp>$FCM/etc/fcm.cfg</samp> but will continue to work in
+  <samp>$HOME/.fcm</samp>. However, it would be desirable to migrate these
+  settings to their equivalents in <samp>$HOME/.metomi/fcm/keyword.cfg</samp>
+  as documented in <a href="annex_cfg.html#keyword">Annex: FCM Configuration
+  File > FCM Keyword Configuration</a>.</p>
+
+  <dl>
+    <dt>SET::URL::<pck><br />
+    SET::REPOS::<pck></dt>
+
+    <dd>
+      <p><samp>$HOME/.metomi/fcm/keyword.cfg</samp> equivalent: <code><a href=
+      "annex_cfg.html#keyword.location">location</a></code> or <code><a href=
+      "annex_cfg.html#keyword.location">location{primary}</a></code>.</p>
+
+      <p>This declares a URL keyword for the package <pck>. The value of
+      the declaration must be a valid Subversion <URL>. Once declared,
+      the URL keyword <pck> will be associated with the specified URL. In
+      subsequent invocations of the <code>fcm</code> command, the following
+      expansion may take place:</p>
+
+      <ul>
+        <li><samp>fcm:<pck></samp>: replaced by
+        <samp><URL></samp>.</li>
+
+        <li><samp>fcm:<pck>_tr</samp> or <samp>fcm:<pck>-tr</samp>:
+        replaced by <samp><URL>/trunk</samp></li>
+
+        <li><samp>fcm:<pck>_br</samp> or <samp>fcm:<pck>-br</samp>:
+        replaced by <samp><URL>/branches</samp></li>
+
+        <li><samp>fcm:<pck>_tg</samp> or <samp>fcm:<pck>-tg</samp>:
+        replaced by <samp><URL>/tags</samp></li>
+      </ul>
+
+      <p>Example:</p>
+      <pre>
+# Associate "var" with "svn://server/VAR_svn/var"
+set::url::var  svn://server/VAR_svn/var
+
+# "fcm:var" is now the same as "svn://server/VAR_svn/var"
+</pre>
+    </dd>
+
+    <dt>SET::REVISION::<pck>::<keyword></dt>
+
+    <dd>
+      <p><samp>$HOME/.metomi/fcm/keyword.cfg</samp> equivalent: <code><a href=
+      "annex_cfg.html#keyword.revision">revision</a></code>.</p>
+
+      <p>This declares <keyword> to be the revision number for the
+      package <pck>. The <keyword> string can contain any
+      characters except spaces. It must not contain only digits (as digits are
+      treated as revision numbers). It must not be the Subversion revision
+      keywords HEAD, BASE, COMMITTED and PREV. It cannot begin and end with a
+      pair of curly brackets (as this will be parsed as a revision date). The
+      package <pck> must be associated with a URL using the
+      SET::URL::<pck> declaration described above before this declaration
+      can make sense. Once defined, <keyword> can be used anywhere in
+      place the defined revision number.</p>
+
+      <p>Example:</p>
+      <pre>
+set::revision::var::v22.0  8410
+
+# E.g. "fcm list -r v22.0 fcm:var" is now the same as
+#      "fcm list -r 8410 fcm:var".
+</pre>
+    </dd>
+
+    <dt>SET::URL_BROWSER_MAPPING_DEFAULT::<key></dt>
+
+    <dd>
+      <p><samp>$HOME/.metomi/fcm/keyword.cfg</samp> equivalent: <code><a href=
+      "annex_cfg.html#keyword.browser.comp-pat">browser.comp-pat</a></code>,
+      <code><a href=
+      "annex_cfg.html#keyword.browser.loc-tmpl">browser.loc-tmpl</a></code>,
+      and <code><a href=
+      "annex_cfg.html#keyword.browser.rev-tmpl">browser.rev-tmpl</a></code>.</p>
+
+      <p>These declarations are used to change the global default for mapping a
+      version control system URL to its corresponding web browser URL.
+      <key> can be LOCATION_COMPONENT_PATTERN, BROWSER_URL_TEMPLATE or
+      BROWSER_REV_TEMPLATE.</p>
+
+      <p>Example:</p>
+      <pre>
+set::url_browser_mapping_default::location_component_pattern ^//([^/]+)/(.*)$
+set::url_browser_mapping_default::browser_url_template http://{1}/intertrac/source:{2}{3}
+set::url_browser_mapping_default::browser_rev_template @{1}
+</pre>
+    </dd>
+
+    <dt>SET::URL_BROWSER_MAPPING::<pck>::<key></dt>
+
+    <dd>
+      <p><samp>$HOME/.metomi/fcm/keyword.cfg</samp> equivalent: <code><a href=
+      "annex_cfg.html#keyword.browser.comp-pat">browser.comp-pat</a></code>,
+      <code><a href=
+      "annex_cfg.html#keyword.browser.loc-tmpl">browser.loc-tmpl</a></code>,
+      and <code><a href=
+      "annex_cfg.html#keyword.browser.rev-tmpl">browser.rev-tmpl</a></code>.</p>
+
+      <p>Similar to SET::URL_BROWSER_MAPPING_DEFAULT::<key>, but settings
+      only apply to the specified <pck>.</p>
+
+      <p>Example:</p>
+      <pre>
+set::url_browser_mapping::var::location_component_pattern ^//([^/]+)/(.*)$
+set::url_browser_mapping::var::browser_url_template http://{1}/intertrac/source:{2}{3}
+set::url_browser_mapping::var::browser_rev_template @{1}
+</pre>
+    </dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/annex_quick_ref.html b/doc/user_guide/annex_quick_ref.html
new file mode 100644
index 0000000..be921cc
--- /dev/null
+++ b/doc/user_guide/annex_quick_ref.html
@@ -0,0 +1,256 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: Annex: Quick Reference</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: Annex: Quick Reference</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>Note: some sub-commands can be invoked with alternate names. For example,
+  <code>fcm help</code> is the same as <code>fcm ?</code>. In this annex, some
+  favourite alternate names are listed, separated by a pipe, i.e. the above
+  example will be given as <samp>fcm help|?</samp>.</p>
+
+  <h2 id="help">Getting help</h2>
+
+  <dl>
+    <dt><code>fcm help|?</code></dt>
+
+    <dd>get list of subcommands</dd>
+
+    <dt><code>fcm help|? SUBCOMMAND</code></dt>
+
+    <dd>get help on SUBCOMMAND</dd>
+  </dl>
+
+  <h2 id="maintaining-wc">Maintaining the working copy</h2>
+
+  <dl>
+    <dt><code>fcm checkout|co [OPTIONS] URL [DEST]</code></dt>
+
+    <dd>Checkout URL (and create a working copy at DEST)</dd>
+
+    <dt><code>fcm checkout|co -r N URL [DEST]</code></dt>
+
+    <dd>Checkout revision N of URL (and create a working copy at DEST)</dd>
+
+    <dt><code>fcm info</code></dt>
+
+    <dd>Print working copy information</dd>
+
+    <dt><code>fcm status|st [OPTIONS]</code></dt>
+
+    <dd>Print status of working copy</dd>
+
+    <dt><code>fcm status|st -u</code></dt>
+
+    <dd>Show update information</dd>
+
+    <dt><code>fcm status|st -v</code></dt>
+
+    <dd>Show verbose information</dd>
+
+    <dt><code>fcm update|up</code></dt>
+
+    <dd>Update working copy with repository changes</dd>
+
+    <dt><code>fcm switch|sw URL</code></dt>
+
+    <dd>Switch your working copy to point to a branch specified by URL</dd>
+
+    <dt><code>fcm commit|ci</code></dt>
+
+    <dd>Commit local changes back into the repository</dd>
+  </dl>
+
+  <h2 id="preparing-changes">Preparing changes</h2>
+
+  <dl>
+    <dt><code>fcm diff|di [OPTIONS]</code></dt>
+
+    <dd>Display working copy changes in unified diff format</dd>
+
+    <dt><code>fcm branch-diff|bdiff|bdi [OPTIONS]</code></dt>
+
+    <dd>Show differences relative to the base of the branch</dd>
+
+    <dt><code>fcm diff|di -g</code></dt>
+
+    <dd>Display working copy changes with a graphical diff tool</dd>
+
+    <dt><code>fcm diff|di -r N</code></dt>
+
+    <dd>Display working copy changes against revision N</dd>
+
+    <dt><code>fcm diff|di -t</code></dt>
+
+    <dd>Display differences in Trac, (with -b only)</dd>
+
+    <dt><code>fcm revert [OPTIONS] PATH</code></dt>
+
+    <dd>Restore the file PATH to the pristine copy</dd>
+
+    <dt><code>fcm revert -R PATH</code></dt>
+
+    <dd>Descend PATH recursively, restoring any modified files to the pristine
+    copy</dd>
+
+    <dt><code>fcm mkdir [PATH]</code></dt>
+
+    <dd>Add a directory PATH under revision control</dd>
+
+    <dt><code>fcm add [OPTIONS] PATH ...</code></dt>
+
+    <dd>Add PATH under revision control</dd>
+
+    <dt><code>fcm add -c [PATH]</code></dt>
+
+    <dd>Check for items not under revision control and add them</dd>
+
+    <dt><code>fcm delete|del|rm [OPTIONS] PATH ...</code></dt>
+
+    <dd>Remove PATH from revision control</dd>
+
+    <dt><code>fcm delete|del|rm -c [PATH]</code></dt>
+
+    <dd>Check for missing items and remove them</dd>
+
+    <dt><code>fcm copy|cp SRC DST</code></dt>
+
+    <dd>Duplicate SRC to DST, remembering history</dd>
+
+    <dt><code>fcm move|mv SRC DST</code></dt>
+
+    <dd>Move or rename SRC to DST, remembering history</dd>
+
+    <dt><code>fcm propset|ps svn:executable ON FILE</code></dt>
+
+    <dd>Indicate that FILE will have executable permission when checked out to a
+    Unix file system.</dd>
+
+    <dt><code>fcm propdel|pd svn:executable FILE</code></dt>
+
+    <dd>Reverse of the above.</dd>
+
+    <dt><code>fcm propset|ps svn:special ON FILE</code></dt>
+
+    <dd>Indicate that FILE is a symbolic link rather than a regular file.</dd>
+
+    <dt><code>fcm propdel|pd svn:special FILE</code></dt>
+
+    <dd>Reverse of the above.</dd>
+  </dl>
+
+  <h2 id="browse">Browsing</h2>
+
+  <dl>
+    <dt><code>fcm log [OPTIONS] [TARGET]</code></dt>
+
+    <dd>Show the log message of a TARGET that can either be working copy or
+    URL</dd>
+
+    <dt><code>fcm log -r N[:M] [TARGET]</code></dt>
+
+    <dd>Show the log message of a range of reivsions</dd>
+
+    <dt><code>fcm propedit|pe --revprop svn:log -r N [TARGET]</code></dt>
+
+    <dd>Edit the commit log message of revision N.</dd>
+
+    <dt><code>fcm list|ls [OPTIONS] [TARGET]</code></dt>
+
+    <dd>List directory entries in TARGET</dd>
+
+    <dt><code>fcm list|ls -r N [TARGET]</code></dt>
+
+    <dd>List directory entries of revision N</dd>
+
+    <dt><code>fcm list|ls -v [TARGET]</code></dt>
+
+    <dd>List directory entries in verbose mode</dd>
+
+    <dt><code>fcm list|ls -R [TARGET]</code></dt>
+
+    <dd>List directory entries recursively down the directories</dd>
+
+    <dt><code>fcm browse [TARGET]</code></dt>
+
+    <dd>Open a WWW browser to browse TARGET with Trac</dd>
+  </dl>
+
+  <h2 id="branch">Branching</h2>
+
+  <dl>
+    <dt><code>fcm branch-info|binfo [OPTIONS] [URL]</code></dt>
+
+    <dd>Show branch information of URL or local working copy</dd>
+
+    <dt><code>fcm branch-delete|bdel [URL]</code></dt>
+
+    <dd>Show branch information and delete the branch</dd>
+
+    <dt><code>fcm branch-create|bcreate NAME [URL]</code></dt>
+
+    <dd>Create a branch</dd>
+
+    <dt><code>fcm branch-list|blist|bls [--show-all|-a] [URL]</code></dt>
+
+    <dd>Lists branches</dd>
+
+    <dt><code>fcm merge [SOURCE]</code></dt>
+
+    <dd>Merge changes from SOURCE to your working copy</dd>
+
+    <dt><code>fcm conflicts|cf</code></dt>
+
+    <dd>Use xxdiff to resolve conflicts in your working copy</dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/annex_quick_ref_tree_conflicts.html b/doc/user_guide/annex_quick_ref_tree_conflicts.html
new file mode 100644
index 0000000..3cd2f99
--- /dev/null
+++ b/doc/user_guide/annex_quick_ref_tree_conflicts.html
@@ -0,0 +1,283 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: Annex: Quick Reference: Tree Conflict Resolution</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: Annex: Quick Reference: Tree Conflict Resolution</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <h2 id="tree:reference:intro">Introduction</h2>
+
+  <p>A tree conflict appears in <code>fcm status</code> like this:</p>
+  <pre>
+!     C subroutine/hello_sub_dummy.h
+      >   local missing, incoming edit upon merge
+</pre>
+
+  <p>and can require complex action to solve, based on the situation. Happily,
+  <code>fcm conflicts</code> automates the resolution of most ordinary tree
+  conflicts.</p>
+
+  <p>This page is intended to give more information about the meaning of the
+  common tree conflicts, and to give guidance on those that aren't handled
+  automatically, such as directory tree conflicts.</p>
+
+  <p>The list below is ordered by output from <code>fcm status</code>. There
+  are two choices that the user must make:</p>
+
+  <ul>
+    <li><em>keep local</em>: Choose the local branch or revision version of a
+    file or directory. Answer <samp>y</samp> in <code>fcm
+    conflicts</code>.</li>
+
+    <li><em>discard local</em>: Choose the merge branch or other revision
+    version of a file or directory. Answer <samp>n</samp> in <code>fcm
+    conflicts</code>.</li>
+  </ul>
+
+  <p>For example, if you are merging the trunk into a working copy of a branch,
+  <em>keep local</em> would refer to <strong>keeping</strong> the changes as
+  they existed on the branch; <em>discard local</em> would refer to accepting
+  the trunk changes and <strong>discarding</strong> the branch ones.</p>
+
+  <p>In this page, we use the word <em>local</em> for your local working copy,
+  and <em>external</em> for the outside source you are updating or merging in
+  from. In the example above, <em>local</em> would mean your working copy of
+  the branch; <em>external</em> would mean the trunk.</p>
+
+  <p>Subversion implements rename as a copy-and-delete operation. This means a
+  rename can show up as a delete (or <em>missing</em>) in the tree conflict
+  information.</p>
+
+  <p>It's very important to find out if your tree conflict arises from a
+  rename, but this information has to be dug out of <code>fcm log</code>. A
+  rename can have occurred locally or externally. An external rename would show
+  up in <code>fcm status</code> as an addition with history (<samp>A</samp>
+  with <samp>+</samp>): for example:</p>
+  <pre>
+A   +   FILENAME
+</pre>
+
+  <p><code>fcm log -v FILENAME</code> can be used to examine if this is really
+  just a rename - it will show up as FILENAME (from ORIGINAL_FILENAME).</p>
+
+  <p>Local renames that have been committed won't show up in <code>fcm
+  status</code>. These can still be found using <code>fcm log -v</code> for
+  each filename, or you can try to remember what happened!</p>
+
+  <h2 id="tree:reference:list">Resolution List</h2>
+
+  <p>This section contains specific help on different types of tree
+  conflict.</p>
+
+  <p>Find the relevant section below by running <code>fcm status</code> and
+  looking up the information below the file in conflict - e.g.:</p>
+  <pre>
+!     C subroutine/hello_sub_dummy.h
+      >   local delete, incoming delete upon merge
+</pre>
+
+  <p>in this case the <samp>local delete, incoming delete upon merge</samp> is
+  the correct section header.</p>
+
+  <p>There are some situations not covered below - <samp>local
+  obstruction</samp> is not covered here, as it is a case of the user
+  corrupting the working copy - try a new checkout. Similarly, <samp>local
+  unversioned</samp> is just a case of a problem with something in the working
+  copy - an unversioned file or directory exists where Subversion wants to put
+  the new stuff. Delete or move it, and try the merge again.</p>
+
+  <p>If you know that a rename has happened, use the <samp>(renaming)</samp>
+  suffix for your section below. Otherwise, choose the <samp>(no
+  renaming)</samp> suffix.</p>
+
+  <p><code>fcm resolve</code> always takes the form <code>fcm resolve --accept
+  working FILENAME</code> for tree conflicts.</p>
+
+  <dl>
+    <dt id="add:add"><samp>local add, incoming add upon merge</samp></dt>
+
+    <dd>
+      <p><dfn>what it means:</dfn>: files or directories added with the same
+      name independently</p>
+
+      <p><dfn>what keep local does</dfn>: uses rename to shuffle the old file
+      to a different name, copies the new file in, renames the new file to the
+      original name but with a temporary-style suffix (e.g. hello.F90 ->
+      hello.F90.xD4r), and again renames the old file to the original name.
+      (Then runs <code>fcm resolve</code>).</p>
+
+      <p><dfn>what discarding local does</dfn>: renames the old file to give it
+      a temporary-style suffix (e.g. hello.F90 -> hello.F90.r6Ys), and
+      copies the new file into the original name. (Then runs <code>fcm
+      resolve</code>).</p>
+    </dd>
+
+    <dt id="edit:delete:no_rename"><samp>local edit, incoming delete upon merge
+    (no renaming)</samp></dt>
+
+    <dd>
+      <p><dfn>meaning</dfn>: file or directory modified on the branch locally,
+      but deleted on the merge branch</p>
+
+      <p><dfn>what keep local does</dfn>:just runs <code>fcm
+      resolve</code>.</p>
+
+      <p><dfn>what discarding local does</dfn>:deletes the file or directory
+      and runs <code>fcm resolve</code>.</p>
+    </dd>
+
+    <dt id="edit:delete:rename"><samp>local edit, incoming delete upon merge
+    (renaming)</samp></dt>
+
+    <dd>
+      <p><dfn>meaning</dfn>: file modified on branch locally, but renamed on
+      merge branch</p>
+
+      <p><dfn>what keep local does</dfn>: copies over the renamed file, and the
+      common ancestor of the file on the branches, and uses them for a text
+      conflict style merge into the old (local) filename. It then removes the
+      renamed file and runs <code>fcm resolve</code>.</p>
+
+      <p><dfn>what discarding local does</dfn>: copies over the renamed file,
+      and the common ancestor of the file on the branches, and uses them for a
+      text conflict style merge into the new renamed file. It then deletes the
+      old file and runs <code>fcm resolve</code>.</p>
+    </dd>
+
+    <dt id="delete:delete:no_rename"><samp>local delete, incoming delete upon
+    merge (no renaming)</samp></dt>
+
+    <dd>
+      <p><dfn>meaning</dfn>: same filename deleted both locally and
+      externally.</p>
+
+      <p><dfn>in both cases</dfn>: just runs <code>fcm resolve</code>.</p>
+    </dd>
+
+    <dt id="delete:rename:rename"><samp>local delete, incoming delete upon
+    merge (just external renaming)</samp></dt>
+
+    <dd>
+      <p><dfn>meaning</dfn>: file deleted locally, but renamed externally</p>
+
+      <p><dfn>what keep local does</dfn>: just runs <code>fcm
+      resolve</code>.</p>
+
+      <p><dfn>what discarding local does</dfn>: just deletes the new renamed
+      file and runs <code>fcm resolve</code>.</p>
+    </dd>
+
+    <dt id="rename:delete:rename"><samp>local delete, incoming delete upon
+    merge (just local renaming)</samp></dt>
+
+    <dd>
+      <p><dfn>meaning</dfn>: file renamed locally, but deleted externally</p>
+
+      <p><dfn>what keep local does</dfn>: just runs <code>fcm
+      resolve</code>.</p>
+
+      <p><dfn>what discarding local does</dfn>: deletes the local renamed file
+      and runs <code>fcm resolve</code>.</p>
+    </dd>
+
+    <dt id="delete:delete:rename"><samp>local delete, incoming delete upon
+    merge (local renaming AND external renaming)</samp></dt>
+
+    <dd>
+      <p><dfn>meaning</dfn>: same file renamed locally AND externally, to two
+      different names.</p>
+
+      <p><dfn>what keep local does</dfn>: copies in the external file and
+      common ancestor file to construct a text-style merge using
+      <code>xxdiff</code> into the locally-renamed filename. Removes the
+      external rename and runs <code>fcm resolve</code>.</p>
+
+      <p><dfn>what discarding local does</dfn>: copies in the external file and
+      common ancestor file to construct a text-style merge using
+      <code>xxdiff</code> into the externally-renamed filename. Removes the
+      local rename and runs <code>fcm resolve</code>.</p>
+    </dd>
+
+    <dt id="missing:edit:no_rename"><samp>local missing, incoming edit upon
+    merge (no renaming)</samp></dt>
+
+    <dd>
+      <p><dfn>meaning</dfn>: locally deleted file, add newer file from merge
+      branch?</p>
+
+      <p><dfn>what keep local does</dfn>: just runs <code>fcm
+      resolve</code>.</p>
+
+      <p><dfn>what discarding local does</dfn>: copies in the external file
+      using its URL and runs <code>fcm resolve</code>.</p>
+    </dd>
+
+    <dt id="missing:edit:rename"><samp>local missing, incoming edit upon merge
+    (renaming)</samp></dt>
+
+    <dd>
+      <p><dfn>meaning</dfn>: locally renamed file, but external changes to the
+      old filename</p>
+
+      <p><dfn>what keep local does</dfn>: copies in the external file and
+      common ancestor to construct a text-style merge using
+      <code>xxdiff</code>, into the locally-renamed filename. Runs <code>fcm
+      resolve</code>.</p>
+
+      <p><dfn>what discarding local does</dfn>: copies in the external file and
+      common ancestor to construct a text-style merge using
+      <code>xxdiff</code>, into the original filename. Deletes the
+      locally-renamed file and adds the original filename, then runs <code>fcm
+      resolve</code>.</p>
+    </dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/api.html b/doc/user_guide/api.html
new file mode 100644
index 0000000..9913835
--- /dev/null
+++ b/doc/user_guide/api.html
@@ -0,0 +1,222 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: A Brief Introduction to the FCM Perl API</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: A Brief Introduction to the FCM Perl API</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>The majority of FCM functionalities are provided by a set of Perl modules.
+  Old modules developed prior to <a href="../release_notes/2-0.html">release
+  2-0</a> reside in the <code>FCM1::*</code> name-space. Modules developed
+  thereafter reside in the <code>FCM::*</code> name-space. These are
+  sub-divided into the following name-spaces:</p>
+
+  <dl>
+    <dt><code>FCM::Class</code></dt>
+
+    <dd>
+      <p>Provides an internal object class framework.</p>
+
+      <p>The majority of the classes in the <code>FCM::*</code> name-space are
+      sub-classes of either <code>FCM::Class::CODE</code> or
+      <code>FCM::Class::HASH</code>. The former creates classes that are
+      blessed <code>CODE</code> references, and is intended for configurable
+      functional (i.e. mostly <em>stateless</em>) objects. The latter creates
+      classes that are blessed <code>HASH</code> references, and is intended
+      for data objects.</p>
+
+      <p>Note: In theory, we could use the standard module <code><a href=
+      "http://search.cpan.org/~rjbs/perl-5.12.3/lib/Class/Struct.pm">Class::Struct</a></code>
+      or the modern <a href="http://www.iinteractive.com/moose/">MOOSE</a>
+      framework. The problem is that the former is not powerful enough to give
+      us what we need, and the latter is not a standard module and is too heavy
+      weight for our intends and purposes. Instead, the developer decides that
+      it is easier to go for a light weight and in house solution.</p>
+    </dd>
+
+    <dt><code>FCM::CLI</code></dt>
+
+    <dd>
+      <p>Provides the logic and configuration of the command line interface
+      (CLI).</p>
+
+      <p>It is made up of the following components:</p>
+
+      <dl>
+        <dt><code>FCM::CLI</code></dt>
+
+        <dd>Logic to provide help and invoke functions of
+        <code>FCM::System</code>.</dd>
+
+        <dt><code>FCM::CLI::Exception</code></dt>
+
+        <dd>CLI exception.</dd>
+
+        <dt><code>FCM::CLI::Parser</code></dt>
+
+        <dd>CLI option parser and configuration.</dd>
+
+        <dt><samp>fcm-*.pod</samp></dt>
+
+        <dd>Help files for the CLI commands.</dd>
+      </dl>
+    </dd>
+
+    <dt><code>FCM::Context::*</code></dt>
+
+    <dd>
+      <p>Provides the data structures for storing the run time contexts.</p>
+
+      <p>The objects of these classes do very little, but they provide the data
+      structures that define the <em>states</em> of the program at run
+      time.</p>
+    </dd>
+
+    <dt><code>FCM::Exception</code></dt>
+
+    <dd>
+      <p>Provides the base class for exceptions.</p>
+    </dd>
+
+    <dt><code>FCM::System</code></dt>
+
+    <dd>
+      <p>Provides a façade to the functionalities of the FCM system.</p>
+
+      <p>The actual implementation is delegated to the following:</p>
+
+      <dl>
+        <dt><code>FCM::System::CM</code></dt>
+
+        <dd>The code management system. Currently a thin adapter to
+        <code>FCM1::Cm</code>.</dd>
+
+        <dt><code>FCM::System::Misc</code></dt>
+
+        <dd>Miscellaneous functions, e.g. <code>browse</code>,
+        <code>cfg-print</code>, <code>keyword-print</code>.</dd>
+
+        <dt><code>FCM::System::Old</code></dt>
+
+        <dd>Thin adapter to the old extract and build systems.</dd>
+
+        <dt><code>FCM::System::Make</code></dt>
+
+        <dd>The logic of the FCM make system.</dd>
+
+        <dt><code>FCM::System::Make::Build</code></dt>
+
+        <dd>FCM make: build system logic.</dd>
+
+        <dt><code>FCM::System::Make::Build::*</code></dt>
+
+        <dd>FCM make: build system components: File type and task specific
+        logic.</dd>
+
+        <dt><code>FCM::System::Make::Extract</code></dt>
+
+        <dd>FCM make: extract system logic.</dd>
+
+        <dt><code>FCM::System::Make::Mirror</code></dt>
+
+        <dd>FCM make: mirror system logic.</dd>
+
+        <dt><code>FCM::System::Make::Preprocess</code></dt>
+
+        <dd>FCM make: preprocess system logic, actually a configuration of
+        <code>FCM::System::Make::Build</code>.</dd>
+
+        <dt><code>FCM::System::Make::Share::*</code></dt>
+
+        <dd>Shared logic between all subsystems in
+        <code>FCM::System::Make::*</code>.</dd>
+      </dl>
+    </dd>
+
+    <dt><code>FCM::Util</code></dt>
+
+    <dd>
+      <p>Provides supporting utilities.</p>
+
+      <p>Functionalities include:</p>
+
+      <ul>
+        <li>abstract utilities for SVN URLs and file system paths.</li>
+
+        <li>configuration file reader.</li>
+
+        <li>event handler.</li>
+
+        <li>file utilities.</li>
+
+        <li>message report.</li>
+
+        <li>name space utilities.</li>
+
+        <li>shell invocation.</li>
+
+        <li>multi-process task runner.</li>
+
+        <li>timer.</li>
+      </ul>
+
+      <p>The logic of the more complex utilities are delegated to modules in
+      the <code>FCM::Util::*</code> name space.</p>
+    </dd>
+  </dl>
+
+  <p>Note: The majority of modules in the old <code>FCM1::*</code> name space
+  are considered deprecated, with the exception of <code>FCM1::Cm</code> and
+  those providing support functionalities for it. The functionalities of these
+  modules will eventually be absorbed into the <code>FCM::System::CM</code>
+  framework.</p>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/build.html b/doc/user_guide/build.html
new file mode 100644
index 0000000..12ec032
--- /dev/null
+++ b/doc/user_guide/build.html
@@ -0,0 +1,1646 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: Annex: The FCM 1 Build System</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: Annex: The FCM 1 Build System</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <h2 id="introduction">Introduction</h2>
+
+  <p><em>The FCM 1 build system is deprecated. The documentation for the current
+  build system can be found at <a href="make.html">FCM Make</a>.</em></p>
+
+  <p>The build system analyses the directory tree containing a set of source
+  code, processes the configuration, and invokes <code>make</code> to
+  compile/build the source code into the project executables. In this chapter,
+  we shall use many examples to explain how to use the build system. At the end
+  of this chapter, you should be able to use the build system, either by
+  defining the build configuration file directly or by using the extract system
+  to generate a suitable build configuration file.</p>
+
+  <h2 id="command">The Build Command</h2>
+
+  <p>To invoke the build system, simply issue the command:</p>
+  <pre>
+fcm build
+</pre>
+
+  <p>By default, the build system searches for a build configuration file
+  <samp>bld.cfg</samp> in <samp>$PWD</samp> and then <samp>$PWD/cfg</samp>. If
+  a build configuration file is not found in these directories, the command
+  fails with an error. If a build configuration file is found, the system will
+  use the configuration specified in the file to perform the build. If you use
+  the extract system to extract your source tree, a build configuration should
+  be written for you automatically at the <samp>cfg/</samp> sub-directory of
+  the destination root directory.</p>
+
+  <p>If the root directory of the build does not exist, the system performs a
+  new full build at this directory. If a previous build already exists at this
+  directory, the system performs an incremental build. If a full (fresh) build
+  is required for whatever reason, you can invoke the build system using the
+  <code>-f</code> option, (i.e. the command becomes <code>fcm build -f</code>).
+  If you simply want to remove all the items generated by a previous build in
+  the destination, you can invoke the build system using the
+  <code>--clean</code> option.</p>
+
+  <p>The build system uses GNU <code>make</code> to perform the majority of the
+  build. GNU <code>make</code> has a <code>-j jobs</code> option to specify the
+  number of <var>jobs</var> to run simultaneously. Invoking the build system
+  with the same option triggers this option when the build system invokes the
+  <code>make</code> command. The argument to the option <var>jobs</var> must be
+  an integer. The default is <samp>1</samp>. For example, the command <code>fcm
+  build -j 4</code> will allow <code>make</code> to perform 4 jobs
+  simultaneously.</p>
+
+  <p>For further information on the build command, please see <a href=
+  "command_ref.html#fcm-build">FCM Command Reference > fcm build</a>.</p>
+
+  <h2 id="basic">Basic Features</h2>
+
+  <p>The build configuration file is the user interface of the build system. It
+  is a line based text file. You can create your own build configuration file
+  or you can use the extract system to create one for you. For a complete set
+  of build configuration file declarations, please refer to the <a href=
+  "annex_bld_cfg.html">Annex: Declarations in FCM build configuration
+  file</a>.</p>
+
+  <h3 id="basic_build">Basic build configuration</h3>
+
+  <p>Suppose we have a directory at <samp>$HOME/example</samp>. Its
+  sub-directory at <samp>$HOME/example/src</samp> contains a source tree to be
+  built. You may want to have a build configuration file
+  <samp>$HOME/example/cfg/bld.cfg</samp>, which may contain:</p>
+  <pre id="example_1">
+# Example 1
+# ----------------------------------------------------------------------
+cfg::type     bld                           # line 1
+cfg::version  1.0                           # line 2
+
+dest          $HOME/example                 # line 4
+
+target        foo.exe bar.exe               # line 6
+
+tool::fc      ifort                         # line 8
+tool::fflags  -O3                           # line 9
+tool::cc      gcc                           # line 10
+tool::cflags  -O3                           # line 11
+
+tool::ldflags -O3 -L$(HOME)/lib -legg -lham # line 13
+</pre>
+
+  <p>Here is an explanation of what each line does:</p>
+
+  <ul>
+    <li><dfn>line 1</dfn>: the label <code>CFG::TYPE</code> declares the type
+    of the configuration file. The value <samp>bld</samp> tells the system that
+    it is a build configuration file.</li>
+
+    <li><dfn>line 2</dfn>: the label <code>CFG::VERSION</code> declares the
+    version of the build configuration file. The current default is
+    <samp>1.0</samp>. Although it is not currently used, if we have to change
+    the format of the configuration file at a later stage, we shall be able to
+    use this number to determine whether we are reading a file with an older
+    format or one with a newer format.</li>
+
+    <li><dfn>line 4</dfn>: the label <code>DEST</code> declares the root
+    directory of the current build.</li>
+
+    <li><dfn>line 6</dfn>: the label <code>TARGET</code> declares a list of
+    <em>default</em> targets. The default targets of the current build will be
+    <samp>foo.exe</samp> and <samp>bar.exe</samp>.</li>
+
+    <li><dfn>line 8</dfn>: the label <code>TOOL::FC</code> declares the Fortran
+    compiler command.</li>
+
+    <li><dfn>line 9</dfn>: the label <code>TOOL::FFLAGS</code> declares the
+    options to be used when invoking the Fortran compiler command.</li>
+
+    <li><dfn>line 10</dfn>: the label <code>TOOL::CC</code> declares the C
+    compiler command.</li>
+
+    <li><dfn>line 11</dfn>: the label <code>TOOL::CFLAGS</code> declares the
+    options to be used when invoking the C compiler command.</li>
+
+    <li><dfn>line 13</dfn>: the label <code>TOOL::LDFLAGS</code> declares the
+    options to be used when invoking the linker command.</li>
+  </ul>
+
+  <p>When we invoke the build system, it reads the above configuration file. It
+  will go through various internal processes, such as dependency generations,
+  to obtain the required information to prepare the <samp>Makefile</samp> of
+  the build. (All of which will be described in later sections.) The
+  <samp>Makefile</samp> of the build will be placed at
+  <samp>$HOME/example/bld</samp>. The system will then invoke <code>make</code>
+  to build the targets specified in line 6, i.e. <samp>foo.exe</samp> and
+  <samp>bar.exe</samp> using the build tools specified between line 8 to line
+  13. On a successful build, the target executables will be sent to
+  <samp>$HOME/example/bin/</samp>. The build system also creates a shell script
+  called <samp>fcm_env.sh</samp> in <samp>$HOME/example/</samp>. If you source
+  the shell script, it will export your <var>PATH</var> environment variable to
+  search the <samp>$HOME/example/bin/</samp> directory for executables.</p>
+
+  <p>N.B. You may have noticed that the <code>-c</code> (compile to object file
+  only) option is missing from the compiler flags declarations. This is because
+  the option is inserted automatically by the build system, unless it is
+  already declared.</p>
+
+  <p>N.B. You can declare the linker using <code>TOOL::LD</code>. If it is not
+  specified, the default is to use the compiler command for the source file
+  containing the main program.</p>
+
+  <dl>
+    <dt>Note - declaration of source files for build</dt>
+
+    <dd>
+      <p>Source files do not have to reside in the <samp>src/</samp>
+      sub-directory of the build root directory. They can be anywhere, but you
+      will have to declare them using the label <code>SRC::<pcks></code>,
+      where <code><pcks></code> is the sub-package name in which the
+      source belongs. If a directory is specified then the build system
+      automatically searches for all source files in this directory. E.g.</p>
+      <pre>
+# Declare a source in the sub-package "foo/bar"
+src::foo/bar  $HOME/foo/bar
+</pre>
+
+      <p>By default, the build system searches the <samp>src/</samp>
+      sub-directory of the build root directory for source files. If all source
+      files are already declared explicitly, you can switch off the automatic
+      directory search by setting the <code>SEARCH_SRC</code> flag to false.
+      E.g.</p>
+      <pre>
+search_src  false
+</pre>
+
+      <p>As mentioned in the previous chapter, the name of a sub-package
+      <pcks> provides a unique namespace for a file. The name of a
+      sub-package is a list of words delimited by a slash <code>/</code>. (The
+      system uses the double colons <code>::</code> and the double underscores
+      <code>__</code> internally. Please avoid using <code>::</code> and
+      <code>__</code> for naming your files and directories.)</p>
+
+      <p>Currently, the build system only supports non-space characters in the
+      package name, as the space character is used as a delimiter between the
+      declaration label and its value. If there are spaces in the path name to
+      a file or directory, you should explicity re-define the package name of
+      that path to a package name with no space using the above method.
+      However, we recommend that only non-space characters are used for naming
+      directories and files to make life simple.</p>
+
+      <p>In the build system, the sub-package name also provides an
+      <em>inheritance</em> relationship for sub-packages. For instance, we may
+      have a sub-package called <samp>foo/bar/egg</samp>, which belongs to the
+      sub-package <samp>foo/bar</samp>, which belongs to the package
+      <samp>foo</samp>.</p>
+
+      <ul>
+        <li>If we declare a global build tool, it applies to all packages.</li>
+
+        <li>If we declare a build tool for <samp>foo</samp>, it applies also to
+        the sub-package <samp>foo/bar</samp> and <samp>foo/bar/egg</samp>.</li>
+
+        <li>If we declare a build tool for <samp>foo/bar</samp>, it applies
+        also to <samp>foo/bar/egg</samp>, but not to other sub-packages in
+        <samp>foo</samp>.</li>
+      </ul>
+    </dd>
+  </dl>
+
+  <h3 id="basic_extract">Build configuration via the extract system</h3>
+
+  <p>As mentioned earlier, you can obtain a build configuration file through
+  the extract system. The following example is what you may have in your
+  extract configuration in order to obtain a similar configuration as <a href=
+  "#example_1">example 1</a>:</p>
+  <pre id="example_2">
+# Example 2
+# ----------------------------------------------------------------------
+cfg::type          ext                           # line 1
+cfg::version       1.0                           # line 2
+
+dest               $HOME/example                 # line 4
+
+bld::target        foo.exe bar.exe               # line 6
+
+bld::tool::fc      ifort                         # line 8
+bld::tool::fflags  -O3                           # line 9
+bld::tool::cc      gcc                           # line 10
+bld::tool::cflags  -O3                           # line 11
+
+bld::tool::ldflags -O3 -L$(HOME)/lib -legg -lham # line 13
+
+# ... and other declarations for source locations ...
+</pre>
+
+  <p>It is easy to note the similarities and differences between <a href=
+  "#example_1">example 1</a> and <a href="#example_2">example 2</a>. <a href=
+  "#example_2">Example 2</a> is an extract configuration file. It extracts to a
+  destination root directory that will become the root directory of the build.
+  Line 6 to line 13 are the same declarations, except that they are now
+  prefixed with <code>BLD::</code>. In an extract configuration file, any lines
+  prefixed with <code>BLD::</code> means that they are build configuration
+  setting. These lines are ignored by the extract system but are parsed down to
+  the output build configuration file, with the <code>BLD::</code> prefix
+  removed. (Note: the <code>BLD::</code> prefix is optional for declarations in
+  a build configuration file.)</p>
+
+  <p>N.B. If you use the extract system to mirror an extract to an alternate
+  location, the extract system will assume that the root directory of the
+  alternate destination is the root directory of the build, and that the build
+  will be carried out in that destination.</p>
+
+  <h3 id="basic_exename">Naming of executables</h3>
+
+  <p>If a source file called <samp>foo.f90</samp> contains a main program, the
+  default behaviour of the system is to name its executable
+  <samp>foo.exe</samp>. The root name of the executable is the same as the
+  original file name, but its file extension is replaced with
+  <samp>.exe</samp>. The output extension can be altered by re-registering the
+  extension for output EXE files. How this can be done will be discussed later
+  in the sub-section <a href="#advanced_file-type">File Type</a>.</p>
+
+  <p>If you need to alter the full name of the executable, you can use the
+  <code>EXE_NAME::</code> declaration. For example, the declaration:</p>
+  <pre>
+bld::exe_name::foo  bar
+</pre>
+
+  <p>will rename the executable of <samp>foo.f90</samp> from
+  <samp>foo.exe</samp> to <samp>bar</samp>.</p>
+
+  <p>Note: the declaration label is <code>bld::exe_name::foo</code> (not
+  <code>bld::exe_name::foo.exe</code>) and the executable will be named
+  <samp>bar</samp> (not <samp>bar.exe</samp>).</p>
+
+  <h3 id="basic_flags">Setting the compiler flags</h3>
+
+  <p>As discussed in the first example, the compiler commands and their flags
+  can be set via the <code>TOOL::</code> declarations. A simple
+  <code>TOOL::FFLAGS</code> declaration, for example, alters the compiler
+  options for compiling all Fortran source files in the build. If you need to
+  alter the compiler options only for the source files in a particular
+  sub-package, it is possible to do so by adding the sub-package name to the
+  declaration label. For example, the declaration label
+  <code>TOOL::FFLAGS::foo/bar</code> will ensure that the declaration only
+  applies to the code in the sub-package <samp>foo/bar</samp>. You can even
+  make declarations down to the individual source file level. For example, the
+  declaration label <code>TOOL::FFLAGS::foo/bar/egg.f90</code> will ensure that
+  the declaration applies only for the file <samp>foo/bar/egg.f90</samp>.</p>
+
+  <p>N.B. Although the prefix <code>TOOL::</code> and the tool names are
+  case-insensitive, sub-package names are case sensitive in the declarations.
+  Internally, tool names are turned into uppercase, and the sub-package
+  delimiters are changed from the slash <code>/</code> (or double colons
+  <code>::</code>) to the double underscores <code>__</code>. When the system
+  generates the <samp>Makefile</samp> for the build, each <code>TOOL</code>
+  declaration will be exported as an environment variable. For example, the
+  declaration <code>tool::fflags/foo/bar</code> will be exported as
+  <samp>FFLAGS__foo__bar</samp>.</p>
+
+  <p>N.B. <code>TOOL</code> declarations for sub-packages are only accepted by
+  the system when it is sensible to do so. For example, it allows you to
+  declare different compiler flags, linker commands and linker flags for
+  different sub-packages, but it does not accept different compilers for
+  different sub-packages. If you attempt to make a <code>TOOL</code>
+  declaration for a sub-package that does not exist, the build system will exit
+  with an error.</p>
+
+  <p>The following is an example setting in an extract configuration file based
+  on <a href="#example_2">example 2</a>:</p>
+  <pre id="example_3">
+# Example 3
+# ----------------------------------------------------------------------
+cfg::type              ext
+cfg::version           1.0
+
+dest                   $HOME/example
+
+bld::target            foo.exe bar.exe
+
+bld::tool::fc          ifort
+bld::tool::fflags      -O3    # line 9
+bld::tool::cc          gcc
+bld::tool::cflags      -O3
+
+bld::tool::ldflags     -L$(HOME)/lib -legg -lham
+
+bld::tool::fflags::ops -O1 -C # line 15
+bld::tool::fflags::gen -O2    # line 16
+
+# ... and other declarations for repositories and source directories ...
+</pre>
+
+  <p>In the example above, line 15 alters the Fortran compiler flags for
+  <samp>ops</samp>, so that all source files in <samp>ops</samp> will be
+  compiled with optimisation level 1 and will have runtime error checking
+  switched on. Line 16, alters the Fortran compiler flags for <samp>gen</samp>,
+  so that all source files in <samp>gen</samp> will be compiled with
+  optimisation level 2. All other Fortran source files will use the global
+  setting declared at line 9, so they they will all be compiled with
+  optimisation level 3.</p>
+
+  <dl>
+    <dt>Note - changing compiler flags in incremental builds</dt>
+
+    <dd>
+      <p>Suppose you have performed a successful build using the configuration
+      in <a href="#example_3">example 3</a>, and you have decided to change
+      some of the compiler flags, you can do so by altering the appropriate
+      flags in the build configuration file. When you trigger an incremental
+      build, the system will detect changes in compiler flags automatically,
+      and update only the required targets. The following hierarchy is
+      followed:</p>
+
+      <ul>
+        <li>If the compiler flags for a particular source file change, only
+        that source file and any targets depending on that source file are
+        re-built.</li>
+
+        <li>If the compiler flags for a container package change, only source
+        files within that container package and any targets depending on those
+        source files are re-built.</li>
+
+        <li>If the global compiler flags change, all source files are
+        re-built.</li>
+
+        <li>If the compiler command changes, all source files are
+        re-built.</li>
+      </ul>
+    </dd>
+  </dl>
+
+  <p>N.B. For a full list of build tools declarations, please see <a href=
+  "annex_bld_cfg.html#tools-list">Annex: Declarations in FCM build
+  configuration file > list of tools</a>.</p>
+
+  <h3 id="basic_interface">Automatic Fortran 9X interface block</h3>
+
+  <p>For each Fortran 9X source file containing standalone subroutines and/or
+  functions, the system generates an interface file and sends it to the
+  <samp>inc/</samp> sub-directory of the build root. An interface file contains
+  the interface blocks for the subroutines and functions in the original source
+  file. In an incremental build, if you have modified a Fortran 9X source file,
+  its interface file will only be re-generated if the content of the interface
+  has changed.</p>
+
+  <p>Consider a source file <samp>foo.f90</samp> containing a subroutine called
+  <samp>foo</samp>. In a normal operation, the system writes the interface file
+  to <samp>foo.interface</samp> in the <samp>inc/</samp> sub-directory of the
+  build root. By default, the root name of the interface file is the same as
+  that of the source file, and is case sensitive. You can change this behaviour
+  using a <code>TOOL::INTERFACE</code> declaration. E.g.:</p>
+  <pre>
+bld::tool::interface  program # The default is "file"
+</pre>
+
+  <p>In such case, the root name of the interface file will be named in lower
+  case after the first program unit in the file.</p>
+
+  <p>The default extension for an interface file is <samp>.interface</samp>.
+  This can be modified through the input and output file type register, which
+  will be discussed in a later section on <a href="#advanced_file-type">File
+  Type</a>.</p>
+
+  <p>In most cases, we modify procedures without altering their calling
+  interfaces. Consider another source file <samp>bar.f90</samp> containing a
+  subroutine <samp>bar</samp>. If <samp>bar</samp> calls <samp>foo</samp>, it
+  is good practice for <samp>bar</samp> to have an explicit interface for
+  <samp>foo</samp>. This can be achieved if the subroutine <samp>bar</samp> has
+  the following within its declaration section:</p>
+  <pre>
+INCLUDE 'foo.interface'
+</pre>
+
+  <p>The source file <samp>bar.f90</samp> is now dependent on the interface
+  file <samp>foo.interface</samp>. This can make incremental build very
+  efficient, as changes in the <samp>foo.f90</samp> file will not normally
+  trigger the re-compilation of <samp>bar.f90</samp>, provided that the
+  interface of the <code>subroutine foo</code> remains unchanged. (However, the
+  system is clever enough to know that it needs to re-link any executables that
+  are dependent on the object file for the <code>subroutine bar</code>.)</p>
+
+  <p>By default, the system uses its own internal logic to extract the calling
+  interfaces of top level subroutines and functions in a Fortran source file to
+  generate an interface block. However, the system can also work with the
+  interface generator <code>f90aib</code>, which is a freeware obtained from
+  <a href=
+  "http://www.ifremer.fr/ditigo/molagnon/fortran90/contenu.html">Fortran 90
+  texts and programs, assembled by Michel Olagnon</a> at the French Research
+  Institute for Exploitation of the Sea. To do so, you need to make a
+  declaration in the build configuration file using the label
+  <code>TOOL::GENINTERFACE</code>. As for any other <code>TOOL</code>
+  declarations, you can attach a sub-package name to the label. The change will
+  then apply only to source files within that sub-package. If
+  <code>TOOL::GENINTERFACE</code> is declared to have the value
+  <code>NONE</code>, interface generation will be switched off. The following
+  are some examples:</p>
+  <pre id="example_4">
+# Example 4
+# ----------------------------------------------------------------------
+# This is an EXTRACT configuration file ...
+
+# ... some other declarations ...
+
+bld::tool::geninterface       f90aib # line 5
+bld::tool::geninterface::bar  none   # line 6
+
+# ... some other declarations ...
+</pre>
+
+  <p>In line 5, the global interface generator is now set to
+  <code>f90aib</code>. In line 6, by setting the interface generator for the
+  package <samp>bar</samp> to the <code>none</code> keyword, no interface file
+  will be generated for source files under the package <samp>bar</samp>.</p>
+
+  <p>Switching off the interface block generator can be useful in many
+  circumstances. For example, if the interface block is already provided
+  manually within the source tree, or if the interface block is never used by
+  other program units, it is worth switching off the interface generator for
+  the source file to speed up the build process.</p>
+
+  <h3 id="basic_dependency">Automatic dependency</h3>
+
+  <p>The build system has a built-in dependency scanner, which works out the
+  dependency relationship between source files, so that they can be built in
+  the correct order. The system scans all source files of known types for all
+  supported dependency patterns. Dependencies of source files in a sub-package
+  are written in a cache, which can be retrieved for incremental builds. (In an
+  incremental build, only changed source files need to be re-scanned for
+  dependency information. Dependency information for other files are retrieved
+  from the cache.) The dependency information is passed to the
+  <code>make</code> rule generator, which writes the <samp>Makefile</samp>.</p>
+
+  <p>The <code>make</code> rule generator generates different <code>make</code>
+  rules for different dependency types. The following dependency patterns are
+  automatically detected by the current system:</p>
+
+  <ul>
+    <li>The <code>USE <module></code> statement in a Fortran source file
+    is the first pattern. The statement has two implications: 1) The current
+    file compiles only if the module has been successfully compiled, and needs
+    to be re-compiled if the module has changed. 2) The executable depending on
+    the current file can only resolve all its externals by linking with the
+    object file of the compiled module. The executable needs to be re-linked if
+    the module and its dependencies has changed.</li>
+
+    <li>The <code>INCLUDE '<name>.interface'</code> statement in a
+    Fortran source file is the second pattern. (The default extension for an
+    interface file is <samp>.interface</samp>. This can be modified through the
+    input and output file type register, which will be discussed in a later
+    section on <a href="#advanced_file-type">File Type</a>.) It has two
+    implications: 1) The current file compiles only if the included interface
+    file is in the INCLUDE search path, and needs to be re-compiled if the
+    interface file changes. 2) The executable depending on the current file can
+    only resolve all its externals by linking with the object file of the
+    source file that generates the interface file. The executable needs to be
+    re-linked if the source file (and its dependencies) associated with the
+    interface file has changed. It is worth noting that for this dependency to
+    work, the root <name> of the interface file should match with that of
+    the source file associated with the interface file. (Please note that you
+    can use pre-processor [#include "<name>.interface] instead of Fortran
+    INCLUDE, but it will not work if you switch on the <a href=
+    "#advanced_pp">pre-processing</a> stage, which will be discussed in a later
+    section.)</li>
+
+    <li>The <code>INCLUDE '<file>'</code> statement (excluding the
+    INCLUDE interface file statement) in a Fortran source file is the third
+    pattern. It has two implications: 1) The current file compiles only if the
+    included file is in the INCLUDE search path, and needs to be re-compiled if
+    the include file changes. 2) The executable needs to be linked with any
+    objects the include file is dependent on. It needs to be re-linked if these
+    objects have changed.</li>
+
+    <li>The <code>#include '<file>'</code> statement in a Fortran/C
+    source or header file is the fourth pattern. It has similar implications as
+    the Fortran INCLUDE statement. However, they have to be handled differently
+    because <code>#include</code> statements are processed by the
+    pre-processor, which may be performed in a separate stage of the FCM build
+    process. This will be further discussed in a later sub-section on <a href=
+    "#advanced_pp">Pre-processing</a>.</li>
+  </ul>
+
+  <p>If you want your code to be built automatically by the FCM build system,
+  you should also design your code to conform to the following rules:</p>
+
+  <ol>
+    <li>Single compilable program unit, (i.e. program, subroutine, function or
+    module), per file.</li>
+
+    <li>Unique name for each compilable program unit.</li>
+
+    <li>Always supply an interface for subroutines and functions, i.e.:
+
+      <ul>
+        <li>Put them in modules.</li>
+
+        <li>Put them in the CONTAINS section within the main program unit.</li>
+
+        <li>Use interface files.</li>
+      </ul>
+    </li>
+
+    <li>If interface files are used, it is good practice to name each source
+    file after the program unit it contains. It will make life a lot simpler
+    when using the <a href="#basic_interface">Automatic Fortran 9X interface
+    block</a> feature, which has already been discussed in the previous
+    section.
+
+      <ul>
+        <li>The problem is that, by default, the root name of the interface
+        file is the same as that of the source file rather than the program
+        unit. If they differ then the build system will create a dependency on
+        the wrong object file (since the object files are named according to
+        the program unit).</li>
+
+        <li>This problem can be avoided by changing the behaviour of the
+        interface file generator to use the name of the program unit instead
+        (using a <code>TOOL::INTERFACE</code> declaration).</li>
+      </ul>
+    </li>
+  </ol>
+
+  <dl>
+    <dt>Note - setting build targets</dt>
+
+    <dd>
+      <p>The <samp>Makefile</samp> generated by the build system contains a
+      list of targets that can be built. The build system allows you to build
+      (or perform the actions of) any targets that are present in the generated
+      <samp>Makefile</samp>. There are two ways to specify the targets to be
+      built.</p>
+
+      <p>Firstly, you can use the <code>TARGET</code> declarations in your
+      build configuration file to specify the default targets to be built.
+      These targets will be set as dependencies of the <samp>all</samp> target
+      in the generated <samp>Makefile</samp>, which is the default target to be
+      built when <code>make</code> is invoked by FCM. It is worth noting that
+      <code>TARGET</code> declarations are cumulative. A later declaration does
+      not override an earlier one - it simply adds more targets to the
+      list.</p>
+
+      <p>Alternatively, you can use the <code>-t</code> option when you invoke
+      the <code>fcm build</code> command. The option takes an argument, which
+      should be a colon <code>:</code> separated list of targets to be built.
+      When the <code>-t</code> option is set, FCM invokes <code>make</code> to
+      build these targets instead. (E.g. if we invoke the build system with the
+      command <code>fcm build -t foo.exe:bar.exe</code>, it will invoke
+      <code>make</code> to build <samp>foo.exe</samp> and
+      <samp>bar.exe</samp>.)</p>
+
+      <p>If you do not specify any explicit targets, the system will search
+      your source tree for main programs:</p>
+
+      <ul>
+        <li>If there are main programs in your source tree, they will be set as
+        the default targets automatically.</li>
+
+        <li>Otherwise, the default is to build the top level library archive
+        containing objects compiled from the source files in the current source
+        tree. (For more information on building library archives, please see
+        the section on <a href="#advanced_library">Creating library
+        archives</a>.)</li>
+      </ul>
+    </dd>
+  </dl>
+
+  <h2 id="advanced">Advanced Features</h2>
+
+  <h3 id="advanced_dependency">Further dependency features</h3>
+
+  <p>Apart from the usual dependency patterns described in the previous
+  sub-section, the automatic dependency scanner also recognises two special
+  directives when they are inserted into a source file:</p>
+
+  <ul>
+    <li>The directive <code>DEPENDS ON: <object></code> in a comment line
+    of a Fortran/C source file: It states that the current file is dependent on
+    the declared external object. The executable depending on the current file
+    needs to link with this external object in order to resolve all its
+    external references. It needs to be re-linked if the declared external
+    object (and its dependencies) has changed.</li>
+
+    <li>The directive <code>CALLS: <executable></code> in a comment line
+    of a script: It states that the current script is dependent on the declared
+    executable file, which can be another script or a binary executable. The
+    current script can only function correctly if the declared executable is
+    found in the search path. This directive is useful to ensure that all
+    dependent executables are built or copied to the correct path.</li>
+  </ul>
+
+  <p>Another way to specify external dependency is to use the
+  <code>EXE_DEP</code> declaration to declare extra dependencies. The
+  declaration normally applies to all main programs, but if the form
+  <code>EXE_DEP::<target></code> is used, it will only apply to
+  <target>, (which must be the name of a main program target). If the
+  declaration is made without a value, the main programs will be set to depend
+  on all object files. Otherwise, the value can be supplied as a space
+  delimited list of items. Each item can be either the name of a sub-package or
+  an object target. For the former, the main programs will be set to depend on
+  all object files within the sub-package. For the latter, the main programs
+  will be set to depend on the object target. The following are some
+  examples:</p>
+  <pre id="example_5">
+# Example 5
+# ----------------------------------------------------------------------
+cfg::type          ext
+cfg::version       1.0
+
+bld::exe_dep::foo.exe  foo/bar egg.o # line 4
+bld::exe_dep                         # line 5
+# ... some other declarations ...
+</pre>
+
+  <p>Here is an explanation of what each line does:</p>
+
+  <ul>
+    <li><dfn>line 4</dfn>: this line declares the dependency on the sub-package
+    <samp>foo/bar</samp> and the object target <samp>egg.o</samp> for building
+    the main program target <samp>foo.exe</samp>. The target
+    <samp>foo.exe</samp> will now depends on all object files in the
+    <samp>foo/bar</samp> sub-package as well as the object target
+    <samp>egg.o</samp>.</li>
+
+    <li><dfn>line 5</dfn>: this line declares that all other main program
+    targets will depend on all (non-program) object files in the build.</li>
+  </ul>
+
+  <dl>
+    <dt>Note - naming of object files</dt>
+
+    <dd>
+      <p>By default, object files are named with the suffix <samp>.o</samp>.
+      For a Fortran source file, the build system uses the lower case name of
+      the first program unit within the file to name its object file. For
+      example, if the first program unit in the Fortran source file
+      <samp>foo.f90</samp> is <code>PROGRAM Bar</code>, the object file will be
+      <samp>bar.o</samp>. For a C source file, the build system uses the lower
+      case root name of the source file to name its object file. For example, a
+      C source file called <samp>egg.c</samp> will have its object file named
+      <samp>egg.o</samp>.</p>
+
+      <p>The reason for using lower case to name the object files is because
+      Fortran is a case insensitive language. Its symbols can either be in
+      lower or upper case. E.g. the <code>SUBROUTINE Foo</code> is the same as
+      the <code>SUBROUTINE foo</code>. It can be rather confusing if the
+      subroutines are stored in different files. When they are compiled and
+      archived into a library, there will be a clash of namespace, as the
+      Fortran compiler thinks they are the same. However, this type of error
+      does not normally get reported. If <samp>Foo</samp> and <samp>foo</samp>
+      are very different code, the user may end up using the wrong subroutine,
+      which may lead to a very long debugging session. By naming all object
+      files in lower case, this type of situation can be avoided. If there is a
+      clash in names due to the use of upper/lower cases, it will be reported
+      as warnings by the build system, (as <em>duplicated targets</em> for
+      building <samp>foo.o</samp>).</p>
+    </dd>
+  </dl>
+
+  <p>It is realised that there are situations when an automatically detected
+  dependency should not be written into the <samp>Makefile</samp>. For example,
+  the dependency may be a standard module provided by the Fortran compiler, and
+  does not need to be built in the usual way. In such case, we need to have a
+  way to exclude this module during an automatic dependency scan.</p>
+
+  <p>The <code>EXCL_DEP</code> declaration can be used to do just that. The
+  following extract configuration contains some examples of the basic usage of
+  the <code>EXCL_DEP</code> declaration:</p>
+  <pre id="example_6">
+# Example 6
+# ----------------------------------------------------------------------
+cfg::type          ext
+cfg::version       1.0
+
+bld::excl_dep  USE::YourFortranMod             # line 4
+bld::excl_dep  INTERFACE::HerFortran.interface # line 5
+bld::excl_dep  INC::HisFortranInc.inc          # line 6
+bld::excl_dep  H::TheirHeader.h                # line 7
+bld::excl_dep  OBJ::ItsObject.o                # line 8
+
+# ... some other declarations ...
+</pre>
+
+  <p>Here is an explanation of what each line does:</p>
+
+  <ul>
+    <li><dfn>line 4</dfn>: this line declares that the Fortran module
+    <samp>YourFortranMod</samp> should be excluded. The value of each
+    <code>EXCL_DEP</code> declaration has two parts. The first part is a label
+    that is used to define the type of dependency to be excluded. For a full
+    list of these labels, please see the <a href=
+    "annex_bld_cfg.html#dependency-types">dependency types table</a> in the
+    <a href="annex_bld_cfg.html">Annex: Declarations in FCM build configuration
+    file</a>. The label <code>USE</code> denotes a Fortran module. The second
+    part of the label is the dependency itself. For instance, if a Fortran
+    source file contains the line: <code>USE YourFortranMod</code>, the
+    dependency scanner will ignore it.</li>
+
+    <li><dfn>line 5</dfn>: this line declares that the include statement for
+    the Fortran 9X interface file <samp>HerFortran.interface</samp> should be
+    excluded. The label <code>INTERFACE</code> denotes a Fortran INCLUDE
+    statement for a Fortran 9X interface block file. For example, if a Fortran
+    source file contains the line: <code>INCLUDE 'HerFortran.interface'</code>,
+    the dependency scanner will ignore it.</li>
+
+    <li><dfn>line 6</dfn>: this line declares that the include statement for
+    <samp>HisFortranInc.inc</samp> should be excluded. The label
+    <code>INC</code> denotes a Fortran INCLUDE statement other than an INCLUDE
+    statement for an interface block file. For example, if a Fortran source
+    file contains the line: <code>INCLUDE 'HisFortranInc.inc'</code>, the
+    dependency scanner will ignore it.</li>
+
+    <li><dfn>line 7</dfn>: this line declares that the header include statement
+    <samp>TheirHeader.h</samp> should be excluded. The label <code>H</code>
+    denotes a pre-processing #include statement. For example, if a source file
+    contains the line: <code>#include 'TheirHeader.h'</code>, the dependency
+    scanner will ignore it.</li>
+
+    <li><dfn>line 8</dfn>: this line declares that the external dependency for
+    <samp>ItsObject.o</samp> should be excluded. The label <code>OBJ</code>
+    denotes a compiled binary object. These dependencies are normally inserted
+    into the source files as special comments. For example, if a source file
+    contains the line: <code>! depends on: ItsObject.o</code>, the dependency
+    scanner will ignore it.</li>
+  </ul>
+
+  <p>An <code>EXCL_DEP</code> declaration normally applies to all files in the
+  build. However, you can suffix it with the name of a sub-package, i.e.
+  <code>EXCL_DEP::<pcks></code>. In such case, the declaration will only
+  apply while scanning for dependencies in the source files in the sub-package
+  named <pcks>.</p>
+
+  <p>You can also exclude all dependency scan of a particular type. To do so,
+  simply declare the type in the value. For example, if you do not want the
+  build system to scan for the <code>CALLS: <executable></code> directive
+  in the comment lines of your scripts, you can make the following
+  declaration:</p>
+  <pre>
+bld::excl_dep  EXE
+</pre>
+
+  <p>The opposite of the <code>EXCL_DEP</code> declaration is the
+  <code>DEP::<pcks></code> declaration, which you can use to add a
+  dependency to a source file (in the package name <code><pcks></code>).
+  The syntax of the declaration is similar to that of <code>EXCL_DEP</code>,
+  but you must specify the package name of a source file for DEP declarations.
+  Please also note that a <code>DEP</code> declaration only works if the
+  particular dependency is supported for the particular source file - as it
+  makes no sense, for example, to specify a USE dependency for a shell
+  script.</p>
+
+  <p>If you need to switch off dependency checking completely, you can use the
+  <code>NO_DEP</code> declaration. For example, to switch off dependency
+  checking for all but the <samp>foo/bar</samp> sub-package, you can do:</p>
+  <pre>
+bld::no_dep           true
+bld::no_dep::foo/bar  false
+</pre>
+
+  <h3 id="advanced_blockdata">Linking a Fortran executable with a BLOCKDATA
+  program unit</h3>
+
+  <p>If it is required to link Fortran executables with BLOCKDATA program
+  units, you must declare the executable targets and the objects containing the
+  BLOCKDATA program units using the <code>BLOCKDATA::<target></code>
+  declarations. For example, if <samp>foo.exe</samp> is an executable target
+  depending on the objects of the BLOCKDATA program units
+  <samp>blkdata.o</samp> and <samp>fbk.o</samp>, you will make the following
+  declarations:</p>
+  <pre>
+bld::blockdata::foo.exe  blkdata fbk
+</pre>
+
+  <p>If all your executables are dependent on <samp>blkdata.o</samp> and
+  <samp>fbk.o</samp>, you will make the following declarations:</p>
+  <pre>
+bld::blockdata  blkdata fbk
+</pre>
+
+  <h3 id="advanced_library">Creating library archives</h3>
+
+  <p>If you are interested in building library archives, the build system
+  allows you to do it in a relatively simple way. For each sub-package in the
+  source tree, there is a target to build a library containing all the objects
+  compiled from the source files (that are not main programs) within the
+  sub-package. If the sub-package contains children sub-packages, the object
+  files of the children will also be included recursively. By default, the
+  library archive is named after the sub-package, in the format
+  <code>lib<pcks>.a</code>. (For example, the library archive for the
+  package <samp>foo/bar/egg</samp> will be named
+  <samp>libfoo__bar__egg.a</samp> by default.) If you do not like the default
+  name for the sub-package library, you can use the
+  <code>LIB::<pcks></code> declaration to rename it, as long as the new
+  name does not clash with other targets. For example, to rename
+  <samp>libfoo__bar__egg.a</samp> to <samp>libham.a</samp>, you will make the
+  following declaration in your extract configuration file:</p>
+  <pre>
+bld::lib::foo/bar/egg  ham
+</pre>
+
+  <p>In addition to sub-package libraries, you can also build a global library
+  archive for the whole source tree. By default, the library is named
+  <samp>libfcm_default.a</samp>, but you can rename it using the
+  <code>LIB</code> declaration as above. For example, to rename the library to
+  <samp>libmy-lib.a</samp>, you will make the following declaration in your
+  extract configuration file:</p>
+  <pre>
+bld::lib  my-lib
+</pre>
+
+  <p>When a library archive is created successfully, the build system will
+  automatically generate the relevant exclude dependency configurations in the
+  <samp>etc/</samp> sub-directory of the build root. You will be able to
+  include these configurations in subsequent builds that utilise the library.
+  The root names of the configuration files match those of the library archives
+  that you can create in the current build, but the extension <samp>*.a</samp>
+  is replaced with <samp>*.cfg</samp>. For example, the exclude dependency
+  configuration for <samp>libmy-lib.a</samp> is <samp>libmy-lib.cfg</samp>.</p>
+
+  <h3 id="advanced_pp">Pre-processing</h3>
+
+  <p>As most modern compilers can handle pre-processing, the build system
+  leaves pre-processing to the compiler by default. However, it is recognised
+  that there are code written with pre-processor directives that can alter the
+  argument list of procedures and/or their dependencies. If a source file
+  requires pre-processing in such a way, we have to pre-process before running
+  the interface block generator and the dependency scanner. The <code>PP</code>
+  declaration can be used to switch on this pre-processing stage. The
+  pre-processing stage can be switched on globally or for individual
+  sub-packages only. The following is an example, using an extract
+  configuration file:</p>
+  <pre id="example_7">
+# Example 7
+# ----------------------------------------------------------------------
+cfg::type          ext
+cfg::version       1.0
+
+bld::pp::gen       true                  # line 4
+bld::pp::var/foo   true                  # line 5
+
+bld::tool::cppkeys GOOD WEATHER FORECAST # line 7
+bld::tool::fppkeys FOO BAR EGG HAM       # line 8
+
+# ... some other declarations ...
+</pre>
+
+  <p>Here is an explanation of what each line does:</p>
+
+  <ul>
+    <li><dfn>line 4-5</dfn>: these switches on the pre-processing stage for all
+    sub-packages under <samp>gen</samp> and <samp>var/foo</samp>.</li>
+
+    <li><dfn>line 7</dfn>: this declares a list of pre-defined macros
+    <samp>GOOD</samp>, <samp>WEATHER</samp> and <samp>FORECAST</samp> for
+    pre-processing all C files.</li>
+
+    <li><dfn>line 8</dfn>: this declares a list of pre-defined macros
+    <samp>FOO</samp>, <samp>BAR</samp>, <samp>EGG</samp> and <samp>HAM</samp>
+    for pre-processing all Fortran files that require processing.</li>
+  </ul>
+
+  <p>Source files requiring pre-processing may contain <code>#include</code>
+  statements to include header files. For including a local file, its name
+  should be embedded within a pair of quotes, i.e. <samp>'file.h'</samp> or
+  <samp>"file.h"</samp>. If the header file is embedded within a pair of
+  <file.h> angle brackets, the system will assume that the file can be
+  found in a standard location.</p>
+
+  <p>The build system allows header files to be placed anywhere within the
+  declared source tree. The system uses the dependency scanner, as described in
+  the previous sub-section to scan for any header file dependencies. All source
+  files requiring pre-processing and all header files are scanned. Header files
+  that are required are copied to the <samp>inc/</samp> subdirectory of the
+  build root, which is automatically added to the pre-processor search path via
+  the <code>-I<dir></code> option. The build system uses an internal
+  logic similar to <code>make</code> to perform pre-processing. Header files
+  are only copied to the <samp>inc/</samp> sub-directory if they are used in
+  <code>#include</code> statements.</p>
+
+  <p>Unlike <code>make</code>, which only uses the timestamp to determine
+  whether an item is out of date, the internal logic of the build system does
+  this by inspecting the content of the file as well. In an incremental build,
+  the pre-processed file is only updated if its content has changed. This
+  avoids unnecessary updates (and hence unnecessary re-compilation) in an
+  incremental build if the changed section of the code does not affect the
+  output file.</p>
+
+  <p>Pre-processed code generated during the pre-processing stage are sent to
+  the <samp>ppsrc/</samp> sub-directory of the build root. It will have a
+  relative path that reflects the name of the declared sub-package. The
+  pre-processed source file will have the same root name as the original source
+  file. For C files, the same extension <samp>.c</samp> will be used. For
+  Fortran files, the case of the extension will normally be dropped, e.g. from
+  <samp>.F90</samp> to <samp>.f90</samp>.</p>
+
+  <p>Following pre-processing, the system will use the pre-processed source
+  file as if it is the original source file. The interface generator will
+  generate the interface file using the pre-processed file, the dependency
+  scanner will scan the pre-processed file for dependencies, and the compiler
+  will compile the pre-processed source.</p>
+
+  <p>The <code>TOOL::CPPKEYS</code> and <code>TOOL::FPPKEYS</code> declarations
+  are used to pre-define macros in the C and Fortran pre-processor
+  respectively. This is implemented by the build system using the pre-processor
+  <code>-D</code> option on each word in the list. The use of these
+  declarations are not confined to the pre-process stage. If any source files
+  requiring pre-processing are left to the compiler, the declarations will be
+  used to set up the commands for compiling these source files.</p>
+
+  <p>The <code>TOOL::CPPKEYS</code> and <code>TOOL::FPPKEYS</code> declarations
+  normally applies globally, but like any other <code>TOOL</code> declarations,
+  they can be suffixed with sub-package names. In such cases, the declarations
+  will apply only to the specified sub-packages.</p>
+
+  <dl>
+    <dt>Note - changing pre-processor flags</dt>
+
+    <dd>
+      <p>As for compiler flags, the build system detects changes in
+      pre-processor flags (<code>TOOL::CPPFLAGS</code> and
+      <code>TOOL::FPPFLAGS</code>) and macro definitions
+      (<code>TOOL::CPPKEYS</code> and <code>TOOL::FPPKEYS</code>). If the
+      pre-processor flags or the macro definitions have changed in an
+      incremental build, the system will re-do all the necessary
+      pre-processing. The following hierarchy is followed:</p>
+
+      <ul>
+        <li>If the pre-processor flags or macro definitions for a particular
+        source file change, only that source file will be pre-processed
+        again.</li>
+
+        <li>If the pre-processor flags or macro definitions for a particular
+        container package change, only source files within that container will
+        be pre-processed again.</li>
+
+        <li>If the global pre-processor flags or macro definitions change, all
+        source files will be pre-processed again.</li>
+
+        <li>If the pre-processor command changes, all source files are
+        pre-processed again.</li>
+      </ul>
+    </dd>
+  </dl>
+
+  <h3 id="advanced_file-type">File type</h3>
+
+  <p>The build system only knows what to do with an input source file if it
+  knows what type of file it is. The type of a source file is normally
+  determined automatically using one of the following three methods (in
+  order):</p>
+
+  <ol>
+    <li>If the file is named with an extension, its extension will be matched
+    against a set of registered file extensions. If a match is found, the file
+    type will be set according to the register.</li>
+
+    <li>If a file does not have an extension or does not match with a
+    registered extension, its name is compared with a set of pre-defined
+    patterns. If a match is found, the file type will be set according to the
+    file type associated with the pattern.</li>
+
+    <li>If the above two methods failed and if the file is a text file, the
+    system will attempt to read the first line of the file. If the first line
+    begins with a <code>#!</code> pattern, the line will be compared with a set
+    of pre-defined patterns. If a match is found, the file type will be set
+    according to the file type associated with the pattern.</li>
+  </ol>
+
+  <p>In addition to the above, if a file is a Fortran or C source file, the
+  system will attempt to open the source file to determine whether it contains
+  a main program, module (Fortran only) or just standalone procedures. All
+  these information will be used later by the build system to process the
+  source file.</p>
+
+  <p>The build system registers a file type with a set of type flags delimited
+  by the double colons <code>::</code>. For example, a Fortran 9X source file
+  is registered as <code>FORTRAN::FORTRAN9X::SOURCE</code>. (Please note that
+  the order of the type flags in the list is insignificant. For example,
+  <code>FORTRAN::SOURCE</code> is the same as <code>SOURCE::FORTRAN</code>.)
+  For a list of all the type flags used by the build system, please see the
+  <a href="annex_bld_cfg.html#infile-ext-types">input file extension type flags
+  table</a> in the <a href="annex_bld_cfg.html">Annex: Declarations in FCM
+  build configuration file</a>.</p>
+
+  <p>The following is a list of default input file extensions and their
+  associated types:</p>
+
+  <dl>
+    <dt><samp>.f .for .ftn .f77</samp></dt>
+
+    <dd><code>FORTRAN::SOURCE</code> Fortran 77 source file (assumed to be
+    fixed format)</dd>
+
+    <dt><samp>.f90 .f95</samp></dt>
+
+    <dd><code>FORTRAN::FORTRAN9X::SOURCE</code> Fortran 9X source file (assumed
+    to be free format)</dd>
+
+    <dt><samp>.F .FOR .FTN .F77</samp></dt>
+
+    <dd><code>FPP::SOURCE</code> Fortran 77 source file (assumed to be fixed
+    format) that requires pre-processing</dd>
+
+    <dt><samp>.F90 .F95</samp></dt>
+
+    <dd><code>FPP::FPP9X::SOURCE</code> Fortran 9X source file (assumed to be
+    free format) that requires pre-processing</dd>
+
+    <dt><samp>.c</samp></dt>
+
+    <dd><code>C::SOURCE</code> C source file</dd>
+
+    <dt><samp>.h .h90</samp></dt>
+
+    <dd><code>CPP::INCLUDE</code> Pre-processor <code>#include</code> header
+    file</dd>
+
+    <dt><samp>.o .obj</samp></dt>
+
+    <dd><code>BINARY::OBJ</code> Compiled binary object</dd>
+
+    <dt><samp>.exe</samp></dt>
+
+    <dd><code>BINARY::EXE</code> Binary executable</dd>
+
+    <dt><samp>.a</samp></dt>
+
+    <dd><code>BINARY::LIB</code> Binary object library archive</dd>
+
+    <dt><samp>.sh .ksh .bash .csh</samp></dt>
+
+    <dd><code>SHELL::SCRIPT</code> Unix shell script</dd>
+
+    <dt><samp>.pl .pm</samp></dt>
+
+    <dd><code>PERL::SCRIPT</code> Perl script</dd>
+
+    <dt><samp>.py</samp></dt>
+
+    <dd><code>PYTHON::SCRIPT</code> Python script</dd>
+
+    <dt><samp>.tcl</samp></dt>
+
+    <dd><code>TCL::SCRIPT</code> Tcl/Tk script</dd>
+
+    <dt><samp>.pro</samp></dt>
+
+    <dd><code>PVWAVE::SCRIPT</code> IDL/PVWave program</dd>
+
+    <dt><samp>.cfg</samp></dt>
+
+    <dd><code>CFGFILE</code> FCM configuration file</dd>
+
+    <dt><samp>.inc</samp></dt>
+
+    <dd><code>FORTRAN::FORTRAN9X::INCLUDE</code> Fortran INCLUDE file</dd>
+
+    <dt><samp>.interface</samp></dt>
+
+    <dd><code>FORTRAN::FORTRAN9X::INCLUDE::INTERFACE</code> Fortran 9X INCLUDE
+    interface block file</dd>
+  </dl>
+
+  <p>N.B. The extension must be unique. For example, the system does not
+  support the use of <samp>.inc</samp> files for both <code>#include</code> and
+  Fortran <code>INCLUDE</code>.</p>
+
+  <p>The following is a list of supported file name patterns and their
+  associated types:</p>
+
+  <dl>
+    <dt><samp>*Scr_* *Comp_* *IF_* *Suite_* *Interface_*</samp></dt>
+
+    <dd><code>SHELL::SCRIPT</code> Unix shell script, GEN-based project naming
+    conventions</dd>
+
+    <dt><samp>*List_*</samp></dt>
+
+    <dd><code>SHELL::SCRIPT::GENLIST</code> Unix shell script, GEN list
+    file</dd>
+
+    <dt><samp>*Sql_*</samp></dt>
+
+    <dd><code>SCRIPT::SQL</code> SQL script, GEN-based project naming
+    conventions</dd>
+  </dl>
+
+  <p>The following is a list of supported <code>#!</code> line patterns and
+  their associated types:</p>
+
+  <dl>
+    <dt><samp>*sh* *ksh* *bash* *csh*</samp></dt>
+
+    <dd><code>SHELL::SCRIPT</code> Unix shell script</dd>
+
+    <dt><samp>*perl*</samp></dt>
+
+    <dd><code>PERL::SCRIPT</code> Perl script</dd>
+
+    <dt><samp>*python*</samp></dt>
+
+    <dd><code>PYTHON::SCRIPT</code> Python script</dd>
+
+    <dt><samp>*tclsh* *wish*</samp></dt>
+
+    <dd><code>TCL::SCRIPT</code> Tcl/Tk script</dd>
+  </dl>
+
+  <p>The build system allows you to add or modify the register for input file
+  extensions and their associated type using the
+  <code>INFILE_EXT::<ext></code> declaration, where <ext> is a file
+  name extension without the leading dot. If file extension alone is
+  insufficient for defining the type of your source file, you can use the
+  <code>SRC_TYPE::<pcks></code> declaration, (where <pcks> is the
+  package name of the source file). For example, in an extract configuration
+  file, you may have:</p>
+  <pre id="example_8">
+# Example 8
+# ----------------------------------------------------------------------
+cfg::type                ext
+cfg::version             1.0
+
+bld::infile_ext::foo     CPP::INCLUDE                # line 4
+bld::infile_ext::bar     FORTRAN::FORTRAN9X::INCLUDE # line 5
+bld::src_type::egg/ham.f FORTRAN::FORTRAN9X::INCLUDE # line 6
+
+# ... some other declarations ...
+</pre>
+
+  <p>Here is an explanation of what each line does:</p>
+
+  <ul>
+    <li><dfn>line 4</dfn>: this line registers the extension <samp>.foo</samp>
+    to be of type <code>CPP::INCLUDE</code>. This means that any input files
+    with <samp>.foo</samp> extension will be treated as if they are
+    pre-processor header files.</li>
+
+    <li><dfn>line 5</dfn>: this line registers the extension <samp>.bar</samp>
+    to be of type <code>FORTRAN::FORTRAN9X::INCLUDE</code>. This means that any
+    input file with <samp>.bar</samp> extension will be treated as if they are
+    Fortran 9X INCLUDE files.</li>
+
+    <li><dfn>line 6</dfn>: this line declares the type for the source file in
+    the package <samp>egg::ham.f</samp> to be
+    <code>FORTRAN::FORTRAN9X::INCLUDE</code>. Without this declaration, this
+    file would normally be given the type <code>FORTRAN::SOURCE</code>.</li>
+  </ul>
+
+  <p>The <code>INFILE_EXT</code> declarations deal with extensions of input
+  files. There is also a <code>OUTFILE_EXT::<type></code> declaration
+  that deals with extensions of output files. The declaration is opposite that
+  of <code>INFILE_EXT</code>. The file <type> is now declared with the
+  label, and the extension is declared as the value. It is worth noting that
+  <code>OUTFILE_EXT</code> declarations use very different syntax for
+  <type>, and the declared extension must include the leading dot. For a
+  list of output types used by the build system, please see the <a href=
+  "annex_bld_cfg.html#outfile-ext-types">output file extension types table</a>
+  in the <a href="annex_bld_cfg.html">Annex: Declarations in FCM build
+  configuration file</a>. An example is given below:</p>
+  <pre id="example_9">
+# Example 9
+# ----------------------------------------------------------------------
+cfg::type                   ext
+cfg::version                1.0
+
+bld::outfile_ext::mod       .MOD   # line 4
+bld::outfile_ext::interface .intfb # line 5
+
+# ... some other declarations ...
+</pre>
+
+  <p>Here is an explanation of what each line does:</p>
+
+  <ul>
+    <li><dfn>line 4</dfn>: this line modifies the extension of compiled Fortran
+    9X module information files from the default <samp>.mod</samp> to
+    <samp>.MOD</samp>.</li>
+
+    <li><dfn>line 5</dfn>: this line modifies the extension of INCLUDE Fortran
+    9X interface block files from the default <samp>.interface</samp> to
+    <samp>.intfb</samp>.</li>
+  </ul>
+
+  <p>N.B. If you have made changes to the file type registers, whether it is
+  for input files or output files, it is always worth re-building your code in
+  full-build mode to avoid unexpected behaviour.</p>
+
+  <h3 id="advanced_inherit">Inherit from a previous build</h3>
+
+  <p>As you can inherit from previous extracts, you can inherit from previous
+  builds. The very same <code>USE</code> statement can be used to declare a
+  build, which the current build will depend on. The only difference is that
+  the declared location must contain a valid build configuration file. In fact,
+  if you use the extract system to obtain your build configuration file, any
+  <code>USE</code> declarations in the extract configuration file will also be
+  <code>USE</code> declarations in the output build configuration file.</p>
+
+  <p>By declaring a previous build with a <code>USE</code> statement, the
+  current build automatically inherits settings from it. The following points
+  are worth noting:</p>
+
+  <ul>
+    <li>Build targets are not normally inherited. However, you can switch on
+    inheritance of build targets using an <code>INHERIT::TARGET</code>
+    declaration, such as:
+      <pre>
+inherit::target  true
+</pre>
+    </li>
+
+    <li>The build root directory and its sub-directories of the inherited build
+    are placed into the search paths. For example, if we have an inherited
+    build at <samp>/path/to/inherited</samp>, and it is used by a build at
+    <samp>/path/to/my_build</samp>, the search path of executable files will
+    become <samp>/path/to/my_build/bin:/path/to/inherited/bin</samp>, so that
+    the <samp>bin/</samp> sub-directory of the current build is searched before
+    the <samp>bin/</samp> sub-directory of the inherited build. If two or more
+    <code>USE</code> statements are declared, the <code>USE</code> statement
+    declared last will have higher priority. For example, if the current build
+    is <var>C</var>, and it USEs build <var>A</var> before build <var>B</var>,
+    the search path will be <samp>C:B:A</samp>.</li>
+
+    <li>Source files are inherited by default. If a source file is declared in
+    the current build that has the same package name as a source file of the
+    inherited build, it will override that in the inherited build. Any source
+    files missing from the current build will be taken from the inherited
+    build.
+
+      <p>You can switch off inheritance of source files using an
+      <code>INHERIT::SRC</code> declaration. This declaration can be suffixed
+      with the name of a sub-package. In such case, the declaration applies
+      only to the inheritance of the sub-package. Otherwise, it applies
+      globally. For example:</p>
+      <pre>
+# Switch off inheritance of source files in the <samp>gen</samp> sub-package
+inherit::src::gen  false
+</pre>
+    </li>
+
+    <li><code>BLOCKDATA</code>, <code>DEP</code>, <code>EXCL_DEP</code>,
+    <code>EXE_DEP</code>, <code>INFILE_EXT</code>, <code>LIB</code>,
+    <code>OUTFILE_EXT</code>, <code>PP</code>, <code>TOOL</code> and
+    <code>SRC_TYPE</code> declarations are automatically inherited. If the same
+    setting is declared in the current incremental build, it overrides the
+    inherited declaration.</li>
+  </ul>
+
+  <p>As an example, suppose we have already performed an extract and build
+  based on the configuration in <a href="#example_2">example 2</a>, we can set
+  up an extract configuration file as follows:</p>
+  <pre id="example_10">
+# Example 10
+# ----------------------------------------------------------------------
+cfg::type            ext
+cfg::version         1.0
+
+use                  $HOME/example               # line 4
+
+dest                 $HOME/example10             # line 6
+
+bld::inherit::target true                        # line 8 
+bld::target          ham.exe egg.exe             # line 9
+
+bld::tool::fflags    -O2 -w                      # line 11
+bld::tool::cflags    -O2                         # line 12
+
+# ... and other declarations for repositories and source directories ...
+</pre>
+
+  <p>Here is an explanation of what each line does:</p>
+
+  <ul>
+    <li><dfn>line 4</dfn>: this line declares a previous extract at
+    <samp>$HOME/example</samp> which the current extract will inherit from. The
+    same line will be written to the output build configuration file. The
+    subsequent build will then inherit from the build at
+    <samp>$HOME/example</samp>.</li>
+
+    <li><dfn>line 6</dfn>: this declares the destination root directory of the
+    current extract, which will become the root directory of the current build.
+    Search paths of the build sub-directories will be set automatically. For
+    example, the search path for executable files created by the current build
+    will be <samp>$HOME/example10/bin:$HOME/example/bin</samp>.</li>
+
+    <li><dfn>line 8</dfn>: this line switches on inheritance of build targets.
+    The build targets in <a href="#example_1">example 1</a>, i.e.
+    <samp>foo.exe</samp> and <samp>bar.exe</samp> will be built as part of the
+    current build.</li>
+
+    <li><dfn>line 9</dfn>: this declares two new build targets
+    <samp>ham.exe</samp> and <samp>egg.exe</samp> to be added to the inherited
+    ones. The default build targets of the current build will now be
+    <samp>foo.exe</samp>, <samp>bar.exe</samp>, <samp>ham.exe</samp> and
+    <samp>egg.exe</samp>.</li>
+
+    <li><dfn>line 11-12</dfn>: these lines modify options used by the Fortran
+    and the C compilers, overriding those inherited from <a href=
+    "#example_1">example 1</a>.</li>
+  </ul>
+
+  <dl>
+    <dt>Build inheritance limitation: handling of include files</dt>
+
+    <dd>
+      <p>The build system uses the compiler/pre-processor's <code>-I</code>
+      option to specify the search path for include files. For example, it uses
+      the option to specify the <samp>inc/</samp> sub-directories of the
+      current build and its inherited build.</p>
+
+      <p>However, some compilers/pre-processors (e.g. <code>cpp</code>) search
+      for include files from the container directory of the source file before
+      searching for the paths specified by the <code>-I</code> options. This
+      behaviour may cause the build to behave incorrectly.</p>
+
+      <p>Consider a source file <samp>egg/hen.c</samp> that includes
+      <samp>fried.h</samp>. If the directory structure looks like:</p>
+      <pre>
+# Sources in inherited build:
+egg/hen.c
+egg/fried.h
+
+# Sources in current build:
+egg/fried.h
+</pre>
+
+      <p>The system will correctly identify that <samp>fried.h</samp> is out of
+      date, and trigger a re-compilation of <samp>egg/hen.c</samp>. However, if
+      the compiler searches for the include files from the container directory
+      of the source file first, it will wrongly use the include file in the
+      inherited build instead of the current one.</p>
+
+      <p>Some compilers (e.g. <code>gfortran</code>) do not behave this way and
+      others (e.g. <code>ifort</code>) have options to prevent include file
+      search in the container directory of the source file. If you are using
+      such a compiler you can avoid the problem for Fortran compilation
+      although this does not fix the problem entirely if you have switched on
+      the pre-processing stage. Otherwise you may have to work around the
+      problem, (e.g. by making a comment change in the source file, or by not
+      using an inherited build at all).</p>
+    </dd>
+  </dl>
+
+  <h3 id="advanced_data">Building data files</h3>
+
+  <p>While the usual targets to be built are the executables associated with
+  source files containing main programs, libraries or scripts, the build system
+  also allows you to build <em>data</em> files. All files with no registered
+  type are considered to be <em>data</em> files. For each container
+  sub-package, there is an automatic target for copying all <em>data</em> files
+  to the <samp>etc/</samp> sub-directory of the build root. The name of the
+  target has the form <code><pcks>.etc</code>, where <pcks> is the
+  name of the sub-package (with package names delimited by the double
+  underscore <code>__</code>). For example, the target name for sub-package
+  <samp>foo/bar</samp> is <samp>foo__bar.etc</samp>. This target is
+  particularly useful for copying, say, all namelists in a sub-package to the
+  <samp>etc/</samp> sub-directory of the build root.</p>
+
+  <p>At the end of a successful build, if the <samp>etc/</samp> sub-directory
+  is not empty, the <samp>fcm_env.sh</samp> script will export the environment
+  variable <var>FCM_ETCDIR</var> to point to the <samp>etc/</samp>
+  sub-directory. You should be able to use this environment variable to locate
+  your data files.</p>
+
+  <h2 id="verbose">Diagnostic verbose level</h2>
+
+  <p>The amount of diagnostic messages generated by the build system is
+  normally set to a level suitable for normal everyday operation. This is the
+  default diagnostic verbose level 1. If you want a minimum amount of
+  diagnostic messages, you should set the verbose level to 0. If you want more
+  diagnostic messages, you can set the verbose level to 2 or 3. You can modify
+  the verbose level in two ways. The first way is to set the environment
+  variable <var>FCM_VERBOSE</var> to the desired verbose level. The second way
+  is to invoke the build system with the <code>-v <level></code> option.
+  (If set, the command line option overrides the environment variable.)</p>
+
+  <p>The following is a list of diagnostic output at each verbose level:</p>
+
+  <dl>
+    <dt>Level 0</dt>
+
+    <dd>
+      <ul>
+        <li>Report the time taken at the end of each stage of the build
+        process.</li>
+
+        <li>Run the <code>make</code> command in silent mode.</li>
+      </ul>
+    </dd>
+
+    <dt>Level 1</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at verbose level 0.</li>
+
+        <li>Report the name of the build configuration file.</li>
+
+        <li>Report the location of the build destination.</li>
+
+        <li>Report date/time at the beginning of each stage of the build
+        process.</li>
+
+        <li>Report removed directories.</li>
+
+        <li>Report number of pre-processed files.</li>
+
+        <li>Report number of generated F9X interface files.</li>
+
+        <li>Report number of source files scanned for dependencies.</li>
+
+        <li>Report name of updated <samp>Makefile</samp>.</li>
+
+        <li>Print compiler/linker commands.</li>
+
+        <li>Report total time.</li>
+      </ul>
+    </dd>
+
+    <dt>Level 2</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at verbose level 1.</li>
+
+        <li>For incremental build in archive mode, report the commands used to
+        extract the archives.</li>
+
+        <li>Report creation and removal of directories.</li>
+
+        <li>Report pre-processor commands.</li>
+
+        <li>Print compiler/linker commands with timestamps.</li>
+      </ul>
+    </dd>
+
+    <dt>Level 3</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at verbose level 2.</li>
+
+        <li>Report update of dummy files.</li>
+
+        <li>Report all shell commands.</li>
+
+        <li>Report pre-processor commands with timestamps.</li>
+
+        <li>Report any F9X interface files generated.</li>
+
+        <li>Report number of lines and number of automatic dependencies for
+        each source file which is scanned.</li>
+
+        <li>Run <code>make</code> on normal mode (as opposed to silent
+        mode).</li>
+
+        <li>Report start date/time and time taken of <code>make</code>
+        commands.</li>
+      </ul>
+    </dd>
+  </dl>
+
+  <h2 id="overview">Overview of the build process</h2>
+
+  <p>The FCM build process can be summarised in five stages. Here is a summary
+  of what is done in each stage:</p>
+
+  <ol>
+    <li><dfn>Parse configuration and setup destination</dfn>: in this
+    pre-requisite stage, the build system parses the configuration file. The
+    <samp>src/</samp> sub-directory is searched recursively for source files.
+    For full builds, it ensures that the sub-directories and files created by
+    the build system are removed. If you invoke <code>fcm build</code> with a
+    <code>--clean</code> option, the system will not go any further.</li>
+
+    <li><dfn>Setup build</dfn>: in this first stage, the system determines
+    whether any settings have changed by using the cache. If so, the cache is
+    updated with the current settings.</li>
+
+    <li><dfn>Pre-process</dfn>: if any files in any source files require
+    pre-processing, they will be pre-processed at this stage. The resulting
+    pre-processed source files will be sent to the <samp>ppsrc/</samp>
+    sub-directory of the build root.</li>
+
+    <li><dfn>Generate dependency</dfn>: the system scans source files of
+    registered types for dependency information. For an incremental build, the
+    information is only updated if a source file is changed. The system then
+    uses the information to write a <samp>Makefile</samp> for the main
+    build.</li>
+
+    <li><dfn>Generate interface</dfn>: if there are Fortran 9X source files
+    with standalone subroutines and functions, the build system generates
+    interface blocks for them. The result of which will be written to the
+    interface files in the <samp>inc/</samp> sub-directory of the build
+    root.</li>
+
+    <li>
+      <dfn>Make</dfn>: the system invokes <code>make</code> on the
+      <samp>Makefile</samp> generated in the previous stage to perform the main
+      build. Following a build, the <em>root</em> directory of the build may
+      contain the following sub-directories (empty ones are removed
+      automatically at the end of the build process):
+
+      <dl>
+        <dt><samp>.cache/.bld/</samp></dt>
+
+        <dd>Cache files, used internally by FCM.</dd>
+
+        <dt><samp>bin/</samp></dt>
+
+        <dd>Executable binaries and scripts.</dd>
+
+        <dt><samp>cfg/</samp></dt>
+
+        <dd>Configuration files.</dd>
+
+        <dt><samp>done/</samp></dt>
+
+        <dd>Dummy <em>done</em> files used internally by the
+        <samp>Makefile</samp> generated by FCM.</dd>
+
+        <dt><samp>etc/</samp></dt>
+
+        <dd>Miscellaneous data files.</dd>
+
+        <dt><samp>flags/</samp></dt>
+
+        <dd>Dummy <em>flags</em> files used internally by the
+        <samp>Makefile</samp> generated by FCM.</dd>
+
+        <dt><samp>inc/</samp></dt>
+
+        <dd>Include files, such as <samp>*.h</samp>, <samp>*.inc</samp>,
+        <samp>*.interface</samp>, and <samp>*.mod</samp>.</dd>
+
+        <dt><samp>lib/</samp></dt>
+
+        <dd>Object library archives.</dd>
+
+        <dt><samp>obj/</samp></dt>
+
+        <dd>Compiled object files.</dd>
+
+        <dt><samp>ppsrc/</samp></dt>
+
+        <dd>Source directories with pre-processed files.</dd>
+
+        <dt><samp>src/</samp></dt>
+
+        <dd>Source directories. This directory is not changed by the build
+        system.</dd>
+
+        <dt><samp>tmp/</samp></dt>
+
+        <dd>Temporary objects and binaries. Files generated by the
+        compiler/linker may be left here.</dd>
+      </dl>
+    </li>
+  </ol>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/changeset.png b/doc/user_guide/changeset.png
new file mode 100644
index 0000000..67c3437
Binary files /dev/null and b/doc/user_guide/changeset.png differ
diff --git a/doc/user_guide/code_management.html b/doc/user_guide/code_management.html
new file mode 100644
index 0000000..20352a3
--- /dev/null
+++ b/doc/user_guide/code_management.html
@@ -0,0 +1,1243 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: Code Management</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: Code Management</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <h2 id="svn">Using Subversion</h2>
+
+  <p>One of the key strengths of Subversion is its documentation. <a href=
+  "http://svnbook.red-bean.com/en/1.8/">Version Control with Subversion</a>
+  (which we'll just refer to as the <cite>Subversion book</cite> from now on)
+  is an excellent book which explains in detail how to use Subversion and also
+  provides a good introduction to all the basic concepts of version control.
+  Rather than trying to write our own explanations (and not doing as good a
+  job) we will simply refer you to the <cite>Subversion book</cite>, where
+  appropriate, for the relevant information.</p>
+
+  <p>In general, the approach taken in this section is to make sure that you
+  first understand how to perform a particular action using the Subversion
+  tools and then describe how this differs using FCM.</p>
+
+  <h3 id="svn_concepts">Basic Concepts</h3>
+
+  <p>In order to use FCM you need to have a basic understanding of version
+  control. If you're not already familiar with Subversion or CVS then please
+  read the chapter <a href=
+  "http://svnbook.red-bean.com/en/1.8/svn.basic.html">Fundamental Concepts</a>
+  from the <cite>Subversion book</cite>. In particular, make sure that you
+  understand:</p>
+
+  <ul>
+    <li>The <q title=
+    "http://svnbook.red-bean.com/en/1.8/svn.basic.version-control-basics.html#svn.basic.vsn-models.copy-merge">
+    Copy-Modify-Merge</q> approach to file sharing.</li>
+
+    <li>Global Revision Numbers.</li>
+  </ul>
+
+  <p>Note that this chapter states that <q title=
+  "http://svnbook.red-bean.com/en/1.8/svn.basic.in-action.html#svn.basic.in-action.wc">
+  working copies do not always correspond to any single revision in the
+  repository</q>. However, the FCM working practices do not encourage this and
+  the wrapper scripts provided by FCM should ensure that your working copy (a
+  local copy of the repository's files and directories where you can prepare
+  changes) always corresponds to exactly one revision.</p>
+
+  <p><acronym title="Concurrent Versions System">CVS</acronym> users should
+  already be familiar with all the basic concepts. This is not surprising since
+  Subversion was designed as a replacement for CVS and it uses the same
+  development model. However, there are some important differences which may
+  confuse those more familiar with CVS. Fortunately, the appendix in the
+  <cite>Subversion book</cite> <a href=
+  "http://svnbook.red-bean.com/en/1.8/svn.forcvs.html">Subversion for CVS
+  Users</a> is specifically written for those moving from CVS to Subversion and
+  you should read this if you are a CVS user.</p>
+
+  <h3 id="svn_basic">Basic Command Line Usage</h3>
+
+  <p>Before we discuss the FCM system you need to have a good understanding of
+  how to perform most of the normal day-to-day tasks using Subversion.
+  Therefore, unless you are already familiar with Subversion, please read the
+  chapter <a href="http://svnbook.red-bean.com/en/1.8/svn.tour.html">Basic
+  Usage</a> from the <cite>Subversion book</cite>.</p>
+
+  <p>So, now you have an understanding of how to do basic tasks using
+  Subversion (you did read the <cite>Basic Usage</cite> chapter didn't you?),
+  how is using FCM different? Well, the key thing to remember is that, instead
+  of using the command <code>svn</code> you need to use the command
+  <code>fcm</code>. The advantages of this are as follows:</p>
+
+  <ul>
+    <li><code>fcm</code> implements all of the commands that <code>svn</code>
+    does (including all the command abbreviations).</li>
+
+    <li>In some cases <code>fcm</code> does very little and basically passes on
+    the command to <code>svn</code>.</li>
+
+    <li>In other cases <code>fcm</code> has a lot of additional functionality
+    compared with the equivalent <code>svn</code> command.</li>
+
+    <li><code>fcm</code> also implements several commands not provided by
+    <code>svn</code>.</li>
+
+    <li><code>fcm</code> provides support for URL and revision keywords.</li>
+
+    <li>Most of the additional features and commands are discussed later in
+    this section or in the following sections.</li>
+  </ul>
+
+  <p>Full details of all the <code>fcm</code> commands available are provided
+  in the <a href="command_ref.html">FCM Command Reference</a> section.</p>
+
+  <h4 id="svn_basic_keywords">URL And Revision Keywords</h4>
+
+  <p>URL keywords can be used to specify URLs in <code>fcm</code> commands. The
+  syntax is <code>fcm:<keyword></code>. Keywords can be defined in the
+  FCM keyword configuration file (i.e. <samp>$FCM/etc/fcm/keyword.cfg</samp>
+  and <samp>$HOME/.metomi/fcm/keyword.cfg</samp>).</p>
+
+  <p>For example, if you define a keyword in your configuration file as
+  follows:</p>
+  <pre>
+location{primary}[um] = svn://fcm2/UM_svn/UM
+</pre>
+
+  <p>then you can abbreviate the URL as in the following examples:</p>
+  <pre>
+# fcm ls svn://fcm2/UM_svn/UM
+fcm ls fcm:um
+
+# fcm ls svn://fcm2/UM_svn/UM/trunk
+fcm ls fcm:um_tr  # OR: fcm ls fcm:um-tr
+
+# fcm ls svn://fcm2/UM_svn/UM/branches
+fcm ls fcm:um_br  # OR: fcm ls fcm:um-br
+
+# fcm ls svn://fcm2/UM_svn/UM/tags
+fcm ls fcm:um_tg  # OR: fcm ls fcm:um-tg
+</pre>
+
+  <p>Using URL keywords has two advantages.</p>
+
+  <ul>
+    <li>They are shorter and easier to remember.</li>
+
+    <li>If the repository needs to be moved then only the keyword definitions
+    need to be updated (although any working copies you have will still need to
+    be <em>relocated</em> by issuing a <a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.switch.html"><code>fcm
+    switch --relocate</code></a> command).</li>
+  </ul>
+
+  <p>In a similar way, revision keywords can be used to specify revision
+  numbers in <code>fcm</code> commands. The keyword can be used anywhere a
+  revision number can be used. Each keyword is associated with a URL keyword
+  and can only be used when referring to that repository.</p>
+
+  <p>For example, if you define a keyword in your configuration file as
+  follows:</p>
+  <pre>
+revision[fcm:vn1.0] = 112
+</pre>
+
+  <p>then the following commands are equivalent:<br />
+  <code>fcm log -r 112 svn://fcm1/FCM_svn/trunk</code><br />
+  <code>fcm log -r vn1.0 fcm:fcm_tr</code></p>
+
+  <p>You can use the <code>fcm keyword-print</code> command to print all
+  registered location keywords. You can also print the location keyword and the
+  revision keywords of a particular project. For example, to print the keywords
+  for the <samp>UM</samp> project, you can type <code>fcm keyword-print
+  fcm:um</code>.</p>
+
+  <h4 id="svn_basic_diff">Examining Changes</h4>
+
+  <p>Code differences can be displayed graphically using <code>xxdiff</code> by
+  using the <code>--graphical</code> (or <code>-g</code>) option to <code>fcm
+  diff</code>. This option can be used in combination with any other options
+  which are accepted by <code>svn diff</code>.</p>
+
+  <p>An example display from <code>xxdiff</code> is shown below.</p>
+
+  <p class="image"><img src="xxdiff1.png" alt="xxdiff 2-way display" /><br />
+  <code>xxdiff</code> 2-way display</p>
+
+  <p>Points to note:</p>
+
+  <ul>
+    <li>By default <code>xxdiff</code> is configured to show horizontal
+    differences. This means that the parts of the line which have changed are
+    highlighted (e.g. the text <samp>useful</samp> is highlighted in the
+    example above).</li>
+
+    <li>The number shown to the right of each file name shows the current line
+    number. The number on the far right is the number of differences found (2
+    in the example above).</li>
+
+    <li>You may find the following keyboard shortcuts useful.
+
+      <ul>
+        <li><kbd>N</kbd> - move to the next difference</li>
+
+        <li><kbd>P</kbd> - move to the previous difference</li>
+
+        <li><kbd>Ctrl-Q</kbd> - exit</li>
+      </ul>
+    </li>
+
+    <li>If you want to use another diff tool instead of <code>xxdiff</code> to
+    examine changes, you can define the <code>graphic-diff</code> setting in a
+    FCM external configuration file (i.e.<samp>$FCM/etc/fcm/external.cfg</samp>
+    or <samp>$HOME/.metomi/fcm/external.cfg</samp>). For example, to use <code>
+      tkdiff</code>, you can do:
+      <pre>
+# in your site's $FCM/etc/fcm/external.cfg:
+# OR: in your $HOME/.metomi/fcm/external.cfg:
+graphic-diff = tkdiff
+</pre>
+    </li>
+  </ul>
+
+  <h4 id="svn_basic_conflicts">Resolving Conflicts</h4>
+
+  <p>Your working copy may contain files or directories <em>in conflict</em> as
+  a result of an update or a merge (covered later). Conflicts arise from the
+  situation where two changes being applied to a file <em>overlap</em>. These
+  can be text-based, as in two changes to the same line of text in a file, or
+  filesystem-based, as in two different renamings of the same file.</p>
+
+  <p>For conflicts in normal (text) files, the command <code>fcm
+  conflicts</code> can be used to help resolve them. (A discussion on binary
+  files is given in the section <a href="working_practices.html#binary">Working
+  with Binary Files</a> later in this document.). For each file in <em>text
+  conflict</em>, the <code>fcm conflicts</code> command calls a graphical merge
+  tool (i.e. <code>xxdiff</code> by default) to display a 3-way diff.</p>
+
+  <p>An example display from <code>xxdiff</code> is shown below.</p>
+
+  <p class="image"><img src="xxdiff2.png" alt="xxdiff 3-way display" /><br />
+  <code>xxdiff</code> 3-way display</p>
+
+  <p>Points to note:</p>
+
+  <ul>
+    <li>The file in the middle is the common ancestor from the merge. The file
+    on the left is your original file and the file on the right is the file
+    containing the changes which you are merging in.</li>
+
+    <li><code>xxdiff</code> is configured to automatically select regions that
+    would end up being selected by an automatic merge (e.g. there are only
+    changes in one of the files). Any difference <em>hunks</em> which cannot be
+    resolved automatically are left <em>unselected</em>.</li>
+
+    <li>Before you can save a merged version you need to go through each
+    unselected difference hunk and decide which text you wish to use.
+
+      <ul>
+        <li>Selecting a diff hunk can be carried out by clicking on it with the
+        left mouse button (or refer to the keyboard shortcuts shown under the
+        <kbd>Region</kbd> menu). The colours update to display which side is
+        selected for output. You can select individual lines with the middle
+        mouse button.</li>
+
+        <li>If you want to select more than one side, you have to invoke the
+        <kbd>Region->Split/swap/join</kbd> command (keyboard shortcut:
+        <kbd>S</kbd>). This will split the current diff hunk so you can select
+        the pieces you want from both sides. Further invocations of this
+        command will cause swapping of the regions, looping through all the
+        different ordering possibilities, and finally joining the regions again
+        (preserving selections where it is possible).</li>
+      </ul>
+    </li>
+
+    <li>The number on the far right is the number of unselected difference
+    hunks (1 in the example above). Once this number is 0 then you are ready to
+    save the merged file.</li>
+
+    <li>If you want to see how the merged file will look with the current
+    selections then select <kbd>Windows->Toggle Merged View</kbd> (keyboard
+    shortcut: <kbd>Alt+Y</kbd>). An extra window then appears showing the
+    merged output that updates interactively as you make selections.</li>
+
+    <li>You may find the following keyboard shortcuts useful.
+
+      <ul>
+        <li><kbd>B</kbd> - move to the next unselected hunk</li>
+
+        <li><kbd>O</kbd> - move to the previous unselected hunk</li>
+      </ul>
+    </li>
+
+    <li>There are several different ways to exit the 3-way diff (available from
+    the <kbd>File</kbd> menu):
+
+      <ul>
+        <li>Exit with MERGE (keyboard shortcut: <kbd>M</kbd>) - This saves the
+        merge result. If there are any unselected difference hunks remaining
+        then you will be warned and given the option of saving the file with
+        conflict markers.</li>
+
+        <li>Exit with ACCEPT (keyboard shortcut: <kbd>A</kbd>) - This saves the
+        file you are merging in (i.e. the right one) as the merge result (i.e.
+        you have <em>accepted</em> all the changes).</li>
+
+        <li>Exit with REJECT (keyboard shortcut: <kbd>R</kbd>) - This saves the
+        original working copy file (i.e. the left one) as the merge result
+        (i.e. you have <em>rejected</em> all the changes).</li>
+      </ul>
+
+      <p>If you just want to exit without making any decisions you can also
+      just close the window.</p>
+    </li>
+
+    <li>For further details please read the <a href=
+    "http://furius.ca/xxdiff/doc/xxdiff-doc.html"><code>xxdiff</code> users
+    manual</a> (available from the <kbd>Help</kbd> menu). In particular, read
+    the section <a href=
+    "http://furius.ca/xxdiff/doc/xxdiff-doc.html#merging-files-and-resolving-conflicts">
+    <em>Merging files and resolving conflicts</em></a>.</li>
+  </ul>
+
+  <p>If you have resolved all the conflicts in a file then you will be prompted
+  on whether to run <code>svn resolved</code> on the file to signal that the
+  file is no longer in conflict.</p>
+  <pre>
+(SHELL PROMPT)$ fcm conflicts
+Conflicts in file: Gen_setup_local1.proc
+You have chosen to ACCEPT all the changes
+Would you like to run "svn resolved"?
+Enter "y" or "n" (or just press <return> for "n"): y
+Resolved conflicted state of 'Gen_setup_local1.proc'
+Conflicts in file: Gen_setup_remote2.proc
+Merge conflicts were not all resolved
+Conflicts in file: Gen_setup_remote3.proc
+All merge conflicts resolved
+Would you like to run "svn resolved"?
+Enter "y" or "n" (or just press <return> for "n"): y
+Resolved conflicted state of 'Gen_setup_remote3.proc'
+</pre>
+
+  <p>It is important to realise that there are some types of merge that
+  <code>xxdiff</code> will not be able to help you with.</p>
+
+  <ul>
+    <li>It you have 2 versions of a file, both with substantial changes to the
+    same piece of code, then the <code>xxdiff</code> display will be extremely
+    colourful and not very helpful.</li>
+
+    <li>In these cases it is often easier to start with one version of the file
+    and manually re-apply the changes from the other version. It might not be
+    obvious how to do this and you may need to speak to the author of the other
+    change to agree how this can be done. Fortunately this situation should be
+    very rare.</li>
+
+    <li>For a more detailed discussion please refer to <a href=
+    "http://software.ericsink.com/scm/scm_file_merge.html">Chapter 3: File
+    Merge</a> in the online book called <a href=
+    "http://software.ericsink.com/scm/source_control.html">Source Control
+    HOWTO</a>.</li>
+  </ul>
+
+  <p>For files in <em>tree conflict</em>, which is otherwise known as a
+  structural or filesystem-based conflict, the command <code>fcm
+  conflicts</code> will manually resolve the problem by prompting you to choose
+  a course of action. You can either keep the file as it was before the merge
+  (<em>keep local</em>), or accept the external changes to the file.</p>
+
+  <p>The most common way to generate a tree conflict after a merge is when a
+  file has been deleted or renamed on one branch, and modified on another.
+  These are incompatible changes, to Subversion, and it doesn't know which
+  action to take. This is the cause of the tree conflict dilemma which the user
+  must solve.</p>
+
+  <p>In the following example, the branch in the working copy has had a
+  deletion of a file. The branch that is being merged in has subsequently
+  modified the file, which means that you may want to incorporate these
+  changes. A tree conflict is therefore flagged up.</p>
+  <pre>
+(SHELL PROMPT)$ fcm merge fcm:tutorial_br/dev/bfitz/r1_366
+Merge(s) available from /tutorial/branches/dev/bfitz/r1_366: 1257
+About to merge in changes from /tutorial/branches/dev/bfitz/r1_366 at 1257 compared with /tutorial/trunk at 1
+This merge will result in the following change:
+--------------------------------------------------------------------------------
+--- Merging r2 through r1257 into '.':
+   C src/subroutine/hello_sub.f90
+Summary of conflicts:
+  Tree conflicts: 1
+--------------------------------------------------------------------------------
+Would you like to go ahead with the merge?
+Enter "y" or "n" (or just press <return> for "n"): y
+Performing merge ...
+--- Merging r2 through r1257 into '.':
+   C src/subroutine/hello_sub.f90
+Summary of conflicts:
+  Tree conflicts: 1
+(SHELL PROMPT)$ fcm status
+ M      .
+!     C src/subroutine/hello_sub.f90
+      >   local missing, incoming edit upon merge
+(SHELL PROMPT)$ fcm info src/subroutine/hello_sub.f90
+Path: src/subroutine/hello_sub.f90
+Name: hello_sub.f90
+Node Kind: none
+Tree conflict: local missing, incoming edit upon merge
+  Source  left: (file) svn://fcm1/tutorial_svn/tutorial/trunk/src/subroutine/hello_sub.f90@1
+  Source right: (file) svn://fcm1/tutorial_svn/tutorial/branches/dev/bfitz/r1_366/src/subroutine/hello_sub.f90@1257
+</pre>
+
+  <p>In this example, running <code>fcm conflicts</code> would give:</p>
+  <pre>
+(SHELL PROMPT)$ fcm conflicts
+[info] src/subroutine/hello_sub.f90: in tree conflict.
+Locally: deleted
+Externally: modified.
+Answer (y) to leave the file deleted.
+Answer (n) to add the file with the changes.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") 
+</pre>
+
+  <p>In this example, to keep the file as it was before (in a deleted state),
+  enter <samp>y</samp>.</p>
+
+  <p>Otherwise, to accept the merge branch version of the file (adding it with
+  the edited changes), enter <samp>n</samp>.</p>
+
+  <p>There are many other types of tree conflicts that can occur, and <code>fcm
+  conflicts</code> does not cover all of them. Tree conflicts arising from
+  updates and switches are not covered (which should be rare under FCM working
+  practice). More importantly, tree conflicts on directories are not covered,
+  because of the potential nesting of conflicts within the directories. It can
+  often be difficult to identify the problem and figure out the solution in the
+  case of directory conflicts, and the easiest solution may be to try to
+  resolve the discrepancy before the merge.</p>
+
+  <p>For further details, see the <a href=
+  "annex_quick_ref_tree_conflicts.html">Tree Conflict</a> annex</p>
+
+  <h4 id="svn_basic_check">Adding and Removing Files</h4>
+
+  <p>If your working copy contains files which are not under version control
+  then you can use the command <code>fcm add --check</code> to add them. This
+  will go through each of the files and prompt to see if you wish to put that
+  file under version control using <code>svn add</code>. For each file you can
+  enter <kbd>y</kbd> for yes, <kbd>n</kbd> for no or <kbd>a</kbd> to assume yes
+  for all following files.</p>
+  <pre>
+(SHELL PROMPT)$ fcm add -c
+?      xxdiff1.png
+?      xxdiff2.png
+?      xxdiff3.png
+?      xxdiff4.png
+Add file 'xxdiff1.png'?
+Enter "y", "n" or "a" (or just press <return> for "n"): y
+A         xxdiff1.png
+Add file 'xxdiff2.png'?
+Enter "y", "n" or "a" (or just press <return> for "n"): n
+Add file 'xxdiff3.png'?
+Enter "y", "n" or "a" (or just press <return> for "n"): a
+A         xxdiff3.png
+A         xxdiff4.png
+</pre>
+
+  <p>Similarly, if your working copy contains files which are missing (i.e. you
+  have deleted them without using <code>svn delete</code>) then you can use the
+  command <code>fcm delete --check</code> to delete them. This will go through
+  each of the files and prompt to see if you wish to remove that file from
+  version control using <code>svn delete</code>.</p>
+
+  <p>As noted in the <a href=
+  "http://subversion.apache.org/faq.html#wc-change-detection">Subversion
+  FAQ</a>, it can be dangerous using these commands. If you have moved or
+  copied a file then simply adding them would cause the history to be lost.
+  Therefore take care to only use these commands on files which really are new
+  or deleted.</p>
+
+  <h4 id="svn_basic_commit">Committing Changes</h4>
+
+  <p>The command <code>fcm commit</code> should be used for committing changes
+  back to the repository. It differs from the <code>svn commit</code> command
+  in a number of important ways:</p>
+
+  <ul>
+    <li>Your working copy <em>must</em> be up to date. <code>fcm commit</code>
+    will abort if it finds that any files are out of date with respect to the
+    repository. This ensures that your working copy reflects how the repository
+    will be after you have committed your changes.
+
+      <ul>
+        <li>This helps to ensure that any tests you have done prior to
+        committing are valid.</li>
+
+        <li><code>fcm commit</code> is not suitable if you need to commit
+        changes from a working copy containing mixed revisions. However, you
+        are very unlikely to need to do this.</li>
+
+        <li>Actually there is a small chance that your working copy might not
+        be up to date when you commit if someone else is committing some
+        changes at the same time. However, this should very seldom happen and,
+        even if it does, the commit would fail if any of the files being
+        changed became out of date (i.e. it is not possible to lose any
+        changes).</li>
+      </ul>
+    </li>
+
+    <li>If it discovers a file named <samp>#commit_message#</samp> in the top
+    level of your working copy it uses this to provide a template commit
+    message (which you can then edit).
+
+      <ul>
+        <li>If you have performed a merge then a message describing the merge
+        will have been added to this file. It is important that you leave this
+        included in the commit message and do not change its format, as it is
+        used by the <code>fcm branch-info</code> command.</li>
+
+        <li>You can, if you wish, add entries to this file as you go along to
+        record what changes you have prepared in your working copy. You can
+        also use the command <code>fcm commit --dry-run</code> to allow you to
+        edit the commit message without committing any changes.</li>
+
+        <li><samp>#commit_message#</samp> is ignored by Subversion (so you
+        won't see it show up as an unversioned files when you run <code>fcm
+        status</code>).</li>
+      </ul>
+    </li>
+
+    <li>It always operates from the top of your working copy. If you issue the
+    <code>fcm commit</code> command from a sub-directory of your working copy
+    then it will automatically work out the top directory and work from there.
+
+      <ul>
+        <li>This ensures that any template commit message gets picked up and
+        that you do not, for example, accidently commit a partial set of
+        changes from a merge.</li>
+      </ul>
+    </li>
+
+    <li>It always commits <em>all</em> the changes in your working copy (it
+    does not accept a list of files to commit).
+
+      <ul>
+        <li>Once again, this avoids any danger of accidently committing a
+        partial set of changes.</li>
+
+        <li>You should only work on one change within a working copy. If you
+        need to prepare another, unrelated change then use a separate working
+        copy.</li>
+      </ul>
+    </li>
+
+    <li>It runs <code>svn update</code> after the commit to ensure that your
+    working copy is at the latest revision and to avoid any confusion caused by
+    your working copy containing mixed revisions.</li>
+  </ul>
+  <pre>
+(SHELL PROMPT)$ fcm commit
+Starting editor to create commit message ...
+Change summary:
+------------------------------------------------------------------------
+[Project: GEN]
+[Branch : branches/test/frsn/r123_foo_bar]
+[Sub-dir: <top>]
+
+M      src/code/GenMod_Control/GenMod_Control.f90
+M      src/code/GenMod_Control/Gen_SetupControl.f90
+------------------------------------------------------------------------
+Commit message is as follows:
+------------------------------------------------------------------------
+An example commit.
+------------------------------------------------------------------------
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): y
+Sending        src/code/GenMod_Control/GenMod_Control.f90
+Sending        src/code/GenMod_Control/Gen_SetupControl.f90
+Transmitting file data ..
+Committed revision 170.
+=> svn update
+At revision 170.
+</pre>
+
+  <h3 id="svn_branching">Branching And Merging</h3>
+
+  <p>Branching is a fundamental concept common to most version control systems.
+  For a good introduction please read the chapter <a href=
+  "http://svnbook.red-bean.com/en/1.8/svn.branchmerge.html">Branching and
+  Merging</a> from the <cite>Subversion book</cite>. Even if you are already
+  familiar with branching using other version control systems you should still
+  read this chapter to see how branching is implemented in Subversion.</p>
+
+  <p>Having read this chapter from the <cite>Subversion book</cite> you should
+  understand:</p>
+
+  <ul>
+    <li>Why each project directory has sub-directories called <em>trunk</em>,
+    <em>branches</em> and <em>tags</em>. This structure is assumed by
+    <code>fcm</code> (Subversion recommends it but doesn't insist on it).</li>
+
+    <li>That when you make a branch you are taking a copy of the entire project
+    file tree. Fortunately, the design of the Subversion repository means that
+    these copies are <q title=
+    "http://svnbook.red-bean.com/en/1.8/svn.branchmerge.using.html#svn.branchmerge.using.create">
+    cheap</q> - they are quick to create and take very little space.</li>
+
+    <li>That Subversion has only implemented merge tracking recently,
+    long after FCM has implemented its own solution optimised for our
+    recommended working practice.</li>
+
+    <li>That each revision of your repository can also be thought of as a
+    <em>changeset</em>.</li>
+
+    <li>That once a change is committed to a repository it cannot be removed
+    (only reversed). Therefore you must take care not to committ a sensitive
+    document or a large data file unintentionally.</li>
+  </ul>
+
+  <p>FCM provides various commands which make working with branches easier (as
+  described in the following sections).</p>
+
+  <h4 id="svn_branching_create">Creating Branches</h4>
+
+  <p>The command <code>fcm branch-create</code> (or simply <code>fcm
+  bcreate</code> or even <code>fcm bc</code>) should be used for creating new
+  branches. It provides a number of features:</p>
+
+  <ul>
+    <li>It applies a standard naming convention for branches. The branch name
+    is automatically constructed for you depending on the option(s) supplied to
+    the command. The full detail of these options are described in the <a href=
+    "command_ref.html#fcm-branch-create">FCM Command Reference > fcm
+    branch-create</a> section.</li>
+
+    <li>By default, it assumes that you are branching from the last changed
+    revision of the <em>trunk</em>.
+
+      <ul>
+        <li>You can use the <code>--branch-of-branch</code> option if you need
+        to create a branch of a branch. A branch of a branch can be useful in
+        many situations. For example, consider a shared branch used by several
+        members of your team to develop, say, a new science scheme, and you
+        have come up with some different ideas of implementing the scheme. You
+        may want to create a branch of the shared branch to develop your idea
+        before merging it back to the shared branch. Note that you can only
+        merge a branch of a branch with it's parent or with another branch
+        created from the same parent. You can't, for example, merge it with the
+        trunk.</li>
+
+        <li>You can do <code>fcm bc NAME SOURCE at REV</code> if you
+        need to create a branch from an earlier revision of the SOURCE.</li>
+      </ul>
+    </li>
+
+    <li>Each branch always contains a full copy of the trunk (or its parent
+    branch) - you cannot create a branch from a sub-tree.
+
+      <ul>
+        <li>There would be no reason to only include a sub-tree in a
+        branch.</li>
+      </ul>
+    </li>
+
+    <li>It applies a standard commit message which defines how the branch has
+    been created. If a Trac ticket is specified using the <code>--ticket
+    <number></code> option, it is added to the commit log message. If you
+    need to add anything to the commit log message, please do so
+    <strong>above</strong> the line that says <samp>--Add your commit message
+    ABOVE - do not alter this line or those below--</samp>.</li>
+  </ul>
+
+  <p>The following is a list of the different types of branches available:</p>
+
+  <dl>
+    <dt>User development branches</dt>
+
+    <dd><samp>branches/dev/<Userid>/<Branch_Name></samp> These are
+    for changes which are intended to be merged back to the trunk once they are
+    complete. Most branches will belong to this type. e.g.
+    branches/dev/frdm/vn6.1_ImprovedDeepConvection,
+    branches/dev/frdm/r2134_NewBranchNamingConvention.</dd>
+
+    <dt>Shared development branches</dt>
+
+    <dd><samp>branches/dev/Share/<Branch_Name></samp></dd>
+
+    <dt>User test branches</dt>
+
+    <dd><samp>branches/test/<Userid>/<Branch_Name></samp> These are
+    for changes which are <em>not</em> intended for the trunk. e.g. Proof of
+    concept work, temporary code written for dealing with a one-off problem,
+    etc.</dd>
+
+    <dt>Shared test branches</dt>
+
+    <dd><samp>branches/test/Share/<Branch_Name></samp></dd>
+
+    <dt>User packages</dt>
+
+    <dd><samp>branches/pkg/<Userid>/<Branch_Name></samp> These are
+    branches which combine together a number of different development branches.
+    Sometimes this will simply be for testing purposes (i.e. for testing a
+    branch in combination with other branches). Other times it may be the
+    package which eventually gets merged to the trunk (rather than the
+    development branches). e.g.
+    branches/pkg/frdm/vn6.1_TestImprovedDeepConvection</dd>
+
+    <dt>Shared packages</dt>
+
+    <dd><samp>branches/pkg/Share/<Branch_Name></samp> E.g.
+    branches/pkg/Share/vn6.1_NewConvectionScheme.</dd>
+
+    <dt>Configurations</dt>
+
+    <dd><samp>branches/pkg/Config/<Branch_Name></samp> These are major
+    packages which combine together a number of different packages and
+    development branches. e.g. branches/pkg/Config/vn6.1_HadGEM1a.</dd>
+
+    <dt>Releases</dt>
+
+    <dd><samp>branches/pkg/Rel/<Branch_Name></samp> These may be bug-fix
+    branches for system releases, if required. They can also be branches on
+    which stable releases are prepared if you don't do this on the trunk
+    (although you lose the ability to branch from stable releases if you work
+    this way). e.g. branches/pkg/Rel/vn6.1_BugFixes.</dd>
+  </dl>
+  <pre>
+(SHELL PROMPT)$ fcm bcreate -k 23 my_test_branch fcm:test
+Starting nedit to create commit message ...
+Change summary:
+------------------------------------------------------------------------
+A    svn://fcm1/repos/OPS/branches/dev/frsn/r118_my_test_branch
+------------------------------------------------------------------------
+Commit message is as follows:
+------------------------------------------------------------------------
+Create an example branch to demonstrate branch creation for the user guide.
+#23: Created /OPS/branches/dev/frsn/r118_my_test_branch from /OPS/trunk at 118.
+------------------------------------------------------------------------
+Would you like to go ahead and create this branch?
+Enter "y" or "n" (or just press <return> for "n"): y
+Creating branch svn://fcm1/repos/OPS/branches/dev/frsn/r118_my_test_branch ...
+
+Committed revision 169.
+</pre>
+
+  <h4 id="svn_branching_list">Listing Branches Created by You or Other
+  Users</h4>
+
+  <p>The command <code>fcm branch-list</code> (or simply <code>fcm bls</code>)
+  can be used to list the branches you have created at the HEAD of a
+  repository. If you specify the <code>--user <userid></code> option, the
+  branches created by <userid> are listed instead. You can specify
+  multiple users with multiple <code>--user <userid></code> options, or
+  with a colon (:) separated list to a single <code>--user
+  <userid:list></code> option. Note that you can also list shared
+  branches by specifying <userid> as <code>Share</code>, configuration
+  branches by specifying <userid> as <code>Config</code> and release
+  branches by specifying <userid> as <code>Rel</code>. The command
+  returns 0 (success) if one or more branches is found for the specified users,
+  or 1 (failure) if no branch is found.</p>
+  <pre>
+(SHELL PROMPT)$ fcm branch-list fcm:gen
+1 branch found for frsn in svn://fcm1/GEN_svn/GEN
+fcm:GEN-br/dev/frsn/r1191_clean_up/
+(SHELL PROMPT)$ echo $?
+0
+(SHELL PROMPT)$ fcm branch-list --user frbj --user frsn fcm:gen
+2 branches found for frbj, frsn in svn://fcm1/GEN_svn/GEN
+fcm:GEN-br/dev/frbj/r1177_gen_ui_for_scs/
+fcm:GEN-br/dev/frsn/r1191_clean_up/
+(SHELL PROMPT)$ echo $?
+0
+(SHELL PROMPT)$ fcm branch-list --user frva fcm:gen
+0 branch found for frva in svn://fcm1/GEN_svn/GEN
+(SHELL PROMPT)$ echo $?
+1
+</pre>
+
+  <h4 id="svn_branching_info">Getting Information About Branches</h4>
+
+  <p>The command <code>fcm branch-info</code> (or simply <code>fcm
+  binfo</code>) can be used to get various information about a branch. In
+  particular, it summarises information about merges to and from the branch and
+  its parent.</p>
+  <pre>
+(SHELL PROMPT)$ fcm branch-info
+URL: svn://fcm1/FCM_svn/FCM/branches/dev/frsn/r1346_merge
+Repository Root: svn://fcm1/FCM_svn
+Revision: 1385
+Last Changed Author: frsn
+Last Changed Rev: 1385
+Last Changed Date: 2006-04-20 11:08:45 +0100 (Thu, 20 Apr 2006)
+--------------------------------------------------------------------------------
+Branch Create Author: frsn
+Branch Create Rev: 1354
+Branch Create Date: 2006-04-04 14:27:47 +0100 (Tue, 04 Apr 2006)
+Branch Parent: svn://fcm1/FCM_svn/FCM/trunk@1346
+Last Merge From Parent, Revision: 1444
+Last Merge From Parent, Delta: /FCM/trunk at 1439 cf. /FCM/trunk at 1395
+Merges Avail From Parent: 1445
+Merges Avail Into Parent: 1453 1452 1449 1446 1444 1443 1441 1434 1397 1396 ...
+</pre>
+
+  <p>If you need information on the current children of the branch, use the
+  <code>--show-children</code> option of the <code>fcm branch-info</code>
+  command. If you need information on recent merges to and from the branch and
+  its siblings, use the <code>--show-siblings</code> option of the <code>fcm
+  branch-info</code> command.</p>
+
+  <p>To find out what changes have been made on a branch relative to its parent
+  you can use the command <code>fcm branch-diff</code> (or simply <code>fcm
+  bdi</code>.</p>
+
+  <ul>
+    <li>You can combine this with the options:
+
+      <dl>
+        <dt><code>--graphical</code></dt>
+
+        <dd>to display the differences using a graphical <em>diff</em>
+        tool</dd>
+
+        <dt><code>--trac</code></dt>
+
+        <dd>to display the differences using Trac</dd>
+
+        <dt><code>--wiki</code></dt>
+
+        <dd>to print a wiki syntax suitable for inserting into Trac</dd>
+      </dl>
+    </li>
+
+    <li>The base of the difference is adjusted to account for any merges from
+    the branch to its parent or vice-versa.</li>
+  </ul>
+
+  <h4 id="svn_branching_switch">Switching your working copy to point to another
+  branch</h4>
+
+  <p>The command <code>fcm switch</code> can be used to switch your working
+  copy to point to another branch. For example, if you have a working copy at
+  <samp>$HOME/work</samp>, currently pointing to the trunk or a branch of a
+  project at <samp>svn://fcm1/FCM_svn/FCM/trunk</samp>, you can switch the
+  working copy to point to another branch of same project:</p>
+  <pre>
+(Shell prompt)$ cd $HOME/work
+(Shell prompt)$ fcm sw dev/frsn/r959_blockdata
+-> svn switch --revision HEAD svn://fcm1/FCM_svn/FCM/branches/dev/frsn/r959_blockdata
+U    doc/user_guide/getting_started.html
+U    doc/user_guide/code_management.html
+U    doc/user_guide/command_ref.html
+U    src/lib/FCM1/SrcFile.pm
+U    src/lib/FCM1/Util.pm
+U    src/lib/FCM1/Build.pm
+U    src/lib/FCM1/Cm.pm
+U    src/lib/FCM1/SrcPackage.pm
+U    src/bin/fcm_internal
+U    src/bin/fcm_gui
+Updated to revision 1009.
+</pre>
+
+  <p>Unlike <code>svn switch</code>, <code>fcm switch</code> does extra
+  checking to ensure that your whole working copy is switched to the new branch
+  at the correct level of sub-directory. In addition, you can specify only the
+  <em>branch</em> part of the URL, such as <samp>trunk</samp>,
+  <samp>branches/dev/fred/r1234_bob</samp> or even
+  <samp>dev/fred/r1234_bob</samp> and the command will work out the full URL
+  for you.</p>
+
+  <h4 id="svn_branching_delete">Deleting Branches</h4>
+
+  <p>The command <code>fcm branch-delete</code> (or simply <code>fcm
+  bdel</code>) can be used to delete branches which are no longer required.
+  Before being asked to confirm that you want to delete the branch, you will
+  first see the same output as from <code>fcm branch-info</code>. This allows
+  you to check, for example, whether your branch is being used anywhere else or
+  whether the latest changes on your branch have been merged to the trunk. You
+  will be prompted to edit your commit log message. If you need to add anything
+  to the commit log message, please do so <strong>above</strong> the line that
+  says <samp>--Add your commit message ABOVE - do not alter this line or those
+  below--</samp>.</p>
+
+  <h4 id="svn_branching_merge">Merging</h4>
+
+  <p>As mentioned earlier, <code>fcm</code> has its own merge tracking solution
+  which is optimised for our recommended working practice. The solution assumes
+  the following:</p>
+
+  <ul>
+    <li>That all merges are performed using FCM and are identified using a
+    standard template in the commit log message.</li>
+
+    <li>That you only ever merge all the changes available on the source branch
+    up to a chosen point (i.e. you can't only include a subset of the changes
+    made to the branch).</li>
+
+    <li>That the source and target are both branches (or the trunk) in the same
+    FCM project.</li>
+
+    <li>That the source and target are directly related, i.e. they must either
+    have a parent/child relationship or they are siblings from the same parent
+    branch.</li>
+  </ul>
+
+  <p>Note that the term <em>source branch</em> and <em>target branch</em>
+  referred to above can also mean the trunk.</p>
+
+  <p>To perform a merge, use the command <code>fcm merge <source></code>.
+  This includes a number of important features:</p>
+
+  <ul>
+    <li>If it finds any local modifications in your working copy then it checks
+    whether you wish to continue (in most cases you won't want to mix a merge
+    with other changes).</li>
+
+    <li>It determines the base revision and path of the <em>common
+    ancestor</em> to be used for the merge, taking into account any merges from
+    the <em>source</em> to the <em>target</em> or vice-versa.</li>
+
+    <li>Before doing the merge, (unless you specify the
+    <code>--non-interactive</code> option), it reports what changes will result
+    from performing the merge and checks that you wish to continue.</li>
+
+    <li>It adds details of the merge, using a standard template, into the
+    commit message file (<samp>#commit_message#</samp>). If you need to add any
+    extra comment, you should do so <strong>above</strong> the line that says
+    <samp>--Add your commit message ABOVE - do not alter this line or those
+    below--</samp>.
+
+      <ul>
+        <li>If you decide to revert the merge, you should remove the template
+        line manually from the commit message file, making sure that you do not
+        alter the standard template by accident.</li>
+
+        <li>If the <code>--auto-log</code> option is specified, it adds the log
+        messages of the merged revisions as well as the standard template. This
+        is particularly useful when a small change is prepared in a branch, and
+        often the same commit log messages have to be repeated when the change is
+        merged and committed to the trunk. The option does not work very well if
+        the branch contains merges from another branch.</li>
+      </ul>
+    </li>
+  </ul>
+  <pre>
+(SHELL PROMPT)$ fcm merge trunk # merge changes from the trunk into the branch
+Eligible merge(s) from FCM/trunk: 1383 1375
+Enter a revision (or just press <return> for "1383"):
+Merge: /FCM/trunk at 1383
+ c.f.: /FCM/trunk at 1371
+-------------------------------------------------------------------------dry-run
+A    doc/fortran_standards/index.html
+U    src/lib/FCM1/ReposBranch.pm
+-------------------------------------------------------------------------dry-run
+Would you like to go ahead with the merge?
+Enter "y" or "n" (or just press <return> for "n"): y
+Merge succeeded.
+</pre>
+
+  <h3 id="svn_gui">Using the GUI</h3>
+
+  <p>So far, all the tools described have been command line tools. Many people
+  will be happy with these but, for those who prefer it, there is also a simple
+  Graphical User Interface (GUI).</p>
+
+  <h4 id="svn_gui_start">Starting the GUI</h4>
+
+  <p>To run the GUI simply issue the command <code>fcm gui</code> from the
+  directory you want as your working directory.</p>
+
+  <p>The GUI consists of several sections:</p>
+
+  <ul>
+    <li>The top section contains a row of buttons to allow you to select which
+    command you want to run.</li>
+
+    <li>Beneath this is shown the current working directory and the top level
+    directory of your working copy (these may be the same).</li>
+
+    <li>Beneath this come various buttons and entry boxes to allow you to
+    configure the command you have selected. These vary according to the
+    command.</li>
+
+    <li>Beneath this comes a further row of buttons
+
+      <ul>
+        <li><em>Quit</em> - this exits the GUI.</li>
+
+        <li><em>Help</em> - this displays the help message for the selected
+        command.</li>
+
+        <li><em>Clear</em> - this empties the text window.</li>
+
+        <li><em>Run</em> - this allows you to run your command.</li>
+      </ul>
+    </li>
+
+    <li>Beneath this comes a scrolling text window where the output from the
+    commands is displayed.</li>
+
+    <li>The bottom section displays help information when you position the
+    cursor over various parts of the GUI.</li>
+  </ul>
+
+  <p class="image"><img src="gui1.png" alt=
+  "Example GUI screen with the Status commands selected" /><br />
+  Example GUI screen with the <kbd>Status</kbd> commands selected</p>
+
+  <p>If you run a more complicated command, like <code>fcm
+  branch-create</code>, which prompts for input then extra entry windows will
+  pop up.</p>
+
+  <p class="image"><img src="gui2.png" alt="Example GUI pop-up window" /><br />
+  Example GUI pop-up window</p>
+
+  <h4 id="svn_gui_commands">GUI Commands</h4>
+
+  <p>The commands available from the GUI should be self explanatory. A few
+  points to note:</p>
+
+  <ul>
+    <li>If the current directory is not a working copy, you will only be able
+    to Checkout a working copy or create a branch from the GUI.</li>
+
+    <li>The <kbd>Checkout</kbd> command is only available if you start the GUI
+    in a directory which is not already a working copy. After successfully
+    running a checkout the GUI automatically sets the working directory to the
+    top of this new working copy.</li>
+
+    <li>With some commands (Status, Diff, Add, Delete, Conflicts) you can
+    choose whether to run from the top level of your working copy or from your
+    working directory. With the remaining commands this would not make sense
+    and they can only be run from the top level.</li>
+
+    <li>You can only issue commands from the GUI if they do not need to prompt
+    you for authentication (i.e. the Subversion command can be run with the
+    <code>--non-interactive</code> option).
+
+      <ul>
+        <li>If authentication is required then the command issued by the GUI
+        will fail. For the <code>branch-create</code>,
+        <code>branch-delete</code> and <code>commit</code> commands, which
+        support the <code>--password</code> option, you should specify your
+        password in <kbd>Other options</kbd> and click <kbd>Run</kbd> again.
+        For other commands, you should run the command in interactive mode on
+        the command line. Use the command displayed in the GUI text window but
+        remove the <code>--non-interactive</code> option.</li>
+
+        <li>Most repositories will be configured so that you only need
+        authentication for writing (not reading). Therefore, the first command
+        requiring authentication will probably be creating a branch or
+        commiting to the trunk.</li>
+
+        <li>You should only need to do this the first time you ever issue such
+        a command on a each repository (unless the repository is moved to a new
+        location) since the Subversion client caches this information for
+        future comamnds .</li>
+      </ul>
+    </li>
+  </ul>
+
+  <h3 id="svn_problems">Known Problems with Subversion</h3>
+
+  <p>There is a limitation with Subversion which you should be aware of. The
+  <code>svn rename</code> command is not a true rename/move operation, but is
+  implemented as a copy and delete. As a result, if you rename an item in a
+  branch, and later attempt to merge it back to the trunk, the operation may
+  not be handled correctly by <code>svn merge</code> (see <a href=
+  "http://subversion.tigris.org/issues/show_bug.cgi?id=898">subversion issue
+  898</a> for further details). Until such time as support for a <q title=
+  "http://subversion.tigris.org/issues/show_bug.cgi?id=898">true rename</q> is
+  implemented in Subversion, you should avoid renaming of files or directories
+  unless you can ensure that no-one is working in parallel on the affected
+  areas of the project.</p>
+
+  <h2 id="trac">Using Trac</h2>
+
+  <p><cite>Trac</cite> has a simple and intuitive web interface which is
+  relatively easy to pick up. It also includes a <a href=
+  "http://trac.edgewall.org/wiki/TracGuide">User and Administration Guide</a>
+  which is full of helpful information (and is referred to extensively in this
+  section).</p>
+
+  <p>Trac contains a menu bar at the top of each page (which we will refer to
+  as the <cite>Trac menu</cite>). This provides access to all the main
+  features.</p>
+
+  <h3 id="trac_login">Logging In</h3>
+
+  <p>Although different projects may choose their own rules, we expect that
+  most systems will have Trac configured so that all the information is
+  viewable by anyone. However, in order to make any changes you will need to
+  login. This ensures that any changes are identified with the appropriate
+  userid.</p>
+
+  <p>In the rest of this section it is assumed that you have logged in to Trac
+  and are therefore able to make changes.</p>
+
+  <p>If you haven't yet got a Trac userid (which should be the same as the
+  userid you use for committing changes to Subversion) then please contact your
+  system manager.</p>
+
+  <h3 id="trac_wiki">Using the Wiki Pages</h3>
+
+  <p>A wiki enables documents to be written in a simple markup language using a
+  web browser. See the Trac Guide for information on the <a href=
+  "http://trac.edgewall.org/wiki/TracWiki">Trac Wiki Engine</a>. Make sure that
+  you read the information provided on:</p>
+
+  <ul>
+    <li><a href="http://trac.edgewall.org/wiki/WikiFormatting">Wiki
+    Formatting</a> which explains how to format your wiki pages.</li>
+
+    <li><a href="http://trac.edgewall.org/wiki/WikiPageNames">Wiki Page
+    Names</a> which explains how <em>CamelCase</em> is used to create <a href=
+    "http://trac.edgewall.org/wiki/WikiNewPage">New Wiki Pages</a>.</li>
+
+    <li><a href="http://trac.edgewall.org/wiki/TracLinks">Trac Links</a> which
+    allow hyperlinking between Trac entities (tickets, reports, changesets,
+    Wiki pages, milestones and source files). This is a fundamental feature of
+    Trac which makes it easy, for example, to link a bug report (ticket) to the
+    changeset which fixed the bug (and vice-versa).</li>
+  </ul>
+
+  <p>Whenever you are viewing a wiki page in Trac you should see several
+  buttons at the bottom of the page:</p>
+
+  <ul>
+    <li><kbd>Edit This Page</kbd> - Clicking this will bring up a page where
+    you can edit the page contents. Before saving your changes you can preview
+    how the modified page will appear. You can also leave a comment explaining
+    what changes you made.</li>
+
+    <li><kbd>Attach File</kbd> - Allows you to attach files to a page, e.g. an
+    image.</li>
+
+    <li>If you have admin rights then you will also see
+
+      <ul>
+        <li><kbd>Delete This Version</kbd> - Delete the particular version of
+        the page you are viewing.</li>
+
+        <li><kbd>Delete Page</kbd> - Delete the page and all its history.</li>
+      </ul>Use with care - these operations are irreversible!
+    </li>
+  </ul>
+
+  <p>At the top of each wiki page at the right hand side you can select
+  <kbd>Page History</kbd>. This shows you the full history of each page with
+  details of when each change was made, who made the change and what the
+  changes were.</p>
+
+  <h3 id="trac_browser">Using the Repository Browser</h3>
+
+  <p>The <a href="http://trac.edgewall.org/wiki/TracBrowser">Trac Browser</a>
+  is used to view the contents of your repository. To get to it just select
+  <kbd>Browse Source</kbd> from the Trac menu. You can view directories and
+  files at any version, see their revision histories and view <a href=
+  "http://trac.edgewall.org/wiki/TracChangeset">changesets</a>. Any wiki
+  formatting in log messages is recognised and interpreted so you can easily
+  link a changeset to a Trac ticket by using <a href=
+  "http://trac.edgewall.org/wiki/TracLinks">Trac Links</a>.</p>
+
+  <h3 id="trac_tickets">Using the Issue Tracker</h3>
+
+  <p>The Trac issue database provides a way of tracking issues within a project
+  (e.g. bug reports, feature requests, software support issues, project tasks).
+  Within Trac an issue is often referred to as a <em>Ticket</em>.</p>
+
+  <p>Please refer to the Trac Guide for the following information:</p>
+
+  <ul>
+    <li>
+      <a href="http://trac.edgewall.org/wiki/TracTickets">The Trac Ticket
+      System</a> - Creating and modifying tickets.
+
+      <ul>
+        <li>Only Trac accounts with admin rights can modify ticket
+        descriptions.</li>
+      </ul>
+    </li>
+
+    <li><a href="http://trac.edgewall.org/wiki/TracQuery">Trac Ticket
+    Queries</a> - List tickets matching your chosen criterion.</li>
+  </ul>
+
+  <h3 id="trac_roadmap">Using the Roadmap</h3>
+
+  <p>Each ticket can be assigned to a milestone. The Trac Roadmap can then be
+  used to provide a view on the ticket system. This can useful to see what
+  changes went into a particular system release or what changes are outstanding
+  before a milestone can be reached.</p>
+
+  <p>Please refer to the Trac Guide for further information on the <a href=
+  "http://trac.edgewall.org/wiki/TracRoadmap">Trac Roadmap</a>.</p>
+
+  <ul>
+    <li>Only Trac accounts with admin rights can add, modify and remove
+    milestones using the web interface.</li>
+  </ul>
+
+  <h3 id="trac_timeline">Using the Timeline</h3>
+
+  <p>The <a href="http://trac.edgewall.org/wiki/TracTimeline">Trac Timeline</a>
+  allows you to list all the activity on a project over any given period. It
+  can list:</p>
+
+  <ul>
+    <li>Creation and changes to wiki pages.</li>
+
+    <li>Creation, closure and changes to tickets.</li>
+
+    <li>Commits to the Subversion repository.</li>
+
+    <li>Milestones reached.</li>
+  </ul>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/command_ref.html b/doc/user_guide/command_ref.html
new file mode 100644
index 0000000..efc3426
--- /dev/null
+++ b/doc/user_guide/command_ref.html
@@ -0,0 +1,2005 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: FCM Command Reference</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: FCM Command Reference</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <h2 id="introduction">Introduction</h2>
+
+  <p>This chapter describes all commands supported by <code>fcm</code>.
+  <code>fcm</code> has its own set of functionalities, but it also wraps all
+  <code>svn</code> commands.</p>
+
+  <p>In most wrappers to <code>svn</code>, <code>fcm</code> simply passes the
+  command directly on to <code>svn</code> (after expanding any keywords). These
+  commands are listed in the <a href="#svn">Other Subversion Commands</a>
+  section.</p>
+
+  <p>Where <code>fcm</code> adds more functionality to an <code>svn</code>
+  command, the command is discussed individually.</p>
+
+  <p>All command abbreviations supported by <code>svn</code> work with
+  <code>fcm</code>.</p>
+
+  <p>Subversion may prompt you for authentication if it is the first time you
+  write to a repository. The command fails if the authentication fails. A
+  command may support the <code>--non-interactive</code> or
+  <code>--svn-non-interactive</code> option. If such an option is specified,
+  Subversion will not prompt you for authentication, and the command will
+  simply fail if authentication is required. Please note that the option is
+  normally specified if you are running a command from the FCM GUI. If
+  authentication is required, you should run the command in interactive mode on
+  a command line, or by using the <code>--password=PASSWORD</code> option in
+  the <kbd>Other options</kbd>.</p>
+
+  <h2 id="env">Environment Variables</h2>
+
+  <p>The following environment variables are used by the <code>fcm</code>
+  command.</p>
+
+  <dl>
+    <dt id="env.FCM_CONF_PATH">FCM_CONF_PATH='/path/to/conf1
+    /path/to/conf2'</dt>
+
+    <dd>This variable is mainly used to test FCM. If specified, override the
+    paths for site and user configuration files.  The value should be a space
+    delimited list of paths where FCM site and user configuration files can be
+    found. The value can also be set to a null string to allow FCM to run with
+    no site or user configuration.  If not defined, the default to look for site
+    and user configuration files from <code>$FCM_HOME/etc/fcm/</code> and
+    <code>~/.metomi/fcm/</code> (where <var>$FCM_HOME/bin/fcm</var> is where the
+    <code>fcm</code> command is invoked.</dd>
+
+    <dt id="env.FCM_DEBUG">FCM_DEBUG=true</dt>
+
+    <dd>If specified, raises the verbosity to the <dfn>debug</dfn> level. This
+    is useful in debugging especially if a command does not accept a
+    <code>-v</code> option.</dd>
+
+    <dt id="env.FCM_GRAPHIC_DIFF">FCM_GRAPHIC_DIFF=<kbd>command</kbd></dt>
+
+    <dd>(Deprecated) Specifies an alternate command for doing graphical diff
+    tool. The <a href="annex_cfg.html#external">external</a> configuration file
+    should be used instead of this environment variable.</dd>
+
+    <dt id="env.FCM_GRAPHIC_MERGE">FCM_GRAPHIC_MERGE=<kbd>command</kbd></dt>
+
+    <dd>(Deprecated) Specifies an alternate command for doing graphical merge
+    tool. The <a href="annex_cfg.html#external">external</a> configuration file
+    should be used instead of this environment variable.</dd>
+
+    <dt id="env.FCM_VERBOSE">FCM_VERBOSE=<kbd>N</kbd></dt>
+
+    <dd>(Deprecated) An alternate way to specify the verbosity for FCM 1 extract
+    and build systems.</dd>
+  </dl>
+
+  <h2 id="fcm-add">fcm add</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm add --check (-c) [PATH]</code><br />
+    <code>fcm add <any valid <em>svn add</em> options></code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p>In the 1st form (i.e. <code>fcm add --check</code>), the system checks
+      for any files which are not currently under version control (i.e. those
+      marked with a <samp>?</samp> by <code>svn status</code>) and prompts the
+      user to make a decision on whether to schedule them for addition at the
+      next commit (using <code>svn add</code>).</p>
+
+      <p>In the 2nd form (i.e. without the <code>--check</code> option),
+      <code>fcm add</code> simply pass control to <code>svn add</code>. (For
+      detail of usage, please refer to the <a href=
+      "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.add.html">Subversion
+      book</a>.)</p>
+
+      <p>For further details refer to the section <a href=
+      "code_management.html#svn_basic_check">Adding and Removing Files</a>.</p>
+    </dd>
+  </dl>
+
+  <h2 id="fcm-branch">fcm branch</h2>
+
+  <dl>
+    <dt>Description</dt>
+
+    <dd>
+      <p>Deprecated. The 4 usages of this command have been replaced by the
+      following commands:</p>
+
+      <dl>
+        <dt><code>fcm branch --create --name NAME</code></dt>
+
+        <dd><a href="#fcm-branch-create">fcm branch-create</a></dd>
+
+        <dt><code>fcm branch --delete</code></dt>
+
+        <dd><a href="#fcm-branch-delete">fcm branch-delete</a></dd>
+
+        <dt><code>fcm branch [--info]</code></dt>
+
+        <dd><a href="#fcm-branch-info">fcm branch-info</a></dd>
+
+        <dt><code>fcm branch --list</code></dt>
+
+        <dd><a href="#fcm-branch-list">fcm branch-list</a></dd>
+      </dl>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>br</dd>
+  </dl>
+
+  <h2 id="fcm-branch-create">fcm branch-create</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm branch-create [OPTIONS] NAME [SOURCE]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p>Creates a new branch.</p>
+
+      <p>The 1st argument <var>NAME</var> must be the short name for your
+      branch. The name of the branch must contain only characters in the set
+      <code>[A-Za-z0-9_-.]</code>. If the <code>--ticket=N</code> option is not
+      specified and <var>NAME</var> contains only a list of positive integers
+      separated by <code>[_-]</code> (an underscore or a hyphen), the command
+      will assume that <var>NAME</var> also specifies the related ticket
+      numbers.</p>
+
+      <p>If the 2nd argument <var>SOURCE</var> is specified, it must either be
+      a URL or a path to a working copy of a standard FCM project. Otherwise,
+      the current working directory must be a working copy of a standard FCM
+      project.</p>
+
+      <p>This command performs the following actions:</p>
+
+      <ul>
+        <li>It determines the last changed revision of the trunk/source branch
+        at the HEAD (or the specified) revision.</li>
+
+        <li>It constructs the branch name from the option you have specified
+        and reports it.</li>
+
+        <li>It checks that the chosen branch name does not currently exist. If
+        so, the command aborts with an error.</li>
+
+        <li>If you do not specify the <code>--non-interactive</code> option, it
+        starts an editor (using a similar convention as <a href=
+        "#fcm-commit">commit</a>) to allow you to add further comment to the
+        commit log message. A standard commit log template and change summary
+        is provided for you below the line that says <samp>--Add your commit
+        message ABOVE - do not alter this line or those below--</samp>. If you
+        need to add any extra message to the log, please do so
+        <strong>above</strong> this line. When you exit the editor, the command
+        will report the commit log before prompting for confirmation that you
+        wish to proceed (it aborts if not).</li>
+
+        <li>It uses <code>svn copy</code> to create the branch.</li>
+      </ul>
+
+      <p>For further details refer to the section <a href=
+      "code_management.html#svn_branching_create">Creating Branches</a>.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--branch-of-branch</code></dt>
+
+        <dd>If the source URL is a valid URL of a branch in a standard FCM
+        project, this option tells the system to create a branch of the source
+        branch. Otherwise, it will normally create a branch from the
+        trunk.</dd>
+
+        <dt><code>--non-interactive</code></dt>
+
+        <dd>Tells the system not to prompt for anything. (The
+        <code>--svn-non-interactive</code> option is set automatically when you
+        specify <code>--non-interactive</code>.)</dd>
+
+        <dt><code>--password=PASSWORD</code></dt>
+
+        <dd>Specifies the password for authentication.</dd>
+
+        <dt><code>--rev-flag=NONE|NORMAL|NUMBER</code></dt>
+
+        <dd>Alters the branch name prefix behaviour. Your branch name will
+        normally be prefixed by the revision number from which it is branched.
+        (E.g. if the branch name is <samp>my_branch</samp> and you are
+        branching from revision 123 of the trunk, the final name will be
+        <samp>r123_my_branch</samp>.) If this revision number is associated
+        with a revision keyword, the keyword will be used in place of the
+        revision number. (E.g. if revision 123 is associated with the keyword
+        vn6.1, <samp>r123_my_branch</samp> will become
+        <samp>vn6.1_my_branch</samp>.) If <code>NORMAL</code> is specified, it
+        uses the default behaviour. If <code>NUMBER</code> is specified, it
+        will always use the revision number as the prefix, regardless of
+        whether the revision number is defined as a keyword or not. If
+        <code>NONE</code> is specified, it will not add a prefix to your branch
+        name.</dd>
+
+        <dt><code>--svn-non-interactive</code></dt>
+
+        <dd>Tells the system to run <code>svn</code> in non-interactive
+        mode.</dd>
+
+        <dt><code>--switch</code>, <code>-s</code></dt>
+
+        <dd><code><a href="#fcm-switch">fcm switch</a></code> the current
+        working directory (if it contains a relevant working copy) to point to
+        the newly created branch after the branch is created.</dd>
+
+        <dt><code>--ticket=N</code>, <code>-k N</code></dt>
+
+        <dd>Specifies one or more Trac ticket numbers, which the branch relates
+        to. Multiple ticket numbers can be set by specifying this option
+        multiple times, or by using a comma-separated list of ticket numbers as
+        the argument to the option. If set, the ticket numbers will be included
+        in the commit log message.</dd>
+
+        <dt><code>--type=TYPE</code>, <code>-t TYPE</code></dt>
+
+        <dd>
+          Specifies the type of branch to create. The argument to the option
+          must be one of the following:
+
+          <dl>
+            <dt><code>DEV::USER</code>, <code>DEV</code>, <code>USER</code>
+            (default)</dt>
+
+            <dd>A development branch for the current user (e.g.
+            <samp>branches/dev/<user_id>/<branch_name></samp>)</dd>
+
+            <dt><code>DEV::SHARE</code>, <code>SHARE</code></dt>
+
+            <dd>A shared development branch (e.g.
+            <samp>branches/dev/Share/<branch_name></samp>)</dd>
+
+            <dt><code>TEST::USER</code>, <code>TEST</code></dt>
+
+            <dd>A test branch for the current user (e.g.
+            <samp>branches/test/<user_id>/<branch_name></samp>)</dd>
+
+            <dt><code>TEST::SHARE</code></dt>
+
+            <dd>A shared test branch (e.g.
+            <samp>branches/test/Share/<branch_name></samp>)</dd>
+
+            <dt><code>PKG::USER</code>, <code>PKG</code></dt>
+
+            <dd>A package branch for the current user (e.g.
+            <samp>branches/pkg/<user_id>/<branch_name></samp>)</dd>
+
+            <dt><code>PKG::SHARE</code></dt>
+
+            <dd>A shared package branch (e.g.
+            <samp>branches/pkg/Share/<branch_name></samp>)</dd>
+
+            <dt><code>PKG::CONFIG</code>, <code>CONFIG</code></dt>
+
+            <dd>A configuration branch (e.g.
+            <samp>branches/pkg/Config/<branch_name></samp>)</dd>
+
+            <dt><code>PKG::REL</code>, <code>REL</code></dt>
+
+            <dd>A release branch (e.g.
+            <samp>branches/pkg/Rel/<branch_name></samp>)</dd>
+          </dl>
+        </dd>
+      </dl>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>bcreate, bc</dd>
+  </dl>
+
+  <h2 id="fcm-branch-delete">fcm branch-delete</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm branch-delete [OPTIONS] [TARGET]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p>Deletes a branch.</p>
+
+      <p>If <var>TARGET</var> is specified, it must either be a URL or a path
+      to a local working copy of a valid branch of a standard FCM project.
+      Otherwise, the current working directory must be a working copy of a
+      valid branch of a standard FCM project.</p>
+
+      <p>This command performs the following actions:</p>
+
+      <ul>
+        <li>Firstly, it provides exactly the same output as <a href=
+        "#fcm-branch-info">fcm branch-info</a>.</li>
+
+        <li>If you do not specify the <code>--non-interactive</code> option, it
+        starts an editor (using a similar convention as <a href=
+        "#fcm-commit">commit</a>) to allow you to add further comment to the
+        commit log message. A standard commit log template and change summary
+        is provided for you below the line that says <samp>--Add your commit
+        message ABOVE - do not alter this line or those below--</samp>. If you
+        need to add any extra message to the log, please do so
+        <strong>above</strong> this line. When you exit the editor, the command
+        will report the commit log before prompting for confirmation that you
+        wish to proceed with deleting the branch (it aborts if not).</li>
+      </ul>
+
+      <p>For further details refer to the section <a href=
+      "code_management.html#svn_branching_delete">Deleting Branches</a>.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <p>The command supports all options of <a href="#fcm-branch-info">fcm
+      branch-info</a> as well as the following:</p>
+
+      <dl>
+        <dt><code>--non-interactive</code></dt>
+
+        <dd>Tells the system not to prompt for anything. (The
+        <code>--svn-non-interactive</code> option is set automatically when you
+        specify <code>--non-interactive</code>.)</dd>
+
+        <dt><code>--password=PASSWORD</code></dt>
+
+        <dd>Specifies the password for authentication.</dd>
+
+        <dt><code>--svn-non-interactive</code></dt>
+
+        <dd>Tells the system to run <code>svn</code> in non-interactive
+        mode.</dd>
+
+        <dt><code>--switch</code>, <code>-s</code></dt>
+
+        <dd>If SOURCE not specified in the argument list,
+        <code><a href="#fcm-switch">fcm switch</a></code> the current working
+        copy to point to the <em>trunk</em> after the branch deletion.</dd>
+      </dl>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>bdelete, bdel, brm</dd>
+  </dl>
+
+  <h2 id="fcm-branch-diff">fcm branch-diff</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm branch-diff [OPTIONS] [TARGET]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p>The command displays the differences between the target branch and its
+      parent. This should show you the differences which you would get if you
+      tried to merge the changes in the branch into its parent.</p>
+
+      <p>If an argument <var>TARGET</var> is specified, it must either be a URL
+      or a path to a local working copy. Otherwise, the current working
+      directory must be a working copy. The specified URL or that of the
+      working copy must be a valid branch in a standard FCM project.</p>
+
+      <p>The command determines the base of the branch relative to its parent.
+      This is adjusted to account for any merges from the branch to its parent
+      or vice-versa. It then reports what path and revision it is comparing
+      against using <code>svn diff</code> or otherwise.</p>
+
+      <p>For further details refer to the section <a href=
+      "code_management.html#svn_basic_diff">Examining Changes</a>.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--diff-cmd=COMMAND</code></dt>
+
+        <dd>Option passed to <code>svn diff</code>.</dd>
+
+        <dt><code>--extensions=EXT</code>, <code>-x EXT</code></dt>
+
+        <dd>Option passed to <code>svn diff</code>.</dd>
+
+        <dt><code>--graphical</code>, <code>-g</code></dt>
+
+        <dd>Tells the <code>svn diff</code> to use a graphical tool to display
+        the differences. (The default graphical diff tool is
+        <code>xxdiff</code>, but you can alter the behaviour by following the
+        instruction discussed in the sub-section on <a href=
+        "code_management.html#svn_basic_diff">Examining Changes</a>.) This
+        switch should not be used with <code>--diff-cmd</code>,
+        <code>--extensions</code>, <code>--trac</code> and
+        <code>--wiki</code>.</dd>
+
+        <dt><code>--summarize</code>, <code>--summarise</code></dt>
+
+        <dd>Reports using <code>svn diff --summarize</code>.</dd>
+        
+	<dt><code>--xml</code></dt>
+
+	<dd>Used with --summarise to change output format to XML.</dd>
+
+        <dt><code>--trac</code>, <code>-t</code></dt>
+
+        <dd>Launches Trac with your default web browser to report the diff.
+        Note: if <var>TARGET</var> is a working copy, local changes in it will
+        not be displayed.</dd>
+
+        <dt><code>--wiki</code>, <code>-w</code></dt>
+
+        <dd>Prints a Trac wiki syntax to represent the diff.</dd>
+      </dl>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>bdiff, bdi</dd>
+  </dl>
+
+  <h2 id="fcm-branch-info">fcm branch-info</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm branch-info [OPTIONS] [TARGET]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p>Displays information about a branch.</p>
+
+      <p>If the argument <var>TARGET</var> is specified, it must either be a
+      URL or a path to a local working copy of a valid branch of a standard FCM
+      project. Otherwise, the current working directory must be a working copy
+      of a valid branch of a standard FCM project.</p>
+
+      <p>It performs the following actions:</p>
+
+      <ul>
+        <li>It reports the basic information of the branch URL, as returned by
+        <code>svn info</code>.</li>
+
+        <li>If <code>--verbose</code> is set, it also prints the log message of
+        the last change revision.</li>
+
+        <li>If the URL is not the trunk:
+
+          <ul>
+            <li>It reports the branch creation information, including the
+            revision, author and date. It also reports the parent URL at REV of
+            the branch. If <code>--verbose</code> is set, it prints the log
+            message of the branch creation revision.</li>
+
+            <li>If the branch does not exist at the HEAD, it reports the
+            revision at which it is deleted.</li>
+
+            <li>It reports the last merges into and from the parent branch. If
+            <code>--verbose</code> is set, it also prints the log message of
+            these merges.</li>
+
+            <li>It reports the revisions available for merging into and from
+            the parent branch. If <code>--verbose</code> is set, it also prints
+            the log message of these revisions.</li>
+          </ul>
+        </li>
+
+        <li>It reports relationship with other branches, depending on
+        options.</li>
+      </ul>
+
+      <p>For further details refer to the section <a href=
+      "code_management.html#svn_branching_info">Getting Information About
+      Branches</a>.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--show-all</code>, <code>-a</code></dt>
+
+        <dd>Turns on <code>--show-children</code>, <code>--show-other</code>
+        and <code>--show-siblings</code>.</dd>
+
+        <dt><code>--show-children</code></dt>
+
+        <dd>Lists the current children of the branch and their create
+        revisions. Where appropriate, it reports the revision of each child,
+        which is last merged from/into the current branch. It also reports the
+        available merges from/into each child into the current branch.</dd>
+
+        <dt><code>--show-other</code></dt>
+
+        <dd>Reports all custom and reverse merges into the current branch.</dd>
+
+        <dt><code>--show-siblings</code></dt>
+
+        <dd>Reports recent merges from/into sibling branches. It also reports
+        the available merges from/into sibling branches where recent merges are
+        detected. If <code>--verbose</code> is set, it also prints the log
+        message of these merges.</dd>
+
+        <dt><code>--verbose</code>, <code>-v</code></dt>
+
+        <dd>Increases the verbosity.</dd>
+      </dl>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>binfo</dd>
+  </dl>
+
+  <h2 id="fcm-branch-list">fcm branch-list</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm branch-list [OPTIONS] [TARGET ...]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p>Searches and lists branches in projects. By default, it lists only
+      branches created by the current user.</p>
+
+      <p>If no <var>TARGET</var> is specified, the current working directory is
+      assumed to be the target. Each target must either be a
+      <var>URL[@REV]</var> or a <var>PATH[@REV]</var> to a working copy of a
+      standard FCM project.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--only=DEPTH:PATTERN</code></dt>
+
+        <dd>Specify a regular expression to match at various depth. E.g. with
+        the normal FCM branch naming convention, <samp>--only=1:dev
+        --only=2:fred</samp> will display only the development branches owned by
+        user ID <samp>fred</samp>. (This option is cumalative, and overrides the
+        <code>--show-all</code> and <code>--user=PATTERN</code> options.)</dd>
+
+        <dt><code>--quiet</code>, <code>-q</code></dt>
+
+        <dd>Decreases verbosity. Only prints branches matching the search
+        criteria.</dd>
+
+        <dt><code>--show-all</code>, <code>-a</code></dt>
+
+        <dd>Prints branches of all users. (This option overrides the
+        <code>--user=USER</code> option.)</dd>
+
+        <dt><code>--url</code></dt>
+
+        <dd>Displays Subversion URL instead of FCM location keywords.</dd>
+
+        <dt><code>--user=PATTERN</code>, <code>-u PATTERN</code></dt>
+
+        <dd>Equivalent to <code>--only=2:^PATTERN$</code> for projects with the
+        normal FCM branch naming convention.  Lists branches created by the
+        specified list of users instead of the current user. With the normal FCM
+        branch naming convention, you can also list shared branches by
+        specifying the user as <code>Share</code>, configuration branches by
+        specifying the user as <code>Config</code> and release branches by
+        specifying the user as <code>Rel</code>. (This option is
+        cumalative.)</dd>
+      </dl>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>blist, bls</dd>
+  </dl>
+
+  <h2 id="fcm-browse">fcm browse</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm browse [OPTIONS] [TARGET ...]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p><code>fcm browse</code> invokes the web-browser to launch the
+      corresponding URL of the web-based repository browser (currently Trac
+      browser) to view the Subversion repository specified by
+      <var>TARGET</var>.</p>
+
+      <p>If <var>TARGET</var> is specified, it must be a path to a local
+      working copying, a Subversion URL or an FCM URL keyword. Otherwise, it is
+      set to <samp>.</samp>, the current working directory. If
+      <var>TARGET</var> is a directory in the local file system, the command
+      will determine whether it is a working copy. If so, its associated
+      Subversion URL will be used. The command fails if the directory is not a
+      working copy. The Subversion URL must be associated with an FCM location
+      keyword, so that the system knows how to map the Subversion URL to the
+      web browser URL.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--browser=COMMAND</code>, <code>-b COMMAND</code></dt>
+
+        <dd>
+          If this option is specified, its argument <var>COMMAND</var> must be
+          a valid command to a web browser. If this option is not specified,
+          the default is to use <code>firefox</code>, or the <var>browser</var>
+          setting in the external configuration files (i.e.
+          <samp>$FCM/etc/fcm/external.cfg</samp> and
+          <samp>$HOME/.metomi/fcm/external.cfg</samp>). For example:
+          <pre>
+browser = konqueror
+</pre>
+        </dd>
+      </dl>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>trac, www</dd>
+  </dl>
+
+  <h2 id="fcm-build">fcm build</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm build [OPTIONS...] [CFGFILE]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p><code>fcm build</code> invokes the deprecated FCM 1 build system.</p>
+
+      <p>The path to a valid build configuration file <var>CFGFILE</var> may be
+      provided as either a URL or a pathname. Otherwise, the build system
+      searches the default locations for a build configuration file.</p>
+
+      <p>For further details, please refer to the chapter on <a href=
+      "build.html">The FCM 1 Build System</a>.</p>
+    </dd>
+
+    <dt>Option</dt>
+
+    <dd>
+      <p>If no option is specified, the system uses the <code>-s 5 -t all -j 1
+      -v 1</code> by default.</p>
+
+      <dl>
+        <dt><code>--archive</code>, <code>-a</code></dt>
+
+        <dd>This option can be specified to switch on the archive mode. In
+        archive mode, sub-directories produced by the build will be archived in
+        <code>tar</code> format at the end of a successful build. This option
+        should not be used if the current build is intended to be re-used as a
+        pre-compiled build.</dd>
+
+        <dt><code>--clean</code></dt>
+
+        <dd>If this option is specified, the build system will parse the
+        configuration file, remove contents generated by the build system in
+        the destination and exit.</dd>
+
+        <dt><code>--full</code>, <code>-f</code></dt>
+
+        <dd>If this option is specified, the build system will attempt to
+        perform a full/clean build by removing any previous build files.
+        Otherwise, the build system will attempt to perform an incremental
+        build where appropriate.</dd>
+
+        <dt><code>--ignore-lock</code></dt>
+
+        <dd>When the build system is invoked, it sets a lock file in the build
+        root directory to prevent other extracts/builds taking place in the
+        same location. The lock file is normally removed when the build system
+        exits. (However, a lock file may be left behind if the user interrupts
+        the command, e.g. by typing <kbd>Ctrl-C</kbd>.) You can bypass the
+        check for lock files by using this option.</dd>
+
+        <dt><code>--jobs=N</code>, <code>-j N</code></dt>
+
+        <dd>This option can be used to specify the number of parallel jobs that
+        can be handled by the <code>make</code> command. The argument
+        <var>N</var> must be a natural integer to represent the number of jobs.
+        If not specified, the default is to perform serial <code>make</code>
+        (i.e. 1 job).</dd>
+
+        <dt><code>--stage=STAGE</code>, <code>-s STAGE</code></dt>
+
+        <dd>
+          This option can be used to limit the actions performed by the build
+          system, up to a named stage determined by the argument
+          <var>STAGE</var>. If not specified, the default is 5. The stages are:
+
+          <ul>
+            <li><dfn>1, s or setup</dfn>: Stage 1, read configuration and set
+            up the build</li>
+
+            <li><dfn>2, pp or pre_process</dfn>: Stage 2, perform
+            pre-processing for source files that require pre-processing</li>
+
+            <li><dfn>3, gd or generate_dependency</dfn>: Stage 3, scan source
+            files for dependency information and generate <code>make</code>
+            rules for them</li>
+
+            <li><dfn>4, gi or generate_interface</dfn>: Stage 4, generate
+            interface files for Fortran 9X source files</li>
+
+            <li><dfn>5, m or make</dfn>: Stage 5, invoke the <code>make</code>
+            command to build the project</li>
+          </ul>
+        </dd>
+
+        <dt><code>--targets=TARGETS</code>, <code>-t TARGETS</code></dt>
+
+        <dd>This option can be used to specify the targets to be built. The
+        argument <var>TARGETS</var> must be a colon-separated list of valid
+        targets. If not specified, the default to be built is the
+        <samp>all</samp> target.</dd>
+
+        <dt><code>--verbose=N</code>, <code>-v N</code></dt>
+
+        <dd>This option can be specified to alter the level of diagnostic
+        output. The argument <var>N</var> to this option must be an integer
+        greater than or equal to 0. The verbose level increases with this
+        number. If not specified, the default verbose level is 1.</dd>
+      </dl>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>bld</dd>
+  </dl>
+
+  <h2 id="fcm-cfg-print">fcm cfg-print</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm cfg-print [OPTIONS] [TARGET ...]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p>Parses each FCM configuration file specified in the argument list, and
+      prints the result to STDOUT.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--fcm1</code>, <code>-f</code></dt>
+
+        <dd>If specified, targets should be in FCM 1 format. Otherwise, they
+        should be in FCM 2 format.</dd>
+      </dl>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>cfg</dd>
+  </dl>
+
+  <h2 id="fcm-cmp-ext-cfg">fcm cmp-ext-cfg</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm cmp-ext-cfg [OPTIONS] CFG1 CFG2</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p><code>fcm cmp-ext-cfg</code> compares the deprecated FCM 1 extract
+      configurations of two similar extract configuration files <var>CFG1</var>
+      and <var>CFG2</var>. It reports repository branches and source
+      directories that are declared in one file but not another. If a source
+      directory is declared in both files, it compares their versions. If they
+      differ, it uses <code>svn log</code> to obtain a list of revision numbers
+      at which changes are made to the source directory. It then reports, for
+      each declared repository branch, the revisions at which changes occur in
+      their declared source directories.</p>
+
+      <p>The list of revisions for each declared repository branch is normally
+      printed out as a simple list in plain text.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--verbose=N</code>, <code>-v N</code></dt>
+
+        <dd>You can use this option to print the log of each revision, by
+        setting <var>N</var> to 2.</dd>
+
+        <dt><code>--wiki-format=TARGET</code>, <code>--wiki=TARGET</code>,
+        <code>-w TARGET</code></dt>
+
+        <dd>Alternatively, you can use this option to change that into an
+        tabular output suitable for inserting into a Trac wiki page. This
+        option must be specified with an argument, which must be the Subversion
+        URL or FCM URL keyword of an FCM project associated with the intended
+        Trac system. The URL allows the command to work out the correct wiki
+        syntax to use.</dd>
+      </dl>
+    </dd>
+  </dl>
+
+  <h2 id="fcm-commit">fcm commit</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm commit [OPTIONS] [PATH]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p><code>fcm commit</code> sends changes from your working copy in the
+      current working directory (or from <var>PATH</var> if it is specified) to
+      the repository.</p>
+
+      <p>This command performs the following actions:</p>
+
+      <ul>
+        <li>It checks that the current working directory (or <var>PATH</var> if
+        it is specified) is a working copy. (If not, it aborts with an
+        error).</li>
+
+        <li>It always commits from the top level of the working copy.</li>
+
+        <li>It checks that there are no files in conflict, missing or out of
+        date (it aborts if there are).</li>
+
+        <li>It checks that any files which have been added have the
+        <var>svn:executable</var> property set correctly (in case a script was
+        added before the execute bit was set correctly).</li>
+
+        <li>It reads in any existing commit message.
+
+          <ul>
+            <li>The commit message is stored in the file
+            <samp>#commit_message#</samp> in the top level of your working
+            copy.</li>
+          </ul>
+        </li>
+
+        <li>It adds the following line to the commit log message: <samp>--Add
+        your commit message ABOVE - do not alter this line or those
+        below--</samp>. This line, and anything below it, is automatically
+        ignored by <code>svn commit</code>. If you need to add any extra
+        message to the log, please do so <strong>above</strong> this line.</li>
+
+        <li>If you have run the <a href="#fcm-merge">merge</a> command before
+        the commit, you will get a standard commit log template below a line
+        that says <samp>--FCM message (will be inserted
+        automatically)--</samp>. Please do not try to alter this message (your
+        changes will be ignored if you do).</li>
+
+        <li>It adds current status information to the commit message showing
+        the list of modifications below a line that says <samp>--Change summary
+        (not part of commit message)--</samp>.</li>
+
+        <li>It starts an editor to allow you to edit the commit message.
+
+          <ul>
+            <li>If defined, the environment variable <var>SVN_EDITOR</var>
+            specifies the editor.</li>
+
+            <li>Otherwise the environment variable <var>VISUAL</var> specifies
+            the editor.</li>
+
+            <li>Otherwise the environment variable <var>EDITOR</var> specifies
+            the editor.</li>
+
+            <li>Otherwise the editor <code>nedit</code> is used.</li>
+          </ul>
+        </li>
+
+        <li>It reports the commit message that will be sent to Subversion and
+        then asks if you want to proceed (it aborts if not).</li>
+
+        <li>It calls <code>svn commit</code> to send the changes to the
+        repository.</li>
+
+        <li>It calls <code>svn update</code> to bring your working copy up to
+        the new revision.</li>
+      </ul>
+
+      <p>For further details refer to the section <a href=
+      "code_management.html#svn_basic_commit">Committing Changes</a>.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--dry-run</code></dt>
+
+        <dd>Prevents the command from committing any changes. This can be used
+        to allow you to add notes to your commit message whilst you are still
+        preparing your change.</dd>
+
+        <dt><code>--password=PASSWORD</code></dt>
+
+        <dd>Specifies the password for authentication.</dd>
+
+        <dt><code>--svn-non-interactive</code></dt>
+
+        <dd>Tells the system to run <code>svn</code> in non-interactive
+        mode.</dd>
+      </dl>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>ci</dd>
+  </dl>
+
+  <h2 id="fcm-conflicts">fcm conflicts</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm conflicts [PATH]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p><code>fcm conflicts</code> helps you to resolve any text files in your
+      working copy which have conflicts by using the graphical merge tool
+      <code>xxdiff</code>. If <var>PATH</var> is set, it must be a working
+      copy, and the command will operate in it. If <var>PATH</var> is not set,
+      the command will operate in your current working directory.</p>
+
+      <p>This command performs the following actions:</p>
+
+      <ul>
+        <li>For each text file reported as being in conflict (i.e. marked with
+        a <samp>C</samp> by <code>svn status</code>) it calls
+        <code>xxdiff</code>.</li>
+
+        <li>If <code>xxdiff</code> reports all conflicts resolved then if asks
+        if you wish to run <code>svn resolved</code> on that file.</li>
+      </ul>
+
+      <p>For further details refer to the section <a href=
+      "code_management.html#svn_basic_conflicts">Resolving Conflicts</a>.</p>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>cf</dd>
+  </dl>
+
+  <h2 id="fcm-delete">fcm delete</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm delete --check (-c)</code><br />
+    <code>fcm delete <any valid <em>svn delete</em> options></code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p>In the 1st form (i.e. <code>fcm delete --check</code>), the system
+      checks for any files which are missing (i.e. those marked with a
+      <samp>!</samp> by <code>svn status</code>) and prompts the user to make a
+      decision on whether to schedule them for deletion at the next commit
+      (using <code>svn delete</code>).</p>
+
+      <p>In the 2nd form (i.e. without the <code>--check</code> option),
+      <code>fcm delete</code> simply pass control to <code>svn delete</code>.
+      (For detail of usage, please refer to the <a href=
+      "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.delete.html">Subversion
+      book</a>.)</p>
+
+      <p>For further details refer to the section <a href=
+      "code_management.html#svn_basic_check">Adding and Removing Files</a>.</p>
+    </dd>
+  </dl>
+
+  <h2 id="fcm-diff">fcm diff</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm diff [OPTIONS] [TARGET ...]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p>Display the differences between two revisions or paths. <code>fcm
+      diff</code> supports all of the arguments and alternate names supported
+      by <code>svn diff</code> (refer to the <a href=
+      "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.diff.html">Subversion
+      book</a> for details).</p>
+
+      <p>For further details refer to the section <a href=
+      "code_management.html#svn_basic_diff">Examining Changes</a>.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <p><code>fcm diff</code> supports the following options in addition to
+      the options of <code>svn diff</code> (refer to the <a href=
+      "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.diff.html">Subversion
+      book</a> for details):</p>
+
+      <dl>
+        <dt><code>--graphical</code>, <code>-g</code></dt>
+
+        <dd>If this option is specified, the command uses a graphical tool to
+        display the differences. (The default graphical diff tool is
+        <code>xxdiff</code>, but you can alter the behaviour by following the
+        instruction discussed in the sub-section on <a href=
+        "code_management.html#svn_basic_diff">Examining Changes</a>.) This
+        option can be used in combination with all other valid options except
+        <code>--diff-cmd</code> and <code>--extensions</code>.</dd>
+
+        <dt><code>--summarise</code></dt>
+
+        <dd>This option is implemented in FCM as a wrapper to the Subversion
+        <code>--summarize</code> option. It prints only a summary of the
+        results.</dd>
+
+        <dt><code>--branch</code>, <code>-b</code></dt>
+
+        <dd>This usage is deprecated. It is replaced by the <a href=
+        "#fcm-branch-diff">fcm branch-diff</a> command.</dd>
+      </dl>
+    </dd>
+  </dl>
+
+  <h2 id="fcm-export-items">fcm export-items</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm export-items [OPTIONS...] SOURCE</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p><code>fcm export-items</code> exports directories in SOURCE as a list
+      of versioned items. The SOURCE should be the URL of a branch in a
+      Subversion repository with the standard FCM layout.</p>
+
+      <p>This command is used to support a legacy working practice, in which
+      directories in a source tree are regarded as individual versioned
+      items.</p>
+
+      <p>The configuration file should be in the deprecated FCM 1 configuration
+      format. The label in each entry should be a path relative to the source
+      URL. If the path ends in <samp>*</samp> then the path is expanded
+      recursively and any sub-directories containing regular files are added to
+      the list of relative paths to export. The value may be empty, or it may
+      be a list of space separated <em>conditions</em>. Each condition is a
+      conditional operator (<code>></code>, <code>>=</code>,
+      <code><</code>, <code><=</code>, <code>==</code> or
+      <code>!=</code>) followed by a revision number. The command uses the
+      revision log to determine the revisions at which the relative path has
+      been updated in the source URL. If these revisions also satisfy the
+      conditions set by the user, they will be considered in the export.</p>
+
+      <p>For further details, please refer to <a href=
+      "system_admin.html#alternate_versions">System Administration >
+      Maintaining alternate versions of namelists and data files</a>.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--config-file=PATH</code>, <code>--file=PATH</code>, <code>-f
+        PATH</code></dt>
+
+        <dd>Specifies the path to the configuration file.
+        (default=<samp>$PWD/fcm-export-items.cfg</samp>)</dd>
+
+        <dt><code>--directory=PATH</code>, <code>-C PATH</code></dt>
+
+        <dd>Specifies the path to the destination.
+        (default=<samp>$PWD</samp>)</dd>
+
+        <dt><code>--new</code></dt>
+
+        <dd>Specifies the new mode. In this mode, everything is re-exported.
+        Otherwise, the system runs in incremental mode, in which the version
+        directories are only updated if they do not already exist.</dd>
+      </dl>
+    </dd>
+  </dl>
+
+  <h2 id="fcm-extract">fcm extract</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm extract [OPTIONS...] [CFGFILE]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p><code>fcm extract</code> invokes the deprecated FCM 1 extract
+      system.</p>
+
+      <p>The path to a valid extract configuration file <var>CFGFILE</var> may
+      be provided as either a URL or a pathname. Otherwise, the extract system
+      searches the default locations for an extract configuration file.</p>
+
+      <p>For further details, please refer to the chapter on <a href=
+      "extract.html">The FCM 1 Extract System</a>.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--clean</code></dt>
+
+        <dd>If this option is specified, the extract system will parse the
+        configuration file, remove contents generated by previous extract in
+        the destination and exit.</dd>
+
+        <dt><code>--full</code>, <code>-f</code></dt>
+
+        <dd>If this option is specified, the extract system will attempt to
+        perform a full extract by removing any previous extracted files.
+        Otherwise, the extract system will attempt to perform an incremental
+        extract where appropriate.</dd>
+
+        <dt><code>--ignore-lock</code></dt>
+
+        <dd>When the extract system is invoked, it sets a lock file in the
+        extract destination root directory to prevent other extracts/builds
+        taking place in the same location. The lock file is normally removed
+        when the extract system exits. (However, a lock file may be left behind
+        if the user interrupts the command, e.g. by typing <kbd>Ctrl-C</kbd>.)
+        You can bypass the check for lock files by using this option.</dd>
+
+        <dt><code>--verbose=N</code>, <code>-v N</code></dt>
+
+        <dd>This option can be specified to alter the level of diagnostic
+        output. The argument <var>N</var> to this option must be an integer
+        greater than or equal to 0. The verbose level increases with this
+        number. If not specified, the default verbose level is 1.</dd>
+      </dl>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>ext</dd>
+  </dl>
+
+  <h2 id="fcm-gui">fcm gui</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm gui [DIR]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p><code>fcm gui</code> starts up the FCM GUI. If <var>DIR</var> is
+      specified then this is used as the working directory.</p>
+
+      <p>For further details, please refer to the section <a href=
+      "code_management.html#svn_gui">Using the GUI</a>.</p>
+    </dd>
+  </dl>
+
+  <h2 id="fcm-keyword-print">fcm keyword-print</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm keyword-print [OPTIONS] [TARGET ...]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p>If no argument is specified, <code>fcm keyword-print</code> prints all
+      the registered FCM location keywords. Otherwise, it prints the location
+      and revision keywords according to the argument <var>TARGET</var>, which
+      must be an FCM URL keyword, a Subversion URL or a path to a Subversion
+      working copy.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--verbose</code>, <code>-v</code></dt>
+
+        <dd>Prints implied location keywords as well.</dd>
+      </dl>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>kp</dd>
+  </dl>
+
+  <h2 id="fcm-loc-layout">fcm loc-layout</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm loc-layout [OPTIONS] [TARGET ...]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p>Parse the URL of a FCM/Subversion <var>TARGET</var>, and print its FCM
+      layout information.</p>
+
+      <p>If no argument is specified, <var>TARGET</var> is the current working
+      directory.</p>
+
+      <p>See also <a href="system_admin.html#svn_layout">System Administration
+      > Subversion > Repository Layout</a>.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--verbose</code>, <code>-v</code></dt>
+
+        <dd>Increase verbosity.</dd>
+      </dl>
+    </dd>
+  </dl>
+
+  <h2 id="fcm-make">fcm make</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm make [OPTIONS] [DECLARATION ...]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p><code>fcm make</code> invokes the FCM make system, which is used to
+      run the extract and build systems and other utilities.</p>
+
+      <p>For further details, please refer to the chapter on <a href=
+      "make.html">FCM Make</a>.</p>
+    </dd>
+
+    <dt>Arguments</dt>
+
+    <dd>
+      <p>Each argument is considered to be a declaration line to append to the
+      configuration file.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--config-file-path=PATH, -F PATH</code></dt>
+
+        <dd>Specifies paths for searching configuration files specified in
+        relative paths.</dd>
+
+        <dt><code>--config-file=PATH, --file=PATH, -f PATH</code></dt>
+
+        <dd>Specifies paths to the configuration files either as a URL or a
+        pathname. (default=<samp>fcm-make.cfg</samp> in the current working
+        directory)</dd>
+
+        <dt><code>--directory=PATH, -C PATH</code></dt>
+
+        <dd>Specifies the path to the destination. (default=<samp>$PWD</samp>
+        or whatever is specified in the <code>dest</code> setting in the
+        configuration file)</dd>
+
+        <dt><code>--ignore-lock</code></dt>
+
+        <dd>Ignores lock file. When the system is invoked, it sets up a lock
+        file in the destination. The lock is normally removed when the system
+        completes the make. While the lock file is in place, another make invoked
+        in the same destination will fail. This option can be used to bypass
+        this check.</dd>
+
+        <dt><code>--jobs=N, -j N</code></dt>
+
+        <dd>Specifies the number of (child) processes that can be run
+        simultaneously.</dd>
+
+        <dt><code>--new</code></dt>
+
+        <dd>Removes items in the destination created by the previous make, and
+        starts a new make.</dd>
+
+        <dt><code>--quiet, -q</code></dt>
+
+        <dd>Decreases the verbosity level.</dd>
+
+        <dt><code>--verbose, -v</code></dt>
+
+        <dd>Increases the verbosity level.</dd>
+      </dl>
+    </dd>
+  </dl>
+
+  <h2 id="fcm-merge">fcm merge</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm merge [OPTIONS] SOURCE</code><br />
+    <code>fcm merge --custom --revision N[:M] [OPTIONS] SOURCE</code><br />
+    <code>fcm merge --custom [OPTIONS] URL1[@REV1] URL2[@REV2]</code><br />
+    <code>fcm merge --reverse --revision [M:]N [OPTIONS]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p><code>fcm merge</code> allows you to merge changes from a source into
+      your working copy.</p>
+
+      <p>Before it begins, the command does the following:</p>
+
+      <ul>
+        <li>If a <var>SOURCE</var> or <var>URL</var> is specified, it can be a
+        full URL or a partial URL starting at the branches, trunk or tags
+        level.
+
+          <ul>
+            <li>If a partial URL is given, and the path name does not begin
+            with <samp>trunk</samp>, <samp>tags</samp> or <samp>branches</samp>
+            then <samp>branches/</samp> is automatically added to the beginning
+            of your path.</li>
+          </ul>
+        </li>
+
+        <li>It determines the <var>TARGET</var> URL by examining your working
+        copy.</li>
+
+        <li>If the current directory is not the top of your working copy, it
+        changes the current directory to the top of your working copy.</li>
+
+        <li>If your working copy is not pointing to a branch of a project
+        managed by FCM, the command aborts with an error.</li>
+
+        <li>If you do not specify the <code>--non-interactive</code> option, it
+        checks for any local modifications in your working copy. If it finds
+        any it reports them and asks you to confirm that you wish to continue
+        (it aborts if not).</li>
+      </ul>
+
+      <dl>
+        <dt>Automatic mode (i.e. neither <code>--custom</code> nor
+        <code>--reverse</code> is specified)</dt>
+
+        <dd>
+          <p>Automatic merges are used to merge changes between two directly
+          related branches, (i.e. the branches must either be created from the
+          same parent or have a parent/child relationship). These merges are
+          tracked by FCM and can be used by subsequent FCM commands. The merge
+          delta is calculated by doing the following:</p>
+
+          <ul>
+            <li>It checks that the <var>SOURCE</var> and <var>TARGET</var> are
+            directly related.</li>
+
+            <li>It determines the base revision and path of the <em>common
+            ancestor</em> of the <var>SOURCE</var> and <var>TARGET</var>.</li>
+
+            <li>The base revision and path are adjusted to account for any
+            merges from the <var>SOURCE</var> to the <var>TARGET</var> or
+            vice-versa.</li>
+
+            <li>It reports the revisions from <var>SOURCE</var> available for
+            merging into <var>TARGET</var>. If the <code>--verbose</code>
+            option is set, it prints the log for these revisions. It aborts if
+            no revision is available for merging.</li>
+
+            <li>If there are 2 or more revisions available for merging and you
+            do not specify the <code>--non-interactive</code> target, it asks
+            you which revision of the <var>SOURCE</var> you wish to merge from.
+            The default is the last changed revision of the <var>SOURCE</var>.
+            The merge delta is between the base and the specified revision of
+            the <var>SOURCE</var>.</li>
+
+            <li>If your working copy is a sub-tree of the <var>TARGET</var>, it
+            ensures that the <var>SOURCE</var> contains only changes in the
+            same sub-tree. Otherwise, the merge is unsafe, and the command will
+            abort with an error.
+
+              <p>N.B.: The command looks for changes in the <var>SOURCE</var>
+              by going through the list of changed files since the
+              <var>SOURCE</var> was last merged into the <var>TARGET</var>. (If
+              there is no previous merge from SOURCE to <var>TARGET</var>, the
+              common ancestor is used.) It is worth noting that there are
+              situations when the command will regard your merge as
+              <em>unsafe</em> (and so will fail incorrectly) even if the
+              changes in the <var>SOURCE</var> outside of the current sub-tree
+              will result in a null merge. This can happen if the changes are
+              the results of a previous merge from the <var>TARGET</var> to the
+              <var>SOURCE</var> or if these changes have been reversed. In such
+              case, you will have to perform your merge in a working copy of a
+              full tree.</p>
+            </li>
+          </ul>
+        </dd>
+
+        <dt>Custom mode (i.e. <code>--custom</code> is specified)</dt>
+
+        <dd>
+          <p>The custom mode is useful if you need to merge changes selectively
+          from another branch. The custom mode can be used in two forms:</p>
+
+          <ul>
+            <li>In the first form, you must specify a <var>SOURCE</var> as well
+            as a revision (range) using the <code>--revision</code> option. If
+            you specify a single revision <var>N</var>, the merge delta is
+            between revision <var>N - 1</var> and revision <var>N</var> of the
+            SOURCE. Otherwise, the merge delta is between revision <var>N</var>
+            and revision <var>M</var>, where <var>N</var> <
+            <var>M</var>.</li>
+
+            <li>In the second form, you must specify two URLs. The merge delta
+            is simply between the two URLs. (For each URL, if you do not
+            specify a peg revision, the command will peg the URL with its last
+            changed revision.)</li>
+          </ul>
+
+          <p>N.B. Unlike automatic merges, custom merges are not tracked or
+          used by subsequent FCM <code>diff</code> or <code>merge</code>
+          commands, (although <code>branch-info</code> can be set to report
+          them). Custom merges are always allowed, even if your working copy is
+          pointing to a sub-tree of a branch. However, there is no checking
+          mechanism to ensure the safety of your sub-tree custom merge so you
+          should only do this if you are confident it is what you want.
+          Therefore, it is recommended that you use automatic merges where
+          possible, and use custom merges only if you know what you are
+          doing.</p>
+        </dd>
+
+        <dt>Reverse mode (i.e. <code>--reverse</code> is specified)</dt>
+
+        <dd>
+          <p>The reverse mode is useful if you need to reverse a changeset (or
+          a range of changesets) in the current source of the working copy. In
+          this mode, you must specify a revision (range) using the
+          <code>--revision</code> option. If you specify a single revision
+          <var>N</var>, the merge delta is between revision <var>N</var> and
+          revision <var>N - 1</var> of the current branch. Otherwise, the merge
+          delta is between revision <var>M</var> and revision <var>N</var>,
+          where <var>M</var> > <var>N</var>.</p>
+
+          <p>N.B. Like custom merges, reverse merges are not tracked or used by
+          subsequent FCM <code>diff</code> or <code>merge</code> commands,
+          (although <code>branch-info</code> can be set to report them).
+          Likewise, reverse merges in sub-trees are always allowed, although
+          there is no checking mechanism to ensure the safety of your sub-tree
+          reverse merge.</p>
+        </dd>
+      </dl>
+
+      <p>Once the merge delta is determined, the command performs the
+      following:</p>
+
+      <ul>
+        <li>If you set the <code>--dry-run</code> option or if you are running
+        in the interactive mode, it reports what changes will result from
+        performing this merge by calling <code>svn merge --dry-run</code>.
+
+          <ul>
+            <li>It prints the actual <code>svn merge --dry-run</code> command
+            if the <code>--verbose</code> option is specified.</li>
+
+            <li>If you specify the <code>--dry-run</code> option, it exits
+            after reporting what changes will result from performing the
+            merge.</li>
+          </ul>
+        </li>
+
+        <li>If you are running in the interactive mode, it asks if you want to
+        go ahead with the merge (it aborts if not).</li>
+
+        <li>It performs the merge by calling <code>svn merge</code> to apply
+        the delta between the base and the <var>SOURCE</var> on your working
+        copy.
+
+          <ul>
+            <li>It prints the actual <code>svn merge</code> command if the
+            <code>--verbose</code> option is specified.</li>
+          </ul>
+        </li>
+
+        <li>It adds a standard template into the commit message to provide
+        details of the merge. The template is written below the line that says
+        <samp>--FCM message (will be inserted automatically)--</samp>. The
+        <a href="fcm-commit">fcm commit</a> command will detect the existence
+        of the template, so that you will not be able to alter it by accident.
+
+          <ul>
+            <li>The commit message is stored in the file
+            <samp>#commit_message#</samp> in the top level of your working
+            copy. It is created by the merge command if it does not already
+            exist.</li>
+
+            <li>If the <code>--auto-log</code> option is specified in the
+            automatic mode, it adds the log messages of the merged revisions as
+            well as the standard template.</li>
+          </ul>
+        </li>
+      </ul>
+
+      <p>For further details refer to the section <a href=
+      "code_management.html#svn_branching_merge">Merging</a>.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--auto-log</code></dt>
+
+        <dd>In automatic mode, adds the log messages of the merged revisions in
+        the commit log. Has no effect in other merge modes.</dd>
+
+        <dt><code>--dry-run</code></dt>
+
+        <dd>Tries operation but make no changes.</dd>
+
+        <dt><code>--non-interactive</code></dt>
+
+        <dd>Tells the system not to prompt for anything.</dd>
+
+        <dt><code>--revision=REV</code>, <code>-r REV</code></dt>
+
+        <dd>Specifies a revision or a revision range.</dd>
+
+        <dt><code>--verbose</code>, <code>-v</code></dt>
+
+        <dd>Prints extra information.</dd>
+      </dl>
+    </dd>
+  </dl>
+
+  <h2 id="fcm-mkpatch">fcm mkpatch</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm mkpatch [OPTIONS] URL [OUTDIR]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p><code>fcm mkpatch</code> creates patches from the specified revisions
+      of the specified <var>URL</var>, which must be a branch URL of a valid
+      FCM project. If the <var>URL</var> is a sub-directory of a branch, it
+      will use the root of the branch.</p>
+
+      <p>If <var>OUTDIR</var> is specified, the output is sent to
+      <var>OUTDIR</var>. Otherwise, the output will be sent to a default
+      location in the current directory (<samp>$PWD/fcm-mkpatch-out/</samp>).
+      The output directory will contain the patch for each revision as well as
+      a script for importing the patch.</p>
+
+      <p>Within the output directory are the <em>patches</em> and the log
+      message file for each revision. It also contains a generated script
+      <code>fcm-import-patch</code> for importing the patches. The user of the
+      script can invoke the script with either a URL or a working copy
+      argument, and the script will attempt to import the patches into the
+      given URL or working copy.</p>
+
+      <p>It is worth noting that changes in Subversion properties, including
+      changes in executable permissions, are not handled by the import
+      script.</p>
+    </dd>
+
+    <dt>Options</dt>
+
+    <dd>
+      <dl>
+        <dt><code>--exclude=PATH</code></dt>
+
+        <dd>Excludes a path in the URL. The specified path must be a relative
+        path of the URL. Glob patterns such as <code>*</code> and
+        <code>?</code> are acceptable. Changes in an excluded path will not be
+        considered in the patch. A changeset containing changes only in the
+        excluded path will not be considered at all. Multiple paths can be
+        specified by using a colon-separated list of paths, or by specifying
+        this option multiple times.</dd>
+
+        <dt><code>--organisation=NAME</code></dt>
+
+        <dd>Specifies the name of your organisation. The command will attempt
+        to parse the commit log message for each revision in the patch. It will
+        remove all merge templates, replace Trac links with a modified string,
+        and add information about the original changeset. If you specify the
+        name of your organisation, it will replace Trac links such as
+        <samp>ticket:123</samp> with <samp>$organisation_ticket:123</samp>, and
+        report the orginal changeset with a message such as
+        <samp>$organisation_changeset:1000</samp>. If the organisation name is
+        not specified then it defaults to <samp>original</samp>.</dd>
+
+        <dt><code>--revision=REV</code>, <code>-r REV</code></dt>
+
+        <dd>Specifies a revision or a revision range, at which the patch
+        will be based on. If a revision is not specified, it will attempt to
+        create a patch based on the changes at the HEAD revision. If a revision
+        range is specified, it will attempt to create a patch for each revision
+        in that range (including the change in the lower range) where changes
+        have taken place in the URL. No output will be written if there is no
+        change in the given revision (range).</dd>
+      </dl>
+    </dd>
+  </dl>
+
+  <h2 id="fcm-project-create">fcm project-create</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm project-create [OPTIONS] PROJECT-NAME REPOS-ROOT-URL</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p>Create a new project and its trunk directory in a repository.</p>
+
+      <p>If you do not specify the <code>--non-interactive</code> option, it
+      starts an editor (using a similar convention as <a href=
+      "#fcm-commit">commit</a>) to allow you to add further comment to the
+      commit log message. A standard commit log template and change summary
+      is provided for you below the line that says <samp>--Add your commit
+      message ABOVE - do not alter this line or those below--</samp>. If you
+      need to add any extra message to the log, please do so
+      <strong>above</strong> this line. When you exit the editor, the command
+      will report the commit log before prompting for confirmation that you
+      wish to proceed (it aborts if not).</p>
+    </dd>
+
+    <dt>Options</dt>
+    <dd>
+      <dl>
+        <dt><code>--non-interactive</code></dt>
+
+        <dd>Tells the system not to prompt for anything. (The
+        <code>--svn-non-interactive</code> option is set automatically when you
+        specify <code>--non-interactive</code>.)</dd>
+
+        <dt><code>--password=PASSWORD</code></dt>
+
+        <dd>Specifies the password for authentication.</dd>
+
+        <dt><code>--svn-non-interactive</code></dt>
+
+        <dd>Tells the system to run <code>svn</code> in non-interactive
+        mode.</dd>
+      </dl>
+    </dd>
+  </dl>
+
+  <h2 id="fcm-switch">fcm switch</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm switch [OPTIONS] URL[@REV1] [PATH]</code><br />
+    <code>fcm switch --relocate [OPTIONS] FROM TO [PATH]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p><code>fcm switch</code> supports the arguments and alternate names
+      supported by <code>svn switch</code>. If <code>--relocate</code> is
+      specified, it supports all options supported by <code>svn switch</code>.
+      Otherwise, it supports <code>--non-interactive</code>,
+      <code>--revision=REV</code> (<code>-r REV</code>) and
+      <code>--quiet</code> (<code>-q</code> only. (Please refer to the <a href=
+      "http://svnbook.red-bean.com/en/1.8/svn.branchmerge.switchwc.html">Subversion
+      book</a> for details).</p>
+
+      <p>If <code>--relocate</code> is specified, FCM will pass the options and
+      arguments directly to the corresponding Subversion command. Otherwise,
+      FCM will ensure that your working copy switches safely through the
+      following actions:</p>
+
+      <ul>
+        <li>If <var>PATH</var> (or the current working directory if
+        <var>PATH</var> is not specified) is not at the top of a working copy,
+        the command will automatically search for the top of the working copy,
+        and the switch command will always apply recursively from that
+        level.</li>
+
+        <li>You can specify only the <em>branch</em> part of the URL, such as
+        <samp>trunk</samp>, <samp>branches/dev/fred/r1234_bob</samp> or even
+        <samp>dev/fred/r1234_bob</samp> and the command will work out the full
+        URL for you.</li>
+
+        <li>If you do not specify the <code>--non-interactive</code> option, it
+        checks for any local modifications in your working copy. If it finds
+        any it reports them and asks you to confirm that you wish to continue
+        (it aborts if not).</li>
+
+        <li>If you have some template messages in the
+        <samp>#commit_message#</samp> file in the top level of your working
+        copy, (e.g. after you have performed a merge), the command will report
+        an error. You should remove the template message manually from the
+        <samp>#commit_message#</samp> file before re-running
+        <code>switch</code>.</li>
+
+        <li>The command will analyse the current working copy URL and the
+        specified URL to ensure that they are in the same project. If your
+        working copy is a sub-tree of a project, the command will assume that
+        you want the same sub-tree in the new URL.</li>
+      </ul>
+
+      <p>For further details refer to the section <a href=
+      "code_management.html#svn_branching_switch">Switching your working copy
+      to point to another branch</a>.</p>
+    </dd>
+  </dl>
+
+  <h2 id="fcm-update">fcm update</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm update [OPTIONS] [PATH ...]</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p><code>fcm update</code> supports the arguments and alternate names
+      supported by <code>svn update</code>. It supports the options
+      <code>--non-interactive</code>, <code>--revision=REV</code> (<code>-r
+      REV</code>) and <code>--quiet</code> (<code>-q</code>) only. (Please
+      refer to the <a href=
+      "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.update.html">Subversion
+      book</a> for details).</p>
+
+      <p>FCM will ensure that your working copies updates safely through the
+      following actions:</p>
+
+      <ul>
+        <li>If <var>PATH</var> (or the current working directory if
+        <var>PATH</var> is not specified) is not at the top of a working copy,
+        the command will automatically search for the top of the working copy,
+        and the update command will always apply recursively from that
+        level.</li>
+
+        <li>If you do not specify the <code>--non-interactive</code> option, it
+        uses <code>svn status --show-updates</code> to display what will be
+        updated in your working copies and to check for local modifications (if
+        you specify <code>--revision=REV</code> (<code>-r REV</code> then it
+        just uses <code>svn status</code>). If it finds any it reports them and
+        asks you to confirm that you wish to continue (it aborts if not).</li>
+      </ul>
+    </dd>
+  </dl>
+
+  <h2 id="fcm-version">fcm version</h2>
+
+  <dl>
+    <dt>Usage</dt>
+
+    <dd><code>fcm version</code></dd>
+
+    <dt>Description</dt>
+
+    <dd>
+      <p>Print FCM version string.</p>
+    </dd>
+
+    <dt>Alternate Names</dt>
+
+    <dd>--version, -V</dd>
+  </dl>
+
+  <h2 id="svn">Other Subversion Commands</h2>
+
+  <p>Other <code>svn</code> commands are supported by <code>fcm</code> with the
+  following minor enhancements:</p>
+
+  <ul>
+    <li>Where appropriate, FCM performs repository and revision keywords
+    expansion.</li>
+
+    <li>The <code>fcm checkout</code> command fails if you attempt to checkout
+    into an existing working copy.</li>
+  </ul>
+
+  <p>The following is a list of the commands:</p>
+
+  <ul>
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.blame.html">svn
+    blame</a></li>
+
+    <li><a href="http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.cat.html">svn
+    cat</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.checkout.html">svn
+    checkout</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.cleanup.html">svn
+    cleanup</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.copy.html">svn
+    copy</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.export.html">svn
+    export</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.import.html">svn
+    import</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.info.html">svn
+    info</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.list.html">svn
+    list</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.lock.html">svn
+    lock</a></li>
+
+    <li><a href="http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.log.html">svn
+    log</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.mergeinfo.html">svn
+    mergeinfo</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.mkdir.html">svn
+    mkdir</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.move.html">svn
+    move</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.patch.html">svn
+    patch</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.propdel.html">svn
+    propdel</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.propedit.html">svn
+    propedit</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.propget.html">svn
+    propget</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.proplist.html">svn
+    proplist</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.propset.html">svn
+    propset</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.relocate.html">svn
+    relocate</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.resolve.html">svn
+    resolve</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.resolved.html">svn
+    resolved</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.revert.html">svn
+    revert</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.status.html">svn
+    status</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.unlock.html">svn
+    unlock</a></li>
+
+    <li><a href=
+    "http://svnbook.red-bean.com/en/1.8/svn.ref.svn.c.upgrade.html">svn
+    upgrade</a></li>
+  </ul>
+
+  <p>Please refer to the <a href=
+  "http://svnbook.red-bean.com/en/1.8/svn.ref.html">Subversion Complete
+  Reference</a> in the Subversion book for details of these commands.</p>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/create_branch.png b/doc/user_guide/create_branch.png
new file mode 100644
index 0000000..8de76cd
Binary files /dev/null and b/doc/user_guide/create_branch.png differ
diff --git a/doc/user_guide/extract.html b/doc/user_guide/extract.html
new file mode 100644
index 0000000..ca04f0e
--- /dev/null
+++ b/doc/user_guide/extract.html
@@ -0,0 +1,1171 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: Annex: The FCM 1 Extract System</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: Annex: The FCM 1 Extract System</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <h2 id="introduction">Introduction</h2>
+
+  <p><em>The FCM 1 extract system is deprecated. The documentation for the
+  current extract system can be found at <a href="make.html">FCM
+  Make</a>.</em></p>
+
+  <p>The extract system provides an interface between the revision control
+  system (currently Subversion) and the build system. Where appropriate, it
+  extracts code from the repository and other user-defined locations to a
+  directory tree suitable for feeding into the build system. In this chapter,
+  we shall use many examples to explain how to use the extract system. At the
+  end of this chapter, you will be able to extract code from the local file
+  system as well as from different branches of different repository URLs. You
+  will also learn how to mirror code to an alternate destination. Finally, you
+  will be given an introduction on how to specify configurations for the build
+  system via the extract configuration file. (For further information on the
+  build system, please see the next chapter <a href="build.html">The Build
+  System</a>.) The last section of the chapter tells you what you can do in the
+  case when Subversion is not available.</p>
+
+  <h2 id="command">The Extract Command</h2>
+
+  <p>To invoke the extract system, simply issue the command:</p>
+  <pre>
+fcm extract
+</pre>
+
+  <p>By default, the extract system searches for an extract configuration file
+  <samp>ext.cfg</samp> in <samp>$PWD</samp> and then <samp>$PWD/cfg</samp>. If
+  an extract configuration file is not found in these directories, the command
+  fails with an error. If an extract configuration file is found, the system
+  will use the configuration specified in the file to perform the current
+  extract.</p>
+
+  <p>If the destination of the extract does not exist, the system performs a
+  new full extract to the destination. If a previous extract already exists at
+  the destination, the system performs an incremental extract, updating any
+  modifications if necessary. If a full (fresh) extract is required for
+  whatever reason, you can invoke the extract system using the <code>-f</code>
+  option, (i.e. the command becomes <code>fcm extract -f</code>). If you simply
+  want to remove all the items generated by a previous extract in the
+  destination, you can invoke the extract system using the <code>--clean</code>
+  option.</p>
+
+  <p>For further information on the extract command, please see <a href=
+  "command_ref.html#fcm-extract">FCM Command Reference > fcm extract</a>.</p>
+
+  <h2 id="simple">Simple Usage</h2>
+
+  <p>The extract configuration file is the main user interface of the extract
+  system. It is a line based text file. For a complete set of extract
+  configuration file declarations, please refer to the <a href=
+  "annex_ext_cfg.html">Annex: Declarations in FCM extract configuration
+  file</a>.</p>
+
+  <h3 id="simple_local">Extract from a local path</h3>
+
+  <p>A simple example of a basic extract configuration file is given below:</p>
+  <pre id="example_1">
+# Example 1
+# ----------------------------------------------------------------------
+cfg::type         ext       # line 1
+cfg::version      1.0       # line 2
+                            # line 3
+dest              $PWD      # line 4
+                            # line 5
+repos::var::user  $HOME/var # line 6
+                            # line 7
+expsrc::var::user code      # line 8
+</pre>
+
+  <p>The above demonstrates how to use the extract system to extract code from
+  a local user directory. Here is an explanation of what each line does:</p>
+
+  <ul>
+    <li><dfn>line 1</dfn>: the label <code>CFG::TYPE</code> declares the type
+    of the configuration file. The value <samp>ext</samp> tells the system that
+    it is an extract configuration file.</li>
+
+    <li><dfn>line 2</dfn>: the label <code>CFG::VERSION</code> declares the
+    version of the extract configuration file. The current default is
+    <samp>1.0</samp>. Although it is not currently used, if we have to change
+    the format of the configuration file at a later stage, we shall be able to
+    use this number to determine whether we are reading a file with an older
+    format or one with a newer format.</li>
+
+    <li><dfn>line 3</dfn>: a blank line or a line beginning with a
+    <code>#</code> is a comment, and is ignored by the interpreter.</li>
+
+    <li><dfn>line 4</dfn>: the label <code>DEST</code> declares the destination
+    root directory of this extract. The value <samp>$PWD</samp> expands to the
+    current working directory.</li>
+
+    <li><dfn>line 5</dfn>: comment line, ignored.</li>
+
+    <li><dfn>line 6</dfn>: the label
+    <code>REPOS::<pck>::<branch></code> declares the top level URL
+    or path of a repository. The package name of the repository is given by
+    <pck>. In our example, we choose <samp>var</samp> as the name of the
+    package. (You can choose any name you like, however, it is usually sensible
+    to use a package name that matches the name of the project or system you
+    are working with.) The branch name in the repository is given by
+    <branch>. (Again, you can choose any name you like, however, it is
+    usually sensible to use a name such as <samp>base</samp>, <samp>user</samp>
+    or something that matches your branch name.) In our example, the word
+    <samp>user</samp> is normally used to denote a local user directory. Hence
+    the statement declares that the repository path for the <samp>var</samp>
+    package in the <samp>user</samp> branch can be found at
+    <samp>$HOME/var</samp>.</li>
+
+    <li><dfn>line 7</dfn>: comment line, ignored.</li>
+
+    <li><dfn>line 8</dfn>: the label
+    <code>EXPSRC::<pck>::<branch></code> declares an
+    <em>expandable</em> source directory for the package <pck> in the
+    branch <branch>. In our example, the package name is
+    <samp>var</samp>, and the branch name is <samp>user</samp>. These match the
+    package and the branch names of the repository declaration in line 6. It
+    means that the source directory declaration is associated with the path
+    <samp>$HOME/var</samp>. The value of the declaration <samp>code</samp> is
+    therefore a sub-directory under <samp>$HOME/var</samp>. By declaring a
+    source directory using an <code>EXPSRC</code> label, the system
+    automatically searches for all sub-directories (recursively) under the
+    declared source directory.</li>
+  </ul>
+
+  <p>Invoking the extract system using the above configuration file will
+  extract all sub-directories under <samp>$HOME/var/code</samp> to
+  <samp>$PWD/src/var/code</samp>. Note: the extract system ignores symbolic
+  links and hidden files, (i.e. file names beginning with a <samp>.</samp>). It
+  will write a build configuration file to <samp>$PWD/cfg/bld.cfg</samp>. The
+  configuration used for this extract will be written to the configuration file
+  at <samp>$PWD/cfg/ext.cfg</samp>.</p>
+
+  <dl>
+    <dt>Note - incremental extract</dt>
+
+    <dd>Suppose you have already performed an extract using the above
+    configuration file. At a later time, you have made some changes to some of
+    the files in the source directory. Re-running the extract system on the
+    same configuration will trigger an incremental extract. In an incremental
+    extract, the system will update only those files that are modified. If the
+    last modified time (or last commit revision) of a source file in the
+    current extract differs from that in the previous extract, the system will
+    attempt a content comparison. The system updates the destination only if
+    the content and/or file access permission of the source differs from that
+    of the destination.</dd>
+  </dl>
+
+  <h3 id="simple_url">Extract from a Subversion URL</h3>
+
+  <p>The next example demonstrates how to extract from a Subversion repository
+  URL:</p>
+  <pre id="example_2">
+# Example 2
+# ----------------------------------------------------------------------
+cfg::type           ext                    # line 1
+cfg::version        1.0                    # line 2
+                                           # line 3
+dest                $PWD                   # line 4
+                                           # line 5
+repos::var::base    svn://server/var/trunk # line 6
+revision::var::base 1234                   # line 7
+                                           # line 8
+expsrc::var::base   code                   # line 9
+</pre>
+
+  <ul>
+    <li><dfn>line 1-5</dfn>: same as <a href="#example_1">example 1</a>.</li>
+
+    <li><dfn>line 6</dfn>: the line declares the repository location of the
+    <samp>base</samp> branch of the <samp>var</samp> package to be the
+    Subversion URL <samp>svn://server/var/trunk</samp>.</li>
+
+    <li><dfn>line 7</dfn>: the label
+    <code>REVISION::<pck>::<branch></code> declares the revision of
+    the repository associated with the package <pck> in the branch
+    <branch>. The current line tells the extract system to use revision
+    1234 of <samp>svn://server/var/trunk</samp>. It is worth noting that the
+    declared revision must be a revision when the declared branch exists. The
+    actual revision used is the last changed revision of the declared one. If
+    the revision is not declared, the default is to use the last changed
+    revision at the HEAD of the branch.</li>
+
+    <li><dfn>line 8</dfn>: comment line, ignored.</li>
+
+    <li><dfn>line 9</dfn>: the line declares an expandable source directory in
+    the repository <samp>svn://server/var/trunk</samp>.</li>
+  </ul>
+
+  <p>Invoking the extract system using the above configuration file will
+  extract all sub-directories under <samp>svn://server/var/trunk/code</samp> to
+  <samp>$PWD/src/var/code</samp>. It will write a build configuration file to
+  <samp>$PWD/cfg/bld.cfg</samp>. The configuration used for this extract will
+  be written to the configuration file at <samp>$PWD/cfg/ext.cfg</samp>.</p>
+
+  <dl>
+    <dt>EXPSRC or SRC?</dt>
+
+    <dd>
+      <p>So far, we have only declared source directories using the
+      <code>EXPSRC</code> statement, which stands for <em>expandable source
+      directory</em>. A source directory declared using this statement will
+      trigger the system to search recursively for any sub-directories under
+      the declared one. Any sub-directories containing regular source files
+      will be included in the extract. Symbolic links, hidden files and empty
+      directories (or those containing only symbolic links and/or hidden files)
+      are ignored.</p>
+
+      <p>If you do not want the system to search for sub-directories underneath
+      your declared source directory, you can declare your source directory
+      using the <code>SRC</code> statement. The <code>SRC</code> statement is
+      essentially the same as <code>EXPSRC</code> except that it does not
+      trigger the automatic recursive search for source directories. In fact,
+      the system implements the <code>EXPSRC</code> statement by expanding it
+      into a list of <code>SRC</code> statements.</p>
+    </dd>
+
+    <dt>Package and sub-package</dt>
+
+    <dd>
+      <p>The second field of a repository, revision or source directory
+      declaration label is the name of the container package. It is a name
+      selected by the user to identify the system or project he/she is working
+      on. (Therefore, it is often sensible to choose an identifier that matches
+      the name of the project or system.) The package name provides a unique
+      namespace for a file container. Source directories are automatically
+      arranged into sub-packages, using the names of the sub-directories as the
+      names of the sub-packages. For example, the declaration at line 9 in
+      <a href="#example_2">example 2</a> will put the source directory in the
+      <samp>var/code</samp> sub-package automatically.</p>
+
+      <p>Note that, in additional to slash <code>/</code>, double colon
+      <code>::</code> and double underscore <code>__</code> (internal only)
+      also act as delimiters for package names. Please avoid using them for
+      naming your files and directories.</p>
+
+      <p>You can declare a sub-package name explicitly in your source directory
+      statement. For example, the following two lines are equivalent:</p>
+      <pre>
+src::var::base                      code/VarMod_Surface
+src::var/code/VarMod_Surface::base  code/VarMod_Surface
+</pre>
+
+      <p>Explicit sub-package declaration should not be used normally, as it
+      requires a lot more typing (although there are some situations where it
+      can be useful, e.g. if you need to re-define the package name).</p>
+
+      <p>Currently, the extract system only supports non-space characters in
+      the package name, as the space character is used as a delimiter between
+      the declaration label and its value. If there are spaces in the path name
+      to a file or directory, you should explicity re-define the package name
+      of that path to a package name with no space using the above method.
+      However, we recommend that only non-space characters are used for naming
+      directories and files to make life simpler.</p>
+    </dd>
+  </dl>
+
+  <dl>
+    <dt>The expanded extract configuration file</dt>
+
+    <dd>
+      <p>At the end of a successful extract, the configuration used by the
+      current extract is written in <samp>cfg/ext.cfg</samp> under the extract
+      destination root. This file is an <em>expanded</em> version of the
+      original, with changes in the following declarations:</p>
+
+      <ul>
+        <li>All revision keywords are converted into revision numbers.</li>
+
+        <li>If a revision is not defined for a repository, it is set to the
+        corresponding revision number of the HEAD revision.</li>
+
+        <li>All URL keywords are converted into the full URLs.</li>
+
+        <li>All <code>EXPSRC</code> declarations are expanded into
+        <code>SRC</code> declarations.</li>
+
+        <li>All other variables are expanded.</li>
+      </ul>
+
+      <p>With this file, it should be possible for a later extract to re-create
+      the current configuration even if the contents of the repository have
+      changed. (This applies only to code stored in the repository.)</p>
+    </dd>
+  </dl>
+
+  <h3 id="simple_mirror">Mirror code to an alternate location</h3>
+
+  <p>The next example demonstrates how to extract from a repository and mirror
+  the code to an alternate location. It is essentially the same as <a href=
+  "#example_2">example 2</a>, except that it has three new lines to describe
+  how the system can mirror the extracted code to an alternate location.</p>
+  <pre id="example_3">
+# Example 3
+# ----------------------------------------------------------------------
+cfg::type           ext
+cfg::version        1.0
+
+dest                $PWD
+
+rdest::machine      tx01                           # line 6
+rdest::logname      frva                           # line 7
+rdest               /scratch/frva/extract/example3 # line 8
+
+repos::var::base    svn://server/var/trunk
+revision::var::base 1234
+
+expsrc::var::base   code
+</pre>
+
+  <p>Here is an explanation of what each line does:</p>
+
+  <ul>
+    <li><dfn>line 6</dfn>: <code>RDEST::MACHINE</code> declares the target
+    machine to which the code will be mirrored. The example mirrors the code to
+    the machine named <samp>tx01</samp>.</li>
+
+    <li><dfn>line 7</dfn>: <code>RDEST::LOGNAME</code> declares the user name
+    of the target machine, to which the user has login access. If this is not
+    declared, the system uses the login name of the current user on the local
+    machine.</li>
+
+    <li><dfn>line 8</dfn>: <code>RDEST</code> declares the root directory of
+    the alternate destination, where the mirror version of the extract will be
+    sent.</li>
+  </ul>
+
+  <p>Invoking the extract system on the above configuration will trigger an
+  extract similar to that given in <a href="#example_2">example 2</a>, but it
+  will also attempt to mirror the contents at <samp>$PWD/src/var/code</samp> to
+  <samp>/scratch/frva/extract/example3/src</samp> on the alternate destination.
+  It will also mirror the expanded extract configuration file
+  <samp>$PWD/cfg/ext.cfg</samp> to
+  <samp>/scratch/frva/extract/example3/cfg/ext.cfg</samp> and
+  <samp>$PWD/cfg/bld.cfg</samp> to
+  <samp>/scratch/frva/extract/example3/cfg/bld.cfg</samp>. It is also worth
+  noting that the content of the build configuration file will be slightly
+  different, since it will include directory names appropriate for the
+  alternate destination.</p>
+
+  <dl>
+    <dt>Note - mirroring command</dt>
+
+    <dd>
+      <p>The extract system currently supports <code>rdist</code> and
+      <code>rsync</code> as its mirroring tool. The default is
+      <code>rsync</code>. To use <code>rdist</code> instead of
+      <code>rsync</code>, add the following line to your extract configuration
+      file:</p>
+      <pre>
+rdest::mirror_cmd  rdist
+</pre>
+
+      <p>If <code>rsync</code> is used to mirror an extract, the system needs to
+      issue a separate remote shell command to create the container directory of
+      the mirror destination. The default is to issue a shell command in the
+      form <samp>ssh -n -oBatchMode=yes LOGNAME at MACHINE mkdir -p DEST</samp>.
+      The following declarations can be used to modify the command:</p>
+      <pre>
+# Examples using the default settings:
+rdest::rsh_mkdir_rsh         ssh
+rdest::rsh_mkdir_rshflags    -n -oBatchMode=yes
+rdest::rsh_mkdir_mkdir       mkdir
+rdest::rsh_mkdir_mkdirflags  -p
+</pre>
+
+      <p>In addition, the default <code>rsync</code> shell command is
+      <samp>rsync -a --exclude='.*' --delete-excluded --timeout=900 --rsh='ssh
+      -oBatchMode=yes' SOURCE DEST</samp>. The following declarations can be
+      used to modify the command:</p>
+      <pre>
+# Examples using the default settings:
+rdest::rsync       rsync
+rdest::rsyncflags  -a --exclude='.*' --delete-excluded --timeout=900 \
+                   --rsh='ssh -oBatchMode=yes'
+</pre>
+    </dd>
+  </dl>
+
+  <h2 id="advanced">Advanced Usage</h2>
+
+  <h3 id="advanced_multi">Extract from multiple repositories</h3>
+
+  <p>So far, we have only extracted from a single location. The extract system
+  is not much use if that is the only thing it can do. In fact, the extract
+  system supports extract of multiple source directories from multiple branches
+  in multiple repositories. The following configuration file is an example of
+  how to extract from multiple repositories:</p>
+  <pre id="example_4">
+# Example 4
+# ----------------------------------------------------------------------
+cfg::type           ext
+cfg::version        1.0
+
+dest                $PWD
+
+repos::var::base    fcm:var_tr              # line 6
+repos::ops::base    fcm:ops_tr              # line 7
+repos::gen::base    fcm:gen_tr              # line 8
+
+revision::gen::base 2468                    # line 10
+
+expsrc::var::base   src/code                    # line 12
+expsrc::var::base   src/scripts                 # line 13
+expsrc::ops::base   src/code                    # line 14
+src::gen::base      src/code/GenMod_Constants   # line 15
+src::gen::base      src/code/GenMod_Control     # line 16
+src::gen::base      src/code/GenMod_FortranIO   # line 17
+src::gen::base      src/code/GenMod_GetEnv      # line 18
+src::gen::base      src/code/GenMod_ModelIO     # line 19
+src::gen::base      src/code/GenMod_ObsInfo     # line 20
+src::gen::base      src/code/GenMod_Platform    # line 21
+src::gen::base      src/code/GenMod_Reporting   # line 22
+src::gen::base      src/code/GenMod_Trace       # line 23
+src::gen::base      src/code/GenMod_UMConstants # line 24
+src::gen::base      src/code/GenMod_Utilities   # line 25
+</pre>
+
+  <p>Here is an explanation of what each line does:</p>
+
+  <ul>
+    <li><dfn>line 6-8</dfn>: these lines declare the repositories for the
+    <samp>base</samp> branches of the <samp>var</samp>, <samp>ops</samp> and
+    <samp>gen</samp> packages respectively. It is worth noting that the values
+    of the declarations are no longer Subversion URLs but are FCM URL keywords.
+    These keywords are normally declared in the central configuration file of
+    the FCM system, and will be expanded into the corresponding Subversion URLs
+    by the FCM system. For further information on URL keywords, please see
+    <a href="code_management.html#svn_basic_keywords">Code Management System
+    > Using Subversion > Basic Command Line Usage > Repository &
+    Revision Keywords</a>.</li>
+
+    <li><dfn>line 10</dfn>: this line declares the revision number for the
+    <samp>base</samp> branch of the <samp>gen</samp> package, i.e. for the
+    <samp>fcm:gen_tr</samp> repository. It is worth noting that the revision
+    numbers for the <samp>var</samp> and <samp>ops</samp> packages have not
+    been declared. By default, their revision numbers will be set to the last
+    changed revision at the HEAD.</li>
+
+    <li><dfn>line 12-14</dfn>: these line declares the source directories for
+    the <samp>base</samp> branches of the <samp>var</samp> and <samp>ops</samp>
+    packages. For the <samp>var</samp> package, we are extracting everything
+    from the <samp>code</samp> and the <samp>scripts</samp> sub-directory. For
+    the <samp>ops</samp> package, we are extracting everything from the
+    <samp>code</samp> directory.</li>
+
+    <li><dfn>line 15-25</dfn>: these line declares the source directories for
+    the <samp>base</samp> branch of the <samp>gen</samp> package. The source
+    directories declared will not be searched for sub-directories underneath
+    the declared directories.</li>
+  </ul>
+
+  <p>We shall end up with a directory tree such as:</p>
+  <pre>
+$PWD
+   |
+   |--- cfg
+   |      |
+   |      |--- bld.cfg
+   |      |--- ext.cfg
+   |
+   |--- src
+          |
+          |--- gen
+          |      |
+          |      |--- code
+          |              |
+          |              |--- GenMod_Constants
+          |              |--- GenMod_Control
+          |              |--- GenMod_FortranIO
+          |              |--- GenMod_GetEnv
+          |              |--- GenMod_ModelIO
+          |              |--- GenMod_ObsInfo
+          |              |--- GenMod_Platform
+          |              |--- GenMod_Reporting
+          |              |--- GenMod_Trace
+          |              |--- GenMod_UMConstants
+          |              |--- GenMod_Utilities
+          |
+          |--- ops
+          |      |
+          |      |--- code
+          |              |
+          |              |--- ...
+          |
+          |--- var
+                 |
+                 |--- code
+                 |       |
+                 |       |--- ...
+                 |
+                 |--- scripts
+                         |
+                         |--- ...
+</pre>
+
+  <dl>
+    <dt>Note - revision number</dt>
+
+    <dd>
+      <p>As seen in the above example, if a revision number is not specified
+      for a repository URL, it defaults to the last changed revision at the
+      HEAD of the branch. The revision number can also be declared in other
+      ways:</p>
+
+      <ul>
+        <li>Any revision arguments acceptable by Subversion are allowed. You
+        can use a valid revision number, a date between a pair of curly
+        brackets (e.g. <samp>{2005-05-01T12:00}</samp>) or the keyword HEAD.
+        However, please do not use the keywords BASE, COMMITTED or PREV as
+        these are reserved for working copy only.</li>
+
+        <li>FCM revision keywords are allowed. These must be defined for the
+        corresponding repository URLs in either the central or the user FCM
+        configuration file. For further information on revision keywords,
+        please see <a href="code_management.html#svn_basic_keywords">Code
+        Management > Using Subversion > Basic Command Line Usage >
+        Repository & Revision Keywords</a>.</li>
+
+        <li>Do not use the keyword USER, as it is used internally by the
+        extract system.</li>
+      </ul>
+
+      <p>If a revision number is specified for a branch, the actual revision
+      used by the extract system is the last changed revision of the branch,
+      which may differ from the declared revision. While this behaviour is
+      useful in most situations, some users may find it confusing to work with.
+      It is possible to alter this behaviour so that extract will fail if the
+      declared revision does not correspond to a changeset of the declared
+      branch. Make the following declaration to switch on this checking:</p>
+      <pre>
+revmatch  true
+</pre>
+    </dd>
+  </dl>
+
+  <h3 id="advanced_branches">Extract from multiple branches</h3>
+
+  <p>We have so far dealt with a single branch in any package. The extract
+  system can be used to <em>combine</em> changes from different branches of a
+  package. An example is given below:</p>
+  <pre id="example_5">
+# Example 5
+# ----------------------------------------------------------------------
+cfg::type               ext
+cfg::version            1.0
+ 
+dest                    $PWD
+ 
+repos::var::base        fcm:var_tr
+repos::ops::base        fcm:ops_tr
+repos::gen::base        fcm:gen_tr
+ 
+revision::gen::base     2468
+ 
+expsrc::var::base       src/code
+expsrc::var::base       src/scripts
+expsrc::ops::base       src/code
+src::gen::base          src/code/GenMod_Constants
+src::gen::base          src/code/GenMod_Control
+src::gen::base          src/code/GenMod_FortranIO
+src::gen::base          src/code/GenMod_GetEnv
+src::gen::base          src/code/GenMod_ModelIO
+src::gen::base          src/code/GenMod_ObsInfo
+src::gen::base          src/code/GenMod_Platform
+src::gen::base          src/code/GenMod_Reporting
+src::gen::base          src/code/GenMod_Trace
+src::gen::base          src/code/GenMod_UMConstants
+src::gen::base          src/code/GenMod_Utilities
+
+repos::var::branch1     fcm:var_br/frva/r1234_new_stuff   # line 27
+repos::var::branch2     fcm:var_br/frva/r1516_bug_fix     # line 28
+repos::ops::branch1     fcm:ops_br/opsrc/r3188_good_stuff # line 29
+</pre>
+
+  <p>The configuration file in <a href="#example_5">example 5</a> is similar to
+  that of <a href="#example_4">example 4</a> except for the last three lines.
+  Here is an explanation of what they do:</p>
+
+  <ul>
+    <li>
+      <dfn>line 27</dfn>: this line declares a repository URL for the
+      <samp>branch1</samp> branch of the <samp>var</samp> package. From the URL
+      of the branch, we know that the branch was created by the user
+      <samp>frva</samp> based on the trunk at revision at 1234. The description
+      of the branch is <samp>branch1</samp>. The following points are worth
+      noting:
+
+      <ul>
+        <li>By declaring a new branch with the same package name to a
+        previously declared branch, it is assumed that both branches reside in
+        the same Subversion repository.</li>
+
+        <li>No revision is declared for this URL, so the default is used which
+        is the last changed revision at the HEAD of the branch.</li>
+
+        <li>No source directory is declared for this URL. By default, if no
+        source directory is declared for a branch repository, it will attempt
+        to use the same set of source directories as the first declared branch
+        of the package. In this case, the source directories declared for the
+        <samp>base</samp> branch of the <samp>var</samp> package will be
+        used.</li>
+      </ul>
+    </li>
+
+    <li><dfn>line 28</dfn>: this line declares another branch called
+    <samp>branch2</samp> for the <samp>var</samp> package. No source directory
+    is declared for this URL either, so it will use the same set of source
+    directories declared for the <samp>base</samp> branch.</li>
+
+    <li><dfn>line 29</dfn>: this line declares a branch called
+    <samp>branch1</samp> for the <samp>ops</samp> package. It will use the same
+    set of source directories declared for the <samp>ops</samp> package
+    <samp>base</samp> branch.</li>
+  </ul>
+
+  <p>When we invoke the extract system, it will attempt to extract from the
+  first declared branch of a package, if the last changed revision of the
+  source directory is the same in all the branches. However, if the last
+  changed revision of the source directory differs for different branches, the
+  system will attempt to obtain an extract priority list for each source
+  directory, using the following logic:</p>
+
+  <ol>
+    <li>The system looks for source directory packages from the first declared
+    branch to the last declared branch.</li>
+
+    <li>The branch in which a source directory package is first declared is the
+    <samp>base</samp> branch of the source directory package.</li>
+
+    <li>The last changed revision of a source directory package in a
+    subsequently declared repository branch is compared with that of the base
+    branch. If the last changed revision is the same as that of the base
+    branch, the source directory of this branch is discarded. Otherwise, it is
+    placed at the end of the extract priority list.</li>
+  </ol>
+
+  <p>For the <samp>var</samp> package in the above example, let us assume that
+  we have three source directory packages X, Y and Z under <samp>code</samp>,
+  and their last changed revisions under <samp>base</samp> are 100. Let's say
+  we have committed some changes to X and Z in the <samp>branch1</samp> branch
+  at revision 102, and other changes to Y and Z in the <samp>branch2</samp>
+  branch at revision 104, the extract priority lists for X, Y and Z will look
+  like:</p>
+
+  <ul>
+    <li>X: base (100, base), branch1 (102), branch2 (100, discarded)</li>
+
+    <li>Y: base (100, base), branch1 (100, discarded), branch2 (104)</li>
+
+    <li>Z: base (100, base), branch1 (102), branch2 (104)</li>
+  </ul>
+
+  <p>Once we have an extract priority list for a source directory, we can begin
+  extracting source files in the source directory. The source directory of the
+  base branch is extracted first, followed by that in the subsequent branches.
+  If a source file in a subsequent branch has the same content as the that in
+  the base branch, it is discarded. Otherwise, the following logic determines
+  the branch to use:</p>
+
+  <ol>
+    <li>If a source file is modified in only one subsequent branch, the source
+    file in that branch is extracted.</li>
+
+    <li>If a source file is modified in two or more subsequent branches, but
+    their modifications are the same, then the source file in the first
+    modification is used.</li>
+
+    <li>If a source file is modified in two or more subsequent branches and
+    their modifications differ, then the behaviour depends on the "conflict
+    mode" setting, which can be <code>fail</code>, <code>merge</code> (default)
+    and <code>override</code>. If the conflict mode is <code>fail</code>, the
+    extract fails. If the conflict mode is <code>merge</code>, the system will
+    attempt to merge the changes using a tool such as <code>diff3</code>. The
+    result of the merge will be used to update the destination. The extract
+    fails only if there are unresolved conflicts in the merge. (In which case,
+    the conflict should be resolved using the version control system before
+    re-running the extract system.) If the conflict mode is
+    <code>override</code>, the change in the latest declared branch takes
+    precedence, and the changes in all other branches will be ignored. The
+    conflict mode can be changed using the <code>CONFLICT</code> declaration in
+    the extract configuration file. E.g:
+      <pre>
+conflict  fail
+</pre>
+    </li>
+  </ol>
+
+  <p>Once the system has established which source files to use, it determines
+  whether the destination file is out of date or not. The destination file is
+  out of date if it does not exist or if its content differs from the version
+  of the source file we are using. The system only updates the destination if
+  it is considered to be out of date.</p>
+
+  <p>The extract system can also combine changes from branches in the Subversion
+  repository and the local file system. This is demonstrated in the next
+  example.</p>
+  <pre id="example_6">
+# Example 6
+# ----------------------------------------------------------------------
+cfg::type               ext
+cfg::version            1.0
+ 
+dest                    $PWD
+  
+repos::var::base        fcm:var_tr
+repos::ops::base        fcm:ops_tr
+repos::gen::base        fcm:gen_tr
+ 
+revision::gen::base     2468
+ 
+expsrc::var::base       src/code
+expsrc::var::base       src/scripts
+expsrc::ops::base       src/code
+src::gen::base          src/code/GenMod_Constants
+src::gen::base          src/code/GenMod_Control
+src::gen::base          src/code/GenMod_FortranIO
+src::gen::base          src/code/GenMod_GetEnv
+src::gen::base          src/code/GenMod_ModelIO
+src::gen::base          src/code/GenMod_ObsInfo
+src::gen::base          src/code/GenMod_Platform
+src::gen::base          src/code/GenMod_Reporting
+src::gen::base          src/code/GenMod_Trace
+src::gen::base          src/code/GenMod_UMConstants
+src::gen::base          src/code/GenMod_Utilities
+
+repos::var::branch1     fcm:var_br/frva/r1234_new_stuff
+repos::var::branch2     fcm:var_br/frva/r1516_bug_fix
+repos::ops::branch1     fcm:ops_br/opsrc/r3188_good_stuff
+
+repos::var::user        $HOME/var                         # line 31
+repos::gen::user        $HOME/gen                         # line 32
+</pre>
+
+  <p><a href="#example_6">Example 6</a> is similar to <a href=
+  "#example_5">example 5</a> except that it is also extracting from local
+  directories. Here is an explanation of the lines:</p>
+
+  <ul>
+    <li><dfn>line 31-32</dfn>: these line declare the repositories for the
+    <samp>user</samp> branches of the <samp>var</samp> and <samp>gen</samp>
+    packages respectively. Both are local paths at the local file system. There
+    are no declarations for source directories for the <samp>user</samp>
+    branches, so they use the same set of source directories of the first
+    declared branches, the <samp>base</samp> branches in both cases.</li>
+  </ul>
+
+  <dl>
+    <dt>Note - the INC declaration</dt>
+
+    <dd>
+      You have probably realised that the above examples have many repeated
+      lines. To avoid having repeated lines in multiple extract configuration
+      files, you can use <code>INC</code> declarations to include other extract
+      configuration files. For example, if the configuration file of example 5
+      is stored in the file <samp>$HOME/example5/ext.cfg</samp>, line 1 to 29
+      of <a href="#example_6">example 6</a> can be replaced with an
+      <code>INC</code> declaration. <a href="#example_6">Example 6</a> can then
+      be written as:
+      <pre>
+inc                     $HOME/example5/ext.cfg
+
+repos::var::user        $HOME/var
+repos::gen::user        $HOME/gen
+</pre>
+
+      <p>Note: the <code>INC</code> declaration supports the special
+      environment variable <var>$HERE</var>. If this variable is already set in
+      the environment, it acts as a normal environment variable. However, if it
+      is not set, it will be expanded into the container directory of the
+      current extract configuration file. This feature is particularly useful
+      if you are including a hierarchy of extract configurations from files in
+      the same container directory in a repository.</p>
+    </dd>
+  </dl>
+
+  <h3 id="advanced_inherit">Inherit from a previous extract</h3>
+
+  <p>All the examples above dealt with standalone extract, that is, the current
+  extract is independent of any other extract. If a previous extract exists in
+  another location, the extract system can inherit from this previous extract
+  in your current extract. This works like a normal incremental extract, except
+  that your extract will only contain the changes you have specified (compared
+  with the inherited extract) instead of the full source directory tree. This
+  type of incremental extract is useful in several ways. For instance:</p>
+
+  <ul>
+    <li>It is fast, because you only have to extract and mirror files that you
+    have changed.</li>
+
+    <li>The subsequent build will also be fast, since it will use incremental
+    build.</li>
+
+    <li>You do not need write access to the original extract. A system
+    administrator can set up a stable version in a central account, which
+    developers can then inherit from.</li>
+
+    <li>You want an incremental extract, but you need to leave the original
+    extract unmodified.</li>
+  </ul>
+
+  <p>The following example is based on <a href="#example_4">example 4</a> and
+  <a href="#example_6">example 6</a>. The assumption is that an extract has
+  already been performed at the directory <samp>~frva/var/vn22.0</samp> based
+  on the configuration file in <a href="#example_4">example 4</a>.</p>
+  <pre id="example_7">
+# Example 7
+# ----------------------------------------------------------------------
+cfg::type               ext
+cfg::version            1.0
+ 
+dest                    $PWD
+
+use                     ~frva/var/vn22.0                  # line 6
+
+repos::var::branch1     fcm:var_br/frva/r1234_new_stuff   # line 8
+repos::var::branch2     fcm:var_br/frva/r1516_bug_fix     # line 9
+repos::ops::branch1     fcm:ops_br/opsrc/r3188_good_stuff # line 10
+
+repos::var::user        $HOME/var                         # line 12
+repos::gen::user        $HOME/gen                         # line 13
+</pre>
+
+  <ul>
+    <li><dfn>line 6</dfn>: this line replaces line 1 to 25 of <a href=
+    "#example_6">example 6</a>. It declares that the current extract should
+    inherit from the previous extract located at
+    <samp>~frva/var/vn22.0</samp>.</li>
+  </ul>
+
+  <p>Running the extract system using the above configuration will trigger an
+  incremental extract, as if you are running an incremental extract having
+  modified the configuration file in <a href="#example_4">example 4</a> to that
+  of <a href="#example_6">example 6</a>. The only difference is that the
+  original extract using the <a href="#example_4">example 4</a> configuration
+  will be left untouched at <samp>~frva/var/vn22.0</samp>, and the new extract
+  will contain only the changes in the branches declared from line 8 to 13.</p>
+
+  <p>Note: extract inheritance allows you to add more branches to a package,
+  but you should not redefine the <code>REPOS</code>, <code>REVISION</code>,
+  <code>EXPSRC</code> or <code>SRC</code> declarations of a branch that is
+  already declared (and already extracted) in the inherited extract. Although
+  the system will not stop you from doing so, you may end up with an extract
+  that does not quite do what it is supposed to do. For example, if the
+  <samp>base</samp> branch in the <samp>foo</samp> package
+  (<tt>repos::foo::base</tt>) is already defined and extracted in an extract
+  you are inheriting from, you should not redefine any of the
+  <tt>*::foo::base</tt> declarations in your current extract. However, you are
+  free to add more branches for the same package with new labels (e.g.
+  <tt>repos::foo::b1</tt>), and indeed new packages that are not already
+  defined in the inherited extract (e.g. <tt>repos::bar::base</tt>).</p>
+
+  <p>If you are setting up an extract to be inherited, you do not have to
+  perform a build. If you don't you will still gain the benefit of incremental
+  file extract, but you will be performing a full build of the code.</p>
+
+  <dl>
+    <dt>Note - inherit and mirror</dt>
+
+    <dd>
+      <p>It is worth bearing in mind that <tt>rdest::*</tt> settings are not
+      inherited. If mirroring is required in the inheriting extract, it will
+      require its own set of <tt>rdest::*</tt> declarations.</p>
+
+      <p>The system will, however, assume that a mirrored version of the
+      inherited extract is available for inheritance from the mirrored
+      destination of the current extract.</p>
+      
+      <p>E.g.: Consider an extract at <samp>/path/to/inherited/</samp> and an
+      inheriting extract at <samp>/path/to/current/</samp>. If the former does
+      not have a mirror, the latter should not have one either. If the former
+      mirrors to <samp>machine@/path/to/inherited/mirror/</samp> and the latter
+      mirrors to <samp>machine@/path/to/current/mirror/</samp>, the system will
+      assume that the subsequent build at
+      <samp>machine@/path/to/current/mirror/</samp> can inherit from the build
+      at <samp>machine@/path/to/inherited/mirror/</samp>. This is illustrated
+      below:</p>
+
+      <pre>
+/path/to/current/       => at machine: /path/to/current/mirror/
+use /path/to/inherited/ => at machine: use /path/to/inherited/mirror/
+</pre>
+    </dd>
+  </dl>
+
+  <h3 id="advanced_build">Extract - Build Configuration</h3>
+
+  <p>Configuration settings for feeding into the build system can be declared
+  through the extract configuration file using the <code>BLD::</code> prefix.
+  Any line in an extract configuration containing a label with such a prefix
+  will be considered a build system variable. At the end of a successful
+  extract, the system strips out the <code>BLD::</code> prefix before writing
+  these variables to the build configuration file. Some example entries are
+  given between line 17 and 22 in the following configuration file:</p>
+  <pre id="example_8">
+# Example 8
+# ----------------------------------------------------------------------
+cfg::type           ext
+cfg::version        1.0
+
+dest                $PWD
+
+repos::var::base    fcm:var_tr
+repos::ops::base    fcm:ops_tr
+repos::gen::base    fcm:gen_tr
+
+revision::gen::base 2468
+
+expsrc::var::base   src/code
+expsrc::var::base   src/scripts
+expsrc::ops::base   src/code
+src::gen::base      src/code/GenMod_Constants
+src::gen::base      src/code/GenMod_Control
+src::gen::base      src/code/GenMod_FortranIO
+src::gen::base      src/code/GenMod_GetEnv
+src::gen::base      src/code/GenMod_ModelIO
+src::gen::base      src/code/GenMod_ObsInfo
+src::gen::base      src/code/GenMod_Platform
+src::gen::base      src/code/GenMod_Reporting
+src::gen::base      src/code/GenMod_Trace
+src::gen::base      src/code/GenMod_UMConstants
+src::gen::base      src/code/GenMod_Utilities
+
+bld::target         VarProg_AnalysePF.exe   # line 27
+
+bld::tool::fc       sxmpif90                # line 29
+bld::tool::cc       sxmpic++                # line 30
+bld::tool::ld       sxmpif90                # line 31
+</pre>
+
+  <p>The above example is essentially the same as <a href="#example_4">example
+  4</a>, apart from the additional build configuration. The following is a
+  simple explanation of what the lines represent: (For detail of the build
+  system, please see the next chapter on <a href="build.html">The Build
+  System</a>.)</p>
+
+  <ul>
+    <li><dfn>line 27</dfn>: the line declares a default target of the
+    build.</li>
+
+    <li><dfn>line 29-31</dfn>: the lines declare the Fortran compiler, the C
+    compiler and the linker respectively.</li>
+  </ul>
+
+  <dl>
+    <dt>Note - use of variables</dt>
+
+    <dd>
+      <p>When you start using the extract system to define compiler flags for
+      the build system, you may end up having to make a lot of long and
+      repetitive declarations. In this case, you may want to define variables
+      to replace the repetitive parts of the declarations.</p>
+
+      <p>Environment variables whose names contain only upper case latin
+      alphabets, numbers and underscores can be referenced in a declaration
+      value via the syntax <code>$NAME</code> or <code>${NAME}</code>. For
+      example:</p>
+      <pre>
+repos::um::base    ${HOME}/svn-wc/um
+bld::tool::fflags  $MY_FFLAGS
+</pre>
+      
+      <p>You can define a user variable by making a declaration with a label
+      that begins with a percent sign <code>%</code>. The value of a user
+      variable remains in memory until the end of the current file is reached.
+      You can reference a user variable in a declaration value via the syntax
+      <code>%NAME</code> or <code>%{NAME}</code>. For example:</p>
+      <pre>
+# Declare a variable %fred
+%fred                     -Cdebug -eC -Wf,-init heap=nan stack=nan
+
+bld::tool::fflags         %fred
+# bld::tool::fflags       -Cdebug -eC -Wf,-init heap=nan stack=nan
+
+bld::tool::fflags::foo    %fred -f0
+# bld::tool::fflags::foo  -Cdebug -eC -Wf,-init heap=nan stack=nan -f0
+
+bld::tool::fflags::bar    -w %fred
+# bld::tool::fflags::bar  -w -Cdebug -eC -Wf,-init heap=nan stack=nan
+</pre>
+
+      <p>Further to this, each declaration results in an internal variable of
+      the same name and you can also refer to any of these internal variables in
+      the same way. So, the example given above could also be written as
+      follows:</p>
+      <pre>
+bld::tool::fflags         -Cdebug -eC -Wf,-init heap=nan stack=nan
+bld::tool::fflags::foo    %bld::tool::fflags -f0
+bld::tool::fflags::bar    -w %bld::tool::fflags
+</pre>
+    </dd>
+
+    <dt>Note - as-parsed configuration</dt>
+
+    <dd>
+      <p>If you use a hierarchy of <code>INC</code> declarations or variables,
+      you may end up with a configuration file that is difficult to understand.
+      To help you with this, the extract system generates an as-parsed
+      configuration file at <samp>cfg/parsed_ext.cfg</samp> of the destination.
+      The content of the as-parsed configuration file is what the extract
+      system actually reads. It should contain everything in your original
+      extract configuration file, except that all <code>INC</code>
+      declarations, environment variables and user/internal variables are
+      expanded.</p>
+    </dd>
+  </dl>
+
+  <h2 id="verbose">Diagnostic verbose level</h2>
+
+  <p>The amount of diagnostic messages generated by the extract system is
+  normally set to a level suitable for normal everyday operation. This is the
+  default diagnostic verbose level 1. If you want a minimum amount of
+  diagnostic messages, you should set the verbose level to 0. If you want more
+  diagnostic messages, you can set the verbose level to 2 or 3. You can modify
+  the verbose level in two ways. The first way is to set the environment
+  variable <var>FCM_VERBOSE</var> to the desired verbose level. The second way
+  is to invoke the extract system with the <code>-v <level></code>
+  option. (If set, the command line option overrides the environment
+  variable.)</p>
+
+  <p>The following is a list of diagnostic output at each verbose level:</p>
+
+  <dl>
+    <dt>Level 0</dt>
+
+    <dd>
+      <ul>
+        <li>Report the time taken to extract the code.</li>
+
+        <li>Report the time taken to mirror the code.</li>
+
+        <li>If <code>rdist</code> is used to mirror the code, run the command
+        with the <code>-q</code> option.</li>
+      </ul>
+    </dd>
+
+    <dt>Level 1</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at verbose level 0.</li>
+
+        <li>Report the name of the extract configuration file.</li>
+
+        <li>Report the location of the extract destination.</li>
+
+        <li>Report date/time at the beginning of the extract step.</li>
+
+        <li>If the revision specified for a repository branch is not its last
+        changed revision, print an information statement to inform the user of
+        the last changed revision of the branch.</li>
+
+        <li>Summarises the destination status and the source status.</li>
+
+        <li>Report date/time at the beginning of the mirror step.</li>
+
+        <li>Report the location of the alternate destination.</li>
+
+        <li>Report total time.</li>
+      </ul>
+    </dd>
+
+    <dt>Level 2</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at verbose level 1.</li>
+
+        <li>If the revision specified for a repository branch is not current
+        (i.e. the specified revision number is less than the revision number of
+        the last commit revision), print an information statement to inform the
+        user of the last commit revision of the branch.</li>
+
+        <li>Report the detail of each change in the destination.</li>
+
+        <li>If <code>rdist</code> is used to mirror the code, run the command
+        without the <code>-q</code> option.</li>
+      </ul>
+    </dd>
+
+    <dt>Level 3</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at verbose level 2.</li>
+
+        <li>Report all shell commands invoked by the extract system with
+        timestamp.</li>
+
+        <li>If <code>rdist</code> is used to mirror the code, print the
+        <samp>distfile</samp> supplied to the command.</li>
+
+        <li>If <code>rsync</code> is used to mirror the code, invoke the
+        command with the <code>-v</code> option.</li>
+      </ul>
+    </dd>
+  </dl>
+
+  <h2 id="nosvn">When Subversion Is Not Available</h2>
+
+  <p>The extract system can still be used if Subversion is not available.
+  Clearly, you can only use local repositories. However, you can still do
+  incremental extract, mirror an extract to an alternate location, or combine
+  code from multiple local repositories.</p>
+
+  <p>If you are using Subversion but your server is down then clearly there is
+  little you can do. However, if you already have an extract then you can
+  re-run <code>fcm extract</code> as long as the extract configuration file
+  only refers to fixed revisions. If this is not the case then you can always
+  use the expanded extract configuration file which can be found in
+  <samp>cfg/ext.cfg</samp> under the extract destination root. This means that
+  you can continue to makes changes to local code and do incremental extracts
+  even whilst your Subversion server is down.</p>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/fcm_overview.png b/doc/user_guide/fcm_overview.png
new file mode 100644
index 0000000..acf3e7d
Binary files /dev/null and b/doc/user_guide/fcm_overview.png differ
diff --git a/doc/user_guide/getting_started.html b/doc/user_guide/getting_started.html
new file mode 100644
index 0000000..26d02f4
--- /dev/null
+++ b/doc/user_guide/getting_started.html
@@ -0,0 +1,1490 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: Getting Started</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: Getting Started</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <h2 id="introduction">Introduction</h2>
+
+  <p>This chapter takes a <em>hands-on</em> approach to help you set up your
+  FCM session, and familiarise yourself with some of the system's basic
+  concepts and working practices. It is designed to complement other sections
+  of the User Guide.</p>
+
+  <p>You may also find it useful to refer to the <a href=
+  "annex_quick_ref.html">Annex: Quick reference</a>.</p>
+
+  <h2 id="setup">How to set yourself up to run FCM</h2>
+
+  <p>It is easy to set yourself up to run FCM. Simply follow the steps
+  below:</p>
+
+  <dl>
+    <dt>Installation</dt>
+
+    <dd>If FCM is not yet installed at your site, please refer to the <a href=
+    "../installation/">FCM: Installation</a> for detail.</dd>
+
+    <dt>Configure your editor for Subversion</dt>
+
+    <dd>
+      <p>When you attempt to create a branch or commit changes to the
+      repository, you will normally be prompted to edit your commit log message
+      using a text editor. The order of priority for determining the editor
+      command (where lower-numbered locations take precedence over
+      higher-numbered locations) is:</p>
+
+      <ol>
+        <li>environment variable <var>SVN_EDITOR</var></li>
+
+        <li>the <var>editor-cmd</var> option in the <var>[helpers]</var>
+        section of the user's <samp>$HOME/.subversion/config</samp> file.</li>
+
+        <li>environment variable <var>VISUAL</var></li>
+
+        <li>environment variable <var>EDITOR</var></li>
+
+        <li>default to <code>vi</code> (or <code>gedit</code> when running the
+        FCM GUI)</li>
+      </ol>
+
+      <p>It is worth bearing in mind that an editor must be able to run in the
+      foreground. For example, you can add one of the followings in your
+      <samp>$HOME/.kshrc</samp> (ksh) or <samp>$HOME/.bashrc</samp> (bash):</p>
+      <pre>
+# GVim
+export SVN_EDITOR='gvim -f'
+
+# Emacs
+export SVN_EDITOR=emacs
+
+# gedit
+export SVN_EDITOR=gedit
+
+# Kate
+export SVN_EDITOR=kate
+</pre>
+    </dd>
+
+    <dt>Configure your e-mail address in Trac</dt>
+
+    <dd>
+      <p>Trac can be configured to send automatic e-mail notifications to
+      authors of any ticket whenever there are changes to that ticket (and we
+      would expect most systems to be configured in this way). You should check
+      that the settings for your name and e-mail address are correct. To do
+      this you need to go to the Settings page once you are logged into Trac.
+      (Click on <kbd>Settings</kbd> just above the menu bar). Check that your
+      settings are entered correctly.</p>
+    </dd>
+
+    <dt>Configure your web browser</dt>
+
+    <dd>
+      <p>FCM assumes that <code>firefox</code> is the command to invoke your
+      default web browser. If you use another web browser, you should configure
+      it in your <samp>$HOME/.metomi/fcm/external.cfg</samp> file. See the
+      section on <a href="command_ref.html#fcm-browse">fcm browse</a> for
+      further information.</p>
+    </dd>
+  </dl>
+
+  <h2 id="tutorial">Tutorial</h2>
+
+  <h3 id="tutorial_intro">Introduction</h3>
+
+  <p>This tutorial leads you through the basics of using FCM to make changes to
+  your source code, and demonstrates the recommended practices for working with
+  it. A tutorial Subversion repository, with its own Trac system, is available
+  for you to practice for working with the FCM system. You will work through
+  the following activities:</p>
+
+  <ul>
+    <li><a href="#tutorial_create-ticket">Create a new ticket</a></li>
+
+    <li><a href="#tutorial_create-branch">Create a branch</a></li>
+
+    <li><a href="#tutorial_checkout">Checkout a working copy</a></li>
+
+    <li><a href="#tutorial_change">Make changes to files in your working
+    copy</a></li>
+
+    <li><a href="#tutorial_commit">Commit your changes to the
+    repository</a></li>
+
+    <li><a href="#tutorial_extract">Test your changes</a></li>
+
+    <li><a href="#tutorial_merge">Merge changes from the trunk and resolve
+    conflicts</a></li>
+
+    <li><a href="#tutorial_review-ticket">Review changes</a></li>
+
+    <li><a href="#tutorial_merge-back">Commit to the trunk</a></li>
+
+    <li><a href="#tutorial_extra-extract">Extra activities on the extract and
+    build systems</a></li>
+
+    <li><a href="#tutorial_delete-branch">Delete your branch</a></li>
+
+    <li><a href="#tutorial_delete-final-comments">Final comments</a></li>
+  </ul>
+
+  <p>We recommend that you create a work area in your filespace, for example,
+  <samp>$HOME/tutorial/work</samp> for your working copy, and
+  <samp>$HOME/tutorial/test</samp> for your build test.</p>
+
+  <p>If you have not already done so, you should set up your desktop
+  environment as described above in the <a href="#setup">How to set yourself up
+  to run FCM</a> section.</p>
+
+  <p>It is also worth knowing that the <a href=
+  "http://svnbook.red-bean.com/en/1.8/">Subversion Book</a> is a great source
+  of reference of Subversion features. In particular, the <a href=
+  "http://svnbook.red-bean.com/en/1.8/svn.basic.html">Fundamental Concepts</a>
+  and <a href="http://svnbook.red-bean.com/en/1.8/svn.tour.html">Basic
+  Usage</a> chapters are well worth reading.</p>
+
+  <h3 id="tutorial_create-ticket">Create a new ticket</h3>
+
+  <p><em>Trac is an integrated web-based issue tracker and wiki system. You
+  will use it to manage and keep track of changes in your project. The issue
+  tracker is called the ticket system. When you want to report a problem or
+  submit a change request, you will create a new ticket. In a typical
+  situation, you and/or your colleagues will make changes to your system in
+  order to resolve the problem or change request, and you will monitor these
+  changes via the ticket.</em></p>
+
+  <p>After completing this sub-section, you will learn how to:</p>
+
+  <ul>
+    <li>launch a Trac system,</li>
+
+    <li>create a new Trac ticket,</li>
+
+    <li>search for a Trac ticket, and</li>
+
+    <li>accept a Trac ticket.</li>
+  </ul>
+
+  <p>Further reading:</p>
+
+  <ul>
+    <li><a href="overview.html">System Overview</a></li>
+
+    <li>Code Management System > <a href=
+    "code_management.html#svn_basic">Basic Command Line Usage</a></li>
+
+    <li>Code Management System > <a href="code_management.html#trac">Using
+    Trac</a></li>
+
+    <li>FCM Command Reference > <a href="command_ref.html#fcm-browse">fcm
+    browse</a></li>
+  </ul>
+
+  <h4>Launch a Trac system</h4>
+
+  <p>To launch the Trac system for the tutorial: type and <kbd>Enter</kbd> the
+  following command:</p>
+  <pre>
+(SHELL PROMPT)$ fcm browse fcm:tutorial
+</pre>
+
+  <p>This is probably the first time you have used the <code>fcm</code>
+  command. The command has the general syntax:</p>
+  <pre>
+fcm <sub-command> [<options...>] <arguments>
+</pre>
+
+  <p>For example, if you type <code>fcm help</code>, it will display a listing
+  of what sub-commands are available, and if you type <code>fcm help
+  <sub-command></code>, it will display help for that particular
+  sub-command.</p>
+
+  <p>The <code>trac</code> sub-command launches the corresponding Trac system
+  browser for a Subversion URL specified in your argument. In this case, we are
+  asking it to display the Trac system browser for the tutorial. The argument
+  <samp>fcm:tutorial</samp> is a FCM URL keyword and will be expanded by FCM
+  into a real Subversion URL (e.g.
+  <samp>svn://fcm1/tutorial_svn/tutorial</samp>). You are encouraged to use FCM
+  URL keywords throughout the tutorial, as it will save you a lot of
+  typing.</p>
+
+  <p>Note: Although we use the Trac system as a browser for a Subversion
+  repository, they do not interact in any other ways. Having access to a Trac
+  system does not guarantee the same privilege to a Subversion repository. In
+  particular, you should note the differences between the URLs of a Subversion
+  repository path and its equivalence in a Trac browser.</p>
+
+  <p>There are other ways to launch the Trac system for a project. If you know
+  its URL, you can launch the Trac system by entering it in the address box of
+  your favourite browser. If you often access a Trac system for a particular
+  project, you should bookmark it in your favourite browser.</p>
+
+  <h4>Create a new Trac ticket</h4>
+
+  <p>Click on <kbd>Login</kbd> just above the menu bar, enter your Unix/Linux
+  user ID as your user name and leave the password empty. Then click on
+  <kbd>OK</kbd> to proceed.</p>
+
+  <p>Once you have logged in, the <kbd>New Ticket</kbd> link will become
+  available on the menu bar. Click on it to display a new ticket form, where
+  you can enter details about your problem or change request. In the tutorial,
+  it does not matter what you enter, but you should feel free to play around
+  with wiki formatting when entering the <em>Full description</em>. (Click on
+  <kbd>WikiFormatting</kbd> to see how you can use it.) For example:</p>
+
+  <ul>
+    <li>Short summary:
+      <pre>
+Tutorial to change repository files and resolve conflicts with the trunk
+</pre>
+    </li>
+
+    <li>Full description:
+      <pre>
+In this tutorial, I shall:
+ 1. use FCM commands
+ 2. play with WikiFormatting in Trac tickets     
+ 3. create a branch and checkout a working copy
+ 4. make changes to files in it
+ 5. commit my changes and assign the ticket for review
+ 6. record the review and assign the ticket back to the author
+ 7. merge in the trunk, and resolve any conflicts 
+ 8. merge my changes back to the trunk
+ 9. close the ticket
+ 10. delete my branch
+</pre>
+    </li>
+
+    <li>Feel free to select an option you desire for each of the other ticket
+    properties: Type, Component, Priority, Version and Milestone.</li>
+  </ul>
+
+  <p>At the bottom of the page, click the <kbd>Preview</kbd> button to see what
+  the description would look like. When you are happy, click the <kbd>Submit
+  changes</kbd> button. Trac will create the new ticket and return it in a
+  state where you can append to it.</p>
+
+  <p>When the ticket is created, you should get an automatic e-mail notication
+  from the Trac system. In real life, depending on the setting, the owner of
+  your Trac system may also get a similar e-mail notification. It is worth
+  noting that each time the ticket is modified, the Trac system will send out
+  an e-mail notification to you (the reporter) and anyone who modified the
+  ticket subsequently.</p>
+
+  <h4>Search for a Trac ticket</h4>
+
+  <p>You should remember the number of your new ticket, as you will have to
+  revisit it later.</p>
+
+  <p>In real work, it is often not practical to have to remember the numbers of
+  all the tickets you have created. Trac provides a powerful custom query for
+  searching a ticket. You can search for the ticket you have just created by
+  clicking the <kbd>View Tickets</kbd> link. Feel free to play with the custom
+  query tool. Add or remove filters and try grouping your results by different
+  categories.</p>
+
+  <p>In addition, you can search your ticket using the keyword
+  <kbd>Search</kbd> utility at the top right hand corner of each Trac page. (If
+  you enter <kbd>#<number></kbd> in the search box, it will take you
+  directly to that ticket.) In the tutorial, however, it may be easiest if you
+  simply leave the tutorial Trac system open, so that you do not have to login
+  again when you come back to your ticket.</p>
+
+  <h4>Start work on your Trac ticket</h4>
+
+  <p>The status of the ticket is <em>new</em>. When you start working on a
+  problem reported in a ticket it is good practice to change the status to
+  <em>in_progress</em> to indicate that you are working on it. For the purpose
+  of the tutorial, however, this is entirely optional since you know you will
+  be doing all the work any way.</p>
+
+  <p>To start work on a ticket, click on <kbd>start work</kbd> in the
+  <em>Action</em> box at the bottom of the page, and then click on <kbd>Submit
+  changes</kbd>.</p>
+
+  <h3 id="tutorial_create-branch">Create a branch</h3>
+
+  <p><em>You create a branch by making a copy of your project at a particular
+  revision. Most often, this will be a particular revision of the trunk, i.e.
+  the main branch/development line in your project. A branch resides in the
+  repository. It allows you to work in parallel with your colleagues without
+  affecting one another, while keeping your changes under version
+  control.</em></p>
+
+  <p>After completing this sub-section, you will learn how to:</p>
+
+  <ul>
+    <li>create a branch in a Subversion repository, and</li>
+
+    <li>update a ticket with a link to a branch.</li>
+  </ul>
+
+  <p>Further reading:</p>
+
+  <ul>
+    <li>Code Management System > Branching & Merging > <a href=
+    "code_management.html#svn_branching_create">Creating Branches</a></li>
+
+    <li>Code Management Working Practices > <a href=
+    "working_practices.html#branching">Branching & Merging</a></li>
+
+    <li>FCM Command Reference > <a href=
+    "command_ref.html#fcm-branch-create">fcm branch-create</a></li>
+  </ul>
+
+  <h4>Create a branch in a Subversion repository</h4>
+
+  <p><strong>Important note: please ensure that your branch is created from
+  revision 1 of the trunk here, or the tutorial on merge will fail to work
+  later.</strong></p>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm branch-create [-rREV] TICKET
+  URL-PROJECT</code> command. (Note: you can write <code>fcm
+  branch-create</code> as <code>fcm bcreate</code> or even <code>fcm
+  bc</code>.) E.g. if your ticket number is <samp>#133</samp>:</p>
+  <pre>
+(SHELL PROMPT)$ fcm bc 133 fcm:tutorial at 1
+</pre>
+
+  <p>You will be prompted to edit the message log file. A standard template is
+  automatically supplied for the commit. However, if you want to add extra
+  comment for the branch, please do so <strong>above</strong> the line that
+  says <samp>--Add your commit message ABOVE - do not alter this line or those
+  below--</samp>. When you are ready, save your change and exit the editor.
+  Answer <kbd>Yes</kbd> when you are prompted to go ahead and create the
+  branch.</p>
+
+  <p>Note: Subversion will prompt you for a password the first time you access
+  a repository. The password will normally be cached by the client, and you
+  will not have to specify a password on subsequent access.</p>
+
+  <p>When creating branches for the first time, you will notice that FCM will
+  create and commit any missing sub-directories it needs to set up your branch
+  inside the repository, before creating your branch and commiting it.</p>
+
+  <p>Take a note of the revision number the branch was created at, and its
+  branch name. (The revision number is the number following the last output
+  that says "Committed revision". In the example above, the branch created at
+  <samp>r811</samp> is called <samp>branches/dev/matt/r1_133</samp>, which is a
+  branch of the <samp>tutorial</samp> project in the
+  <samp>svn://fcm1/tutorial_svn</samp> repository.)</p>
+
+  <h4>Update your ticket with a link to your branch</h4>
+
+  <p>If you wish, you can update your ticket with details of the branch. Note
+  that this step is entirely optional. It is useful for developments which will
+  take a long time to complete. For short lived branches, this step is probably
+  unnecessary.</p>
+
+  <p>In the ticket you have created, refer to the revision number in the
+  <kbd>Add/Change</kbd> box, for example:</p>
+  <pre>
+r811: created source:tutorial/branches/dev/matt/r1_133 at 811.
+</pre>
+
+  <p>Note:</p>
+
+  <ul>
+    <li><samp>source:tutorial/branches/dev/matt/r1_133 at 811</samp> is a Trac
+    wiki link. In this syntax, you do not have to put in the root URL, (e.g.
+    <samp>svn://fcm1/tutorial_svn/</samp>), but you should specify your branch
+    using the project name (<samp>tutorial</samp>), the branch name
+    (<samp>branches/dev/matt/r1_133</samp>), and a revision number. Trac will
+    translate this into a link to that branch.</li>
+
+    <li>Trac will translate the syntax <code>r<number></code> or
+    <code>[<number>]</code> into a link to the numbered changeset.</li>
+  </ul>
+
+  <p>Click on <kbd>Preview</kbd> and check that the links work correctly, and
+  on <kbd>Submit changes</kbd> when you are ready.</p>
+
+  <h3 id="tutorial_checkout">Checkout a working copy</h3>
+
+  <p><em>A Subversion working copy is an ordinary directory tree on your local
+  system, containing a collection of files. It is your private working area in
+  which you can make changes before publishing them back to the repository. You
+  create a working copy by using the checkout command on some subtree of the
+  repository.</em></p>
+
+  <p>After completing this sub-section, you will learn how to:</p>
+
+  <ul>
+    <li>checkout a Subversion working copy.</li>
+  </ul>
+
+  <p>Further reading:</p>
+
+  <ul>
+    <li>Code Management System > <a href=
+    "code_management.html#svn_concepts">Basic Concepts</a></li>
+
+    <li>FCM Command Reference > <a href="command_ref.html#svn">Other
+    Subversion Commands</a></li>
+  </ul>
+
+  <h4>Checkout a Subversion working copy</h4>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm checkout</code> (or simply
+  <code>fcm co</code>) command. E.g.:</p>
+  <pre>
+(SHELL PROMPT)$ fcm checkout fcm:tutorial_br/dev/matt/r1_133 tutorial
+</pre>
+
+  <ul>
+    <li>In the example, we have replaced the leading part of the Subversion URL
+    <samp>svn://fcm1/tutorial_svn/tutorial/branches</samp> with the FCM URL
+    keyword <samp>fcm:tutorial_br</samp>. This is mainly to save you from
+    having to type in the full URL. However, you may find it easier to
+    copy-and-paste the full Subversion URL from the output generated when you
+    created the branch.</li>
+
+    <li>In the above command, we have asked the system to create a working copy
+    in <samp>$PWD/tutorial</samp>. If you do not specify a local directory
+    <var>PATH</var> in the <code>checkout</code> command, it will create a
+    working copy in the current working directory, using the basename of the
+    URL. For example, when you checkout the branch you have just created, the
+    command would create the working copy in <samp>$PWD/r1_133</samp>, which is
+    often undesirable. Make a note of the location of your working copy, in
+    case you forget where you have put it.</li>
+
+    <li>If you do not specify a revision to checkout, it will checkout the
+    HEAD, i.e. the latest, revision.</li>
+  </ul>
+
+  <p>Example:</p>
+  <pre>
+=> fcm checkout fcm:tutorial_br/dev/matt/r1_133 tutorial
+A    tutorial/doc
+A    tutorial/doc/hello.html
+A    tutorial/src
+A    tutorial/src/subroutine
+A    tutorial/src/subroutine/hello_c.c
+A    tutorial/src/subroutine/hello_sub.f90
+A    tutorial/src/module
+A    tutorial/src/module/hello_num.f90
+A    tutorial/src/module/hello_constants.f90
+A    tutorial/src/program
+A    tutorial/src/program/hello.f90
+A    tutorial/fcm-make.cfg
+Checked out revision 811.
+</pre>
+
+  <h3 id="tutorial_change">Make changes to files in your working copy</h3>
+
+  <p><em>Subversion provides various useful commands to help you monitor your
+  working copy. The most useful ones are "diff", "revert" and "status". You
+  will also find "add", "copy", "delete" and "move" useful when you are
+  rearranging your files and directories.</em></p>
+
+  <p>After completing this sub-section, you will learn how to:</p>
+
+  <ul>
+    <li>make and revert changes,</li>
+
+    <li>add and remove files,</li>
+
+    <li>inspect the status of a working copy, and</li>
+
+    <li>display changes in a working copy.</li>
+  </ul>
+
+  <p>Further reading:</p>
+
+  <ul>
+    <li>Code Management System > <a href=
+    "code_management.html#svn_basic">Basic Command Line Usage</a></li>
+
+    <li>FCM Command Reference > <a href="command_ref.html#fcm-add">fcm
+    add</a>, <a href="command_ref.html#fcm-diff">fcm diff</a>, <a href=
+    "command_ref.html#fcm-delete">fcm delete</a>, <a href=
+    "command_ref.html#svn">Other Subversion Commands</a></li>
+  </ul>
+
+  <h4>Make and revert changes</h4>
+
+  <p>For the later part of the tutorial to work, you must make the following
+  modifications:</p>
+
+  <ul>
+    <li>Change to the <samp>src/module/</samp> sub-directory in your working
+    copy.</li>
+
+    <li>Edit <samp>hello_constants.f90</samp>, using your favourite editor, and
+    change: <samp>Hello World!</samp> to <kbd>Hello Earthlings!</kbd>. Save
+    your change and exit the editor.</li>
+
+    <li>Edit <samp>hello_num.f90</samp> and either add a comment, or make a
+    minor code change - for example, use one of :
+      <pre>
+!This will print a really really big integer.
+WRITE(*, *) "Sadly, there's no encoding for Martian base-60"
+</pre>
+    </li>
+  </ul>
+
+  <p>Try the following so that you know how to restore a changed file:</p>
+
+  <ul>
+    <li>Change to the <samp>src/subroutine/</samp> directory of your working
+    copy.</li>
+
+    <li>Make a change in file <b>hello_c.c</b>, using your favourite
+    editor.</li>
+
+    <li>To see that you have <strong>M</strong>odified this file: issue the
+    <code>fcm status</code> command</li>
+
+    <li>Run the <code>revert</code> command to get the file back unmodified:
+      <pre>
+(SHELL PROMPT)$ fcm revert hello_c.c
+</pre>
+    </li>
+  </ul>
+
+  <h4>Add and remove files</h4>
+
+  <p>You may also want to try the following FCM commands in your
+  <samp>doc/</samp> sub-directory. You can safely make changes here since they
+  will not interfere with your code changes.</p>
+
+  <ul>
+    <li>change to the <samp>doc/</samp> directory of your working copy.</li>
+
+    <li>Echo some text into a new file and then run the <code>add</code>
+    command, which lets the repository know you're adding a new file at the
+    next commit. For example:
+      <pre>
+(SHELL PROMPT)$ echo 'Some text' >new_file.txt
+(SHELL PROMPT)$ fcm add new_file.txt
+</pre>
+    </li>
+
+    <li>Make a copy of <samp>hello.html</samp> and remove the original, using
+    the <code>copy</code> and <code>delete</code> commands. For example:
+      <pre>
+(SHELL PROMPT)$ fcm copy hello.html add.html
+(SHELL PROMPT)$ fcm delete hello.html
+</pre>
+    </li>
+
+    <li>You can use a simple <code>move</code> sub-command for the above
+    <code>copy</code> and <code>delete</code>.</li>
+  </ul>
+
+  <h4>Inspect the status of a working copy</h4>
+
+  <p>Change to the root directory of your working copy.</p>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm status</code> (or simply
+  <code>fcm st</code>) command.</p>
+
+  <p>Example:</p>
+  <pre>
+=> fcm status
+D      doc/hello.html
+A      doc/new_file.txt
+A  +   doc/add.html
+M      src/module/hello_num.f90
+M      src/module/hello_constants.f90
+</pre>
+
+  <p>This confirms the actions you have taken. You have
+  <strong>D</strong>eleted a file, <strong>A</strong>dded a new file,
+  <strong>A</strong>dded a file with history (<strong>+</strong>) and
+  <strong>M</strong>odified two others. It also confirms the action of the
+  <code>revert</code> command.</p>
+
+  <h4>Display changes in a working copy</h4>
+
+  <p>You can view the changes you have made to your working copy.</p>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm diff --graphical</code> (or
+  simply <code>fcm di -g</code>) command.</p>
+
+  <p>A listing of the files you have changed will be displayed, and a graphical
+  diff tool will open up for each modified file.</p>
+
+  <h3 id="tutorial_commit">Commit your changes to the repository</h3>
+
+  <p><em>The change in your working copy remains local until you commit it to
+  the repository where it becomes permanent. If you are planning to make a
+  large number of changes, you are encouraged to commit regularly to your
+  branch at appropriate intervals.</em></p>
+
+  <p>After completing this sub-section, you will learn how to:</p>
+
+  <ul>
+    <li>commit your changes, and</li>
+
+    <li>inspect your changes using Trac.</li>
+  </ul>
+
+  <p>Further reading:</p>
+
+  <ul>
+    <li>Code Management System > <a href=
+    "code_management.html#svn_basic">Basic Command Line Usage</a></li>
+
+    <li>FCM Command Reference > <a href="command_ref.html#fcm-commit">fcm
+    commit</a></li>
+  </ul>
+
+  <h4>Commit changes</h4>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm commit</code> (or simply
+  <code>fcm ci</code>) command.</p>
+
+  <p>A text editor will appear to allow you to edit the commit message. You
+  must add a commit message to describe your change <strong>above</strong> the
+  line that says <samp>--Add your commit message ABOVE - do not alter this line
+  or those below--</samp>. (A suggestion is given as the highlighted text in
+  the example below.) Your commit will fail if you do not enter a commit
+  message.</p>
+
+  <p>Save your change and exit the editor. Answer <kbd>Yes</kbd> when you are
+  prompted to confirm the commit. For example:</p>
+  <pre>
+[info] gvim -f: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Project: tutorial]
+[Branch : branches/dev/matt/r1_133]
+[Sub-dir: <top>]
+
+D       doc/hello.html
+A       doc/new_file.txt
+A  +    doc/add.html
+M       src/module/hello_num.f90
+M       src/module/hello_constants.f90
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+#133: tutorial is fun.
+--------------------------------------------------------------------------------
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): y
+Adding         doc/add.html
+Deleting       doc/hello.html
+Adding         doc/new_file.txt
+Sending        src/module/hello_constants.f90
+Sending        src/module/hello_num.f90
+Transmitting file data ...
+Committed revision 812.
+=> svn update
+At revision 812.
+</pre>
+
+  <h4>Inspect changes using Trac</h4>
+
+  <p>Click on <kbd>Timeline</kbd> in Trac. Drill down to your changeset and see
+  how it appears. (Alternatively, if you enter <samp>r<number></samp>
+  into the search box at the top right, it will take you directly to the
+  numbered changeset.)</p>
+
+  <p>Note:</p>
+
+  <ul>
+    <li>Wiki Formatting, used in the commit message, has customised the
+    changeset message.</li>
+
+    <li>All your changes are listed.</li>
+  </ul>
+
+  <h3 id="tutorial_extract">Test your changes</h3>
+
+  <p><em>You should test the changes in your branch before asking a colleague
+  to review them. FCM features a build system that allows you to build your
+  code easily. As your changes may be located in a repository branch and/or a
+  working copy, you should work with the extract system to extract the correct
+  code to build. The extract system allows you to extract code from the
+  repository, combining changes in different branches and your working copy,
+  before generating a configuration file and a suitable source tree for feeding
+  into the build system.</em></p>
+
+  <p><em>In this sub-section of the tutorial, you will be shown how to extract
+  and build the code from your branch. (There are some <a href=
+  "#tutorial_extra-extract">extra activities on the extract and build
+  systems</a> in a later sub-section of the tutorial should you want to explore
+  the extract and build systems in more depth.) In the example here, the
+  extract and build systems will be shown to you in their simplest form. In
+  real life, the managers of the systems you are developing code for will
+  provide you with more information on how to extract and build their
+  systems.</em></p>
+
+  <p>After completing this sub-section, you will learn how to:</p>
+
+  <ul>
+    <li>set up a simple FCM make configuration file for extract and build,
+    and</li>
+
+    <li>use the <a href="make.html">FCM Make</a> system to perform simple
+    extracts and builds.</li>
+  </ul>
+
+  <p>Further reading:</p>
+
+  <ul>
+    <li><a href="make.html">FCM Make</a></li>
+  </ul>
+
+  <p>You should extract and build your code in a different directory to your
+  working copy. For example, you may want to create a sub-directory
+  <samp>$HOME/tutorial/test/</samp> and change to it:</p>
+  <pre>
+(SHELL PROMPT)$ mkdir -p $HOME/tutorial/test
+(SHELL PROMPT)$ cd $HOME/tutorial/test
+</pre>
+
+  <h4>Set up a FCM make configuration file</h4>
+
+  <p>To set up a FCM make configuration file from scratch, launch your
+  favourite editor and add the following lines:</p>
+  <pre>
+steps = extract build
+extract.ns = tutorial
+extract.location[tutorial] = branches/dev/$LOGNAME/r1_133
+extract.path-root[tutorial] = src
+build.target{task} = link
+</pre>
+
+  <p>Note:</p>
+
+  <ul>
+    <li>The <code><a href=
+    "annex_cfg.html#make.extract.location">extract.location</a></code>
+    declaration is set to <samp>branches/dev/$LOGNAME/r1_133</samp>. If you
+    have named your branch differently, you should modify the right hand side
+    of the declaration.</li>
+
+    <li>The build system uses <code>gfortran</code> and <code>gcc</code> as the
+    default Fortran and C (respectively) compilers. If you do not have these
+    compilers installed on your system, you can configure your Fortran and C
+    compilers using the <code><a href=
+    "annex_cfg.html#make.build.prop.fc">build.prop{fc}</a></code> and
+    <code><a href="annex_cfg.html#make.build.prop.cc">build.prop{cc}</a></code>
+    declarations.</li>
+  </ul>
+
+  <p>Save the file as <samp>fcm-make.cfg</samp> and exit your editor.</p>
+
+  <h4>Perform an extract and a build</h4>
+
+  <p>Issue the command <code><a href="command_ref.html#fcm-make">fcm
+  make</a></code> and you should get an output similar to the following:</p>
+  <pre>
+(SHELL PROMPT)$ fcm make
+[init] make                # 2011-11-03 10:31:10Z
+[init] make config-parse   # 2011-11-03 10:31:10Z
+[info] config-file=/home/matt/tutorial/test/fcm-make.cfg
+[done] make config-parse   # 0.0s
+[init] make dest-init      # 2011-11-03 10:31:10Z
+[info] dest=frsn at eld081:/home/matt/tutorial/test
+[info] mode=new
+[done] make dest-init      # 0.0s
+[init] make extract        # 2011-11-03 10:31:10Z
+[info] location tutorial: 0: svn://fcm1/tutorial_svn/tutorial/branches/dev/matt/r1_133@811
+[info]   dest:    5 [A added]
+[info] source:    5 [U from base]
+[done] make extract        # 0.1s
+[init] make build          # 2011-11-03 10:31:10Z
+[info] sources: total=5, analysed=5, elapsed-time=0.0s, total-time=0.0s
+[info] compile   targets: modified=5, unchanged=0, total-time=0.3s
+[info] compile+  targets: modified=2, unchanged=0, total-time=0.0s
+[info] ext-iface targets: modified=1, unchanged=0, total-time=0.0s
+[info] link      targets: modified=1, unchanged=0, total-time=0.0s
+[info] TOTAL     targets: modified=9, unchanged=0, elapsed-time=0.4s
+[done] make build          # 0.4s
+[done] make                # 0.5s
+</pre>
+
+  <p>If nothing goes wrong, you should end up with the sub-direcories
+  <samp>extract/</samp> and <samp>build/</samp> in your working directory. The
+  <samp>extract/</samp> sub-directory contains the result of the extract and
+  the <samp>build/</samp> sub-directory contains the result of the build.</p>
+
+  <p>N.B. You should also find a <samp>.fcm-make/</samp> sub-directory. It is
+  used by <code>fcm make</code> as a working area for your extract and build.
+  It also contains a diagnostic <samp>log</samp> file generated by the latest
+  <code>fcm make</code> command. The log file contains the diagnostic output in
+  high verbosity. If anything goes wrong, it is worth checking the content of
+  the log file for clues.</p>
+
+  <p>The executable you have built is <samp>hello.exe</samp>, which is located
+  in the <samp>build/bin/</samp> sub-directory. You can test your executable by
+  running it. You should get an output similar to the following:</p>
+  <pre>
+(SHELL PROMPT)$ PATH=$PWD/build/bin:$PATH hello.exe
+hello: Hello Earthlings!
+hello_sub: Hello Earthlings!
+hello_huge_number: maximum integer: 2147483647
+hello_c: Hello World!
+</pre>
+
+  <h3 id="tutorial_merge">Merge changes from the trunk and resolve
+  conflicts</h3>
+
+  <p><em>Your branch is normally isolated from other development lines in your
+  project. However, at some point during your development, you may need to
+  merge your changes with those of your colleagues. In some cases, it is
+  desirable to merge changes regularly from the trunk to keep your branch up to
+  date with the latest development. The automatic merge provided by FCM allows
+  you to do this easily.</em></p>
+
+  <p><em>A merge results in a conflict if changes being applied to a file
+  overlap. FCM uses a graphical merge tool to help you resolve overlaps in file
+  text changes (</em>text conflicts<em>). If some of the changes include a
+  deletion, renaming, or addition of the file, a filesystem conflict (</em>tree
+  conflict<em>) may occur, which needs to be dealt with manually.</em></p>
+
+  <p>After completing this sub-section, you will learn how to:</p>
+
+  <ul>
+    <li>merge changes from the trunk into your working copy, and</li>
+
+    <li>resolve text and tree conflicts in your working copy.</li>
+  </ul>
+
+  <p>Further reading:</p>
+
+  <ul>
+    <li>Code Management System > Basic Command Line Usage > <a href=
+    "code_management.html#svn_basic_conflicts">Resolving Conflicts</a></li>
+
+    <li>Code Management System > <a href=
+    "code_management.html#svn_branching">Branching & Merging</a></li>
+
+    <li>Code Management Working Practices > <a href=
+    "working_practices.html#branching">Branching & Merging</a></li>
+
+    <li>FCM Command Reference > <a href="command_ref.html#fcm-conflicts">fcm
+    conflicts</a></li>
+
+    <li>FCM Command Reference > <a href="command_ref.html#fcm-merge">fcm
+    merge</a></li>
+  </ul>
+
+  <h4>Merge changes from the trunk into a working copy</h4>
+
+  <p>Perform the merge in your working copy.</p>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm merge</code> command.
+  E.g.</p>
+  <pre>
+(SHELL PROMPT)$ fcm merge trunk
+</pre>
+
+  <p>If there is more than one revision of the source that you can merge with,
+  you will be prompted for the revision number you wish to merge from. You will
+  not be prompted in this case, because there is only one revision of the
+  source that you can merge with.</p>
+
+  <p>Example:</p>
+  <pre>
+Eligible merge(s) from /tutorial/trunk: 2
+Merge: /tutorial/trunk at 2
+ c.f.: /tutorial/trunk at 1
+-------------------------------------------------------------------------dry-run
+--- Merging r2 into '.':
+U    src/subroutine/hello_c.c
+A    src/module/hello_number.f90
+C    src/module/hello_constants.f90
+   C src/module/hello_num.f90
+Summary of conflicts:
+  Text conflicts: 1
+  Tree conflicts: 1
+-------------------------------------------------------------------------dry-run
+Would you like to go ahead with the merge?
+Enter "y" or "n" (or just press <return> for "n"): y
+Merge succeeded.
+</pre>
+
+  <h4>Status of the working copy after a merge</h4>
+
+  <p>In the output of the merge, the <samp>C</samp> status at the beginning of
+  a line indicates that the first file you changed,
+  <samp>src/module/hello_constants.f90</samp>, is now in <em>text
+  conflict</em>. The <samp>C</samp> status in the 4th column of a line
+  indicates that the second file you changed,
+  <samp>src/subroutines/hello_num.f90</samp>, is now in <em>tree conflict</em>.
+  If you run <code>fcm status</code>, you will see extra information about the
+  merge, which may help you to resolve the conflicts:</p>
+  <pre>
+=> fcm status
+ M      .
+?       #commit_message#
+M       src/subroutine/hello_c.c
+?       src/module/hello_constants.f90.merge-left.r1
+?       src/module/hello_constants.f90.merge-right.r2
+?       src/module/hello_constants.f90.working
+      C src/module/hello_num.f90
+      >   local edit, incoming delete upon merge
+A  +    src/module/hello_number.f90
+C       src/module/hello_constants.f90
+</pre>
+
+  <p>In the case of the file <samp>hello_constants.f90</samp>, the extra files
+  created (ending with <samp>working</samp>, <samp>merge-left.r1</samp>,
+  <samp>merge-right.r2</samp>) will be used to resolve the text conflict using
+  the 3-way difference tool <code>xxdiff</code>.</p>
+
+  <p>In the case of the file <samp>hello_num.f90</samp>, the extra line
+  underneath (<em>local edit, incoming delete upon merge</em>) displays the
+  conflict or dilemma that you must resolve - you have made a change to the
+  file in your branch (<em>local edit</em>) but someone has deleted the file on
+  the trunk (<em>incoming delete upon merge</em>). If you inspect the log of
+  the trunk, by typing e.g. <code>fcm log -v -rHEAD:1
+  fcm:tutorial/trunk</code>, you will find that someone has renamed
+  <samp>src/module/hello_num.f90</samp> to
+  <samp>src/module/hello_number.f90</samp>.</p>
+
+  <p>The line: <samp>M .</samp> just refers to Subversion's merge tracking,
+  which is not relevant here.</p>
+
+  <p>You will now have to resolve the conflicts.</p>
+
+  <h4>Resolve the conflicts</h4>
+
+  <p>Issue the <code>fcm conflicts</code> (or simply <code>fcm cf</code>)
+  command.</p>
+
+  <p>The <code>xxdiff</code> program comes into play:</p>
+
+  <p class="image"><img src="xxdiff_tutorial.png" alt="3-way diff" /></p>
+
+  <p>See the sub-section on <a href=
+  "code_management.html#svn_basic_conflicts">resolving conflicts</a>, or the
+  <cite>xxdiff User's Manual</cite> (click on <em>Help</em>) to guide you
+  through this process. (If you do not want to learn how to use
+  <code>xxdiff</code> now, you can just click on the highlighted line in the
+  left hand column, and select <kbd>Exit with MERGED</kbd> from the
+  <em>File</em> menu. This saves the file you are merging in as the result of
+  the merge, i.e. you have <em>merged</em> the changes).</p>
+
+  <p>On resolving this conflict, you will be asked to run <code>svn
+  resolved</code>. Answer <kbd>Yes</kbd>.</p>
+
+  <p>You are now prompted to try to solve the <em>tree conflict</em>.</p>
+  <pre>
+[info] src/module/hello_num.f90: in tree conflict.
+Locally: modified
+Externally: renamed to src/module/hello_number.f90
+Answer (y) to keep the old name.
+Answer (n) to accept the rename.
+You can then merge in changes.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") 
+</pre>
+
+  <p>Entering <samp>y</samp> will keep the file as it is, and entering
+  <samp>n</samp> will accept the external changes. Your problem is that the
+  edit you made to <samp>hello_num.f90</samp> is no longer valid on the trunk,
+  because there the file has been renamed to <samp>hello_number.f90</samp>. To
+  Subversion, it looks like <samp>hello_num.f90</samp> disappeared. Your
+  choices would be either to delete the new file by answering <samp>y</samp>,
+  or incorporate your changes into the new file (<samp>hello_number.f90</samp>)
+  by answering <samp>n</samp>. As the new filename comes from the trunk, we
+  would normally accept it and incorporate changes into it, rather than delete
+  it.</p>
+
+  <p>Answer <samp>n</samp> to accept the renaming of the file and merge in
+  changes. This will occur using <code>xxdiff</code>, as above.</p>
+
+  <p>Exit the <code>xxdiff</code> window as before, with <kbd>Exit with
+  MERGED</kbd>.</p>
+
+  <p>If you now run <code>status</code>, you will notice that these extra
+  conflict files have disappeared, and there are no more <samp>C</samp>
+  filename statuses.</p>
+
+  <p>Example:</p>
+  <pre>
+=> fcm status
+ M      .
+?       #commit_message#
+M       src/subroutine/hello_c.c
+D       src/module/hello_num.f90
+A  +    src/module/hello_number.f90
+</pre>
+
+  <p>You have now resolved all the conflicts.</p>
+
+  <h4>Commit after the merge</h4>
+
+  <p>It is important to remember that the <code>fcm merge</code> command only
+  applies changes to your working copy. Therefore, you must now commit the
+  change in order for it to become permanent in the repository. Similar to
+  other changes, it is a good practice to use <code>fcm diff</code> to inspect
+  the changes before committing.</p>
+
+  <p>When you run <code>fcm commit</code>, you will be prompted to edit the
+  commit log as usual. However, you may notice that a standard template is
+  already provided for you by the <code>fcm merge</code> command. In most
+  cases, the standard message should be sufficient. However, if you want to add
+  extra comment to the commit, please do so <strong>above</strong> the line
+  that says <samp>--Add your commit message ABOVE - do not alter this line or
+  those below--</samp>. This is useful, for example, if there were significant
+  issues addressed in the merge.</p>
+
+  <h3 id="tutorial_review-ticket">Review changes</h3>
+
+  <p><em>For the purpose of this tutorial, we assume that your changes are
+  complete, have been tested and committed to the repository, and are now ready
+  for review. You should assign the ticket to the reviewer and inform him/her
+  where to find the changes you wish him/her to review. The reviewer will
+  record any issues in the ticket, perhaps linking to other documents as
+  required. Once completed, he/she will record the outcome in the ticket and
+  assign it back to the you.</em></p>
+
+  <p>After completing this sub-section, you will learn how to:</p>
+
+  <ul>
+    <li>display changes in a branch, and</li>
+
+    <li>re-assign a ticket.</li>
+  </ul>
+
+  <p>Further reading:</p>
+
+  <ul>
+    <li>Code Management System > Branching & Merging > <a href=
+    "code_management.html#svn_branching_info">Getting Information About
+    Branches</a></li>
+
+    <li>Code Management System > <a href="code_management.html#trac">Using
+    Trac</a></li>
+
+    <li>Code Management Working Practices > <a href=
+    "working_practices.html#tickets">Using Tickets</a></li>
+
+    <li>FCM Command Reference > <a href="command_ref.html#fcm-diff">fcm
+    diff</a></li>
+  </ul>
+
+  <h4>Display changes in a branch</h4>
+
+  <p>Before you ask someone to review your code, it is often a good idea to
+  have a look at the changes one more time. To view the changes in a branch,
+  you can look at all the changes relative to its base.</p>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm branch-diff
+  --graphical</code> (or simply <code>fcm bdi -g</code>) command.</p>
+
+  <p>You should be presented with the differences between the branch and the
+  trunk (since the last merge).</p>
+
+  <p>Note: you can also use the <code>--trac</code> (<code>-t</code>) option
+  instead of <code>--graphical</code> (<code>-g</code>) to view the changes in
+  a branch using Trac rather than using a graphical diff tool.</p>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm branch-diff --trac</code> (or
+  simply <code>fcm bdi -t</code>) command.</p>
+
+  <p>Take note of the Trac URL for displaying the differences. The part that
+  begins with <code>diff:</code> is of particular interest to you, as it is a
+  Trac link that can be inserted into a Trac wiki/ticket. In the above example,
+  the Trac link would look like:
+  <samp>diff:/tutorial/trunk at 2///tutorial/branches/dev/matt/r1_133 at 813</samp>.</p>
+
+  <h4>Re-assign a ticket to a reviewer</h4>
+
+  <p>Back in your ticket, add an appropriate comment showing where to find your
+  changes, in the <em>Add/Change</em> box. Include a link to your branch and a
+  diff link (see above) in the comment. For example:</p>
+  <pre>
+The [log:tutorial/branches/dev/matt/r1_133 at 811:813] branch proposes
+changes to the greeting in hello_constants.f90. It also contains some new
+documents. See
+[diff:/tutorial/trunk at 2///tutorial/branches/dev/matt/r1_133 at 813] for the
+changes.
+
+Fred, could you review the change, please?
+</pre>
+
+  <p>Note: the syntax
+  <samp>[log:tutorial/branches/dev/matt/r1_133 at 811:813]</samp> will be
+  translated by Trac into a link to the revision log browser to display the log
+  between revision 811 and 813 of the <samp>branches/dev/matt/r1_133</samp>
+  branch in the <samp>tutorial</samp> project; and the syntax
+  <samp>[diff:/tutorial/trunk at 2///tutorial/branches/dev/matt/r1_133 at 813]</samp>
+  will be translated into a link to display the differences between the trunk
+  at revision 2 and the branch at revision 813. Click on <kbd>Preview</kbd> and
+  check that the links work correctly.</p>
+
+  <p>To re-assign a ticket to your reviewer, click on the <kbd>reassign
+  to</kbd> button in the <em>Action</em> box section and enter the reviewer's
+  User ID.</p>
+
+  <p>When you are ready, click on <kbd>Submit changes</kbd>.</p>
+
+  <h4>Reassign the ticket back to the author</h4>
+
+  <p>For the purpose of this tutorial, you will act as the reviewer of the
+  changes you have made. Following the review, you should record its outcome
+  and re-assign the ticket back to the author. Enter the comment <kbd>No issues
+  were found during the review</kbd>. Click on the <kbd>reassign to</kbd>
+  button in the <em>Action</em> box section, and enter your guest account name.
+  Click on <kbd>Submit changes</kbd> when you are ready.</p>
+
+  <h3 id="tutorial_merge-back">Commit to the trunk</h3>
+
+  <p><em>Your changes in the branch have been tested and reviewed. It is now
+  time to merge and commit it to the trunk. Once you have committed your
+  change, you will close your ticket to complete the work cycle.</em></p>
+
+  <p>After completing this sub-section, you will learn how to:</p>
+
+  <ul>
+    <li>switch a working copy,</li>
+
+    <li>merge and commit your changes into the trunk, and</li>
+
+    <li>close a ticket</li>
+  </ul>
+
+  <p>Further reading:</p>
+
+  <ul>
+    <li>Code Management System > <a href=
+    "code_management.html#svn_branching">Branching & Merging</a></li>
+
+    <li>Code Management Working Practices > <a href=
+    "working_practices.html#branching">Branching & Merging</a></li>
+
+    <li>FCM Command Reference > <a href="command_ref.html#fcm-switch">fcm
+    switch</a></li>
+  </ul>
+
+  <h4>Switch a working copy to point to the trunk</h4>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm switch</code> (or simply
+  <code>fcm sw</code>) command. E.g.:</p>
+  <pre>
+(SHELL PROMPT)$ fcm sw trunk
+</pre>
+
+  <p><dfn>Command line</dfn>: To check that your working copy is pointing to
+  the trunk, issue the <code>fcm info</code> command.</p>
+
+  <h4>Merge and commit your changes into the trunk</h4>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm merge</code> command.
+  E.g.</p>
+  <pre>
+(SHELL PROMPT)$ fcm merge branches/dev/matt/r1_133
+</pre>
+
+  <p>Example:</p>
+  <pre>
+Eligible merge(s) from /tutorial/branches/dev/matt/r1_133: 813 812
+Enter a revision (or just press <return> for "813"):
+Merge: /tutorial/branches/dev/matt/r1_133 at 813
+ c.f.: /tutorial/trunk at 2
+-------------------------------------------------------------------------dry-run
+--- Merging differences between repository URLs into '.':
+A    doc/new_file.txt
+A    doc/add.html
+D    doc/hello.html
+U    src/module/hello_number.f90
+U    src/module/hello_constants.f90
+-------------------------------------------------------------------------dry-run
+Would you like to go ahead with the merge?
+Enter "y" or "n" (or just press <return> for "n"): y
+Merge succeeded.
+</pre>
+
+  <p>Since there is more than one revision available for merging, you will be
+  prompted for the revision number you wish to merge from. The default is the
+  last changed revision of your branch. which is the revision you want to merge
+  with, so you should just proceed with the default.</p>
+
+  <p>Since we merged in the latest changes from the trunk into the branch,
+  there should be no conflicts from this merge.</p>
+
+  <p>Once again, please remember that the merge command only changes your
+  working copy. You need to commit the change before it becomes permanent in
+  the repository. Before you commit to the trunk, however, it is often sensible
+  to have a last look at what you are going to change using the
+  <code>diff</code> command.</p>
+
+  <p>Note: We have set up the repository to prevent any commits to the trunk to
+  preserve the tutorial for other users, so your commit to the trunk will fail.
+  However, you should try doing it any way to complete the exercise.</p>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm commit</code> (or simply
+  <code>fcm ci</code>) command.</p>
+
+  <p>A text editor will appear to allow you to edit the commit message. You
+  must add a commit message to describe your change <strong>above</strong> the
+  line that says <samp>--Add your commit message ABOVE - do not alter this line
+  or those below--</samp>. Since you are going to commit changes to the trunk,
+  you should provide a useful message, including a link to your ticket. For
+  example:</p>
+  <pre>
+#133: tutorial completed.
+</pre>
+
+  <p>When you are ready, save your change and exit the editor.</p>
+
+  <p>As we have said before, the command will fail when you try to proceed with
+  the commit.</p>
+
+  <h4>Close your ticket</h4>
+
+  <p>As you have completed your work, you should now update and close your
+  ticket. In real life, you will typically include a closing comment with an
+  appropriate Trac wiki link to the changeset in the trunk that fixes the
+  ticket.</p>
+
+  <p>Since you cannot commit to the trunk in the tutorial, you can include a
+  Trac link to the latest changeset in your branch. For example, you can put
+  <samp>r813: fixed.</samp> in the comment. To mark the ticket as
+  <em>fixed</em>, move down to the <em>Action</em> box section, click on
+  <kbd>resolve as</kbd> and choose <kbd>fixed</kbd>. Use <kbd>Preview</kbd> to
+  ensure that your links work correctly. When you are happy, click on
+  <kbd>Submit changes</kbd>.</p>
+
+  <h3 id="tutorial_extra-extract">Extra activities on the extract and build
+  systems</h3>
+
+  <p><em>The extract and build systems are very flexible. If you have time, you
+  may want to explore their uses in more depth.</em></p>
+
+  <p>After completing this sub-section, you will learn how to:</p>
+
+  <ul>
+    <li>extract from a working copy,</li>
+
+    <li>change a compiler flag, and</li>
+
+    <li>extract from a particular branch and/or revision from the
+    repository.</li>
+  </ul>
+
+  <p>Further reading:</p>
+
+  <ul>
+    <li><a href="make.html">FCM Make</a></li>
+  </ul>
+
+  <h4>Extract from a working copy</h4>
+
+  <p>Modify the source files in your working copy and commit the changes back
+  to your branch in the repository. Re-run <code><a href=
+  "command_ref.html#fcm-make">fcm make</a></code> and see the results of the
+  changes.</p>
+
+  <p>In fact, you can test changes in your working copy directly using a
+  similar extract and build mechanism. In such case, you need to modify the
+  <code><a href=
+  "annex_cfg.html#make.extract.location">extract.location</a></code>
+  declaration. For example:</p>
+  <pre>
+extract.location[tutorial] = $HOME/work/tutorial
+</pre>
+
+  <h4>Change a compiler flag</h4>
+
+  <p>Modify the compiler flags, and re-run <code><a href=
+  "command_ref.html#fcm-make">fcm make</a></code> and see the results of the
+  changes. To modify the compiler flags, edit your FCM make configuration file,
+  and add the declarations for changing compiler flags. For example:</p>
+  <pre>
+# Declare extra options for Fortran compiler
+build.prop{fc.flags} = -i8 -O3
+</pre>
+
+  <p>For further information on how to set your compiler flags, please refer to
+  the <a href="make.html">FCM Make</a> > <a href=
+  "make.html#build">Build</a>.</p>
+
+  <h4>Extract from a particular branch and/or revision</h4>
+
+  <p>Try extracting from an earlier revision of your branch. Suppose the HEAD
+  of your branch is revision 813, and the branch was created at an earlier
+  revision. You can extract your branch at, say, revision 811 by adding the
+  following to the <code><a href=
+  "annex_cfg.html#make.extract.location">extract.location</a></code>
+  declaration in your FCM make configuration file:</p>
+  <pre>
+extract.location[tutorial] = branches/dev/$LOGNAME/r1_133 at 811
+</pre>
+
+  <p>You can also try extracting from the trunk. To extract from the
+  <samp>trunk at HEAD</samp>, simply comment out or remove the <code><a href=
+  "annex_cfg.html#make.extract.location">extract.location</a></code>
+  declaration. To extract from a given revision of the trunk, you will need to
+  modify the <code><a href=
+  "annex_cfg.html#make.extract.location">extract.location</a></code>
+  declaration in your FCM make configuration file. For example:</p>
+  <pre>
+extract.location[tutorial] = trunk at 1
+</pre>
+
+  <h3 id="tutorial_delete-branch">Delete your branch</h3>
+
+  <p><em>You should remove your branch when it is no longer required. When you
+  remove it, it becomes invisible from the HEAD revision, but will continue to
+  exist in the repository, should you want to refer to it in the
+  future.</em></p>
+
+  <p>After completing this sub-section, you will learn how to:</p>
+
+  <ul>
+    <li>list branches owned by you, and</li>
+
+    <li>delete a branch.</li>
+  </ul>
+
+  <p>Further reading:</p>
+
+  <ul>
+    <li>Code Management System > Branching & Merging > <a href=
+    "code_management.html#svn_branching_list">Listing Branches Created by You
+    or Other Users</a></li>
+
+    <li>Code Management System > Branching & Merging > <a href=
+    "code_management.html#svn_branching_delete">Deleting Branches</a></li>
+  </ul>
+
+  <h4>List branches owned by you</h4>
+
+  <p>If you forget what your branch is called and/or what other branches you
+  have created, you can get a listing of all the branches you have created in a
+  project.</p>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm branch-list</code> (or simply
+  <code>fcm bls</code>) command</p>
+
+  <h4>Delete a branch</h4>
+
+  <p>Switch your working copy to point back to your branch. Before you do so,
+  revert any changes you have made in the working copy by issuing the <code>fcm
+  revert -R .</code> command. If a <samp>#commit_message#</samp> file exists,
+  remove it by issuing the <code>rm '#commit_message#'</code> command.</p>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm switch <URL></code> (or
+  simply <code>fcm sw <URL></code>) command.</p>
+
+  <p>You can continue your work in the branch if you wish, but once you have
+  finished all the work, you should delete it.</p>
+
+  <p><dfn>Command line</dfn>: issue the <code>fcm branch-delete</code> (or
+  simply <code>fcm bdel</code>) command.</p>
+
+  <p>Example:</p>
+  <pre>
+URL: svn://fcm1/tutorial_svn/tutorial/branches/dev/matt/r1_133
+Repository Root: svn://fcm1/tutorial_svn
+Repository UUID: cb858ce8-0f05-0410-9e64-efa98b760b62
+Revision: 813
+Node Kind: directory
+Last Changed Author:
+Last Changed Rev: 813
+Last Changed Date: 2005-11-09 09:11:57 +0000 (Wed, 09 Nov 2005)
+--------------------------------------------------------------------------------
+Branch Create Rev: 811
+Branch Create Date: 2005-11-09 08:34:22 +0000 (Wed, 09 Nov 2005)
+Branch Parent: svn://fcm1/tutorial_svn/tutorial/trunk@1
+--------------------------------------------------------------------------------
+Last Merge From Trunk: /tutorial/branches/dev/matt/r1_133 at 813 /tutorial/trunk at 2
+Avail Merges Into Trunk: 813 812
+[info] gvim -f: starting commit message editor...
+Change summary:
+------------------------------------------------------------------------
+D    svn://fcm1/tutorial_svn/tutorial/branches/dev/matt/r1_133
+------------------------------------------------------------------------
+Commit message is as follows:
+------------------------------------------------------------------------
+Deleted tutorial/branches/dev/matt/r1_133.
+------------------------------------------------------------------------
+Would you like to go ahead and delete this branch?
+Enter "y" or "n" (or just press <return> for "n"): y
+Deleting branch svn://fcm1/tutorial_svn/tutorial/branches/dev/matt/r1_133 ...
+
+Committed revision 813.
+</pre>
+
+  <p>You will be prompted to edit the commit message file. A standard template
+  is automatically supplied for the commit. However, if you want to add extra
+  comment for the branch, please do so <strong>above</strong> the line that
+  says <samp>--Add your commit message ABOVE - do not alter this line or those
+  below--</samp>. Save your change and exit the editor.</p>
+
+  <p>Your working copy is now pointing to a branch that no longer exists at the
+  HEAD revision of the repository. If you want to try the tutorial again, you
+  may want to create another branch, and switch your working copy to point to
+  the new branch. Otherwise, you can remove your working copy by issuing a
+  careful <code>rm -rf</code> command.</p>
+
+  <h3 id="tutorial_delete-final-comments">Final comments</h3>
+
+  <p>We have guided you through the basics of the complete change process,
+  using recommended ways of working. Most of the basic and important commands
+  have been covered by the tutorial. (The exceptions are <code>fcm log</code>
+  and <code>fcm update</code>, which you may have to use regularly. For
+  information on these commands, please refer to the section on <a href=
+  "http://svnbook.red-bean.com/en/1.8/svn.tour.history.html#svn.tour.history.log">
+  svn log</a> and <a href=
+  "http://svnbook.red-bean.com/en/1.8/svn.tour.cycle.html#svn.tour.cycle.update">
+  Update Your Working Copy</a> in the <a href=
+  "http://svnbook.red-bean.com/en/1.8/">Subversion book</a>.) You should now be
+  in a position to continue with your development work with FCM. However, if at
+  any time you are unsure about any aspect of using FCM, please consult the
+  relevant section of the <a href="index.html">FCM User Guide</a>.</p>
+
+  <p>Feel free to use the tutorial, at any time, for testing out any aspect of
+  the system. You may wish to do this rather than use your own repository and
+  ticket system, to avoid cluttering them with unwanted junk.</p>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/gui1.png b/doc/user_guide/gui1.png
new file mode 100644
index 0000000..b22f6af
Binary files /dev/null and b/doc/user_guide/gui1.png differ
diff --git a/doc/user_guide/gui2.png b/doc/user_guide/gui2.png
new file mode 100644
index 0000000..2248f33
Binary files /dev/null and b/doc/user_guide/gui2.png differ
diff --git a/doc/user_guide/index.html b/doc/user_guide/index.html
new file mode 100644
index 0000000..593f06e
--- /dev/null
+++ b/doc/user_guide/index.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <h1>FCM: User Guide</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <h2>Contents</h2>
+
+  <ul class="fcm-top-content unstyled">
+    <li><a href="introduction.html">Introduction</a></li>
+
+    <li><a href="overview.html">System Overview</a></li>
+
+    <li><a href="getting_started.html">Getting Started</a></li>
+
+    <li><a href="code_management.html">Code Management System</a></li>
+
+    <li><a href="working_practices.html">Code Management Working
+    Practices</a></li>
+
+    <li><a href="make.html">FCM Make</a></li>
+
+    <li><a href="system_admin.html">System Administration</a></li>
+
+    <li><a href="command_ref.html">FCM Command Reference</a></li>
+
+    <li><a href="api.html">A Brief Introduction to the FCM Perl API</a></li>
+  </ul>
+
+  <p>Annex:</p>
+
+  <ul class="fcm-top-content unstyled">
+    <li><a href="annex_quick_ref.html">Quick reference</a></li>
+
+    <li><a href="annex_quick_ref_tree_conflicts.html">Quick reference: Tree
+    Conflict Resolution</a></li>
+
+    <li><a href="annex_cfg.html">FCM Configuration File</a></li>
+
+    <li><a href="annex_fcm_cfg.html">Declarations in FCM 1 central/user
+    configuration file</a></li>
+
+    <li><a href="extract.html">The FCM 1 Extract System</a></li>
+
+    <li><a href="annex_ext_cfg.html">Declarations in FCM 1 extract
+    configuration file</a></li>
+
+    <li><a href="build.html">The FCM 1 Build System</a></li>
+
+    <li><a href="annex_bld_cfg.html">Declarations in FCM 1 build
+    configuration file</a></li>
+  </ul>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/introduction.html b/doc/user_guide/introduction.html
new file mode 100644
index 0000000..d5fad9f
--- /dev/null
+++ b/doc/user_guide/introduction.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: Introduction</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: Introduction</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>This is the User Guide for the <em>Flexible Configuration Management</em>
+  system which is known as <em>FCM</em>. It is designed to tell you everything
+  you need to know if you want to develop code which has been configured within
+  FCM. In addition it also provides the extra information you will need if you
+  are system manager of a project within FCM.</p>
+
+  <p>This guide consists of the following sections:</p>
+
+  <dl>
+    <dt><a href="overview.html">System Overview</a></dt>
+
+    <dd>A brief description of the main features of FCM.</dd>
+
+    <dt><a href="getting_started.html">Getting Started</a></dt>
+
+    <dd>How to start using FCM. It includes a tutorial for you to work through
+    and familiarise yourself with some FCM activities.</dd>
+
+    <dt><a href="code_management.html">Code Management System</a></dt>
+
+    <dd>How to use the code management system to manage code changes.</dd>
+
+    <dt><a href="working_practices.html">Code Management Working
+    Practices</a></dt>
+
+    <dd>Recommended ways of working with the code management system.</dd>
+
+    <dt><a href="make.html">FCM Make</a></dt>
+
+    <dd>How to use the make command to invoke the extract and build systems.</dd>
+
+    <dt><a href="system_admin.html">System Administration</a></dt>
+
+    <dd>How to configure and maintain a new system within FCM.</dd>
+
+    <dt><a href="command_ref.html">FCM Command Reference</a></dt>
+
+    <dd>Detailed information about each of the <code>fcm</code> commands.</dd>
+
+    <dt>Annex:</dt>
+
+    <dd>
+      <dl>
+        <dt><a href="annex_quick_ref.html">Quick reference</a></dt>
+
+        <dd>A quick reference to many useful FCM code management system
+        commands.</dd>
+
+        <dt><a href="annex_cfg.html">FCM Configuration File</a></dt>
+
+        <dd>Detailed definitions of what declarations are allowed in
+        different FCM configuration files.</dd>
+      </dl>
+
+      <p>The annex also contains further sections relating to some deprecated
+      features.</p>
+    </dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/make.html b/doc/user_guide/make.html
new file mode 100644
index 0000000..f4d3fee
--- /dev/null
+++ b/doc/user_guide/make.html
@@ -0,0 +1,2302 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: FCM Make</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: FCM Make</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <h2 id="introduction">Introduction</h2>
+
+  <p>The FCM make system provides a common environment for running the extract
+  system, build system, and other utilities. Simply put, it controls a chain of
+  steps. It is NOT to be confused with the standard Unix utility
+  <code>make</code>.</p>
+
+  <p>See <a href="annex_cfg.html#make">Annex: FCM Configuration File > FCM
+  Make Configuration</a> for a full list of declarations in an FCM Make
+  configuration file.</p>
+
+  <h2 id="concept">FCM Make: Concept</h2>
+
+  <p>The FCM make system can be invoked using the command line interface:</p>
+  <pre>
+fcm make [OPTIONS] [DECLARAION ...]
+</pre>
+
+  <p>The FCM make system relies on a configuration file to tell it what to do.
+  It looks for configurations with the following logic:</p>
+
+  <ol>
+    <li>It reads each configuration file specified using the
+    <code>--config-file=PATH</code> option in the order they are
+    specified.</li>
+    
+    <li>If the <code>--config-file=PATH</code> option is not specified, it will
+    attempt to read <code>fcm-make.cfg</code> if it exists.</li>
+
+    <li>It looks at the current working directory, or the directory specified
+    in the <code>--directory=PATH</code> option for configuration files
+    specified as relative paths.</li>
+
+    <li>If one or more <code>--config-file-path=PATH</code> option is
+    specified, it also searches for configuration files under the specified
+    values, in the order the options are specified.</li>
+
+    <li>Finally, each <var>KEY=VALUE</var> command line argument is considered
+    a configuration declaration line.</li>
+  </ol>
+
+  <p>For details of the other command line options please see <a href=
+  "command_ref.html#fcm-make">FCM Command Reference > fcm make</a>.</p>
+
+  <p>The FCM make configuration file is expected to be an FCM configuration
+  file. (See <a href="annex_cfg.html">Annex: FCM Configuration File</a>.) A
+  typical FCM make configuration file may look like:</p>
+  <pre>
+steps = extract build                           # 1
+
+extract.ns = egg ham bacon                      # 2
+# ... more extract configuration
+
+build.target = egg.exe ham.exe bacon            # 3
+# ... more build configuration
+</pre>
+
+  <p>At point 1, the <code><a href="annex_cfg.html#make.steps">steps</a></code>
+  declaration tells the system to invoke two steps. Their IDs are
+  <samp>extract</samp> and <samp>build</samp>, both are IDs for built-in
+  systems. (The other 2 being <samp>mirror</samp> and <samp>preprocess</samp>.)
+  Each step has its own declarations, which are prefixed with the step ID and a
+  full stop. The declarations at point 2 are used by the logic for running the
+  <samp>extract</samp> step and the declarations at point 3 are used by the
+  logic for running the <samp>build</samp> step.</p>
+
+  <p>There are times when you may need to invoke the same system (e.g. the
+  build system) in slightly different configurations. To do this you need to
+  use the <code><a href="annex_cfg.html#make.step.class">step.class</a></code>
+  declaration to define custom step IDs for each build. E.g.:</p>
+  <pre>
+step.class[build-this build-that] = build       # 1
+steps = extract build-this build-that           # 2
+
+extract.ns = egg ham bacon                      # 3
+# ... more extract configuration
+
+build-this.prop{fc.defs} = DEFS TO BUILD THIS   # 4
+build-this.target = this.exe
+# ... more build configuration for "build-this"
+
+build-that.prop{fc.defs} = DEFS TO BUILD THAT   # 5
+build-that.target = that.exe
+# ... more build configuration for "build-that"
+</pre>
+
+  <p>At point 1, we define the IDs <samp>build-this</samp> and
+  <samp>build-that</samp> to be an instance of the <samp>build</samp> class. At
+  point 2, we tell the system to run <samp>extract</samp>, then
+  <samp>build-this</samp>, then <samp>build-that</samp>. At point 4 and 5, we
+  define the configurations for <samp>build-this</samp> and
+  <samp>build-that</samp> respectively.</p>
+
+  <dl>
+    <dt>Directory Structure</dt>
+
+    <dd>
+      <p>When you run <code>fcm make</code>, it will create a directory
+      structure that may look like:</p>
+      <pre>
+.fcm-make/...
+extract/...
+build/...
+...
+</pre>
+
+      <p>Each normal sub-directory, such as <samp>extract/</samp> and
+      <samp>build/</samp>, contains the result of each step. The hidden
+      sub-directory <samp>.fcm-make/</samp> is used by <code>fcm make</code> as
+      a working area. It may contain the following items:</p>
+
+      <p><samp>.fcm-make/cache/</samp> An area for caching non-local items.
+      E.g. the extract system exports source trees from the version control
+      system into this cache.</p>
+
+      <p><samp>fcm-make-as-parsed.cfg ->
+      .fcm-make/config-as-parsed.cfg</samp> The configuration file as
+      parsed by the latest <code>fcm make</code> command at this
+      destination.</p>
+
+      <p><samp>fcm-make-on-success.cfg ->
+      .fcm-make/config-on-success.cfg</samp> The configuration file
+      that can be used to repeat the latest successful <code>fcm make</code>
+      command at this destination.</p>
+
+      <p><samp>.fcm-make/ctx.gz</samp> A serialised data structure that
+      represents the context of the latest <code>fcm make</code> command at
+      this destination. It is a <code>gzip</code> file containing data in the
+      <a href="http://perldoc.perl.org/Storable.html">Perl Storable</a>
+      format.</p>
+
+      <p><samp>fcm-make.log -> .fcm-make/log</samp>
+      A diagnostic log generated by the latest
+      <code>fcm make</code> command at this destination. The content should be
+      equivalent to the diagnostic output in STDOUT and STDERR at
+      <code>-vv</code> verbosity.</p>
+    </dd>
+  </dl>
+
+  <h2 id="extract">Extract</h2>
+
+  <p>The extract system provides an interface between the version control
+  system (currently Subversion) and the build system. Where appropriate, it
+  extracts code and combines changes from the repositories and other
+  user-defined locations to a directory tree suitable for feeding into the build
+  system.</p>
+
+  <p>The extract system supports the <code>--jobs=N</code> option of the
+  <code>fcm make</code> command. It uses <var>N</var> child processes to get
+  source trees information and to export source trees from their repositories in
+  parallel.</p>
+
+  <h3 id="extract.basic">Extract: Basic</h3>
+
+  <p>The following is an example of how to extract the source trees from a file
+  system path and the trunks of 2 known projects (version controlled in
+  Subversion repositories with a standard FCM layout):</p>
+  <pre>
+steps = extract                    # line 1
+extract.ns = ops var um            # line 2
+extract.location[ops] = $HOME/ops  # line 3
+</pre>
+
+  <p>Here is an explanation of what each line does:</p>
+
+  <ul>
+    <li><dfn>line 1</dfn>: the <code><a href=
+    "annex_cfg.html#make.steps">steps</a></code> declaration tells the make
+    system to invoke the extract system.</li>
+
+    <li><dfn>line 2</dfn>: the <code><a href=
+    "annex_cfg.html#make.extract.ns">extract.ns</a></code> declaration is used
+    to specify a space delimited list of names of the projects to extract.</li>
+
+    <li><dfn>line 3</dfn>: the <code><a href=
+    "annex_cfg.html#make.extract.location">extract.location</a></code>
+    declaration is used to specify the base source tree of a project
+    (<samp>ops</samp> in this case) to extract. The value of the declaration
+    can be a path in the file system or a location in a version control system.
+    In this case, the declaration specifies the base source tree of the
+    <samp>ops</samp> project to be a path in the file system at
+    <samp>$HOME/ops</samp>. If the base source tree of a specified project
+    name-space is not declared, the system will make an assumption. For a
+    project hosted in a Subversion repository, the system will assume
+    <samp>trunk at HEAD</samp> if the project URL is registered in the keyword
+    configuration file or with the <code><a href=
+    "annex_cfg.html#make.extract.location.primary">extract.location{primary}</a></code>
+    declaration (see below).</li>
+  </ul>
+
+  <p>If you save this file as <samp>fcm-make.cfg</samp> and invoke the
+  <code>fcm make</code> command you should end up with a source tree in the
+  current working directory that looks like (hidden path not shown):</p>
+  <pre>
+extract/ops/...
+extract/um/...
+extract/var/...
+</pre>
+
+  <p>The result of the extract can be found in the <samp>extract/</samp>
+  sub-directory. Note: The generated source tree will not contain any symbolic
+  links or hidden files (e.g. file names beginning with a <samp>.</samp>),
+  because the extract system ignores them.</p>
+
+  <div class="well">
+    <p><strong><i class="icon-pencil"></i> Note - project name-space and
+    location</strong></p>
+
+    <p>Whoever installed FCM at your site would have defined the name-spaces
+    and the locations of the common projects at your site using the keyword
+    configuration file at <samp>$FCM/etc/fcm-keyword.cfg</samp> (where
+    <samp>$FCM/bin</samp> is the path at which FCM is installed).
+    Alternatively, users can define their own set of project name-spaces and
+    their locations using <samp>$HOME/.metomi/fcm/keyword.cfg</samp>. For
+    information on keyword configuration files, please refer to <a href=
+    "system_admin.html#fcm-keywords">System Administration > FCM
+    keywords</a>. If a project name-space is not defined in a keyword
+    configuration, it can be defined in the FCM make configuration using the
+    <code><a href=
+    "annex_cfg.html#make.extract.location.primary">extract.location{primary}</a></code>
+    declaration. E.g. to define the location of the <samp>foo</samp> project,
+    you can do: <samp>extract.location{primary}[foo] =
+    svn://server/repos/foo</samp>.</p>
+  </div>
+
+  <div class="well">
+    <p><strong><i class="icon-pencil"></i> Note - incremental mode</strong></p>
+
+    <p>Suppose you have already invoked <code>fcm make</code> using the above
+    configuration file. At a later time, you have made some changes to some of
+    the files in the source trees. Re-running <code>fcm make</code> on the same
+    configuration will trigger the incremental mode. In the incremental mode,
+    the extract system will update only those files that are modified. If the
+    last modified time (or last commit revision) of a source file in the
+    current extract differs from that in the previous extract, the system will
+    attempt a content comparison. The system updates the destination only if
+    the content and/or file access permission of the source differs from that
+    of the destination. To avoid the incremental mode and start afresh, invoke
+    <code>fcm make</code> with the <code>--new</code> option.</p>
+  </div>
+
+  <h3 id="extract.location-types">Extract: Location Types</h3>
+
+  <p>The extract system currently supports the following location types:</p>
+
+  <dl class="dl-horizontal">
+    <dt>fs</dt>
+
+    <dd>A readable location in the local file system. E.g.
+    <code>~/my-project</code>, <code>/home/lily/my-project</code>, etc.</dd>
+
+    <dt>ssh</dt>
+
+    <dd>A readable location in the file system of a remote host accessible via
+    <code>ssh</code> and <code>rsync</code>, specified in the form
+    <var>[USER@]HOST:PATH</var>. The <code>ssh</code> access must be
+    without a password, and you must be able to run the <a href=
+    "http://www.gnu.org/software/coreutils/">GNU coreutils</a> version of the
+    <code>find</code> and <code>stat</code> on the remote host. E.g.
+    <code>mylinuxbox:my-project</code>,
+    <code>holly at hpc:/data/holly/my-project</code>.</dd>
+
+    <dt>svn</dt>
+
+    <dd>A Subversion location, which may be a working copy or a valid Subversion
+    URL. Supported URL schemes are: <code>file</code>, <code>http</code>,
+    <code>https</code>, <code>svn</code> and <code>svn+ssh</code>. E.g.
+    <code>svn://mysvnhost/my-project/trunk@40778</code>,
+    <code>~/my-project at 32734</code>.</dd>
+  </dl>
+
+  <p>See also <a href=
+  "annex_cfg.html#make.extract.location">extract.location</a>.</p>
+
+  <h3 id="extract.path-root">Extract: Redefine the Root of the Source Tree of a
+  Project</h3>
+
+  <p>Consider a project called <samp>foo</samp> with a source tree that looks
+  like:</p>
+  <pre>
+doc/...
+src/bar/...
+src/baz/...
+</pre>
+
+  <p>Suppose you are only interested in the contents of the <samp>src/</samp>
+  sub-tree. You can specify the root of the extract using the <code><a href=
+  "annex_cfg.html#make.extract.path-root">extract.path-root</a></code>
+  declaration. E.g.:</p>
+  <pre>
+steps = extract              # line 1
+extract.ns = foo             # line 2
+extract.path-root[foo] = src # line 3
+</pre>
+
+  <p>Running <code>fcm make</code> with this configuration should give a source
+  tree that looks like (hidden path not shown):</p>
+  <pre>
+extract/foo/bar/...
+extract/foo/baz/...
+</pre>
+
+  <h3 id="extract.path-filter">Extract: Filter the Paths in the Source Tree of
+  a Project</h3>
+
+  <p>Going back to the above source tree in the <samp>foo</samp> project,
+  imagine the <samp>src/bar/</samp> sub-directory contains:</p>
+  <pre>
+src/bar/wine/red.c
+src/bar/wine/rose.c
+src/bar/wine/white.c
+src/bar/...
+</pre>
+
+  <p>If you do not want <samp>src/bar/wine/rose.c</samp> in the extract, you
+  can ask for it to be excluded using the <code><a href=
+  "annex_cfg.html#make.extract.path-excl">extract.path-excl</a></code>
+  declaration. E.g.:</p>
+  <pre>
+steps = extract                          # line 1
+extract.ns = foo
+extract.path-root[foo] = src             # line 3
+extract.path-excl[foo] = bar/wine/rose.c # line 4
+</pre>
+
+  <p>Note: Because the root is redefined in line 3, the path in the
+  <code><a href=
+  "annex_cfg.html#make.extract.path-excl">extract.path-excl</a></code>
+  declaration in line 4 is declared from the new root level.</p>
+
+  <p>Running <code>fcm make</code> with this configuration should give a source
+  tree that looks like (hidden path not shown):</p>
+  <pre>
+extract/foo/bar/wine/red.c
+extract/foo/bar/wine/white.c
+extract/foo/bar/...
+extract/foo/baz/...
+</pre>
+
+  <p>On the other hand, if you only want <samp>src/bar/wine/rose.c</samp> in
+  the extract, you can ask the system to exclude everything in
+  <samp>src/bar/wine/</samp> but include <samp>src/bar/wine/rose.c</samp> using
+  the <code><a href=
+  "annex_cfg.html#make.extract.path-incl">extract.path-incl</a></code>
+  declaration. E.g.:</p>
+  <pre>
+steps = extract                          # line 1
+extract.ns = foo                         # line 2
+extract.path-root[foo] = src             # line 3
+extract.path-excl[foo] = bar/wine        # line 4
+extract.path-incl[foo] = bar/wine/rose.c # line 5
+</pre>
+
+  <p>Running <code>fcm make</code> with this configuration should give a source
+  tree that looks like (hidden path not shown):</p>
+  <pre>
+extract/foo/bar/wine/rose.c
+extract/foo/bar/...
+extract/foo/baz/...
+</pre>
+
+  <h3 id="extract.location-diff">Extract: Combining Changes from Multiple
+  Branches of a Project</h3>
+
+  <p>We have so far only dealt with extracts from a single base source tree in
+  each project. The extract system can also be used to <em>combine</em> changes
+  from different source trees (against a base source tree) of a project.</p>
+
+  <p>E.g. consider a project called <samp>food</samp>. In the latest release
+  (<samp>trunk at 3739</samp>), the source tree looks like this:</p>
+  <pre>
+doc/...
+src/egg/boiled.y
+src/egg/microwave.x
+src/egg/omelette.c
+src/egg/poached.f90
+src/...
+</pre>
+
+  <p>Jamie, Gordon and Rick are all developing changes against the latest
+  release of the project.</p>
+
+  <p>Jamie has made the following changes (displayed using the notation of
+  <code>svn status</code>) in his branch at
+  <samp>branches/dev/jamie/r3739_t381 at 3984</samp>:</p>
+  <pre>
+D      src/egg/boiled.y
+A      src/egg/fried.pl
+M      src/egg/omelette.c
+</pre>
+
+  <p>Gordon has made the following changes in his branch at
+  <samp>branches/dev/gordon/r3739_t376 at 3993</samp>:</p>
+  <pre>
+M      src/egg/omelette.c
+M      src/egg/poached.f90
+</pre>
+
+  <p>Rick has made the following changes in his working copy at
+  <samp>~rick/food</samp>, but has yet to commit his changes back:</p>
+  <pre>
+M      src/egg/boiled.y
+A      src/egg/scrambled.bash
+</pre>
+
+  <p>To combine their changes in an extract, the FCM make configuration file
+  should look like:</p>
+  <pre id="example.extract">
+steps = extract
+extract.ns = food
+extract.location[food] = trunk at 3739
+extract.location{diff}[food] = \
+    branches/dev/jamie/r3739_t381 at 3984 \
+    branches/dev/gordon/r3739_t376 at 3993 \
+    ~rick/food
+</pre>
+
+  <p>Here we have an <code><a href=
+  "annex_cfg.html#make.extract.location">extract.location</a></code>
+  declaration like before. This time it is pointing to the latest release of
+  the <samp>food</samp> project. The changes against the base source tree are
+  declared using the <code><a href=
+  "annex_cfg.html#make.extract.location.diff">extract.location{diff}</a></code>
+  declaration.</p>
+
+  <p>Invoking <code>fcm make</code> with this configuration file will result in
+  a source tree that looks like (hidden file not shown):</p>
+  <pre>
+extract/doc/...
+extract/src/egg/fried.pl
+extract/src/egg/microwave.x
+extract/src/egg/omelette.c
+extract/src/egg/poached.f90
+extract/src/egg/scrambled.bash
+extract/src/...
+</pre>
+
+  <p>Note:</p>
+
+  <ul>
+    <li>There is no <samp>extract/src/egg/boiled.y</samp> file in the extract
+    tree because it is deleted by Jamie's branch. Even though this file is
+    modified in Rick's working copy, the extract will ignore this and assume
+    that the deletion takes precedence.</li>
+
+    <li>The <samp>extract/src/egg/fried.pl</samp> file comes from Jamie's
+    branch.</li>
+
+    <li>The <samp>extract/src/egg/microwave.x</samp> file comes from the latest
+    release (base).</li>
+
+    <li>The <samp>extract/src/egg/omelette.c</samp> file is the result of a
+    merge of the changes in Jamie's and Gordon's branches. This example assumes
+    that there is no conflict in the merge. If the merge results in a conflict,
+    the extract will fail.</li>
+
+    <li>The <samp>extract/src/egg/poached.f90</samp> file comes from Gordon's
+    branch.</li>
+
+    <li>The <samp>extract/src/egg/scrambled.bash</samp> file comes from Rick's
+    working copy.</li>
+  </ul>
+
+  <h3 id="extract.inherit">Extract Inheritance</h3>
+
+  <p>If a previous extract with a similar configuration exists in another
+  location, it can be more efficient to inherit from this previous extract in
+  your current extract. This works like a normal incremental extract, except
+  that your extract will only contain the changes you have specified (compared
+  with the inherited extract) instead of the full directory tree in the
+  destination. This type of incremental extract is useful in several ways. For
+  instance:</p>
+
+  <ul>
+    <li>It is fast, because you only have to extract files that you have
+    changed.</li>
+
+    <li>The subsequent build will also be fast, since it will act like an
+    incremental build.</li>
+
+    <li>You do not need write access to the original extract. A system
+    administrator can set up a stable version in a central account, which
+    developers can then inherit from.</li>
+
+    <li>You want an incremental extract, but you need to leave the original
+    extract unmodified.</li>
+  </ul>
+
+  <p>Consider the <a href="#example.extract">previous example</a>. Imagine an
+  extract already exists for the latest release for the <samp>food</samp>
+  project at <samp>/home/food/latest/</samp>, and now you want to test the
+  changes introduced by Jamie, Gordon and Rick. You can <code><a href=
+  "annex_cfg.html#make.use">use</a></code> the original extract with the
+  changes:</p>
+  <pre>
+use = /home/food/latest
+extract.location{diff}[food] = \
+    branches/dev/jamie/r3739_t381 at 3984 \
+    branches/dev/gordon/r3739_t376 at 3993 \
+    ~rick/food
+</pre>
+
+  <p>Invoking <code>fcm make</code> with this configuration file will result in
+  a source tree that looks like (hidden file not shown):</p>
+  <pre>
+extract/src/egg/fried.pl
+extract/src/egg/omelette.c
+extract/src/egg/poached.f90
+extract/src/egg/scrambled.bash
+</pre>
+
+  <p>The extract will work in an incremental-like mode. The only difference is
+  that the original extract at <samp>/home/food/latest/</samp> will be left
+  untouched, and the new extract will contain only the changes introduced by
+  the diff locations. Note that, although
+  <samp>extract/src/egg/boiled.y</samp> remains in the original extract, it
+  will not be used in any subsequent build step.</p>
+
+  <dl>
+    <dt>Extract inheritance limitation</dt>
+
+    <dd>
+      <p>Extract inheritance allows you to add more diff locations to a project,
+      but you should not include any other declarations relating to the extract.
+      Doing so is not safe and should trigger an exception.</p>
+
+      <p>In some situations this implies that it will not be possible to use
+      inherited extracts. You should use a new extract if, for instance, a new
+      diff location contains a change which requires the use of source files in
+      a previously excluded name-space.</p>
+    </dd>
+  </dl>
+
+  <h3 id="extract.diagnostic">Extract Diagnostic</h3>
+
+  <p>The amount of diagnostic messages generated by the extract system is
+  dependent on the diagnostic verbosity level that can be modified by the
+  <code>-v</code> and <code>-q</code> options to the <code>fcm make</code>
+  command.</p>
+
+  <p>The following is a list of diagnostic output at each verbosity level:</p>
+
+  <dl>
+    <dt>-q</dt>
+
+    <dd>
+      <ul>
+        <li>Exceptions.</li>
+      </ul>
+    </dd>
+
+    <dt>default</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at the -q level.</li>
+
+        <li>Start time of the extract.</li>
+
+        <li>The number and location of each source tree in each project. The
+        base source tree is number 0. E.g.:
+          <pre>
+[info] location um: 0: svn://fcm2/UM_svn/UM/trunk@11732
+[info] location um: 1: svn://fcm2/UM_svn/UM/branches/dev/Share/VN7.3_hg3_dust_443@11858
+[info] location um: 2: svn://fcm2/UM_svn/UM/branches/dev/Share/VN7.3_hg3_ccw_precip@11857
+[info] location um: 3: svn://fcm2/UM_svn/UM/branches/dev/hadco/VN7.3_HG3_porting_lsp_fixes@12029
+...
+</pre>
+        </li>
+
+        <li>The number of targets by their destination status and their source
+        status. E.g.:
+          <pre>
+[info]   dest:    3 [A added]
+[info]   dest:  134 [a added, overriding inherited]
+[info]   dest:    6 [d deleted, overriding inherited]
+[info]   dest: 2818 [U unchanged]
+[info] source:    3 [A added by a diff source tree]
+[info] source:    6 [D deleted by a diff source tree]
+[info] source:   16 [G merged from 2+ diff source trees]
+[info] source:  118 [M modified by a diff source tree]
+[info] source: 2818 [U from base]
+</pre>
+        </li>
+
+        <li>Total time.</li>
+      </ul>
+    </dd>
+
+    <dt>-v</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at the default level.</li>
+
+        <li>The destination and source status for each modified target. E.g.:
+          <pre>
+...
+[info] aM um:0,13    atmosphere/short_wave_radiation/r2_lwrad3c.F90
+[info] aG um:0,6,13  control/top_level/scm_main.F90
+[info] AA um:-,8     include/constant/cnv_parc_lim.h
+...
+</pre>
+
+          <p>The 2 letters following <samp>[info]</samp> is the destination
+          status and the source status of the target. This is followed by the
+          name-space of the project, a colon and the source tree number(s)
+          providing the source for the target (in a comma separated list). This
+          is then followed by the name-space of the target path. The source
+          tree number <samp>0</samp> denotes the base source tree, a dash
+          <samp>-</samp> in place of a <samp>0</samp> means that the source
+          only exists in a diff source tree.</p>
+        </li>
+      </ul>
+    </dd>
+
+    <dt>-vv</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at the -v level.</li>
+
+        <li>Each shell command invoked with elapsed time and return code.</li>
+      </ul>
+    </dd>
+  </dl>
+
+  <p>Here is an explanation of each target destination status:</p>
+
+  <dl>
+    <dt><samp>[A added]</samp></dt>
+
+    <dd>Target newly added to the destination.</dd>
+
+    <dt><samp>[a added, overriding inherited]</samp></dt>
+
+    <dd>Target added to the current extract destination, i.e. modified compared
+    with the target with the same name-space in an inherited extract
+    destination.</dd>
+
+    <dt><samp>[D deleted]</samp></dt>
+
+    <dd>Target deleted from the extract destination during an incremental
+    extract.</dd>
+
+    <dt><samp>[d deleted, overriding inherited]</samp></dt>
+
+    <dd>Target deleted from the current extract destination, i.e. a target with
+    the same name-space exists in an inherited extract destination.</dd>
+
+    <dt><samp>[M modified]</samp></dt>
+
+    <dd>Target modified in the extract destination during an incremental
+    extract.</dd>
+
+    <dt><samp>[U unchanged]</samp></dt>
+
+    <dd>Target unchanged compared with the previous (or any inherited)
+    extract.</dd>
+
+    <dt><samp>[? unknown]</samp></dt>
+
+    <dd>Target does not have a destination. This destination status is normally
+    associated with the <samp>[D deleted]</samp> source status.</dd>
+  </dl>
+
+  <p>Here is an explanation of each target source status:</p>
+
+  <dl>
+    <dt><samp>[A added by a diff source tree]</samp></dt>
+
+    <dd>The source of the target comes from a diff source tree, and the base
+    source tree does not have a source in the same name-space.</dd>
+
+    <dt><samp>[D deleted by a diff source tree]</samp></dt>
+
+    <dd>Target is deleted by a diff source tree, (i.e. exists in base source
+    tree, but missing from a diff source tree).</dd>
+
+    <dt><samp>[G merged from 2+ diff source trees]</samp></dt>
+
+    <dd>The source of the target comes from 2 or more diff source trees, (i.e.
+    the actual source is the result of a merge between all the changes).</dd>
+
+    <dt><samp>[M modified by a diff source tree]</samp></dt>
+
+    <dd>The source of the target comes from a diff source tree.</dd>
+
+    <dt><samp>[U from base]</samp></dt>
+
+    <dd>The source of the target comes from the base source tree, (i.e. the
+    source is unchanged by any diff source tree).</dd>
+
+    <dt><samp>[? unknown]</samp></dt>
+
+    <dd>The target has no source. This source status is normally displayed in an
+    incremental extract, where a target in a previous extract is not a target in
+    the current extract, and is normally associated with the <samp>[D
+    deleted]</samp> destination status.</dd>
+  </dl>
+
+  <h2 id="mirror">Mirror</h2>
+
+  <p>The mirror system provides a way to mirror the results of the make steps
+  to another location, where the FCM make may need to continue. It is typically
+  used after an extract to set up the build on an alternate platform.</p>
+
+  <h3 id="mirror.basic">Mirror: Basic</h3>
+
+  <p>Consider the following example:</p>
+  <pre>
+steps = extract mirror                            # 1
+# ... some extract declarations
+mirror.target = user at somewhere:/path/in/somewhere # 2
+mirror.prop{config-file.steps} = preprocess build # 3
+# ... some preprocess declarations
+# ... some build declarations
+</pre>
+
+  <p>When the system runs with this configuration, the system will mirror the
+  result of the extract to the <a href=
+  "annex_cfg.html#make.mirror.target">mirror.target</a>, i.e.
+  <samp>user at somewhere:/path/in/somewhere</samp> using <code>ssh</code> and
+  <code>rsync</code>. With the <a href=
+  "annex_cfg.html#make.mirror.prop.config-file.steps">config-file.steps</a>
+  property set at point 3, it will write a <samp>fcm-make.cfg</samp> in the
+  mirror target so that the <samp>preprocess</samp> and <samp>build</samp>
+  steps can continue there.</p>
+
+  <h3 id="mirror.diagnostic">Mirror Diagnostic</h3>
+
+  <p>The amount of diagnostic messages generated by the mirror system is
+  dependent on the diagnostic verbosity level that can be modified by the
+  <code>-v</code> and <code>-q</code> options to the <code>fcm make</code>
+  command.</p>
+
+  <p>The following is a list of diagnostic output at each verbosity level:</p>
+
+  <dl>
+    <dt>-q</dt>
+
+    <dd>
+      <ul>
+        <li>Exceptions.</li>
+      </ul>
+    </dd>
+
+    <dt>default</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at the -q level.</li>
+
+        <li>Start time of the mirror.</li>
+
+        <li>The updated destination and its source.</li>
+
+        <li>Total time.</li>
+      </ul>
+    </dd>
+
+    <dt>-v</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at the default level.</li>
+      </ul>
+    </dd>
+
+    <dt>-vv</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at the -v level.</li>
+
+        <li>Each shell command invoked with elapsed time and return code.</li>
+      </ul>
+    </dd>
+  </dl>
+
+  <h2 id="build">Build</h2>
+
+  <p>The build system performs actions on a set of source files using its
+  predefined logic and the properties specified in the configuration. For
+  example, it will attempt to create a binary executable for a source file
+  containing a Fortran program.</p>
+
+  <p>The build system supports the <code>--jobs=N</code> option of the <code>fcm
+  make</code> command. It uses <var>N</var> child processes to analyse the
+  source files and to update the targets in parallel.</p>
+
+  <h3 id="build.basic">Build: Basic</h3>
+
+  <p>Consider a source tree at <samp>$HOME/my-source-tree/</samp> containing
+  some Fortran source files including at least one with a main program (and
+  maybe other supported types of source files), you can set up an FCM make
+  configuration file to build an executable. E.g.:</p>
+  <pre>
+steps = build
+build.target{task} = link
+build.source = $HOME/my-source-tree
+</pre>
+
+  <p>In this simple 3 line configuration, the <code><a href=
+  "annex_cfg.html#make.steps">steps</a></code> declaration tells the make
+  system to invoke the build system, the <code><a href=
+  "annex_cfg.html#make.build.target">build.target</a></code> declaration tells
+  the system to build any targets which require linking (i.e. any main
+  programs), and the <code><a href=
+  "annex_cfg.html#make.build.source">build.source</a></code> declaration
+  specifies the location of the source tree.</p>
+
+  <p>If you save this file as <samp>fcm-make.cfg</samp> and invoke the
+  <code>fcm make</code> command it should attempt to build the source tree in
+  the current working directory, using the default properties. If the default
+  Fortran compiler <samp>gfortran</samp> is installed, and nothing goes wrong,
+  you will end up with a directory tree that looks like (hidden path not
+  shown):</p>
+  <pre>
+build/bin/...
+build/include/...
+build/o/...
+</pre>
+
+  <p>The result of the build can be found in the sub-directories of the
+  <samp>build/</samp> sub-directory. Each <samp>build/*/</samp> sub-directory
+  contains a category of targets:</p>
+
+  <dl>
+    <dt id="build.category.bin"><samp>bin</samp></dt>
+
+    <dd>e.g. executable binary and script.</dd>
+
+    <dt id="build.category.etc"><samp>etc</samp></dt>
+
+    <dd>e.g. data files.</dd>
+
+    <dt id="build.category.include"><samp>include</samp></dt>
+
+    <dd>e.g. include files and Fortran module definition files.</dd>
+
+    <dt id="build.category.lib"><samp>lib</samp></dt>
+
+    <dd>e.g. object archives.</dd>
+
+    <dt id="build.category.o"><samp>o</samp></dt>
+
+    <dd>e.g. object files</dd>
+  </dl>
+
+  <p>Sub-directories are only created as necessary, so you may not find all of
+  the above in your destination tree.</p>
+
+  <p>To use a different compiler and/or compiler options for Fortran/C/C++, you
+  use the <a href="annex_cfg.html#make.build.prop">build.prop</a> declaration to
+  redefine the build properties. E.g.:</p>
+  <pre>
+steps = build
+build.target{task} = link
+build.source = $HOME/my-source-tree
+
+# Set Fortran compiler/linker
+build.prop{fc} = ifort
+# Set Fortran compiler options
+build.prop{fc.flags} = -i8 -r8 -O3
+# Add include paths to Fortran compiler
+build.prop{fc.include-paths} = /a/path/to/include /more/path/to/include
+# Set link libraries for Fortran executables
+build.prop{fc.lib-paths} = /path/to/my-lib
+build.prop{fc.libs} = mine
+# Set C compiler/linker
+build.prop{cc} = icc
+# Set C compiler options
+build.prop{cc.flags} = -O3
+# Set C++ compiler options
+build.prop{cxx.flags} = -O2
+# Set link libraries for C executables
+build.prop{cc.lib-paths} = /path/to/my-lib /path/to/your-lib
+build.prop{cc.libs} = mine yours
+# Set linker, if compiler cannot be used as linker
+#build.prop{ld} = ld
+</pre>
+
+  <h3 id="build.source-locations">Build Source Locations</h3>
+
+  <p>The build system locates its source files from various places,
+  including:</p>
+
+  <ul>
+    <li>Inherited locations. (See <a href="#build.inherit">Build
+    Inheritance</a>.)</li>
+
+    <li>Usable target locations of previous steps in the make. E.g. targets of
+    an extract step, and <samp><a href=
+    "#preprocess.category.src">src</a></samp> category targets of a preprocess
+    step can both be source files of the build system. The <code><a href=
+    "annex_cfg.html#make.build.prop.no-step-source">build.prop{no-step-source}</a></code>
+    declaration can be used to switch off this behaviour.</li>
+
+    <li>Locations specified by the <code><a href=
+    "annex_cfg.html#make.build.source">build.source</a></code> declaration.
+    Note: If you assign a relative path to the <code><a href=
+    "annex_cfg.html#make.build.source">build.source</a></code> declaration, the
+    system will assume the path to be relative to the make destination (not the
+    current working directory, unless they happen to be the same).</li>
+  </ul>
+
+  <p>There are situations when it may not be desirable to consider every source
+  file in a build. You can apply filters by source file name-spaces using the
+  <code><a href="annex_cfg.html#make.build.ns-excl">build.ns-excl</a></code>
+  and <code><a href=
+  "annex_cfg.html#make.build.ns-incl">build.ns-incl</a></code> declarations.
+  E.g.:</p>
+  <pre>
+# To include items in "foo" and "bar/baz" only
+build.ns-excl = /              # exclude everything ...
+build.ns-incl = foo bar/baz    # but include items from these name-spaces
+</pre>
+
+  <h3 id="build.source-ns">Build Source Name-spaces and Properties</h3>
+
+  <p>Each source file is assigned a name-space (which is used to fine tune the
+  build properties, such as the compiler flags). If the source file is a target
+  of an extract, the name-space will be the same as the extract target (the
+  relative path of the <samp>extract/</samp> sub-directory in the make
+  destination). If the source file is a file in a source tree specified by
+  <code><a href="annex_cfg.html#make.build.source">build.source</a></code>, the
+  name-space is the relative path to the specified value. The <code><a href=
+  "annex_cfg.html#make.build.source">build.source</a></code> declaration also
+  accepts an optional name-space, in which case the name-space of each source
+  file in the tree will be prefixed with the specified name-space. Suppose you
+  have a source tree in <samp>$HOME/food</samp>:</p>
+  <pre>
+$HOME/food/egg.c
+$HOME/food/ham.f90
+</pre>
+
+  <p>If you specify the source tree with <samp>build.source =
+  $HOME/food</samp>, then <samp>$HOME/food/egg.c</samp> will be given the
+  name-space <samp>egg.c</samp> and <samp>$HOME/food/ham.f90</samp> will be
+  given the name-space <samp>ham.f90</samp>.</p>
+
+  <p>On the other hand, if you specify the source tree with
+  <samp>build.source[food] = $HOME</samp>, then
+  <samp>$HOME/food/egg.c</samp> will be given the name-space
+  <samp>food/egg.c</samp> and <samp>$HOME/food/ham.f90</samp> will be given the
+  name-space <samp>food/ham.f90</samp>.</p>
+
+  <p>The name-space is organised in a simple hierarchy. For instance, the
+  <samp>foo/bar/egg</samp> name-space belongs to <samp>foo/bar</samp>, which
+  belongs to <samp>foo</samp>, which belongs to the root name-space. (The root
+  name-space is either an empty string or a <samp>/</samp>.)</p>
+
+  <p>For instance, you can set the flags of the Fortran compiler using the
+  <code><a href=
+  "annex_cfg.html#make.build.prop.fc.flags">build.prop{fc.flags}</a></code>
+  declaration at different name-space levels:</p>
+  <pre>
+# The global Fortran compiler flags
+build.prop{fc.flags} = -O3
+
+# The Fortran compiler flags for the "food" name-space
+build.prop{fc.flags}[food] = -O2 -i8 -r8
+
+# The Fortran compiler flags for a source file
+build.prop{fc.flags}[food/bacon.f90] = -O0 -g -C
+</pre>
+
+  <h3 id="build.source-types">Build Source Types</h3>
+
+  <p>Before the build system can do anything with its source files, it needs to
+  know what they are. It determines the type of each source file by looking at
+  its file name extension, then the file name itself, and then the
+  <code>#!</code> line for a text file. Source files without a type are treated
+  as data files.</p>
+
+  <p>The following types are associated with file extensions:</p>
+
+  <dl>
+    <dt><a href="annex_cfg.html#make.build.prop.file-ext.c">c</a> (C source
+    file)</dt>
+
+    <dd>.c .i .m .mi</dd>
+
+    <dt><a href="annex_cfg.html#make.build.prop.file-ext.cxx">cxx</a> (C++
+    source file)</dt>
+
+    <dd>.cc .cp .cxx .cpp .CPP .c++ .C .mm .M .mii</dd>
+
+    <dt><a href="annex_cfg.html#make.build.prop.file-ext.fortran">fortran</a>
+    (Fortran source file)</dt>
+
+    <dd>.F .FOR .FTN .F90 .F95 .f .for .ftn .f90 .f95 .inc</dd>
+
+    <dt><a href="annex_cfg.html#make.build.prop.file-ext.h">h</a> (Preprocessor
+    header file)</dt>
+
+    <dd>.h</dd>
+
+    <dt><a href="annex_cfg.html#make.build.prop.file-ext.script">script</a>
+    (script in various languages)</dt>
+
+    <dd>(empty)</dd>
+  </dl>
+
+  <p>The <code>prop{file-ext.type} = extensions</code> declaration can be used
+  to modify the extensions associated with a type. E.g. if you need to add
+  <samp>.fort</samp> as a file extension for a Fortran source file, you can
+  do:</p>
+  <pre>
+build.prop{file-ext.fortran} = .F .FOR .FORT .FTN .F90 .F95 \
+                               .f .for .fort .ftn .f90 .f95 .inc
+</pre>
+
+  <p>You can associate file names to some file types using a
+  <code>prop{file-pat.type} = regular-expression</code> declaration. E.g. if
+  you have executable scripts in the source tree with no <code>#!</code> lines
+  but are recognised by a <samp>*Scr_*</samp> pattern of their file names, you
+  can specify a regular expression to match their file names using the <a href=
+  "annex_cfg.html#make.build.prop.file-pat.script">file-pat.script</a>
+  property:</p>
+  <pre>
+build.prop{file-pat.script} = (?msx-i:\w+Scr_\w+)
+</pre>
+
+  <p>All other text files with a <code>#!</code> line are recognised as scripts
+  by the build system.</p>
+
+  <h3 id="build.source-analysis">Build Source Analysis</h3>
+
+  <p>Each source file with a known type (that is not ignored) is analysed by
+  the build system for dependencies and other information. Here is a list of
+  what the system looks for in each type of file:</p>
+
+  <dl>
+    <dt>c and cxx</dt>
+
+    <dd>
+      <p><dfn>main program</dfn>: e.g. <samp>int main()</samp>.</p>
+
+      <p><dfn>dependency on include</dfn>: e.g. <samp>#include
+      "name.h"</samp>.</p>
+
+      <p><dfn>dependency on object</dfn>: e.g. <samp>/* depends on: name.o
+      */</samp> (for <a href="#build.source-analysis.legacy">legacy
+      support</a>).</p>
+    </dd>
+
+    <dt>fortran</dt>
+
+    <dd>
+      <p><dfn>main program</dfn>: e.g. <samp>program name</samp>.</p>
+
+      <p><dfn>list of symbols</dfn>: i.e. names of top level program units
+      including blockdata, function, module, program and subroutine.</p>
+
+      <p><dfn>dependency on include</dfn>: e.g. <samp>#include "name.h"</samp>
+      and <samp>include 'name.f90'</samp>.</p>
+
+      <p><dfn>dependency on module</dfn>: e.g. <samp>use name</samp>.</p>
+
+      <p><dfn>dependency on object</dfn>: e.g. <samp>! depends on:
+      name.o</samp> (for <a href="#build.source-analysis.legacy">legacy
+      support</a>).</p>
+    </dd>
+
+    <dt>h</dt>
+
+    <dd>
+      <p><dfn>dependency on include</dfn>: e.g. <samp>#include
+      "file-name"</samp> (and <samp>include 'file-name'</samp> for <a href=
+      "#build.source-analysis.legacy">legacy support</a>).</p>
+    </dd>
+
+    <dt>script</dt>
+
+    <dd>
+      <p><dfn>dependency on executable</dfn>: e.g. <samp># calls: name</samp>
+      (for <a href="#build.source-analysis.legacy">legacy support</a>).</p>
+    </dd>
+  </dl>
+
+  <div class="well">
+    <p><strong><i class="icon-pencil"></i> Note: The following features are for
+    legacy support.</strong></p>
+
+    <dl id="build.source-analysis.legacy">
+      <dt><code>DEPENDS ON: x</code> directives in C/Fortran source files</dt>
+
+      <dd>The <code>DEPENDS ON: x</code> directive can be used to identify
+      dependencies on other compiled objects. However, it is much better to
+      specify this kind of dependency information in the configuration for the
+      build where necessary. In any case, in modern Fortran code almost all
+      dependencies should be identified automatically via the use of modules
+      and/or interface files.</dd>
+
+      <dt><code>calls: x</code> directives in scripts</dt>
+
+      <dd>The <code>calls: x</code> directive can be used to identify a
+      dependency on another executable. However, it is much better to specify
+      this kind of dependency information in the configuration for the build, and
+      leave the source code to concentrate on the run time logic.</dd>
+
+      <dt><samp>*.h</samp> files as Fortran include files</dt>
+
+      <dd><samp>*.h</samp> files are normally identified as C header files.
+      However, they are also being used by some old Fortran programs as include
+      files. Therefore, when the system analyses a <samp>*.h</samp> file, it has
+      to detect the Fortran include syntax, i.e. <samp>include 'file-name'</samp>
+      as well as the regular C preprocessor include syntax.</dd>
+    </dl>
+  </div>
+
+  <div class="well">
+    <p><strong><i class="icon-pencil"></i> Note: Dependency Analysis and Fortran
+    OpenMP Sentinels.</strong></p>
+
+    <p>The build system recognises statements with Fortran OpenMP sentinels that
+    affect build dependencies. E.g.:</p>
+
+    <pre>
+!$ USE my_omp_mod, ONLY: my_omp_sub
+! ...
+!$ INCLUDE 'my_omp_logic'
+</pre>
+
+    <p>These dependencies are normally ignored. However, if a relevant
+    <code>build.prop{fc.flag-omp}</code> property is specified, the build system
+    will treat these statements as normal dependency statements.</p>
+  </div>
+
+  <p>There are some situations when it is not possible for the system to
+  identify a dependency. E.g. a Fortran source file may depend on external
+  objects that are not detected by the automatic analysis. Therefore, the
+  system allows you to specify manual dependencies in the configuration file
+  using the <code>build.prop{dep.type}</code> and
+  <code>build.prop{ns-dep.type}</code> declarations. E.g.:</p>
+  <pre>
+# Tell the system that (the object of) food/egg.c depends on chicken.o
+build.prop{dep.o}[food/egg.c] = chicken.o
+
+# Tell the system that (the object of) meal/big.c depends on all objects in the
+# "food" and "drink" name-spaces
+build.prop{ns-dep.o}[meal/big.c] = food drink
+</pre>
+
+  <p>Like all declarations that accept name-spaces, if you specify a name-space
+  in this declaration, the property will apply to all source files in the
+  name-space. If you do not specify a name-space, it applies to the root
+  name-space (i.e. globally to all relevant source files).</p>
+
+  <p>The following manual dependency declarations are recognised:</p>
+
+  <dl>
+    <dt><a href=
+    "annex_cfg.html#make.build.prop.dep.bin">build.prop{dep.bin}</a></dt>
+
+    <dd>Specifies a list of dependencies on a script or a binary
+    executable.</dd>
+
+    <dt><a href=
+    "annex_cfg.html#make.build.prop.dep.f.module">build.prop{dep.f.module}</a></dt>
+
+    <dd>Specifies a list of Fortran module import dependencies. Note: a
+    dependency on a Fortran module called <samp>module_1</samp> becomes an
+    <dfn>include</dfn> dependency on <samp>module_1.mod</samp> when the system
+    turns the source file into its targets.</dd>
+
+    <dt><a href=
+    "annex_cfg.html#make.build.prop.dep.include">build.prop{dep.include}</a></dt>
+
+    <dd>Specifies a list of include file dependencies.</dd>
+
+    <dt><a href=
+    "annex_cfg.html#make.build.prop.dep.o">build.prop{dep.o}</a></dt>
+
+    <dd>Specifies a list of link-time object dependencies.</dd>
+
+    <dt><a href=
+    "annex_cfg.html#make.build.prop.dep.o.special">build.prop{dep.o.special}</a></dt>
+
+    <dd>Specifies a list of special type of link-time object dependencies.
+    Normally, an object file can be put in an object archive before being
+    linked with the main object. There are special cases when an object file
+    must be specified on the command line of the linker. (E.g. an object file
+    containing a Fortran blockdata program unit.) This special behaviour must
+    be declared using this declaration.</dd>
+
+    <dt><a href=
+    "annex_cfg.html#make.build.prop.ns-dep.o">build.prop{ns-dep.o}</a></dt>
+
+    <dd>Specifies a list of link-time object dependencies on all objects in the
+    specified name-space.</dd>
+  </dl>
+
+  <p>There are times when you know that your source tree does not contain a
+  particular type of dependency, in which case you can switch off the automatic
+  analysis by using the <code>build.prop{no-dep.type}</code> declaration. E.g.
+  if you know that all include files in the <samp>food</samp> name-space are
+  provided outside of the source tree, you can do:</p>
+  <pre>
+# Do not check for "include" dependencies
+build.prop{no-dep.include}[food] = *
+</pre>
+
+  <p>All the types supported by the <code>build.prop{dep.type}</code>
+  declarations are supported by the <code>build.prop{no-dep.type}</code>,
+  except that there is no <code>build.prop{no-dep.o.special}</code>
+  (because this type of dependency is never automatic).</p>
+
+  <h3 id="build.target-source">Build Targets from Source Files</h3>
+
+  <p>The system derives the build targets from the source files. E.g. a C
+  source file <samp>egg.c</samp> is turned into a compile target to generate
+  <samp>egg.o</samp>.</p>
+
+  <p>The following is a list of what targets are available for each type of
+  file. The title of each item in the list is in the format <dfn>source type
+  -> target key</dfn>. The <dfn>description</dfn> of each target describes
+  what the target is, and where appropriate, explains how the target keys are
+  named. The <dfn>task</dfn> is the action the target needs to perform to get
+  up to date. The <dfn>category and destination</dfn> is the sub-directory and
+  destination of the target. The <dfn>properties</dfn> are the list of
+  properties that may be used by the <dfn>task</dfn> to update the target. The
+  <dfn>dependencies</dfn> list the types of dependencies the target may have.
+  The <dfn>update if</dfn> is the condition when the target is considered out
+  of date. The <dfn>pass on</dfn> information is a list of dependeny types
+  which a target can pass on the status, (see <a href=
+  "#build.target-update">Build Targets Update in Incremental Mode</a> for an
+  explanation of what this means.)</p>
+
+  <dl>
+    <dt>c/cxx -> name</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: source file as an include file.</p>
+
+      <p><dfn>task</dfn>: <a href="#build.task.install">install</a>.</p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#build.category.include">include</a> and include/name</p>
+
+      <p><dfn>dependencies</dfn>: include and o (object).</p>
+
+      <p><dfn>update if</dfn>: source file is modified.</p>
+
+      <p><dfn>pass on</dfn>: include, o and o.special.</p>
+    </dd>
+
+    <dt>c/cxx -> name.o</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: object file. The file is named by mapping the
+      base name of the original source file in lower case characters, with the
+      file extension replaced by the first value of the <code><a href=
+      "annex_cfg.html#make.build.prop.file-ext.o">file-ext.o</a></code>
+      property.</p>
+
+      <p><dfn>task</dfn>: <a href="#build.task.compile">compile</a> (cc).</p>
+
+      <p><dfn>category and destination</dfn>: <a href="#build.category.o">o</a>
+      and o/name.o</p>
+
+      <p><dfn>properties</dfn>: <a href=
+      "annex_cfg.html#make.build.prop.cc">cc</a>, <a href=
+      "annex_cfg.html#make.build.prop.cc.flags">cc.flags</a>, <a href=
+      "annex_cfg.html#make.build.prop.cc.defs">cc.defs</a>, <a href=
+      "annex_cfg.html#make.build.prop.cc.flag-compile">cc.flag-compile</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.cc.flag-define">cc.flag-define</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.cc.flag-include">cc.flag-include</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.cc.include-paths">cc.include-paths</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.cc.flag-omp">cc.flag-omp</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.cc.flag-output">cc.flag-output</a></p>
+
+      <p><dfn>dependencies</dfn>: include and o (object).</p>
+
+      <p><dfn>update if</dfn>: source file or any of the required properties
+      are modified, or if any include dependencies are updated.</p>
+
+      <p><dfn>pass on</dfn>: o and o.special.</p>
+    </dd>
+
+    <dt>c/cxx (with main function) -> name.exe</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: binary executable. The file is named after the
+      base name of the original source file, with the file extension replaced by
+      the first value of the <code><a href=
+      "annex_cfg.html#make.build.prop.file-ext.bin">file-ext.bin</a></code>
+      property.</p>
+
+      <p><dfn>task</dfn>: <a href="#build.task.link">link</a> (cc).</p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#build.category.bin">bin</a> and bin/name.exe</p>
+
+      <p><dfn>properties</dfn>:
+      <a href="annex_cfg.html#make.build.prop.ar">ar</a>,
+      <a href="annex_cfg.html#make.build.prop.ar.flags">ar.flags</a>,
+      <a href="annex_cfg.html#make.build.prop.file-ext.a">file-ext.a</a>,
+      <a href="annex_cfg.html#make.build.prop.cc">cc</a>,
+      <a href="annex_cfg.html#make.build.prop.cc.flags-ld">cc.flags-ld</a>,
+      <a href="annex_cfg.html#make.build.prop.cc.flag-lib">cc.flag-lib</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.cc.flag-lib-path">cc.flag-lib-path</a>,
+      <a href="annex_cfg.html#make.build.prop.cc.libs">cc.libs</a>,
+      <a href="annex_cfg.html#make.build.prop.cc.lib-paths">cc.lib-paths</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.cc.flag-omp">cc.flag-omp</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.cc.flag-output">cc.flag-output</a></p>
+
+      <p><dfn>dependencies</dfn>: name.o and other objects (o and
+      o.special).</p>
+
+      <p><dfn>update if</dfn>: source file or any of the required properties
+      are modified, or if any dependencies are updated.</p>
+    </dd>
+
+    <dt>fortran -> name</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: source file as an include file.</p>
+
+      <p><dfn>task</dfn>: <a href="#build.task.install">install</a>.</p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#build.category.include">include</a> and include/name</p>
+
+      <p><dfn>dependencies</dfn>: include and o (object). A source's f.module
+      dependency on a module called <samp>xyz</samp> is turned into an include
+      dependency on the <samp>xyz.mod</samp>.</p>
+
+      <p><dfn>update if</dfn>: source file is modified.</p>
+
+      <p><dfn>pass on</dfn>: include, o and o.special.</p>
+    </dd>
+
+    <dt>fortran (with a valid Fortran program unit) -> unit.o</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: object file. The file is named by concatenating
+      the lower case characters of the name of the first program unit in the
+      source file and the first value of the <code><a href=
+      "annex_cfg.html#make.build.prop.file-ext.o">file-ext.o</a></code>
+      property.</p>
+
+      <p><dfn>task</dfn>: <a href="#build.task.compile">compile</a> (fc).</p>
+
+      <p><dfn>category and destination</dfn>: <a href="#build.category.o">o</a>
+      and o/unit.o</p>
+
+      <p><dfn>properties</dfn>: <a href=
+      "annex_cfg.html#make.build.prop.fc">fc</a>, <a href=
+      "annex_cfg.html#make.build.prop.fc.flags">fc.flags</a>, <a href=
+      "annex_cfg.html#make.build.prop.fc.defs">fc.defs</a>, <a href=
+      "annex_cfg.html#make.build.prop.fc.flag-compile">fc.flag-compile</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.fc.flag-define">fc.flag-define</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.fc.flag-include">fc.flag-include</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.fc.include-paths">fc.include-paths</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.fc.flag-module">fc.flag-module</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.fc.flag-omp">fc.flag-omp</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.fc.flag-output">fc.flag-output</a></p>
+
+      <p><dfn>dependencies</dfn>: include and o (object). A source's f.module
+      dependency on a module called <samp>xyz</samp> is turned into an include
+      dependency on the <samp>xyz.mod</samp>.</p>
+
+      <p><dfn>update if</dfn>: source file or any of the required properties
+      are modified, or if any include dependencies are updated.</p>
+
+      <p><dfn>pass on</dfn>: o and o.special.</p>
+
+      <p><dfn>remark</dfn>: trigger <samp>unit.mod</samp> targets if source
+      file contains a Fortran module.</p>
+    </dd>
+
+    <dt>fortran (with function or subroutine) -> name.interface</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Fortran interface file. The file is named by
+      concatenating the base name of the source file with the file extension
+      replaced by the <code><a href=
+      "annex_cfg.html#make.build.prop.file-ext.f90-interface">file-ext.f90-interface</a></code>
+      property.</p>
+
+      <p><dfn>task</dfn>: <a href="#build.task.ext-iface">ext-iface</a></p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#build.category.include">include</a> and include/name.interface</p>
+
+      <p><dfn>dependencies</dfn>: unit.o</p>
+
+      <p><dfn>update if</dfn>: source file or unit.o is modified.</p>
+
+      <p><dfn>pass on</dfn>: include, o and o.special.</p>
+    </dd>
+
+    <dt>fortran (each module in source) -> unit.mod</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: Fortran module definition file. The file is
+      named by concatenating the lower case characters of the name of the
+      module and the first value of the <code><a href=
+      "annex_cfg.html#make.build.prop.file-ext.f90-mod">file-ext.f90-mod</a></code>
+      property.</p>
+
+      <p><dfn>task</dfn>: <a href="#build.task.compile-plus">compile+</a>.</p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#build.category.include">include</a> and include/unit.mod</p>
+
+      <p><dfn>dependencies</dfn>: unit.o</p>
+
+      <p><dfn>update if</dfn>: source file or unit.o is modified.</p>
+
+      <p><dfn>pass on</dfn>: o.</p>
+    </dd>
+
+    <dt>fortran (with program) -> name.exe</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: binary executable. The object file is named
+      after the base name of the original source file, with the file extension
+      replaced by the first value of the <code><a href=
+      "annex_cfg.html#make.build.prop.file-ext.bin">file-ext.bin</a></code>
+      property.</p>
+
+      <p><dfn>task</dfn>: <a href="#build.task.link">link</a> (fc).</p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#build.category.bin">bin</a> and bin/name.exe</p>
+
+      <p><dfn>properties</dfn>:
+      <a href="annex_cfg.html#make.build.prop.ar">ar</a>,
+      <a href="annex_cfg.html#make.build.prop.ar.flags">ar.flags</a>,
+      <a href="annex_cfg.html#make.build.prop.fc">fc</a>,
+      <a href="annex_cfg.html#make.build.prop.fc.flags-ld">fc.flags-ld</a>,
+      <a href="annex_cfg.html#make.build.prop.fc.flag-lib">fc.flag-lib</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.fc.flag-lib-path">fc.flag-lib-path</a>,
+      <a href="annex_cfg.html#make.build.prop.fc.libs">fc.libs</a>,
+      <a href="annex_cfg.html#make.build.prop.fc.lib-paths">fc.lib-paths</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.fc.flag-omp">fc.flag-omp</a>,
+      <a href=
+      "annex_cfg.html#make.build.prop.fc.flag-output">fc.flag-output</a></p>
+
+      <p><dfn>dependencies</dfn>: unit.o and other objects (o and
+      o.special).</p>
+
+      <p><dfn>update if</dfn>: source file or any of the required properties
+      are modified, or if any dependencies are updated.</p>
+    </dd>
+
+    <dt>h -> name</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: a header (include) file.</p>
+
+      <p><dfn>task</dfn>: <a href="#build.task.install">install</a>.</p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#build.category.include">include</a> and include/name</p>
+
+      <p><dfn>dependencies</dfn>: include and o (object).</p>
+
+      <p><dfn>update if</dfn>: source file is modified.</p>
+
+      <p><dfn>pass on</dfn>: include, o and o.special.</p>
+    </dd>
+
+    <dt>script -> name</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: an executable script.</p>
+
+      <p><dfn>task</dfn>: <a href="#build.task.install">install</a>.</p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#build.category.bin">bin</a> and bin/name</p>
+
+      <p><dfn>dependencies</dfn>: bin (executable).</p>
+
+      <p><dfn>update if</dfn>: source file is modified.</p>
+
+      <p><dfn>pass on</dfn>: bin.</p>
+    </dd>
+
+    <dt>data -> name-space</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: a data file.</p>
+
+      <p><dfn>task</dfn>: <a href="#build.task.install">install</a>.</p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#build.category.etc">etc</a> and etc/name-space</p>
+
+      <p><dfn>update if</dfn>: source file is modified.</p>
+    </dd>
+  </dl>
+
+  <p>Here is an explanation of what each build system <dfn>task</dfn> does:</p>
+
+  <dl>
+    <dt id="build.task.archive">archive</dt>
+
+    <dd>Creates an object archive by invoking an archiver command. (See
+    <a href="#build.target-ns">Build Targets from Name-space</a>.)</dd>
+
+    <dt id="build.task.compile">compile</dt>
+
+    <dd>Creates an object file by invoking the C/C++/Fortran compiler on the
+    source file.</dd>
+
+    <dt id="build.task.compile-plus">compile+</dt>
+
+    <dd>Copies the Fortran module definition file created by a compile task to
+    the include sub-directory.</dd>
+
+    <dt id="build.task.ext-iface">ext-iface</dt>
+
+    <dd>Extracts the calling interfaces of all functions and subroutines in a
+    Fortran source file (free format only) and writes the results in an
+    interface block that can be included by other Fortran source files with an
+    <samp>INCLUDE 'name.interface'</samp> statement. In an incremental build,
+    if you have modified a Fortran source file, its interface file will only be
+    re-generated if the content of the interface has changed. This can make
+    incremental build very efficient, as non-interface changes in a function or
+    subroutine will only trigger a re-link of the executable.</dd>
+
+    <dt id="build.task.install">install</dt>
+
+    <dd>Copies the source file to the destination.</dd>
+
+    <dt id="build.task.link">link</dt>
+
+    <dd>Creates an executable by invoking the archiver to load all required
+    objects into an archive, and then the C/C++/Fortran compiler on the object
+    file previously compiled using a source file containing a main program, with
+    the temporary archive.</dd>
+  </dl>
+
+  <h3 id="build.target-prop">Build Targets and Properties</h3>
+
+  <p>If you need to specify a property for a specific target, you can either use
+  their source file namespace or the target key. E.g. If the
+  <samp>sausage.o</samp> target is generated from the source file in the
+  <samp>src/food/sausage.f90</samp> namespace, you can specify its Fortran
+  compiler flags <code><a href=
+  "annex_cfg.html#make.build.prop.fc.flags">build.prop{fc.flags}</a></code> by
+  doing either:</p>
+
+  <pre>
+build.prop{fc.flags}[sausage.o] = -O4
+# would be the same as:
+build.prop{fc.flags}[src/food/sausage.f90] = -O4
+</pre>
+
+  <h3 id="build.target-source-fortran">Build Targets from Source Files: Fortran
+  Specifics</h3>
+
+  <p>To ensure that a Fortran application is built automatically, its source
+  code should be designed with the following considerations:</p>
+
+  <p><dfn>The name of each compilable program unit should be unique</dfn> in
+  the source tree, bearing in mind that Fortran is NOT case sensitive.</p>
+
+  <p><dfn>Always supply an interface for functions and subroutines</dfn>,
+  i.e.:</p>
+
+  <ul>
+    <li>Place functions and subroutines in a module, and give them the
+    <code>PUBLIC</code> attribute. Import them with the <code>USE
+    <module></code> statement. We recommend adding the <code>ONLY</code>
+    clause in a <code>USE <module></code> when importing symbols from a
+    module. This makes it easier to locate the source of each symbol, and avoids
+    unintentional access to other <code>PUBLIC</code> symbols within the
+    <code>MODULE</code>. If you are importing from an intrinsic module, you
+    should add the <code>INTRINSIC</code> clause to the <code>USE
+    <module></code> statement to tell the build system not to look for the
+    module from your source tree.</li>
+
+    <li>Place functions and subroutines in the <code>CONTAINS</code> section of
+    a standalone program unit. There are two advantages for this approach.
+    Firstly, the sub-programs will get an automatic interface when the container
+    program unit is compiled. Secondly, it should be easier for the compiler to
+    provide optimisation when the sub-programs are internal to the caller. The
+    disadvantage of this approach is that the sub-programs are local to the
+    caller, and so they cannot be called by other program units. Therefore, this
+    approach is only suitable for small sub-programs local to a particular
+    program unit.</li>
+
+    <li>Use the build system's automatic interface file feature. See below.</li>
+  </ul>
+
+  <p>For each free format Fortran source file, e.g. <samp>name.f90</samp>, with
+  1 or more top level function and/or subroutine, the system creates a target
+  with the <a href="#build.task.ext-iface">ext-iface</a> task in the
+  <a href="#build.category.include">include</a> category, e.g.
+  <samp>name.interface</samp>, to extract the calling interfaces of all
+  functions and subroutines into an interface block. Another Fortran source
+  file, e.g. <samp>caller.f90</samp> that relies on the functions and/or
+  subroutines in <samp>name.f90</samp> can have an <samp>INCLUDE
+  'name.interface'</samp> statement in its specification section, which serves
+  2 purposes:</p>
+
+  <ul>
+    <li>It allows <samp>caller.f90</samp> to call the functions and/or
+    subroutines in <samp>name.f90</samp> with explicit interfaces.</li>
+
+    <li>It introduces an <a href=
+    "annex_cfg.html#make.build.prop.dep.include">include</a> dependency for
+    <samp>caller.o</samp> on <samp>name.interface</samp>.</li>
+  </ul>
+
+  <p>In an incremental build, if you modify <samp>name.f90</samp>, the system
+  will only regenerate <samp>name.interface</samp> if only the calling
+  interfaces of the functions and/or subroutines in <samp>name.f90</samp> have
+  changed. Consequently, non-interface changes in <samp>name.f90</samp> will
+  not trigger the re-compile of <samp>caller.o</samp>, but will only trigger a
+  re-link of the executable.</p>
+
+  <h3 id="build.target-selection">Build Targets Selection and Rename</h3>
+
+  <p>You need to tell the build system what targets to build or it will do
+  nothing. The <code><a href=
+  "annex_cfg.html#make.build.target">build.target</a></code> declaration allows
+  you to select targets according to their categories, source name-spaces,
+  tasks and keys. The logic is demonstrated by the following example:</p>
+  <pre>
+# Selects targets matching these keys
+build.target = egg.bin ham.o bacon.sh
+# OR (
+#     those doing these tasks
+build.target{task} = install link
+#     AND in these categories
+build.target{category} = bin
+#     AND in these name-spaces
+build.target{ns} = foo bar/baz
+# )
+</pre>
+
+  <p>There are times when an automatic target name is not what you want. In
+  which case, you can rename a target using the <code><a href=
+  "annex_cfg.html#make.build.target-rename">build.target-rename</a></code>
+  declaration to specify an alternate name. E.g. if the target
+  <samp>bacon.sh</samp> should be called <samp>streaky</samp>, you can do:</p>
+  <pre>
+build.target-rename = bacon.sh:streaky
+</pre>
+
+  <p>In order for a target to build, all its dependencies must be satisfied. If
+  a target has a dependency that is not available in the list of targets, the
+  build will fail. Normally, you can avoid this by using one of the
+  <code>build.prop{no-dep.*}</code> declarations to switch off a non-existent
+  dependency, as described in the <a href="#build.source-analysis">Build Source
+  Analysis</a> section. However, there may be times when this is inefficient or
+  insufficient, in which case you can use the property <code><a href=
+  "annex_cfg.html#make.build.prop.ignore-missing-dep-ns">ignore-missing-dep-ns</a></code>
+  to specify a list of source name-spaces, in which targets can ignore missing
+  dependencies. E.g.:</p>
+  <pre>
+# Allows targets in the "foo" and "bar/baz"
+# name-spaces to ignore missing dependencies.
+build.prop{ignore-missing-dep-ns} = foo bar/baz
+</pre>
+
+  <h3 id="build.target-file-ext">Build Targets File Extensions</h3>
+
+  <p>You can rename the file name extension of the targets using
+  <code>build.prop{file-ext.type}</code> declaration (provided that the file
+  name extension is supported by your compiler, etc). E.g. if you want your
+  binary executables to have <samp>.bin</samp> extension rather than the
+  default <samp>.exe</samp>, you can do:</p>
+  <pre>
+build.prop{file-ext.bin} = .bin
+</pre>
+
+  <p>The following file extensions are currently used by the system:</p>
+
+  <dl>
+    <dt><a href="annex_cfg.html#make.build.prop.file-ext.a">a</a> (object
+    archive)</dt>
+
+    <dd>.a</dd>
+
+    <dt><a href="annex_cfg.html#make.build.prop.file-ext.bin">bin</a> (binary
+    executable)</dt>
+
+    <dd>.exe</dd>
+
+    <dt><a href=
+    "annex_cfg.html#make.build.prop.file-ext.f90-interface">f90-interface</a>
+    (Fortran free format interface file)</dt>
+
+    <dd>.interface</dd>
+
+    <dt><a href="annex_cfg.html#make.build.prop.file-ext.f90-mod">f90-mod</a>
+    (Fortran compiler module definition file)</dt>
+
+    <dd>.mod</dd>
+
+    <dt><a href="annex_cfg.html#make.build.prop.file-ext.o">o</a> (object
+    file)</dt>
+
+    <dd>.o</dd>
+  </dl>
+
+  <h3 id="build.target-ns">Build Targets from Name-space</h3>
+
+  <p>Apart from source file targets, the build system also generates targets
+  for each (directory-level) name-space. One target is for creating an object
+  archive to contain all object files in the name-space. The other target is a
+  convenient shorthand to allow all data files in the name-space to be
+  installed. The following is the full description:</p>
+
+  <dl>
+    <dt>name-space > name-space/libo.a</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: object archive.</p>
+
+      <p><dfn>task</dfn>: <a href="#build.task.archive">archive</a>.</p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#build.category.lib">lib</a> and lib/name-space/libo.a</p>
+
+      <p><dfn>dependencies</dfn>: all o (object) targets in the name-space.</p>
+
+      <p><dfn>properties</dfn>: <a href=
+      "annex_cfg.html#make.build.prop.ar">ar</a>, <a href=
+      "annex_cfg.html#make.build.prop.ar.flags">ar.flags</a></p>
+
+      <p><dfn>update if</dfn>: any dependencies or properties are modified.</p>
+    </dd>
+
+    <dt>name-space > name-space/.etc</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: dummy file.</p>
+
+      <p><dfn>task</dfn>: <a href="#build.task.install">install</a>.</p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#build.category.etc">etc</a> and etc/name-space/.etc</p>
+
+      <p><dfn>dependencies</dfn>: all data files in the name-space.</p>
+
+      <p><dfn>update if</dfn>: any dependencies are modified.</p>
+    </dd>
+  </dl>
+
+  <h3 id="build.target-update">Build Targets Update in Incremental Mode</h3>
+
+  <p>In incremental mode, a target is only updated if it is marked out of date.
+  A target is considered out of date if:</p>
+
+  <ul>
+    <li>the source file's MD5 checksum is changed.</li>
+
+    <li>a required property is modified.</li>
+
+    <li>a dependency is marked as modified, and the dependency is a type that
+    the target cannot pass on. E.g. If <samp>object_1.o</samp> depends on
+    <samp>object_2.o</samp>, and <samp>object_2.o</samp> is marked as modified,
+    the system does not need to re-compile <samp>object_1.o</samp> (as long as
+    its source file and properties remain unchanged). However,
+    <samp>object_1.o</samp> will have to pass the information up the dependency
+    tree, so that a target with a <samp><a href=
+    "#build.task.link">link</a></samp> task to build an executable (or an
+    <samp><a href="#build.task.archive">archive</a></samp> task to build a
+    library) will know that it needs to be updated.</li>
+
+    <li>a dependency is passing on a <q>modified</q> status for a dependency
+    type, which cannot be passed on by the target.</li>
+
+    <li>the target does not exist or its MD5 checksum is changed.</li>
+  </ul>
+
+  <p>If, after an update, the target's MD5 checksum is the same as before, the
+  target will be considered unchanged and up to date. In an incremental build,
+  the use of checksum ensures that any targets manually modified by the user
+  after the previous build is rebuilt accordingly. It also prevents unnecessary
+  updates of targets in incremental and inherited builds.</p>
+
+  <p>E.g. Consider an incremental build where the only change is the content of
+  a Fortran module <samp>my_mod.f90</samp>. The content change should trigger
+  an update of the <samp>my_mod.o</samp> and <samp>my_mod.mod</samp> targets,
+  and everything depending on them. However, if the source content is modified
+  in such a way that it does not affect the module's public interface, most
+  compilers will generate an identical <samp>my_mod.mod</samp>. The system can
+  detect this by comparing the checksums. If <samp>my_mod.mod</samp> is
+  unchanged, the build system will not need to trigger the re-compile of all
+  targets depending on <samp>my_mod.mod</samp>, and it will only need to
+  re-link the executable. This allows incremental builds to be more
+  efficient.</p>
+
+  <h3 id="build.inherit">Build Inheritance</h3>
+
+  <p>If a previous build with a similar configuration exists in another
+  location, it can be more efficient to inherit from this previous build in
+  your current build. This works like a normal incremental build, except that
+  your build will only contain the changes you have specified (compared with
+  the inherited build) instead of the full set of targets.</p>
+
+  <p>The current build inherits all properties and target settings, as well as
+  sources and targets from the inherited build. While properties and target
+  settings can be overridden with a corresponding declaration, source
+  inheritance can only be prevented by using a <code><a href=
+  "annex_cfg.html#make.build.prop.no-inherit-source">build.prop{no-inherit-source}</a></code>
+  declaration. E.g.:</p>
+  <pre>
+# Prevents inheritance from some name-spaces:
+build.prop{no-inherit-source} = food/mint drink/soft/cola.c
+</pre>
+
+  <p>For multiple inheritance, the last one takes precedence, and any search
+  for source files or targets are recursive and depth first. For instance, if
+  we have the following declarations in the current FCM make configuration:</p>
+  <pre>
+use = /path/to/a /path/to/b /path/to/c
+</pre>
+
+  <p>and the following in the FCM make configuration of
+  <samp>/path/to/b</samp>:</p>
+  <pre>
+use /path/to/d
+</pre>
+
+  <p>The relationship looks like:</p>
+  <pre>
+/path/to/current
+    /path/to/c
+    /path/to/b
+        /path/to/d
+    /path/to/a
+</pre>
+
+  <p>Therefore, we would expect the search path to follow the order:</p>
+  <pre>
+/path/to/current
+/path/to/c
+/path/to/b
+/path/to/d
+/path/to/a
+</pre>
+
+  <p>In its normal setting, the system does not inherit targets in the
+  <samp>bin</samp>, <samp>etc</samp> and <samp>lib</samp> categories. A target
+  in one of these categories is rebuilt in the current destination, whether the
+  inherited target is up to date or not. This allows someone to use the
+  executables of the build by setting the <var>PATH</var> environment variable
+  to point only to <samp>$DEST/build/bin/</samp> (where <var>$DEST</var> is the
+  destination of the current make). If this behaviour is undesirable for
+  whatever reason, it can be altered using the <code><a href=
+  "annex_cfg.html#make.build.prop.no-inherit-target-category">build.prop{no-inherit-target-category}</a></code>
+  declaration.</p>
+
+  <dl>
+    <dt>Build inheritance limitation: handling of include files</dt>
+
+    <dd>
+      <p>The build system uses the compiler's <code>-I</code> option to specify
+      the search path for include files. E.g. it uses this option to specify
+      the <samp>inc/</samp> sub-directories of the current build and its
+      inherited build.</p>
+
+      <p>However, some compilers (e.g. <code>cpp</code>) search for include
+      files from the container directory of the source file before searching
+      for the paths specified by the <code>-I</code> options. This behaviour
+      may cause the build to behave incorrectly.</p>
+
+      <p>Consider a source file <samp>egg/hen.c</samp> that includes
+      <samp>fried.h</samp>. If the directory structure looks like:</p>
+      <pre>
+# Sources in inherited build:
+egg/hen.c
+egg/fried.h
+
+# Sources in current build:
+egg/fried.h
+</pre>
+
+      <p>The system will correctly identify that <samp>fried.h</samp> is out of
+      date, and trigger a re-compilation of <samp>egg/hen.c</samp>. However, if
+      the compiler searches for the include files from the container directory
+      of the source file first, it will wrongly use the include file in the
+      inherited build instead of the current one.</p>
+
+      <p>If your directory structure does not have any include files in the same
+      directory as the source files that include them then you do not need to
+      worry. If it does then you need to check whether you are affected by this
+      problem before using an inherited build. The situation will vary
+      according to whether the affected code uses Fortran or preprocessor
+      include statements and also whether you are using the
+      <a href="#preprocess">preprocess system</a>. Some compilers (e.g.
+      <code>gfortran</code>) work fine for Fortran includes but not preprocessor
+      includes. Others (e.g. <code>ifort</code>) have options which can be used
+      (e.g. <code>-assume nosource_include</code>) to get the desired behaviour.
+      The FCM distribution includes some simple test code to help you test how
+      your chosen compilers behave. If you cannot ensure the correct behaviour
+      then it is safer not to use inherited builds.</p>
+    </dd>
+  </dl>
+
+  <h3 id="build.diagnostic">Build Diagnostic</h3>
+
+  <p>The amount of diagnostic messages generated by the build system is
+  dependent on the diagnostic verbosity level that can be modified by the
+  <code>-v</code> and <code>-q</code> options to the <code>fcm make</code>
+  command.</p>
+
+  <p>The following is a list of diagnostic output at each verbosity level:</p>
+
+  <dl>
+    <dt>-q</dt>
+
+    <dd>
+      <ul>
+        <li>Exceptions.</li>
+      </ul>
+    </dd>
+
+    <dt>default</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at the -q level.</li>
+
+        <li>Start time of the build.</li>
+
+        <li>The summary of source analysis.</li>
+
+        <li>The summary of targets. Each row except the last reports the number
+        of modified and unchanged targets with a given type of task, and the
+        total time spent to perform the tasks. The last row reports the total
+        number of modified and unchanged targets, and the actual elapsed time.
+        It is worth noting that the elapsed time in a multi-process build
+        should be significant shorter than the sum of the total time for each
+        type of task. E.g.:
+          <pre>
+[info] compile   targets: modified=5, unchanged=0, total-time=0.5s
+[info] compile+  targets: modified=1, unchanged=0, total-time=0.0s
+[info] ext-iface targets: modified=2, unchanged=0, total-time=0.0s
+[info] install   targets: modified=1, unchanged=0, total-time=0.0s
+[info] link      targets: modified=1, unchanged=0, total-time=0.1s
+[info] TOTAL     targets: modified=10, unchanged=0, elapsed-time=0.7s
+</pre>
+        </li>
+
+        <li>Total time.</li>
+      </ul>
+    </dd>
+
+    <dt>-v</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at the default level.</li>
+
+        <li>Elapsed time and name-space for each analysed source.</li>
+
+        <li>Task name, elapsed time, target status (<samp>M</samp> for modified
+        or <samp>U</samp> for unchanged), target key, and source name-space for
+        each modified target. E.g.:
+          <pre>
+[info] compile    0.0 M hello_func.o         <- lib/function/hello_func.f90
+[info] ext-iface  0.0 M hello_func.interface <- lib/function/hello_func.f90
+[info] install    0.0 M hello_inc.f90        <- include/hello_inc.f90
+[info] compile    0.0 M hello_1.o            <- bin/hello_1.f90
+[info] link       0.1 M hello_1.exe          <- bin/hello_1.f90
+</pre>
+        </li>
+      </ul>
+    </dd>
+
+    <dt>-vv</dt>
+
+    <dd>
+      <ul>
+        <li>Everything at the -v level.</li>
+
+        <li>A list of dependencies (type and name) of each analysed source.
+        E.g.
+          <pre>
+[info] analyse  0.0 bin/hello_1.f90
+[info]              -> (  include) hello_inc.f90
+[info]              -> (        o) hello_void.o
+[info]              -> ( f.module) hello_mod
+</pre>
+        </li>
+
+        <li>A list of the available targets from the sources. Each row contains
+        the source namespace, the target task, the target category and the key
+        of the target. E.g.:
+          <pre>
+[info] source->target / -> (archive) lib/ libo.a
+[info] source->target hello.f90 -> (link) bin/ hello.exe
+[info] source->target hello.f90 -> (install) include/ hello.f90
+[info] source->target hello.f90 -> (compile) o/ hello.o
+[info] source->target world.f90 -> (install) include/ world.f90
+[info] source->target world.f90 -> (compile+) include/ world.mod
+[info] source->target world.f90 -> (compile) o/ world.o
+</pre>
+        </li>
+
+        <li>A list of the required targets for this build. Each row contains
+        the task, the category and the key of the target. E.g.:
+          <pre>
+[info] required-target: link      bin     hello_1.exe
+</pre>
+        </li>
+
+        <li>The dependency tree of all the required targets. (N.B. A
+        <samp>(n-deps=N)</samp> at the end of a line means that the target has
+        already appeared earlier and that it has <samp>N</samp> direct
+        dependencies, which will not be reported again.) E.g.:
+          <pre>
+[info] target hello_1.exe
+[info] target  - hello_void.o
+[info] target  - hello_1.o
+[info] target  -  - hello_mod.mod
+[info] target  -  -  - hello_mod.o
+[info] target  -  - hello_void.o
+[info] target  -  - hello_inc.f90
+[info] target  -  -  - hello_sub.interface
+[info] target  -  -  -  - hello_sub.o
+[info] target  -  -  -  -  - hello_func.interface
+[info] target  -  -  -  -  -  - hello_func.o
+[info] target  - hello_2.o
+[info] target  -  - hello_mod.mod (n-deps=1)
+</pre>
+        </li>
+
+        <li>Each shell command invoked with elapsed time and return code.</li>
+
+        <li>STDOUT and STDERR from shell commands invoked by the build
+        tasks, e.g. diagnostic output from compilers and linkers.</li>
+      </ul>
+    </dd>
+  </dl>
+
+  <h2 id="preprocess">Preprocess</h2>
+
+  <p>As most modern compilers can handle preprocessing, you should normally
+  leave preprocessing to the compiler. However, it is recognised that some code
+  is written with preprocessor directives that can alter the calling interface
+  of the procedure and/or their dependencies. If a source file requires
+  preprocessing in such a way, we have to preprocess it before feeding it to
+  the build system. The preprocess system can be used to do this. It is
+  typically run as a step before build.</p>
+
+  <p>However, using a separate preprocess step is not the best way of working,
+  as it adds an overhead to the build process. If your code requires
+  preprocessing, you should try to design it to avoid changes in the above.</p>
+
+  <p>In practice, the only reasonable use of a preprocessor with Fortran is for
+  code selection. For example, preprocessing is useful for isolating machine
+  specific libraries or instructions, where it may be appropriate to use inline
+  alternatives for small sections of code. Another example is when multiple
+  versions of the same procedure exist in the source tree and you need to use
+  the preprocessor to select the correct version for your build.</p>
+
+  <p>Avoid using the a preprocessor for code inclusion, as you should be able
+  to do the same via the Fortran <code>INCLUDE</code> statement. You should
+  also avoid embedding preprocessor macros within the continuations of a
+  Fortran statement, as it can make your code very confusing.</p>
+
+  <p>The preprocess system works using the same logic as the build system, but
+  is configured primarily to preprocess C/C++ and Fortran source files. We
+  shall document only the main differences to the build system in the remainder
+  of this section.</p>
+
+  <h3 id="preprocess.basic">Preprocess: Basic</h3>
+
+  <p>A typical usage of the preprocess system may look like:</p>
+  <pre>
+steps = extract preprocess build
+
+# ... some extract configuration
+
+# Only preprocess source files in these name-spaces
+preprocess.target{ns} = foo/bar egg/fried.F90
+# Specifies the macro definitions for the Fortran preprocessor
+preprocess.prop{fpp.defs} = THING=stuff HIGH=tall
+# Specifies the macro definitions for the C preprocessor
+preprocess.prop{cpp.defs} = LOWER=lower UNDER LINUX
+
+# ... some build configuration
+</pre>
+
+  <p>The result of the preprocess can be found in the sub-directories of the
+  <samp>preprocess/</samp> sub-directory. There are only 2 target
+  categories:</p>
+
+  <dl>
+    <dt id="preprocess.category.include"><samp>include</samp></dt>
+
+    <dd>e.g. include files.</dd>
+
+    <dt id="preprocess.category.src"><samp>src</samp></dt>
+
+    <dd>e.g. preprocessed source files</dd>
+  </dl>
+
+  <h3 id="preprocess.source-type">Preprocess Source Types</h3>
+
+  <p>Only files in the following types (with the given file extensions) are
+  recognised by the preprocess system:</p>
+
+  <dl>
+    <dt><a href="annex_cfg.html#make.preprocess.prop.file-ext.cpp">cpp</a>
+    (C/C++ source file)</dt>
+
+    <dd>.c .m .cc .cp .cxx .cpp .CPP .c++ .C .mm .M</dd>
+
+    <dt><a href="annex_cfg.html#make.preprocess.prop.file-ext.fpp">fpp</a>
+    (Fortran source file requiring preprocessing)</dt>
+
+    <dd>.F .FOR .FTN .F90 .F95</dd>
+
+    <dt><a href="annex_cfg.html#make.preprocess.prop.file-ext.h">h</a>
+    (Preprocessor header file)</dt>
+
+    <dd>.h</dd>
+  </dl>
+
+  <h3 id="preprocess.source-analysis">Preprocess Source Analysis</h3>
+
+  <p>The preprocess system only looks for include dependencies using the
+  pattern <samp>#include "name.h"</samp>. Macros using the angle brackets
+  syntax (e.g. <samp>#include <name.h></samp>) are ignored.</p>
+
+  <h3 id="preprocess.target-source">Preprocess Targets from Source Files</h3>
+
+  <p>The preprocess system only generates targets from source files, (i.e.
+  targets are not generated by name-spaces). Targets of the preprocess system
+  perform one of the following <dfn>tasks</dfn>:</p>
+
+  <dl>
+    <dt id="preprocess.task.install">install</dt>
+
+    <dd>Copies the source file to the destination.</dd>
+
+    <dt id="preprocess.task.process">process</dt>
+
+    <dd>Creates a new source file by invoking the C/Fortran preprocessor on the
+    original source file.</dd>
+  </dl>
+
+  <p>By default, it attempts to build all targets with a <samp><a href=
+  "#preprocess.task.process">process</a></samp> task, i.e.:</p>
+  <pre>
+preprocess.target =
+preprocess.target{task} = process
+preprocess.target{category} =
+preprocess.target{ns} =
+</pre>
+
+  <p>Here is a list of what targets are available for each type of file:</p>
+
+  <dl>
+    <dt>cpp -> name-space</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: the preprocessed version of the original
+      file.</p>
+
+      <p><dfn>task</dfn>: <a href="#preprocess.task.process">process</a>
+      (cpp).</p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#preprocess.category.src">src</a> and src/name-space</p>
+
+      <p><dfn>properties</dfn>: <a href=
+      "annex_cfg.html#make.preprocess.prop.cpp">cpp</a>, <a href=
+      "annex_cfg.html#make.preprocess.prop.cpp.flags">cpp.flags</a>, <a href=
+      "annex_cfg.html#make.preprocess.prop.cpp.defs">cpp.defs</a>, <a href=
+      "annex_cfg.html#make.preprocess.prop.cpp.flag-define">cpp.flag-define</a>,
+      <a href=
+      "annex_cfg.html#make.preprocess.prop.cpp.flag-include">cpp.flag-include</a>,
+      <a href=
+      "annex_cfg.html#make.preprocess.prop.cpp.include-paths">cpp.include-paths</a></p>
+      <p><dfn>dependencies</dfn>: include.</p>
+
+      <p><dfn>update if</dfn>: source file or any of the required properties
+      are modified, or if any include dependencies are updated.</p>
+    </dd>
+
+    <dt>fpp -> name-space</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: the preprocessed version of the original
+      file.</p>
+
+      <p><dfn>task</dfn>: <a href="#preprocess.task.process">process</a>
+      (fpp).</p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#preprocess.category.src">src</a> and src/name-space</p>
+
+      <p><dfn>properties</dfn>: <a href=
+      "annex_cfg.html#make.preprocess.prop.fpp">fpp</a>, <a href=
+      "annex_cfg.html#make.preprocess.prop.fpp.flags">fpp.flags</a>, <a href=
+      "annex_cfg.html#make.preprocess.prop.fpp.defs">fpp.defs</a>, <a href=
+      "annex_cfg.html#make.preprocess.prop.fpp.flag-define">fpp.flag-define</a>,
+      <a href=
+      "annex_cfg.html#make.preprocess.prop.fpp.flag-include">fpp.flag-include</a>,
+      <a href=
+      "annex_cfg.html#make.preprocess.prop.fpp.include-paths">fpp.include-paths</a></p>
+
+      <p><dfn>dependencies</dfn>: include.</p>
+
+      <p><dfn>update if</dfn>: source file or any of the required properties
+      are modified, or if any include dependencies are updated.</p>
+    </dd>
+
+    <dt>h -> name</dt>
+
+    <dd>
+      <p><dfn>description</dfn>: a header (include) file.</p>
+
+      <p><dfn>task</dfn>: <a href="#preprocess.task.install">install</a>.</p>
+
+      <p><dfn>category and destination</dfn>: <a href=
+      "#preprocess.category.include">include</a> and include/name</p>
+
+      <p><dfn>dependencies</dfn>: include.</p>
+
+      <p><dfn>update if</dfn>: source file is modified.</p>
+    </dd>
+  </dl>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/overview.html b/doc/user_guide/overview.html
new file mode 100644
index 0000000..8f20123
--- /dev/null
+++ b/doc/user_guide/overview.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: System Overview</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: System Overview</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <p>The FCM system is designed to simplify the task of managing and building
+  source code. It consists of the following components:</p>
+
+  <h2 id="code-management">Code Management</h2>
+
+  <p>FCM uses <a href="http://subversion.apache.org/">Subversion</a> for
+  version control. For a summary of its main features please refer to the
+  <a href="http://svnbook.red-bean.com/">Version Control with
+  Subversion</a> book. Subversion is a generalised tool which can be used
+  in lots of different ways. This makes some day-to-day tasks more complex
+  than they need be. FCM defines a simplified process and appropriate
+  naming conventions. It then adds a layer on top of Subversion to provide
+  a natural interface which is specifically tailored to this process. Where
+  appropriate it simply makes use of the command line tools provided by
+  Subversion. However, in other cases it provides significant additional
+  functionality, e.g.:</p>
+
+  <ul>
+    <li>By making some assumptions about the repository layout (i.e. by
+    imposing a standard working practice) FCM simplifies the task of
+    creating branches and enforces a standard branch naming
+    convention.</li>
+
+    <li>Having defined working practices and standard log messages allows
+    FCM to greatly simplify the process of merging changes between
+    branches.</li>
+
+    <li>FCM makes use of <a href="http://furius.ca/xxdiff/">xxdiff</a> (a
+    graphical diff and merge tool) to allow users to easily examine changes
+    they have made and to simplify the process of resolving any conflicts
+    which result from a merge.</li>
+  </ul>
+
+  <p>FCM uses <a href="http://trac.edgewall.org/">Trac</a>, a powerful web
+  based tool, to manage software projects. <a href=
+  "http://trac.edgewall.org/">Trac</a> has the following features:</p>
+
+  <ul>
+    <li>A flexible issue tracker which can be used to keep track of bugs,
+    feature requests, etc. Each issue (known as a <q title=
+    "http://trac.edgewall.org/wiki/TracTickets">ticket</q> within Trac) can
+    be given a priority and assigned to a particular person. Changes made
+    to your Subversion repository can easily be traced to the relevant
+    ticket. Where appropriate, tickets can be used to record information
+    about who has reviewed each change.</li>
+
+    <li>A <q title="http://trac.edgewall.org/wiki/TracRoadmap">roadmap</q>
+    feature which helps you to plan and manage project releases. Each
+    ticket can be associated with a particular milestone. Trac can then
+    easily show you what features or fixes went into a particular release
+    or what work remains before a particular milestone is reached.</li>
+
+    <li>A <q title="http://trac.edgewall.org/wiki/TracWiki">wiki</q> which
+    can be used for project documentation.</li>
+
+    <li>A browser for viewing your Subversion repository which allows you
+    to browse the project tree / files and examine revision logs and
+    changesets.</li>
+
+    <li>A timeline view which summarises all the activity on a project
+    (changes to the tickets, wiki pages or the Subversion repository).</li>
+  </ul>
+
+  <h2 id="build-and-extract">Build and Extract</h2>
+
+  <p>FCM features a powerful build system, mainly aimed at building modern
+  Fortran software applications. It has the following features:</p>
+
+  <ul>
+    <li>Parallel build.</li>
+
+    <li>Efficient incremental build. Changes to the MD5 checksums of source
+    files and/or the build configuration (e.g. changes to the compiler
+    flags) trigger the appropriate re-compilation.</li>
+
+    <li>Inheritance of items from an existing build.</li>
+
+    <li>Build dependency analysis.</li>
+
+    <li>Automatic generation of include files to contain the calling
+    interfaces of standalone functions and subroutinues in Fortran source
+    files.</li>
+
+    <li>Extract of source files from multiple repositories and working
+    copies.</li>
+
+    <li>Extract and merge of source files from different branches of
+    development.</li>
+
+    <li>Minimal configuration.</li>
+  </ul>
+
+  <h2 id="illustration">Illustration</h2>
+
+  <p>The diagram below illustrates how these components fit together.</p>
+
+  <p><img class="img-polaroid" src="fcm_overview.png"
+  alt="FCM system overview" /></p>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/system_admin.html b/doc/user_guide/system_admin.html
new file mode 100644
index 0000000..86b2c99
--- /dev/null
+++ b/doc/user_guide/system_admin.html
@@ -0,0 +1,612 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: System Administration</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: System Administration</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <h2 id="introduction">Introduction</h2>
+
+  <p>This chapter provides an administration guide for managers of projects or
+  systems which are using FCM.</p>
+
+  <p>Note that, where this section refers to the <em>FCM team</em> this applies
+  only to Met Office users. Users at other sites will either need to refer to the
+  equivalent team within their organisation or will need to perfom these tasks
+  themselves.</p>
+
+  <h2 id="svn">Subversion</h2>
+
+  <h3 id="svn_layout">Repository Layout</h3>
+
+  <p>In theory you can set up your repository to have any random layouts.
+  However, many <code>fcm</code> commands have to make assumptions on a set of
+  working practices in order to function. The easiest way is to go with the
+  default:</p>
+  <pre>
+<root>
+    |
+    |-- <project 1>
+    |       |
+    |       |-- trunk
+    |       |-- branches
+    |       |-- tags
+    |
+    |-- <project 2>
+    |       |
+    |       |-- trunk
+    |       |-- branches
+    |       |-- tags
+    |
+    |-- ...
+</pre>
+  
+  <p>In the default layout, each project is a sub-directory under the repository
+  root. Each project has a <code>trunk</code> sub-directory, and optionally a
+  <code>branches</code> sub-directory and a <code>tags</code> sub-directory. The
+  main line of development of the project lives directly under the
+  <code>trunk</code> sub-directory. A branch lives 3 levels under the
+  <code>branches</code> sub-directory. A tag lives 1 level under the
+  <code>tags</code> sub-directory.</p>
+
+  <p>FCM allows you to customise the layout for each repository by adding an
+  <code>fcm:layout</code> property at the HEAD of root of the repository.
+  E.g.:</p>
+
+  <pre>
+(shell prompt)$ fcm co -q -N svn://host/repos repos-root
+(shell prompt)$ cd repos-root
+(shell prompt)$ fcm pe fcm:layout .
+</pre>
+
+  <p>The default settings are given in the following. In the editor started by
+  <code>fcm pe</code>, add these settings and modify the VALUE of each KEY=VALUE
+  pair.</p>
+
+  <pre>
+depth-project =
+depth-branch = 3
+depth-tag = 1
+dir-trunk = trunk
+dir-branch = branches
+dir-tag = tags
+level-owner-branch = 2
+level-owner-tag =
+template-branch = {category}/{owner}/{name_prefix}{name}
+template-tag =
+</pre>
+
+  <p>The settings will become effective when you <code>fcm commit</code> them.
+  An empty VALUE denotes an undefined value. The meanings of the settings are
+  described below:</p>
+
+  <dl>
+    <dt><code>depth-project</code></dt>
+
+    <dd>Number of sub-directories expected to be used by the name of a project.
+    An undefined value means that a project can live under an
+    arbitrary number of sub-directories (or directly) below the repository
+    root.</dd>
+
+    <dt><code>depth-branch</code></dt>
+
+    <dd>Number of sub-directories (under the sub-directory defined by
+    <code>dir-branch</code>) expected to be used by the name of a branch. This
+    setting must be defined.</dd>
+
+    <dt><code>depth-tag</code></dt>
+
+    <dd>Number of sub-directories (under the sub-directory defined by
+    <code>dir-tag</code>) expected to be used by the name of a tag. This
+    setting must be defined.</dd>
+
+    <dt><code>dir-trunk</code></dt>
+
+    <dd>The sub-directory (under the project) where the trunk source tree lives.
+    This setting must be defined.</dd>
+
+    <dt><code>dir-branch</code></dt>
+
+    <dd>The sub-directory (under the project) where (the sub-directories
+    containing) all the branch source trees live.</dd>
+
+    <dt><code>dir-tag</code></dt>
+
+    <dd>The sub-directory (under the project) where (the sub-directories
+    containing) all the tag source trees live.</dd>
+
+    <dt><code>level-owner-branch</code></dt>
+
+    <dd>The sub-directory level in the name of a branch containing its owner.</dd>
+
+    <dt><code>level-owner-tag</code></dt>
+
+    <dd>The sub-directory level in the name of a tag containing its owner.</dd>
+
+    <dt><code>template-branch</code></dt>
+
+    <dd>The template string to construct a branch name.</dd>
+
+    <dt><code>template-tag</code></dt>
+
+    <dd>The template string to construct a tag name.</dd>
+  </dl>
+
+  <p>You will need to decide whether to use a single project tree for your
+  system or whether to use multiple projects.</p>
+
+  <p>Advantages of a single project tree:</p>
+
+  <ul>
+    <li>Changes to any part of the system can always be committed as a single
+    logical changeset. If you split your system into multiple projects then you
+    may have occasions when a logical change involves more than one project and
+    hence requires multiple commits (and branches).</li>
+  </ul>
+
+  <p>Disadvantages of a single project tree:</p>
+
+  <ul>
+    <li>If you have a large system then your working copies may become very
+    large and unwieldy. Basic commands such as <code>checkout</code> and
+    <code>status</code> can become frustratingly slow if your working copy is
+    too large.</li>
+
+    <li>Depending on how you work, you may end up doing lots more merges of
+    files that are unrelated to your work.</li>
+  </ul>
+
+  <p>One common approach is to split the admin type files (e.g. site
+  configurations that are unrelated to the main release) into a separate project
+  from the core system files. If you include any large data files under version
+  control you may also want to use a separate project for them to avoid making
+  your working copies very large when editing code.</p>
+
+  <p>Note that there is often no obvious right or wrong answer so you just have
+  to make a decision and see how it works out. You can always re-arrange your
+  repository in the future (although be aware that this will break any changes
+  being prepared on branches at the time).</p>
+
+  <p>You also need to decide whether your system requires its own repository
+  (or multiple repositories) or whether it can share with another system.</p>
+
+  <ul>
+    <li>The main disadvantage of having separate repositories for each system
+    is the maintenance overhead (although this is almost all automated by the
+    FCM team so is not a big deal).</li>
+
+    <li>We normally configure a single Trac environment per repository. If the
+    repository contains multiple systems then it makes it difficult to use the
+    Trac milestones to handle system releases. However, Trac now supports
+    restricting itself to a sub-directory within a repository so, again, this
+    is not a big deal.</li>
+
+    <li>If you share a repository with other systems then your revision numbers
+    can increase even when there are no changes to your system. This doesn't
+    matter but some people don't like it.</li>
+  </ul>
+
+  <p>For simplicity, in most cases you will probably want your own repository
+  for your system.</p>
+
+  <p>You will not normally want to have multiple repositories for a system. One
+  exception may be if you are storing large data files where you might not want
+  to keep all the old versions for ever. Removing old versions can't be done
+  without changing all the revision numbers which would mess up all your code
+  history and Trac tickets. Storing the large data files in a separate
+  repository reduces the impact if you do decide to remove old versions in the
+  future. One disadvantage of this approach is that, for the moment at least,
+  Trac only handles one repository so you will need a separate Trac environment for
+  the data files.</p>
+
+  <p>For further details please see the section <a href=
+  "http://svnbook.red-bean.com/en/1.8/svn.reposadmin.planning.html#svn.reposadmin.projects.chooselayout">
+  Planning Your Repository Organization</a> from the Subversion book.</p>
+
+  <h3 id="svn_create">Creating a Repository</h3>
+
+  <p>Normally the FCM team will help you to set up your initial repository.
+  However, it is quite simple if you need to do it yourself. First you need to
+  issue the command <code>svnadmin create /path/to/repos</code>. This creates an
+  empty repository which is now ready to accept an initial import. To do so, you
+  should create a directory tree in a suitable location, and issue the <code>fcm
+  project-create</code> command. At the root of the repository should be the
+  project directories. Each project should then contain the <samp>trunk</samp>
+  sub-directory.  The sub-directories <samp>branches</samp> and
+  <samp>tags</samp> are optional.  You can import your source files to the
+  <samp>trunk</samp> after the project is created. For example, if your
+  directory tree is located at <samp>$HOME/foo</samp>, you will do the following
+  to import it to a new repository:</p>
+  <pre>
+(SHELL PROMPT)$ svnadmin create FOO_svn
+(SHELL PROMPT)$ fcm project-create FOO file://$PWD/FOO_svn
+(SHELL PROMPT)$ fcm checkout file://$PWD/FOO_svn/FOO $HOME/svn-wc/foo
+(SHELL PROMPT)$ cd $HOME/svn-wc/foo
+(SHELL PROMPT)$ cp -r $HOME/foo/* .
+(SHELL PROMPT)$ fcm add *
+(SHELL PROMPT)$ fcm status
+(SHELL PROMPT)$ fcm commit
+</pre>
+
+  <p>Note that the <code>svnadmin</code> command takes a <var>PATH</var> as an
+  argument, as opposed to a URL for the <code>svn</code> command.</p>
+
+  <p>For further details please see the section <a href=
+  "http://svnbook.red-bean.com/en/1.8/svn.reposadmin.planning.html#svn.reposadmin.projects.chooselayout">
+  Planning Your Repository Organization</a> from the Subversion book.</p>
+
+  <h3 id="svn_access">Access Control</h3>
+
+  <p>Restrictions such as preventing anonymous read access or restricting write
+  access to the trunk to a limited set of users can be arranged if
+  necessary.</p>
+
+  <h3 id="svn_hosting">Repository Hosting</h3>
+
+  <p>The FCM team will organise the hosting of your repository. A number of
+  facilities will be set up for you as standard.</p>
+
+  <p>Your repository will be set up on a central FCM server and access will be
+  provided via <code>svnserve</code> (which we use in preference to
+  <cite>Apache</cite> for performance reasons). The FCM team will advise you of
+  the URL, and put in place standard hook scripts and backup procedures.</p>
+
+  <p>Note that if you want to use a Subversion repository for your own
+  individual use there is no need to get the FCM team to host it. You can
+  simply create your repository and then use a <code>file://</code> URL to
+  access it.</p>
+
+  <h2 id="trac">Trac</h2>
+
+  <h3 id="trac_config">Trac Configuration</h3>
+
+  <p>Normally the FCM team will set up your Trac environment for you. This
+  section describes some things you may wish to be configured. This can be done
+  when the Trac environment is set up or later if you are unsure what you will
+  require at first.</p>
+
+  <h4 id="trac_access">Access Control</h4>
+
+  <p>You will not normally want to allow anonymous users to make changes to
+  your Trac environment since this means that changes may not get identified with a
+  userid. The FCM team will normally set up your Trac environment such that any
+  authenticated users can make changes. Further restrictions such as
+  restricting write access to named accounts or preventing anonymous read
+  access can be arranged if necessary.</p>
+
+  <p>The system manager will normally be given <var>TRAC_ADMIN</var>
+  privileges. This allows them to do additional things which normal users
+  cannot do such as:</p>
+
+  <ul>
+    <li>Delete wiki pages (the latest version or the entire page).</li>
+
+    <li>Add or modify milestones, components and versions.</li>
+
+    <li>Modify ticket descriptions and delete ticket attachments.</li>
+
+    <li>Make wiki pages read-only.</li>
+
+    <li>Alter the permissions.</li>
+  </ul>
+
+  <p>For further details please see the section <a href=
+  "http://trac.edgewall.org/wiki/TracPermissions">Trac Permissions</a> from the
+  Trac documentation.</p>
+
+  <h4 id="trac_email">Email Notification</h4>
+
+  <p>By default, each Trac environment is configured such that the owner and
+  reporter and anyone on the <var>CC</var> list are notified whenever a change
+  is made to a ticket. If system mangers wish to be notified of all ticket
+  changes then this can also be configured. Alternatively, email notifications
+  can be disabled if they are not wanted.</p>
+
+  <h4 id="trac_misc">Other Configurable Items</h4>
+
+  <p>There are lots of other things that can be configured in your Trac
+  environment
+  such as:</p>
+
+  <ul>
+    <li>Custom fields</li>
+
+    <li>System icon</li>
+
+    <li>Stylesheets</li>
+  </ul>
+
+  <p>For further details please see the sections <a href=
+  "http://trac.edgewall.org/wiki/TracIni">The Trac Configuration File</a> and
+  <a href="http://trac.edgewall.org/wiki/TracTickets">The Trac Ticket
+  System</a> from the Trac documentation.</p>
+
+  <h3 id="trac_hosting">Trac Hosting</h3>
+
+  <p>The FCM team will organise the hosting of your Trac environment. It will be set
+  up on the same server that hosts your Subversion repository and access will
+  be provided via a web server. The FCM team will advise you of the URL, and put
+  in place the backup procedures.</p>
+
+  <h2 id="fcm-keywords">FCM Keywords</h2>
+
+  <p>When you set up a repository for a new project, you will normally want the
+  FCM team to set up a URL keyword for it in the FCM central configuration
+  file. The name of the project should be a short string containing only word
+  characters.</p>
+
+  <p>Individual projects can store revision keywords using the Subversion
+  property <code>fcm:revision</code> at registered URLs. Using the UM as an
+  example: if <samp>UM</samp> is a registered URL keyword, you can add the
+  <code>fcm:revision</code> property at the head of the UM project by doing a
+  non-recursive checkout. E.g.:</p>
+  <pre>
+(prompt)$ fcm co -q -N fcm:um um
+(prompt)$ fcm pe fcm:revision um
+</pre>
+
+  <p>In the editor, add the following and <code>fcm commit</code>:</p>
+  <pre>
+vn6.3 = 402
+vn6.4 = 1396
+vn6.5 = 2599
+vn6.6 = 4913
+vn7.0 = 6163
+</pre>
+
+  <p>In a subsequent invocation of <code>fcm</code>, if a revision keyword is
+  specified for a URL in the UM namespace, the command will attempt to load it
+  from the <code>fcm:revision</code> property at the head of the UM project.
+  Revision keywords can also be defined in the FCM central configuration file
+  if you prefer.</p>
+
+  <p>If the project has an associated Trac browser, you can also declare
+  browser URL mapping in the central configuration file. This allows FCM to
+  associate the Subversion URL with a Trac browser URL. There is an automatic
+  default for mapping URLs hosted by the FCM team at the Met Office. External
+  users of FCM may want to adjust this default for their site.</p>
+
+  <p>To change the default browser URL mapping, you need to make some
+  <code>browser.*[namespace] = value</code> declarations in your site's
+  <samp>$FCM/etc/fcm/keyword.cfg</samp> file. There are 3 components to this
+  declaration: <var>browser.comp-pat</var>, <var>browser.loc-tmpl</var> and
+  <var>browser.rev-tmpl</var>. The <var>browser.comp-pat</var> is a regular
+  expression, which is used to separate the scheme-specific part of a version
+  control system URL into a number of components by capturing its substrings.
+  These components are then used to fill in the numbered fields in the
+  <var>browser.loc-tmpl</var>. The template should have one more field than the
+  number of components captured by <var>browser.comp-pat</var>. The last field
+  is used to place the revision, which is generated via the
+  <var>browser.rev-tmpl</var>. This template should have a single numbered
+  field for filling in the revision number. This is best demonstrated by an
+  example. Consider the declarations:</p>
+  <pre>
+browser.comp-pat = (?msx-i:\A//([^/]+)/(.*)\z)
+browser.loc-tmpl = http://{1}/intertrac/source:{2}{3}
+browser.rev-tmpl = @{1}
+</pre>
+
+  <p>If we have a Subversion URL <samp>svn://repos/path/to/a/file</samp>, the
+  <var>browser.comp-pat</var> will capture the components [<samp>repos</samp>,
+  <samp>path/to/a/file</samp>]. When this is applied to the
+  <code>browser.loc-tmpl</code>, <var>{1}</var> will be translated to
+  <samp>repos</samp> and <var>{2}</var> will be translated to
+  <samp>path/to/a/file</samp>. A revision is not given in this case, and so
+  <var>{3}</var> is inserted with an empty string. The result is
+  <samp>http://repos/intertrac/path/to/a/file</samp>. If the revision is
+  <samp>1357</samp>, the <var>browser.rev-tmpl</var> will be used to translate
+  it to <samp>@1357</samp>, which is then inserted to <var>{3}</var> of the
+  <var>browser.loc-tmpl</var>. The result is therefore
+  <samp>http://repos/intertrac/path/to/a/file@1357</samp>.</p>
+
+  <p>For more information on how to set up the keywords, please refer to
+  <a href="code_management.html#svn_basic_keywords">Repository & Revision
+  Keywords</a> and the <a href="annex_cfg.html">Annex: FCM Configuration
+  File</a>.</p>
+
+  <h2 id="make-cfg">FCM Make Configuration</h2>
+
+  <p>The <code>fcm make</code> command (for invoking the extract and build
+  systems) is very flexibile and can be used in lots of different ways. It is
+  therefore difficult to give specific advice explaining how to configure them.
+  However, based on experience with a number of systems, the following general
+  advice can be offered.</p>
+
+  <ul>
+    <li>Standard FCM Make configuration files should be defined and stored
+    within the repository. Users then include these files into their
+    configurations, before applying their local changes.</li>
+
+    <li>The files should be designed to include one another in a hierarchy. For
+    example, you may have one core file which defines all the repository and
+    source locations plus a series of platform/compiler specific files which
+    include the core file. More complex setups are also possible if you need to
+    cater for other options such as different optimisation levels, 32/64 bit,
+    etc.</li>
+
+    <li>When including other configuration files, always make use of the
+    special <var>$HERE</var> variable (rather than, for instance, referring to
+    a fixed repository location). When your configuration file is parsed, this
+    special variable is normally expanded into the container directory of the
+    current configuration file. This means that the include statements should
+    work correctly whether you are referring to configuration files in the
+    repository trunk, in a branch or in a local working copy.</li>
+
+    <li>Make good use of variables (e.g. <samp>$name_spaces</samp>) to simplify
+    repetitive declarations and make your configuration files easier to
+    maintain.</li>
+
+    <li>Use continuation lines to split long lines and make them easier to
+    read.</li>
+  </ul>
+
+  <p>Probably the best advice is to look at what has already been set up for
+  other systems. The FCM team can advise on the best systems to examine.</p>
+
+  <p>When you create a stable build you should keep a FCM Make configuration
+  file that can reproduce the build. One easy way to do this is to create your
+  build using the standard configuration files and the latest versions of the
+  code.  You can then save the configuration file which is created on
+  success.</p>
+
+  <h2 id="alternate_versions">Maintaining Alternate Versions of Namelists and
+  Data Files</h2>
+
+  <p>Sometimes it is useful to be able to access particular revisions of some
+  directories from a FCM repository without having to go via Subversion.
+  Typical examples are namelist or data files used as inputs to a program. The
+  <code>fcm export-items</code> command is designed to help with this. It can
+  be used to maintain a set of extracted version directories from a FCM
+  repository. The command has the following options:</p>
+
+  <dl>
+    <dt><code>--config-file=PATH</code>, <code>--file=PATH</code>, <code>-f
+    PATH</code></dt>
+
+    <dd>Specifies the path to the configuration file.
+    (default=<samp>$PWD/fcm-export-items.cfg</samp>)</dd>
+
+    <dt><code>--directory=PATH</code>, <code>-C PATH</code></dt>
+
+    <dd>Specifies the path to the destination. (default=<samp>$PWD</samp>)</dd>
+
+    <dt><code>--new</code></dt>
+
+    <dd>Specifies the new mode. In this mode, everything is re-exported.
+    Otherwise, the system runs in incremental mode, in which the version
+    directories are only updated if they do not already exist.</dd>
+  </dl>
+
+  <p>The 1st argument SOURCE should be the URL of a branch in a Subversion
+  repository with the standard FCM layout.</p>
+
+  <p>The configuration file should be in the deprecated FCM 1 configuration
+  format. The label in each entry should be a path relative to the source URL.
+  If the path ends in <samp>*</samp> then the path is expanded recursively and
+  any sub-directories containing regular files are added to the list of relative
+  paths to extract. The value may be empty, or it may be a list of space
+  separated <em>conditions</em>. Each condition is a conditional operator
+  (<code>></code>, <code>>=</code>, <code><</code>,
+  <code><=</code>, <code>==</code> or <code>!=</code>) followed by a
+  revision number. The command uses the revision log to determine the revisions
+  at which the relative path has been updated in the source URL. If these
+  revisions also satisfy the conditions set by the user, they will be
+  considered in the export.</p>
+
+  <p>Example:</p>
+  <pre>
+(SHELL PROMPT)$ cat >fcm-export-items.cfg <<EOF
+namelists/VerNL_AreaDefinition   >1000 !=1234
+namelists/VerNL_GRIBToPPCode     >=600 <3000
+namelists/VerNL_StationList      
+elements/*                       >1000
+EOF
+(SHELL PROMPT)$ fcm export-items fcm:ver_tr
+</pre>
+
+  <p>N.B.</p>
+
+  <ol>
+    <li>Each time a sub-directory is revised, the script assigns a sequential
+    <em>v</em> number for the item. Each <em>v</em> number for a sub-directory,
+    therefore, is associated with a revision number. For each exported
+    revision directory, there is a corresponding <em>v</em> number symbolic
+    link pointing to it.</li>
+
+    <li>The system also creates a symbolic link <samp>latest</samp> to point to
+    the latest exported revision directory.</li>
+  </ol>
+
+  <h2 id="work-practice">Defining Working Practices and Policies</h2>
+
+  <p>Some options on working practices and policies are defined in the chapter
+  on <a href="working_practices.html">Code Management Working Practices</a>.
+  Individual projects should document the approach they have adopted. In
+  addition, each project may also need to define its own working practices and
+  policies to suit its local need. For example each project may need to
+  specify:</p>
+
+  <ul>
+    <li>Whether changes are allowed directly on the trunk or whether branches
+    have to be used in all cases.</li>
+
+    <li>Whether all users are allowed to make changes to the trunk.</li>
+
+    <li>Whether Trac tickets have to be raised for all changes to the
+    trunk.</li>
+
+    <li>Whether Trac tickets should be raised for all support queries or
+    whether a Trac ticket should only be raised once there is an agreed
+    "issue".</li>
+
+    <li>Whether branches should normally be made from the latest code or from a
+    stable release.</li>
+
+    <li>Whether a user is allowed to resolve conflicts directly when merging a
+    branch into the trunk or whether he/she should merge the trunk into the
+    branch and resolve the conflicts in the branch first.</li>
+
+    <li>Whether all code changes to the trunk need to be reviewed.</li>
+
+    <li>What testing is required before changes can be merged to the
+    trunk.</li>
+
+    <li>Whether history entries are maintained in source files or whether
+    individual source files changes need to be described in the Subversion log
+    message.</li>
+
+    <li>Branch deletion policy.</li>
+
+    <li>Whether any files in the project require locking before being
+    changed.</li>
+  </ul>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/working_practices.html b/doc/user_guide/working_practices.html
new file mode 100644
index 0000000..a6a4204
--- /dev/null
+++ b/doc/user_guide/working_practices.html
@@ -0,0 +1,822 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FCM: User Guide: Code Management Working Practices</title>
+  <meta name="author" content="FCM team" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link rel="shortcut icon" href="../etc/fcm-icon.png" type="image/png" />
+  <link href="../etc/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+  <link href="../etc/fcm.css" rel="stylesheet" media="screen" />
+</head>
+<body>
+  <div class="navbar navbar-inverse">
+    <div class="navbar-inner">
+      <a class="brand" href=".."><span class="fcm-version">FCM</span></a>
+      <ul class="nav">
+        <li><a href="../installation/">Installation</a></li>
+
+        <li class="active"><a href=".">User Guide</a></li>
+      </ul>
+    </div>
+  </div>
+
+  <div class="page-header">
+    <div class="fcm-page-content pull-right well well-small"></div>
+    <h1>FCM: User Guide: Code Management Working Practices</h1>
+  </div>
+
+  <div class="container">
+  <div class="row">
+  <div class="span12">
+
+  <h2 id="introduction">Introduction</h2>
+
+  <p>The previous chapter described how to use the various parts of the FCM
+  code management system. They also described aspects of working practices
+  which are enforced by the system. This section discusses other recommended
+  working practices. They are optional in the sense that you don't have to
+  follow them to use FCM. It is a matter for individual projects to decide
+  which working practices to adopt (although we expect most projects/systems
+  using FCM to adopt similar practices).</p>
+
+  <h2 id="changes">Making Changes</h2>
+
+  <p>This sub-section gives an overview of the recommended approach for
+  preparing changes. Particular topics are discussed in more detail in later
+  sub-sections where appropriate.</p>
+
+  <p>The recommended process for making a change is as follows:</p>
+
+  <ol>
+    <li>Before work starts on any coding you should make sure that there is a
+    Trac ticket open which explains the purpose of the change.
+
+      <ul>
+        <li>Make sure that you set the ticket milestone to indicate which
+        release of the system you are aiming to include your change in.</li>
+
+        <li>Accept the ticket to indicate that you are working on the
+        change.</li>
+
+        <li>For further advice on using tickets see <a href="#tickets">Trac
+        Tickets</a> later in this section.</li>
+      </ul>
+    </li>
+
+    <li>Create a branch
+
+      <ul>
+        <li>For very simple changes you may be happy to prepare your changes
+        directly on the trunk. For further details see <a href=
+        "#branching_when">When to Branch</a> later in this section.</li>
+
+        <li>Create your branch either from the latest revision or from a stable
+        release (see <a href="#branching_where">Where to Branch From</a> later
+        in this section).</li>
+      </ul>
+    </li>
+
+    <li>Prepare your code changes on the branch
+
+      <ul>
+        <li>Commit interim versions to your branch on a regular basis as you
+        develop your change. This makes it much easier to keep track of what
+        you're changing and to revert changes if necessary.</li>
+
+        <li>You may wish to merge in changes from the trunk. For further
+        details see <a href="#branching_update">Merging From the Trunk</a>
+        later in this section.
+
+          <ul>
+            <li>Make sure that you always commit any local changes to your
+            branch before doing a merge. Otherwise it becomes impossible to
+            distinguish your changes from those you have merged in. It is also
+            impossible to revert the merge without losing your local
+            changes.</li>
+
+            <li>Likewise, always commit the merge to your branch (after
+            resolving any conflicts) before making any further changes.</li>
+          </ul>
+        </li>
+
+        <li>Don't include unrelated changes. If you want to make some changes
+        which aren't really associated with your other changes then use a
+        separate ticket and branch for these changes.</li>
+      </ul>
+    </li>
+
+    <li>Once your changes are ready for review, update the Trac ticket to
+    record which revision of the branch is to be reviewed and assign the ticket
+    to your reviewer.</li>
+
+    <li>If the reviewer is happy with the change then he/she should update the
+    ticket to record that the change is approved and assign the ticket back to
+    you.
+
+      <ul>
+        <li>The reviewer can use the command <code>fcm branch-diff
+        <branch_name></code> to examine all of the changes on the
+        branch.</li>
+
+        <li>If changes are necessary then these should be prepared and then the
+        ticket updated to refer to the new revision under review.</li>
+      </ul>
+    </li>
+
+    <li>Once the change is approved it can be merged back to the trunk
+
+      <ul>
+        <li>If you have been merging the latest changes from the trunk onto
+        your branch then the merge should be automatic. If not you may have
+        conflicts to resolve.</li>
+
+        <li>Make sure that each merge is a separate commit to the trunk. i.e.
+        Don't combine changes from several branches in one commit. This makes
+        it easier to reverse changes if necessary. It also makes the changeset
+        easier to understand.</li>
+
+        <li>Make sure that you use a good log message to describe your change.
+        For further details see <a href="#messages">Commit Log Messages</a>
+        later in this section.</li>
+
+        <li>Once the changes are commited, update the ticket to refer to the
+        changeset. Then the ticket can be closed.</li>
+      </ul>
+    </li>
+
+    <li>Once you are finished with the branch it should be deleted.</li>
+  </ol>
+
+  <h2 id="wc">Working Copies</h2>
+
+  <p>Some points to consider regarding working copies:</p>
+
+  <ol>
+    <li>If the size of your project is small then you will probably find it
+    easiest to work with a complete copy of the project (either the trunk or
+    your branch). This means that you always have immediate access to all the
+    files and that you are always able to perform merges using your normal
+    working copy.</li>
+
+    <li>If you have a large project then you may prefer to work on a sub-tree
+    of your project.
+
+      <p><dfn>Pros:</dfn></p>
+
+      <ul>
+        <li>Subversion operations on your working copy are faster.</li>
+
+        <li>Your working copies use up less disk space. Remember that you may
+        be working on several changes at once on separate branches so you may
+        wish to have several working copies.</li>
+      </ul>
+
+      <p><dfn>Cons:</dfn></p>
+
+      <ul>
+        <li>You cannot always perform merge operations in sub-trees (if the
+        changes which need to be merged include files outside of your
+        sub-tree). To handle this we suggest that if you need to perform a
+        merge using a complete copy of your project you check it out in your
+        <var>$LOCALDATA</var> area (local disk space which is not backed up) to
+        be used purely for doing the merge.</li>
+
+        <li>You may find that your change involves more files than you
+        originally thought and that some of the files to be changed lie outside
+        of your working copy. You then have to make sure that you have
+        committed any changes before checking out a larger working copy.</li>
+      </ul>
+    </li>
+  </ol>
+
+  <h2 id="branching">Branching & Merging</h2>
+
+  <h3 id="branching_when">When to Branch</h3>
+
+  <p>If you are making a reasonably large change which will take more than a
+  hour or two to prepare then there are clear advantages to doing this work on
+  a branch.</p>
+
+  <ul>
+    <li>You can commit intermediate versions to the branch.</li>
+
+    <li>If you need to merge in changes from the trunk then you have a record
+    of your files prior to the merge.</li>
+
+    <li>The version of the code which gets reviewed is recorded. If subsequent
+    changes are required then only those changes will need reviewing.</li>
+  </ul>
+
+  <p>However, if you are only making a small change (maybe only one line)
+  should you create a branch for this? There are two possible approaches:</p>
+
+  <dl>
+    <dt>Always Branch</dt>
+
+    <dd>
+      <p>ALL coding changes are prepared on branches.</p>
+
+      <p><dfn>Pros:</dfn> Same process is followed in all cases.</p>
+
+      <p><dfn>Cons:</dfn> The extra work required to create the branch and
+      merge it back to the trunk may seem unnecessary for a very small
+      change.</p>
+    </dd>
+
+    <dt>Branch When Needed</dt>
+
+    <dd>
+      <p>Small changes can be committed directly to the trunk (after testing
+      and code review).</p>
+
+      <p><dfn>Pros:</dfn> Avoids the overhead of using branches.</p>
+
+      <p><dfn>Cons:</dfn> Danger of underestimating the size of a change. What
+      you thought was a small change may turn out to be larger than you thought
+      (although you can always move it onto a branch if this happens).</p>
+    </dd>
+  </dl>
+
+  <p>This is a matter for project policy although, in general, we would
+  recommend the <cite>Branch When Needed</cite> approach.</p>
+
+  <h3 id="branching_where">Where to Branch From</h3>
+
+  <p>When you create a new branch you have two choices for which revision to
+  create the branch from:</p>
+
+  <dl>
+    <dt>The latest revision of the trunk</dt>
+
+    <dd>
+      <p>This is the preferred choice where possible. It minimised the chances
+      of conflicts when you need to incorporate your changes back onto the
+      trunk.</p>
+    </dd>
+
+    <dt>An older revision of the trunk</dt>
+
+    <dd>
+      <p>There are a number of reasons why you may need to do this. For
+      example:</p>
+
+      <ul>
+        <li>You are using a stable version to act as your <em>control</em>
+        data.</li>
+
+        <li>You need to know that your baseline is well tested (e.g. scientific
+        changes).</li>
+
+        <li>Your change may need to be merged with other changes relative to a
+        stable version for testing purposes or for use in a package (see
+        <a href="#packages">Creating Packages</a> later in this section).</li>
+      </ul>
+    </dd>
+  </dl>
+
+  <h3 id="branching_update">Merging From the Trunk</h3>
+
+  <p>Once you've created your branch you need to decide whether you now work in
+  isolation or whether you periodically merge in the latest changes from the
+  trunk.</p>
+
+  <ul>
+    <li>Regularly merging from the trunk minimises the work involved when you
+    are ready to merge back to the trunk. You deal with any merge issues as you
+    go along rather than all at the end (by which time your branch and the
+    trunk could have diverged significantly).</li>
+
+    <li>One downside of merging from the trunk is that the baseline for your
+    changes is a moving target. This may not be what you want if you have some
+    <em>control</em> results that you have generated.</li>
+
+    <li>Another downside of merging from the trunk is that it may introduce
+    bugs. Although any code on the trunk should have been tested and reviewed
+    it is unlikely to be as well tested as code from a stable release.</li>
+
+    <li>Unless you originally created your branch from the latest revision of
+    the trunk it is unlikely that you are going to want to merge in changes
+    from the trunk. The exception to this is once your change is complete when
+    it may make sense to merge all the changes on the trunk into your branch as
+    a final step. This is discussed in <a href="#branching_commit">Merging Back
+    to the Trunk</a> below.</li>
+  </ul>
+
+  <p>So, there are basically three methods of working:</p>
+
+  <dl>
+    <dt>Branch from a stable version and prepare all your changes in
+    isolation</dt>
+
+    <dd>Necessary if you need to make your change relative to a well tested
+    release.</dd>
+
+    <dt>Branch from the latest code but then prepare all your changes in
+    isolation</dt>
+
+    <dd>Necessary if you need a stable baseline for your <em>control</em>
+    data.</dd>
+
+    <dt>Branch from the latest code and then update your branch from the trunk
+    on a regular basis</dt>
+
+    <dd>This is considered <em>best practice</em> for parallel working and
+    should be used where possible.</dd>
+  </dl>
+
+  <h3 id="branching_commit">Merging Back to the Trunk</h3>
+
+  <p>Before merging your change back to the trunk you will need to test your
+  change and get it reviewed. There are two options for what code to test and
+  review:</p>
+
+  <dl>
+    <dt>Test and review your changes in isolation, then merge to the trunk and
+    deal with any conflicts</dt>
+
+    <dd>
+      <p>This may be the best method if:</p>
+
+      <ul>
+        <li>Your changes have already been tested against a stable baseline and
+        re-testing after merging would be impracticable.</li>
+
+        <li>Your branch needs to be available for others to merge in its
+        changes in isolation.</li>
+      </ul>
+    </dd>
+
+    <dt>Merge in the latest code from the trunk before your final test and
+    review</dt>
+
+    <dd>
+      <p>This has the advantage that you are testing and reviewing the actual
+      code which will be committed to the trunk. However, it is possible that
+      other changes could get committed to the trunk whilst you are completing
+      your testing and review. There are several ways of dealing with this:</p>
+
+      <ul>
+        <li>Use locking to prevent it happening. The danger with this is that
+        you may prevent others from being able to get their change tested and
+        reviewed, hence inhibiting parallel devlopment.</li>
+
+        <li>Insist that the change is re-tested and reviewed. The problem with
+        this is that there is no guarantee that the same thing won't happen
+        again.</li>
+
+        <li>Merge in the new changes but don't insist on further testing or
+        review.
+
+          <ul>
+            <li>In most cases any changes won't clash so there is little to
+            worry about.</li>
+
+            <li>Where there are clashes then, in most cases, they will be
+            trivial with little danger of any side-effects.</li>
+
+            <li>Where the clashes are significant then, in most cases, this
+            will be very obvious whilst you are resolving the conflicts. In
+            this case you should repeat the testing and get the updates
+            reviewed.</li>
+          </ul>This is the recommended approach since it doesn't inhibit
+          parallel development and yet the chances of a bad change being
+          committed to the trunk are still very small.
+        </li>
+      </ul>
+
+      <p>You should also consider what can be done to minimise the time taken
+      for testing and review.</p>
+
+      <ul>
+        <li>Try to keep your changes small by breaking them down where
+        possible. Smaller changes are easier and quicker to review. This also
+        helps to minimise merge problems by getting changes back onto the trunk
+        earlier.</li>
+
+        <li>Automate your testing as far as possible to speed up the
+        process.</li>
+      </ul>
+    </dd>
+  </dl>
+
+  <p>Most projects will require the developer who prepared the change to merge
+  it back to the trunk once it is complete. However, larger projects may wish
+  to consider restricting this to a number of experienced / trusted
+  developers.</p>
+
+  <ul>
+    <li>This makes it easier to control and prioritise the merges.</li>
+
+    <li>It applies an extra level of quality control.</li>
+
+    <li>It minimises the risk of mistakes being merged back on to the trunk by
+    less experienced developers</li>
+
+    <li>Scientific developers can concentrate on the scientific work.</li>
+
+    <li>One issue is that the person doing the merge to the trunk may need help
+    from the original developer to prepare a suitable log message.</li>
+  </ul>
+
+  <h3 id="branching_delete">When to Delete Branches</h3>
+
+  <p>Once you are finished with your branch it is best to delete it to avoid
+  cluttering up the directory tree (remember that the branch and all its
+  history will still be available). There are two obvious approaches to
+  deleting branches:</p>
+
+  <dl>
+    <dt>Delete the branch as soon as it has been merged back to the trunk
+    (prior to closing any associated Trac ticket)</dt>
+
+    <dd>This is the tidiest approach which minimises the chances of old
+    branches being left around.</dd>
+
+    <dt>Delete the branch once a stable version of the system has been released
+    which incorporates your change</dt>
+
+    <dd>If a bug is found in your change during integration testing then you
+    can prepare the fix on the original branch (without having to do any
+    additional work to restore the branch).</dd>
+  </dl>
+
+  <h2 id="binary">Working with Binary Files</h2>
+
+  <p>The <code>fcm conflicts</code> command and <code>xxdiff</code> can only
+  help you resolve conflicts in text files. If you have binary files in your
+  repository you need to consider whether conflicts in these files would cause
+  a problem.</p>
+
+  <h3 id="binary_conflicts">Resolving Conflicts in Binary Files</h3>
+
+  <p>Conflicts in some types of binary files can be resolved manually. When you
+  are satisfied that the conflicts are resolved, issue the <code>fcm
+  resolved</code> command on the file to remove the conflict status. (You will
+  be prevented from committing if you have a conflicting file in your working
+  copy.)</p>
+
+  <p>If you have a conflicting MS Office 2003+ document, you may be able to
+  take advantage of the <kbd>Tools > Compare and Merge Documents</kbd>
+  facility. Consider a working copy, which you have just updated from revision
+  100 to revision 101, and someone else has committed some changes to a file
+  <samp>doument.doc</samp> you are editing, you will get:</p>
+  <pre>
+(SHELL PROMPT)$ fcm conflicts
+Conflicts in file: document.doc
+document.doc: ignoring binary file, please resolve conflicts manually.
+(SHELL PROMPT)$ fcm status
+=> svn st
+?      document.doc.r100
+?      document.doc.r101
+C      document.doc
+</pre>
+
+  <p>Open <samp>document.doc.r101</samp> with MS Word. In <kbd>Tools >
+  Compare and Merge Documents...</kbd>, open <samp>document.doc</samp>. You
+  will be in Track Changes mode automatically. Go through the document to
+  accept, reject or merge any changes. Save the document and exit MS Word when
+  you are ready. Finally, issue the <code>fcm resolved</code> command to remove
+  the conflict status:</p>
+  <pre>
+(SHELL PROMPT)$ fcm resolved document.doc
+=> svn resolved document.doc
+Resolved conflicted state of 'document.doc'
+(SHELL PROMPT)$ fcm status
+=> svn st
+M      document.doc
+</pre>
+
+  <p>Another type of conflict that you may be able to resolve manually is where
+  the binary file is generated from another file which can be merged. For
+  instance, some people who use LaTeX also store a PDF version of the document
+  in the repository. In such cases it is easy to resolve the conflict by
+  re-generating the PDF file from the merged LaTeX file and then issuing the
+  <code>fcm resolved</code> command to remove the conflict status. Note that,
+  in this particular case, a better approach might be to automate the
+  generation of the PDF file outside of the repository.</p>
+
+  <h3 id="binary_locking">Using Locking</h3>
+
+  <p>For files with binary formats, such as artwork or sound, it is often
+  impossible to merge conflicting changes. In these situations, it is necessary
+  for users to take strict turns when changing the file in order to prevent
+  time wasted on changes that are ultimately discarded.</p>
+
+  <p>Subversion supports <q title=
+  "http://svnbook.red-bean.com/en/1.8/svn.advanced.locking.html">locking</q> to
+  allow you to prevent other users from modifying a file while you are
+  preparing changes. For details please refer to the chapter <a href=
+  "http://svnbook.red-bean.com/en/1.8/svn.advanced.locking.html">Locking</a>
+  from the Subversion book. Note that:</p>
+
+  <ul>
+    <li>FCM does not add any functionality to the locking commands provided by
+    Subversion.</li>
+
+    <li>If you need to lock a file you must do this in a working copy of the
+    trunk. There is nothing to stop you preparing the changes in a branch
+    (maybe you want to prepare the change in combination with a number of other
+    changes which do not require locking). However, you must always remember to
+    lock the file in the trunk first to prevent other users from preparing
+    changes to the file in parallel.</li>
+
+    <li>Locking isn't the only way of preventing conflicts with binary files.
+    If you only have a small project team and a small number of binary files
+    you may find it easier to use other methods of communication such as emails
+    or just talking to each other. Alternatively, you may have a working
+    practice that particular files are only modified by particular users.</li>
+  </ul>
+
+  <h2 id="messages">Commit Log Messages</h2>
+
+  <p>Certain guidelines should be adhered to when writing log messages for code
+  changes when committing to the trunk:</p>
+
+  <ul>
+    <li>Start with a link to the ticket that raises the issues you are
+    addressing.</li>
+
+    <li>Add a keyword to indicate the command/module affected by this change.</li>
+
+    <li>Add a summary of the change.</li>
+
+    <li>Use Trac wiki syntax that can be displayed nicely in plain text.</li>
+
+    <li>E.g. <samp>#429: user guide: improve commit log guidelines.</samp></li>
+  </ul>
+
+  <p>If you realise that you have made a mistake in the commit log, you can
+  modify it by using the command <code>fcm propedit svn:log --revprop -r REV
+  TARGET</code>. Take care since this is an <a href=
+  "http://svnbook.red-bean.com/en/1.8/svn.advanced.props.html">unversioned</a>
+  property so you run the risk of losing information if you aren't careful with
+  your edits.</p>
+
+  <p>There are two possible approaches to recording the changes to individual
+  files:</p>
+
+  <dl>
+    <dt>Maintain history entries in file headers</dt>
+
+    <dd>
+      <p><dfn>Pros:</dfn> You don't need access to the Subversion repository in
+      order to be able to view a files change history (e.g. external
+      collaborators).</p>
+
+      <p><dfn>Cons:</dfn></p>
+
+      <ul>
+        <li>History entries will produce clashes whenever files are changed in
+        parallel (although these conflicts are trivial to resolve).</li>
+
+        <li>Source files which are changed regularly can become cluttered with
+        very long history entries.</li>
+
+        <li>It is not possible to include history entries in some types of
+        file.</li>
+      </ul>
+    </dd>
+
+    <dt>Record which files have changed in the commit log message</dt>
+
+    <dd>
+      <p>The log message should name every modified file and explain why it was
+      changed. Make sure that the log message includes some sort of description
+      for every change. The value of the log becomes much less if developers
+      cannot rely on its completeness. Even if you've only changed comments,
+      note this in the message. For example:</p>
+      <pre>
+ * working_practices.html:
+   Added guidelines for writing log messages.
+</pre>
+
+      <p>If you make exactly the same change in several files, list all the
+      changed files in one entry. For example:</p>
+      <pre>
+ * code_management.html, system_admin.html, index.html:
+   Ran pages through tidy to fix HTML errors.
+</pre>
+
+      <p>It shouldn't normally be necessary to include the full path in the
+      file name - just make sure it is clear which of the changed files you are
+      referring to. You can get a full list of the files changed using
+      <code>fcm log -v</code>.</p>
+    </dd>
+  </dl>
+
+  <p>When you're committing to your own branch then you can be much more
+  relaxed about log messages. Use whatever level of detail you find helpful.
+  However, if you follow similar guidelines then this will help when it comes
+  to preparing the log message when your change is merged back to the
+  trunk.</p>
+
+  <h2 id="tickets">Trac Tickets</h2>
+
+  <h3 id="tickets_create">Creating Tickets</h3>
+
+  <p>There are two different approaches to using the issue tracker within
+  Trac:</p>
+
+  <dl>
+    <dt>All problems should be reported using Trac tickets</dt>
+
+    <dd>
+      <p><dfn>Pros:</dfn> The issue tracker contains a full record of all the
+      problems reported and enhancements requested.</p>
+
+      <p><dfn>Cons:</dfn> The issue tracker gets cluttered up with lots of
+      inappropriate tickets, (which can make it much harder to search the
+      issues and can slow down the response to simple issues).</p>
+
+      <ul>
+        <li>Duplicate tickets.</li>
+
+        <li>Issues already discussed in the documentation.</li>
+
+        <li>Problems which turn out to be unrelated to the system.</li>
+
+        <li>Problems which are poorly described.</li>
+
+        <li>Things which would be better solved by a quick conversation.</li>
+      </ul>
+    </dd>
+
+    <dt>A Trac ticket shouldn't be created until the issue has been agreed</dt>
+
+    <dd>
+      <p>Problems and issues should first be discussed with the project team /
+      system maintainers. Depending on the project, this could be via email, on
+      the newsgroups or through a quick chat over coffee.</p>
+
+      <p>Nothing is lost this way. Issues which are appropriate for the issue
+      tracker still get filed. It just happens slightly later, after initial
+      discussion has helped to clarify the best description for the issue.</p>
+    </dd>
+  </dl>
+
+  <h3 id="tickets_use">Using Tickets</h3>
+
+  <p>This sub-section provides advice on the best way of using tickets:</p>
+
+  <ol>
+    <li>In general, mature systems will require that there is a Trac ticket
+    related to every changeset made to the trunk. However this doesn't mean
+    that there should be a separate ticket for each change.
+
+      <ul>
+        <li>If a change is made to the trunk and then a bug is subsequently
+        found then, if this happens before the next release of the system, the
+        subsequent change can be recorded on the same ticket.</li>
+
+        <li>There can often be changes which don't really affect the system
+        itself since they are just system administration details. One way of
+        dealing with this is to open a ticket for each release in which to
+        record all such miscellaneous changes. It will probably be acceptable
+        to review these changes after they have been committed, prior to the
+        system release.</li>
+      </ul>
+    </li>
+
+    <li>Whenever you refer to source files/directories in tickets, make sure
+    that you refer to particular revisions of the files. This ensures that the
+    links will work in the future, even if those files are no longer in the
+    latest revision. For example:<br />
+    <samp>Changes now ready for review:
+    source:/OPS/branches/dev/frdm/r123_MyBranch at 234</samp></li>
+
+    <li>For some types of information, simply appending to the ticket may not
+    be the best way of working. For example, design notes or test results may
+    be best recorded elsewhere, preferably in a wiki page. If using wiki pages
+    we recommend using a naming convention to identify the wiki page with the
+    associated ticket, for example:<br />
+    <samp>Please refer to [wiki:ticket/123/Design design notes]</samp><br />
+    <samp>See separate [wiki:ticket/123/TestResults test results]</samp><br />
+    Note that the square brackets have to be used since a page name containing
+    numbers is not recognised automatically.</li>
+  </ol>
+
+  <h2 id="packages">Creating Packages</h2>
+
+  <p>Sometimes you may need to combine the changes from several different
+  branches. For example:</p>
+
+  <ul>
+    <li>Your branch is just part of a larger change which needs to be tested in
+    its entirety before committing to the trunk.</li>
+
+    <li>You have some diagnostic code stored on a branch which you want to
+    combine with another branch for testing purposes.</li>
+  </ul>
+
+  <p>We refer to this as creating a <em>package</em>.</p>
+
+  <p>To create a package you simply create a new branch as normal. The
+  <em>type</em> should be a <em>package</em> or possibly a
+  <em>configuration</em> branch to help you distinguish it from your other
+  branches. You then simply merge in all of the branches that you want to
+  combine using <code>fcm merge</code>.</p>
+
+  <ul>
+    <li>The chance of conflicts will be reduced if the branches you are
+    combining have been created from the same point on the trunk. Your package
+    branch should also be created from the same point on the trunk.
+
+      <ul>
+        <li><em>Currently, <code>fcm merge</code> will not work unless this is
+        true.</em></li>
+      </ul>
+    </li>
+
+    <li>If further changes are made on a branch you are using in a package then
+    you can incorporate these changes into your package using <code>fcm
+    merge</code>. Note, however, that if you have a branch which is being used
+    in a package then you should avoid merging changes from the trunk into your
+    branch. If you do then it will be very difficult to get updates to your
+    branch merged into the package.</li>
+  </ul>
+
+  <p>The <code>fcm branch-info</code> command is very useful for maintaining
+  packages. It tells you all of the branches which have been merged into your
+  package and whether there are any more recent changes on those branches.</p>
+
+  <h2 id="releases">Preparing System Releases</h2>
+
+  <p>There are two ways of preparing system releases:</p>
+
+  <dl>
+    <dt>A system release is simply a particular revision of the trunk</dt>
+
+    <dd>
+      <p>In order to do this it will be necessary to restrict changes on the
+      trunk whilst the release is being prepared.</p>
+
+      <ul>
+        <li>Users can continue to develop changes not intended for inclusion in
+        this release on branches.</li>
+
+        <li>This may be a problem if preparing the release takes too long.</li>
+      </ul>
+    </dd>
+
+    <dt>Create a release branch where the release is finalised</dt>
+
+    <dd>
+      <p>You then lose the ability to be able to branch from the release.</p>
+
+      <p>It may be harder to identify what changes have been made between
+      releases (since you can't simply look at all the changesets made between
+      two revisions of the trunk).</p>
+    </dd>
+  </dl>
+
+  <h2 id="rapid">Rapid vs Staged Development Practices</h2>
+
+  <p>Most of this section on working practices has focussed on projects/systems
+  which are quite mature. Such systems are likely to have regular releases and
+  will, for example, insist that all changes to the trunk are reviewed and
+  tested.</p>
+
+  <p>If your system is still undergoing rapid development and has not yet
+  reached any sort of formal release then you will probably want to adopt a
+  much more relaxed set of working practices. For example:</p>
+
+  <ul>
+    <li>Changes don't need to be reviewed.</li>
+
+    <li>More changes will be committed to the trunk. Only very large changes
+    will be prepared on branches.</li>
+
+    <li>No requirement to have a Trac ticket associated with each change.</li>
+  </ul>
+
+  <p>We have tried to avoid building too many assumptions about working
+  practices into the FCM system. This gives projects the flexibility to decide
+  which working practices are appropriate for their system. Hopefully this
+  means that FCM can be used for large or small systems and for rapidly
+  evolving or very stable systems.</p>
+
+  </div>
+  </div>
+  </div>
+
+  <hr/>
+  <div class="container-fluid text-center">
+    <div class="row-fluid"><div class="span12">
+    <address><small>
+      © British Crown Copyright 2006-14
+      <a href="http://www.metoffice.gov.uk">Met Office</a>.
+      See <a href="../etc/fcm-terms-of-use.html">Terms of Use</a>.<br />
+      This document is released under the British <a href=
+      "http://www.nationalarchives.gov.uk/doc/open-government-licence/" rel=
+      "license">Open Government Licence</a>.<br />
+    </small></address>
+    </div></div>
+  </div>
+
+  <script type="text/javascript" src="../etc/jquery.min.js"></script>
+  <script type="text/javascript" src="../etc/bootstrap/js/bootstrap.min.js"></script>
+  <script type="text/javascript" src="../etc/fcm.js"></script>
+  <script type="text/javascript" src="../etc/fcm-version.js"></script>
+</body>
+</html>
diff --git a/doc/user_guide/xxdiff1.png b/doc/user_guide/xxdiff1.png
new file mode 100644
index 0000000..084b277
Binary files /dev/null and b/doc/user_guide/xxdiff1.png differ
diff --git a/doc/user_guide/xxdiff2.png b/doc/user_guide/xxdiff2.png
new file mode 100644
index 0000000..7ff3350
Binary files /dev/null and b/doc/user_guide/xxdiff2.png differ
diff --git a/doc/user_guide/xxdiff_tutorial.png b/doc/user_guide/xxdiff_tutorial.png
new file mode 100644
index 0000000..f308878
Binary files /dev/null and b/doc/user_guide/xxdiff_tutorial.png differ
diff --git a/etc/fcm/admin.cfg.example b/etc/fcm/admin.cfg.example
new file mode 100644
index 0000000..b29783f
--- /dev/null
+++ b/etc/fcm/admin.cfg.example
@@ -0,0 +1,86 @@
+#-------------------------------------------------------------------------------
+# FCM Admin Configuration Example
+# See also the Perl module FCM::Admin::Config
+#-------------------------------------------------------------------------------
+# To use, copy this file to "admin.cfg".
+# Uncomment a line to activate a setting.
+# Default values are given below.
+#-------------------------------------------------------------------------------
+## Email address of FCM system admin
+#  admin_email = $USER
+## Notification email address (for the "From:" field in notification emails).
+#  notification_from =
+
+## Location for log files
+#  log_dir = /var/log/fcm
+
+## Location where FCM is installed
+#  fcm_home = $FCM_HOME
+## Location where FCM site specific items are installed
+#  fcm_site_home =
+
+## Locations (space delimited) to mirror items in "mirror_keys"
+#  mirror_dests =
+## Items (space delimited) to mirror to "mirror_dests", e.g. "fcm_site_home"
+#  mirror_keys =
+
+## Location to backup Subversion repositories
+#  svn_backup_dir = /var/svn/backups
+## Location to create dumps for commits to Subversion repositories
+#  svn_dump_dir = /var/svn/dumps
+### Name of group where Subversion repositories should be created in
+#  svn_group =
+## PATH environment variable for Subversion hooks
+#  svn_hook_path_env =
+## Location to serve Subversion repositories
+#  svn_live_dir = /srv/svn
+## Name of svnserve password file
+#  svn_passwd_file =
+## File name suffix that may be added to each Subversion repository
+#  svn_project_suffix =
+
+## List of admin users for all Trac environments
+#  trac_admin_users =
+## Location to backup Trac environments
+#  trac_backup_dir = /var/trac/backups
+### Name of group where Trac files should be created in
+#  trac_group =
+## Host name (from a user's perspective) where Trac environments are served
+#  trac_host_name = localhost
+## Name of "trac.ini" in each Trac environment
+#  trac_ini_file = trac.ini
+## Location to serve Trac environments
+#  trac_live_dir = /srv/trac
+## Template to create a Trac environment URL (from a user's perspective)
+#  trac_live_url_tmpl = https://{host}/trac/{project}
+## Name of Trac password file (under Apache)
+#  trac_passwd_file =
+
+## Specify the name of the tool for obtaining user info (ldap or passwd)
+#  user_info_tool = passwd
+
+## LDAP settings, only relevant if user_info_tool = ldap
+## File containing the password to the LDAP server, if required
+# ldappw = ~/.ldappw
+## The URI of the LDAP server
+# ldap_uri =
+## The DN in the LDAP server to bind with to search the directory
+# ldap_binddn =
+## The DN in the LDAP server that is the base for a search
+# ldap_basedn =
+## The attributes for UID, common name and email in the LDAP directory
+# ldap_attrs = uid cn mail
+## If specified, use the value as extra (AND) filters to an LDAP search
+# ldap_filter_more =
+
+## PASSWD settings, only relevant if user_info_tool = passwd
+## Domain name to suffix user IDs to create an email address
+#  passwd_email_domain =
+## Maximum GID considered to be a normal user group.
+#  passwd_gid_max =
+## Maximum UID considered to be a normal user.
+#  passwd_uid_max =
+## Minimum GID considered to be a normal user group.
+#  passwd_gid_min = 1000
+## Minimum UID considered to be a normal user.
+#  passwd_uid_min = 1000
diff --git a/etc/fcm/external.cfg.example b/etc/fcm/external.cfg.example
new file mode 100644
index 0000000..3119d15
--- /dev/null
+++ b/etc/fcm/external.cfg.example
@@ -0,0 +1,9 @@
+#-------------------------------------------------------------------------------
+# FCM External Configuration Example
+#-------------------------------------------------------------------------------
+# For detail, please refer to:
+# FCM User Guide Annex: FCM Configuration File > FCM External Configuration
+#-------------------------------------------------------------------------------
+
+# E.g. Web browser
+browser = firefox
diff --git a/etc/fcm/keyword.cfg.example b/etc/fcm/keyword.cfg.example
new file mode 100644
index 0000000..3c9bbae
--- /dev/null
+++ b/etc/fcm/keyword.cfg.example
@@ -0,0 +1,9 @@
+#-------------------------------------------------------------------------------
+# FCM Keyword Configuration Example
+#-------------------------------------------------------------------------------
+# For detail, please refer to:
+# FCM User Guide Annex: FCM Configuration File > FCM Keyword Configuration
+#-------------------------------------------------------------------------------
+
+# E.g. Location of FCM
+location{primary}[fcm] = svn://host/fcm
diff --git a/etc/fcm/make.cfg.example b/etc/fcm/make.cfg.example
new file mode 100644
index 0000000..2a3f028
--- /dev/null
+++ b/etc/fcm/make.cfg.example
@@ -0,0 +1,9 @@
+#-------------------------------------------------------------------------------
+# FCM Make Configuration Example
+#-------------------------------------------------------------------------------
+# For detail, please refer to:
+# FCM User Guide Annex: FCM Configuration File > FCM Make Configuration
+#-------------------------------------------------------------------------------
+
+# E.g. C compiler for the build system
+build.prop{cc} = cc
diff --git a/etc/svn-hooks/post-commit b/etc/svn-hooks/post-commit
new file mode 100755
index 0000000..e486c64
--- /dev/null
+++ b/etc/svn-hooks/post-commit
@@ -0,0 +1,23 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+set -eu
+mkdir -p "$1/log"
+nohup "$FCM_HOME/sbin/post-commit-bg" "$@" \
+    </dev/null >>"$1/log/post-commit.log" 2>&1 &
diff --git a/etc/svn-hooks/post-revprop-change b/etc/svn-hooks/post-revprop-change
new file mode 100755
index 0000000..8240fb2
--- /dev/null
+++ b/etc/svn-hooks/post-revprop-change
@@ -0,0 +1,23 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+set -eu
+mkdir -p "$1/log"
+nohup "$FCM_HOME/sbin/post-revprop-change-bg" "$@" \
+    <&0 >>"$1/log/post-revprop-change.log" 2>&1 &
diff --git a/etc/svn-hooks/pre-commit b/etc/svn-hooks/pre-commit
new file mode 100755
index 0000000..8c618da
--- /dev/null
+++ b/etc/svn-hooks/pre-commit
@@ -0,0 +1,22 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+set -eu
+mkdir -p "$1/log"
+exec "$FCM_HOME/sbin/pre-commit" "$@" >>"$1/log/pre-commit.log"
diff --git a/etc/svn-hooks/pre-revprop-change b/etc/svn-hooks/pre-revprop-change
new file mode 100755
index 0000000..fa0338a
--- /dev/null
+++ b/etc/svn-hooks/pre-revprop-change
@@ -0,0 +1,22 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+set -eu
+mkdir -p "$1/log"
+exec "$FCM_HOME/sbin/pre-revprop-change" "$@" >>"$1/log/pre-revprop-change.log"
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..ec87327
--- /dev/null
+++ b/index.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>FCM</title>
+  <meta http-equiv="refresh" content="0;url=doc/">
+</head>
+
+<body>
+<p>If not automatically redirected, please click
+<a href="doc/">FCM Documentation</a>.</p>
+</body>
+</html>
diff --git a/lib/FCM/Admin/Config.pm b/lib/FCM/Admin/Config.pm
new file mode 100644
index 0000000..caa1358
--- /dev/null
+++ b/lib/FCM/Admin/Config.pm
@@ -0,0 +1,353 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+package FCM::Admin::Config;
+use base qw{FCM::Class::HASH};
+
+use FCM::Context::Locator;
+use FCM::Util;
+use File::Basename qw{dirname};
+use File::Spec::Functions qw{catfile};
+use FindBin;
+
+our $UTIL = FCM::Util->new();
+
+my $TRAC_LIVE_URL_TMPL = 'https://{host}/trac/{project}';
+my $USER_ID = (getpwuid($<))[0];
+
+__PACKAGE__->class({
+    # Emails
+    admin_email         => {isa => '$', default => $USER_ID},
+    notification_from   => {isa => '$'},
+
+    # Location for log files
+    log_dir             => {isa => '$', default => '/var/log/fcm'},
+
+    # FCM installation locations
+    fcm_home            => {isa => '$', default => dirname($FindBin::Bin)},
+    fcm_site_home       => {isa => '$', default => q{}},
+
+    # FCM installation mirror locations
+    mirror_dests        => {isa => '$', default => q{}},
+    mirror_keys         => {isa => '$', default => q{}},
+
+    # Subversion repositories settings
+    svn_backup_dir      => {isa => '$', default => '/var/svn/backups'},
+    svn_dump_dir        => {isa => '$', default => '/var/svn/dumps'},
+    svn_group           => {isa => '$', default => q{}},
+    svn_hook_path_env   => {isa => '$', default => q{}},
+    svn_live_dir        => {isa => '$', default => '/srv/svn'},
+    svn_passwd_file     => {isa => '$', default => q{}},
+    svn_project_suffix  => {isa => '$', default => q{}},
+
+    # Trac environments settings
+    trac_admin_users    => {isa => '$', default => q{}},
+    trac_backup_dir     => {isa => '$', default => '/var/trac/backups'},
+    trac_group          => {isa => '$', default => q{}},
+    trac_host_name      => {isa => '$', default => 'localhost'},
+    trac_ini_file       => {isa => '$', default => 'trac.ini'},
+    trac_live_dir       => {isa => '$', default => '/srv/trac'},
+    trac_live_url_tmpl  => {isa => '$', default => $TRAC_LIVE_URL_TMPL},
+    trac_passwd_file    => {isa => '$', default => q{}},
+
+    # User information tool settings
+    user_info_tool      => {isa => '$', default => 'passwd'},
+
+    # User information tool, LDAP settings
+    ldappw              => {isa => '$', default => '~/.ldappw'},
+    ldap_uri            => {isa => '$', default => q{}},
+    ldap_binddn         => {isa => '$', default => q{}},
+    ldap_basedn         => {isa => '$', default => q{}},
+    ldap_attrs          => {isa => '$', default => q{uid cn mail}},
+    ldap_filter_more    => {isa => '$', default => q{}},
+
+    # User information tool, passwd settings
+    passwd_email_domain => {isa => '$', default => q{}},
+    passwd_gid_max      => {isa => '$'},
+    passwd_uid_max      => {isa => '$'},
+    passwd_gid_min      => {isa => '$', default => 1000},
+    passwd_uid_min      => {isa => '$', default => 1000},
+});
+
+
+# Returns a unique instance of this class.
+my $INSTANCE;
+sub instance {
+    my ($class) = @_;
+    if (!defined($INSTANCE)) {
+        $INSTANCE = $class->new();
+        # Load $FCM_HOME/etc/fcm/admin.cfg and $HOME/.metomi/fcm/admin.cfg
+        $UTIL->cfg_init(
+            'admin.cfg',
+            sub {
+                my $config_reader = shift();
+                while (defined(my $entry = $config_reader->())) {
+                    my $label = $entry->get_label();
+                    if (exists($INSTANCE->{$label})) {
+                        $INSTANCE->{$label} = $entry->get_value();
+                    }
+                }
+            },
+        );
+    }
+    return $INSTANCE;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::Admin::Config
+
+=head1 SYNOPSIS
+
+    $config = FCM::Admin::Config->instance();
+    $dir = $config->get_svn_backup_dir();
+    # ...
+
+=head1 DESCRIPTION
+
+This class is used to retrieve/store configurations required by FCM
+admininstration scripts.
+
+It is a sub-class of L<FCM::Class::HASH|FCM::Class::HASH>.
+
+=head1 METHODS
+
+=over 4
+
+=item FCM::Admin::Config->instance()
+
+Returns a unique instance of this class. On first call, creates the instance
+with the configurations set to their default values; and loads from the
+site/user configuration at $FCM_HOME/etc/fcm/admin.cfg and
+$HOME/.metomi/fcm/admin.cfg.
+
+=back
+
+=head1 ATTRIBUTES
+
+Email addresses.
+
+=over 4
+
+=item admin_email
+
+The e-mail address of the FCM administrator.
+
+=item notification_from
+
+Notification email address (for the "From:" field in notification emails).
+
+=back
+
+Location for log files.
+
+=over 4
+
+=item log_dir
+
+The location for log files.
+
+=back
+
+Locations of FCM installation.
+
+=over 4
+
+=item fcm_home
+
+The source path of the default FCM distribution.
+
+=item fcm_site_home
+
+The source path of the default FCM site distribution.
+
+=back
+
+Settings on how to mirror FCM installation.
+
+=over 4
+
+=item mirror_dests
+
+A space-delimited list of destinations to mirror FCM installation.
+
+=item mirror_keys
+
+A string containing a list of source keys. Each source key should point
+to a source location in this $config. The source locations will be distributed
+to the list of destinations in C<mirror_dests>.
+
+=back
+
+Subversion repositories settings.
+
+=over 4
+
+=item svn_backup_dir
+
+The path to a directory containing the backups of SVN repositories.
+
+=item svn_dump_dir
+
+The path to a directory containing the revision dumps of SVN
+repositories.
+
+=item svn_group
+
+The group name in which Subversion repositories should be created in.
+
+=item svn_hook_dir
+
+The path to a directory containing source files of SVN hook scripts.
+
+=item svn_hook_path_env
+
+The value of the PATH environment variable, in which SVN hook scripts
+should run with.
+
+=item svn_live_dir
+
+The path to a directory containing the live SVN repositories.
+
+=item svn_passwd_file
+
+The base name of the SVN password file.
+
+=item svn_project_suffix
+
+The suffix added to the name of each SVN repository.
+
+=back
+
+Trac environment settings.
+
+=over 4
+
+=item trac_admin_users
+
+A space-delimited list of admin users for all Trac environments.
+
+=item trac_backup_dir
+
+The path to a directory containing the backups of Trac environments.
+
+=item trac_group
+
+The group name in which Trac environment files should be created in.
+
+=item trac_host_name
+
+The host name of the Trac server, from the user's perspective.
+
+=item trac_ini_file
+
+The base name of the Trac INI file.
+
+=item trac_live_dir
+
+The path to a directory containing the live Trac environments.
+
+=item trac_live_url_tmpl
+
+The template string for determining the URL of the Trac environment of a
+project.
+
+=item trac_passwd_file
+
+The base name of the Trac password file.
+
+=back
+
+=over 4
+
+User information tool settings.
+
+=item user_info_tool
+
+The name of the tool for obtaining user information.
+
+=back
+
+LDAP settings, only relevant if C<user_info_tool = ldap>
+
+=over 4
+
+=item ldappw
+
+File containing the password to the LDAP server, if required.
+
+=item ldap_uri
+
+The URI of the LDAP server.
+
+=item ldap_binddn
+
+The DN in the LDAP server to bind with to search the directory.
+
+=item ldap_basedn
+
+The DN in the LDAP server that is the base for a search.
+
+=item ldap_attrs
+
+The attributes for UID, common name and email in the LDAP directory.
+
+=item ldap_filter_more
+
+If specified, use the value as extra (AND) filters to an LDAP search.
+
+=back
+
+PASSWD settings, only relevant if user_info_tool = passwd
+
+=over 4
+
+=item passwd_email_domain
+
+Domain name to suffix user IDs to create an email address.
+
+=item passwd_gid_max
+
+Maximum GID considered to be a normal user group.
+
+=item passwd_uid_max
+
+Maximum UID considered to be a normal user.
+
+=item passwd_gid_min
+
+Minimum GID considered to be a normal user group. (default=1000)
+
+=item passwd_uid_min
+
+Minimum UID considered to be a normal user. (default=1000)
+
+=back
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Admin/Project.pm b/lib/FCM/Admin/Project.pm
new file mode 100644
index 0000000..45490b5
--- /dev/null
+++ b/lib/FCM/Admin/Project.pm
@@ -0,0 +1,260 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+package FCM::Admin::Project;
+
+use overload q{""} => \&get_name;
+use FCM::Admin::Config;
+use File::Spec;
+
+my $ARCHIVE_EXTENSION = q{.tgz};
+
+# ------------------------------------------------------------------------------
+# Creates a new instance of this class.
+sub new {
+    my ($class, $args_ref) = @_;
+    return bless({%{$args_ref}}, $class);
+}
+
+# ------------------------------------------------------------------------------
+# Returns the name of the project.
+sub get_name {
+    my ($self) = @_;
+    return $self->{name};
+}
+
+# ------------------------------------------------------------------------------
+# Returns the base name of the backup archive of the project's SVN repository.
+sub get_svn_archive_base_name {
+    my ($self) = @_;
+    return $self->get_svn_base_name() . $ARCHIVE_EXTENSION;
+}
+
+# ------------------------------------------------------------------------------
+# Returns the path of the backup archive of the project's SVN repository.
+sub get_svn_backup_path {
+    my ($self) = @_;
+    return File::Spec->catfile(
+        FCM::Admin::Config->instance()->get_svn_backup_dir(),
+        $self->get_svn_archive_base_name(),
+    );
+}
+
+# ------------------------------------------------------------------------------
+# Returns the base name of the project's Subversion repository.
+sub get_svn_base_name {
+    my ($self) = @_;
+    return
+        $self->get_name()
+        . FCM::Admin::Config->instance()->get_svn_project_suffix();
+}
+
+# ------------------------------------------------------------------------------
+# Returns the path to the revision dumps of the project's SVN repository.
+sub get_svn_dump_path {
+    my ($self) = @_;
+    return File::Spec->catfile(
+        FCM::Admin::Config->instance()->get_svn_dump_dir(),
+        $self->get_svn_base_name(),
+    );
+}
+
+# ------------------------------------------------------------------------------
+# Returns the path to the project's SVN live repository's hooks directory.
+sub get_svn_live_hook_path {
+    my ($self) = @_;
+    return File::Spec->catfile($self->get_svn_live_path(), q{hooks});
+}
+
+# ------------------------------------------------------------------------------
+# Returns the path to the project's SVN live repository.
+sub get_svn_live_path {
+    my ($self) = @_;
+    return File::Spec->catfile(
+        FCM::Admin::Config->instance()->get_svn_live_dir(),
+        $self->get_svn_base_name(),
+    );
+}
+
+# ------------------------------------------------------------------------------
+# Returns the file:// URI to the project's SVN live repository.
+sub get_svn_file_uri {
+    my ($self) = @_;
+    return q{file://} . $self->get_svn_live_path();
+    # Note: can use URI::file in theory, but it returns file:/path (instead of
+    #       file:///path) which Subversion does not like.
+}
+
+# ------------------------------------------------------------------------------
+# Returns the base name of the project's Trac environment backup archive.
+sub get_trac_archive_base_name {
+    my ($self) = @_;
+    return $self->get_name() . $ARCHIVE_EXTENSION;
+}
+
+# ------------------------------------------------------------------------------
+# Returns the path to the project's Trac backup archive.
+sub get_trac_backup_path {
+    my ($self) = @_;
+    return File::Spec->catfile(
+        FCM::Admin::Config->instance()->get_trac_backup_dir(),
+        $self->get_trac_archive_base_name(),
+    );
+}
+
+# ------------------------------------------------------------------------------
+# Returns the path to the project's Trac live environment's database.
+sub get_trac_live_db_path {
+    my ($self) = @_;
+    return File::Spec->catfile($self->get_trac_live_path(), qw{db trac.db});
+}
+
+# ------------------------------------------------------------------------------
+# Returns the path to the project's Trac live environment's INI file.
+sub get_trac_live_ini_path {
+    my ($self) = @_;
+    return File::Spec->catfile($self->get_trac_live_path(), qw{conf trac.ini});
+}
+
+# ------------------------------------------------------------------------------
+# Returns the path to the project's Trac live environment.
+sub get_trac_live_path {
+    my ($self) = @_;
+    return File::Spec->catfile(
+        FCM::Admin::Config->instance()->get_trac_live_dir(),
+        $self->get_name(),
+    );
+}
+
+# ------------------------------------------------------------------------------
+# Returns the URL to the project's Trac live environment.
+sub get_trac_live_url {
+    my ($self) = @_;
+    my $return = FCM::Admin::Config->instance()->get_trac_live_url_tmpl();
+    for (
+        ['{host}', FCM::Admin::Config->instance()->get_trac_host_name()],
+        ['{project}', $self->get_name()],
+    ) {
+        my ($key, $value) = @{$_};
+        my $index = index($return, $key);
+        if ($index > -1) {
+            substr($return, $index, length($key), $value);
+        }
+    }
+    $return;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::Admin::Project
+
+=head1 SYNOPSIS
+
+    use FCM::Admin::Project;
+    $project = FCM::Admin::Project->new({name => 'foo'});
+    $path = $project->get_svn_live_path();
+
+=head1 DESCRIPTION
+
+An object of this class represents a project hosted/managed by FCM. The methods
+of this class relies on L<FCM::Admin::Config|FCM::Admin::Config> for many of the
+configurations.
+
+=head1 METHODS
+
+=over 4
+
+=item FCM::Admin::Project->new({name => $name})
+
+Returns a new instance. A name of the project must be specified.
+
+=item $project->get_name()
+
+Returns the name of the project.
+
+=item $project->get_svn_archive_base_name()
+
+Returns the base name of the backup archive of the project's Subversion
+repository.
+
+=item $project->get_svn_backup_path()
+
+Returns the path to the backup archive of the project's Subversion repository.
+
+=item $project->get_svn_base_name()
+
+Returns the base name of the project's Subversion repository.
+
+=item $project->get_svn_dump_path()
+
+Returns the path to the revision dumps of the project's Subversion repository.
+
+=item $project->get_svn_live_hook_path()
+
+Returns the path to the project's SVN live repository's hooks directory.
+
+=item $project->get_svn_live_path()
+
+Returns the path to the project's SVN live repository.
+
+=item $project->get_svn_file_uri()
+
+Returns the file:// URI to the project's SVN live repository.
+
+=item $project->get_trac_archive_base_name()
+
+Returns the base name of the project's Trac environment backup archive.
+
+=item $project->get_trac_backup_path()
+
+Returns the path to the project's Trac backup archive.
+
+=item $project->get_trac_live_db_path()
+
+Returns the path to the project's Trac live environment's database.
+
+=item $project->get_trac_live_ini_path()
+
+Returns the path to the project's Trac live environment's INI file.
+
+=item $project->get_trac_live_path()
+
+Returns the path to the project's Trac live environment.
+
+=item $project->get_trac_live_url()
+
+Returns the URL to the project's Trac live environment.
+
+=back
+
+=head1 SEE ALSO
+
+L<FCM::Admin::Config|FCM::Admin::Config>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Admin/Runner.pm b/lib/FCM/Admin/Runner.pm
new file mode 100644
index 0000000..ee9a1e5
--- /dev/null
+++ b/lib/FCM/Admin/Runner.pm
@@ -0,0 +1,299 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+package FCM::Admin::Runner;
+
+use IO::Handle;
+use POSIX qw{strftime};
+
+# The default values of the attributes
+my %DEFAULT = (
+    exceptions     => [],
+    max_attempts   => 3,
+    retry_interval => 5,
+    stderr_handle  => \*STDERR,
+    stdout_handle  => \*STDOUT,
+);
+
+my $INSTANCE;
+
+# ------------------------------------------------------------------------------
+# Returns a unique instance of this class. Creates the instance on first call.
+sub instance {
+    my ($class) = @_;
+    if (!defined($INSTANCE)) {
+        $INSTANCE = bless({%DEFAULT}, $class);
+    }
+    return $INSTANCE;
+}
+
+# ------------------------------------------------------------------------------
+# Adds a new exception to the list of exceptions.
+sub _add_exception {
+    my ($self, $exception) = @_;
+    push(@{$self->get_exceptions()}, $exception);
+}
+
+# ------------------------------------------------------------------------------
+# Returns the list of exceptions (or a reference to the list in scalar context).
+sub get_exceptions {
+    my ($self) = @_;
+    return (wantarray() ? @{$self->{exceptions}} : $self->{exceptions});
+}
+
+# ------------------------------------------------------------------------------
+# Returns the latest exception in the exception list.
+sub get_latest_exception {
+    my ($self) = @_;
+    if (exists($self->get_exceptions()->[-1])) {
+        return $self->get_exceptions()->[-1];
+    }
+    else {
+        return;
+    }
+}
+
+# ------------------------------------------------------------------------------
+# Returns the maximum number of attempts for the "run_with_retries" method.
+sub get_max_attempts {
+    my ($self) = @_;
+    return $self->{max_attempts};
+}
+
+# ------------------------------------------------------------------------------
+# Returns the retry interval for the "run_with_retries" method.
+sub get_retry_interval {
+    my ($self) = @_;
+    return $self->{retry_interval};
+}
+
+# ------------------------------------------------------------------------------
+# Returns the file handle for STDERR.
+sub get_stderr_handle {
+    my ($self) = @_;
+    if (!IO::Handle::opened($self->{stderr_handle})) {
+        $self->{stderr_handle} = $DEFAULT{stderr_handle};
+    }
+    return $self->{stderr_handle};
+}
+
+# ------------------------------------------------------------------------------
+# Returns the file handle for STDOUT.
+sub get_stdout_handle {
+    my ($self) = @_;
+    if (!IO::Handle::opened($self->{stdout_handle})) {
+        $self->{stdout_handle} = $DEFAULT{stdout_handle};
+    }
+    return $self->{stdout_handle};
+}
+
+# ------------------------------------------------------------------------------
+# Runs $sub_ref->(@arguments) with a diagnostic $message. Dies on error.
+sub run {
+    my ($self, $message, $sub_ref, @arguments) = @_;
+    printf(
+        {$self->get_stdout_handle()}
+        qq{%s: %s\n}, strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()), $message,
+    );
+    eval {
+        if (!$sub_ref->(@arguments)) {
+            die(qq{\n});
+        }
+    };
+    if ($@) {
+        my $e = $@;
+        chomp($e);
+        my $exception
+            = sprintf(qq{ERROR %s%s\n}, $message, ($e ? qq{ - $e} : qq{}));
+        $self->_add_exception($exception);
+        die($exception);
+    }
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Runs $sub_ref->(@arguments) with a diagnostic $message. Warns on error.
+sub run_continue {
+    my ($self, $message, $sub_ref, @arguments) = @_;
+    my $rc;
+    eval {
+        $rc = $self->run($message, $sub_ref, @arguments);
+    };
+    if ($@) {
+        print({$self->get_stderr_handle()} $@);
+        return;
+    }
+    return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# Runs $sub_ref->(@arguments) with a diagnostic $message. Retries on error.
+sub run_with_retries {
+    my ($self, $message, $sub_ref, @arguments) = @_;
+    for my $i_attempt (1 .. $self->get_max_attempts()) {
+        my $attempt_message = sprintf(
+            qq{%s, attempt %d of %d},
+            $message, $i_attempt, $self->get_max_attempts(),
+        );
+        if ($i_attempt == $self->get_max_attempts()) {
+            return $self->run($attempt_message, $sub_ref, @arguments);
+        }
+        else {
+            if ($self->run_continue($attempt_message, $sub_ref, @arguments)) {
+                return 1;
+            }
+            sleep($self->get_retry_interval());
+        }
+    }
+}
+
+# ------------------------------------------------------------------------------
+# Sets the maximum number of attempts for the "run_with_retries" method.
+sub set_max_attempts {
+    my ($self, $value) = @_;
+    $self->{max_attempts} = $value;
+}
+
+# ------------------------------------------------------------------------------
+# Sets the retry interval for the "run_with_retries" method.
+sub set_retry_interval {
+    my ($self, $value) = @_;
+    $self->{retry_interval} = $value;
+}
+
+# ------------------------------------------------------------------------------
+# Sets the file handle for STDERR.
+sub set_stderr_handle {
+    my ($self, $value) = @_;
+    if (defined($value) && IO::Handle::opened($value)) {
+        $self->{stderr_handle} = $value;
+    }
+}
+
+# ------------------------------------------------------------------------------
+# Sets the file handle for STDOUT.
+sub set_stdout_handle {
+    my ($self, $value) = @_;
+    if (defined($value) && IO::Handle::opened($value)) {
+        $self->{stdout_handle} = $value;
+    }
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::Admin::Runner
+
+=head1 SYNOPSIS
+
+    $runner = FCM::Admin::Runner->instance();
+    $runner->run($message, sub { ... });
+
+=head1 DESCRIPTION
+
+Provides a simple way to run a piece of code with a time-stamped diagnostic
+message.
+
+=head1 METHODS
+
+=over 4
+
+=item FCM::Admin::Runner->instance()
+
+Returns a unique instance of FCM::Admin::Runner.
+
+=item $runner->get_exceptions()
+
+Returns a list containing all the exceptions captured by the previous
+invocations of the $runner->run() method. In SCALAR context, returns a reference
+to the list.
+
+=item $runner->get_latest_exception()
+
+Returns the latest exception captured by the $runner->run() method. Returns
+undef if there is no captured exception in the list.
+
+=item $runner->get_max_attempts()
+
+Returns the number of maximum retries for the
+$runner->run_with_retries($message,$sub_ref, at arguments) method. (Default: 3)
+
+=item $runner->get_retry_interval()
+
+Returns the interval (in seconds) between retries for the
+$runner->run_with_retries($message,$sub_ref, at arguments) method. (Default: 5)
+
+=item $runner->get_stderr_handle()
+
+Returns the file handle for standard error output. (Default: \*STDERR)
+
+=item $runner->get_stdout_handle()
+
+Returns the file handle for standard output. (Default: \*STDOUT)
+
+=item $runner->run($message,$sub_ref, at arguments)
+
+Prints the diagnostic $message and runs $sub_ref (with extra @arguments).
+Returns true if $sub_ref returns true. die() with a message that looks like
+"ERROR $message\n" if $sub_ref returns false or die().
+
+=item $runner->run_continue($message,$sub_ref, at arguments)
+
+Same as $runner->run($message,$sub_ref, at arguments), but only issue a warning
+(and returns false) if $sub_ref returns false or die().
+
+=item $runner->run_with_retries($message,$sub_ref, at arguments)
+
+Attempts $runner->run($message,$sub_ref, at arguments) for a number of times up to
+$runner->get_max_attempts(), with a delay of $runner->get_retry_interval()
+between each attempt. die() if $sub_ref still returns false in the final
+attempt. Returns true on success.
+
+=item $runner->set_max_attempts($value)
+
+Sets the maximum number of attempts in the
+$runner->run_with_retries($message,$sub_ref, at arguments) method.
+
+=item $runner->set_retry_interval($value)
+
+Sets the interval (in seconds) between retries for the
+$runner->run_with_retries($message,$sub_ref, at arguments) method.
+
+=item $runner->set_stderr_handle($value)
+
+Sets the file handle for standard error output to an alternate file handle. The
+$value must be a valid file descriptor.
+
+=item $runner->set_stdout_handle($value)
+
+Sets the file handle for standard output to an alternate file handle. The $value
+must be a valid file descriptor.
+
+=back
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Admin/System.pm b/lib/FCM/Admin/System.pm
new file mode 100644
index 0000000..2c87cea
--- /dev/null
+++ b/lib/FCM/Admin/System.pm
@@ -0,0 +1,1387 @@
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+package FCM::Admin::System;
+
+use Config::IniFiles;
+use DBI; # See also: DBD::SQLite
+use Exporter qw{import};
+use FCM::Admin::Config;
+use FCM::Admin::Project;
+use FCM::Admin::Runner;
+use FCM::Admin::User;
+use FCM::Admin::Util qw{
+    read_file
+    run_copy
+    run_create_archive
+    run_extract_archive
+    run_mkpath
+    run_rename
+    run_rmtree
+    run_rsync
+    run_symlink
+    write_file
+};
+use Fcntl qw{:mode}; # for S_IRGRP, S_IWGRP, S_IROTH, etc
+use File::Basename qw{basename dirname};
+use File::Find qw{find};
+use File::Spec::Functions qw{catfile rel2abs};
+use File::Temp qw{tempdir tempfile};
+use IO::Compress::Gzip qw{gzip};
+use IO::Dir;
+use IO::Pipe;
+use IO::Zlib;
+use List::Util qw{first};
+use POSIX qw{strftime};
+use Text::ParseWords qw{shellwords};
+
+our @EXPORT_OK = qw{
+    add_svn_repository
+    add_trac_environment
+    backup_svn_repository
+    backup_trac_environment
+    backup_trac_files
+    distribute_wc
+    filter_projects
+    get_projects_from_svn_backup
+    get_projects_from_svn_live
+    get_projects_from_trac_backup
+    get_projects_from_trac_live
+    get_users
+    housekeep_svn_hook_logs
+    install_svn_hook
+    manage_users_in_svn_passwd
+    manage_users_in_trac_passwd
+    manage_users_in_trac_db_of
+    recover_svn_repository
+    recover_trac_environment
+    recover_trac_files
+    vacuum_trac_env_db
+    verify_users
+};
+
+our $NO_OVERWRITE = 1;
+our $BUFFER_SIZE = 4096;
+our @SVN_REPOS_ROOT_HOOK_ITEMS = qw{commit.conf svnperms.conf};
+our %USER_INFO_TOOL_OF = (
+    'ldap'   => 'FCM::Admin::Users::LDAP',
+    'passwd' => 'FCM::Admin::Users::Passwd',
+);
+our $USER_INFO_TOOL;
+
+our $UTIL = $FCM::Admin::Config::UTIL;
+my $CONFIG = FCM::Admin::Config->instance();
+my $RUNNER = FCM::Admin::Runner->instance();
+
+# ------------------------------------------------------------------------------
+# Adds a new Subversion repository.
+sub add_svn_repository {
+    my ($project_name) = @_;
+    my $project = FCM::Admin::Project->new({name => $project_name});
+    if (-e $project->get_svn_live_path()) {
+        die(sprintf(
+            "%s: Subversion repository already exists at %s.\n",
+            $project_name,
+            $project->get_svn_live_path(),
+        ));
+    }
+    my $repos_path = $project->get_svn_live_path();
+    $RUNNER->run(
+        "creating Subversion repository at $repos_path",
+        sub {!system(qw{svnadmin create}, $repos_path)},
+    );
+    my $group = $CONFIG->get_svn_group();
+    if ($group) {
+        _chgrp_and_chmod($project->get_svn_live_path(), $group);
+    }
+    install_svn_hook($project);
+    housekeep_svn_hook_logs($project);
+}
+
+# ------------------------------------------------------------------------------
+# Adds a new Trac environment.
+sub add_trac_environment {
+    my ($project_name) = @_;
+    my $project = FCM::Admin::Project->new({name => $project_name});
+    if (-e $project->get_trac_live_path()) {
+        die(sprintf(
+            "%s: Trac environment already exists at %s.\n",
+            $project_name,
+            $project->get_trac_live_path(),
+        ));
+    }
+    my @repository_arguments = (q{}, q{});
+    if (-d $project->get_svn_live_path()) {
+        @repository_arguments = (q{svn}, $project->get_svn_live_path());
+    }
+    my $RUN = sub{$RUNNER->run(@_)};
+    my $TRAC_ADMIN = sub {
+        my ($log, @args) = @_;
+        my @command = (q{trac-admin}, $project->get_trac_live_path(), @args);
+        $RUN->($log, sub {!system(@command)});
+    };
+    $TRAC_ADMIN->(
+        "initialising Trac environment",
+        q{initenv},
+        $project_name,
+        q{sqlite:db/trac.db},
+        @repository_arguments,
+        q{--inherit=../../trac.ini},
+    );
+    my $group = $CONFIG->get_trac_group();
+    if ($group) {
+        _chgrp_and_chmod($project->get_trac_live_path(), $group);
+    }
+    for my $item (qw{component1 component2}) {
+        $TRAC_ADMIN->(
+            "removing example component $item", q{component remove}, $item,
+        );
+    }
+    for my $item (qw{1.0 2.0}) {
+        $TRAC_ADMIN->(
+            "removing example version $item", q{version remove}, $item,
+        );
+    }
+    for my $item (qw{milestone1 milestone2 milestone3 milestone4}) {
+        $TRAC_ADMIN->(
+            "removing example milestone $item", q{milestone remove}, $item,
+        );
+    }
+    for my $item (
+        ['major'    => 'normal'  ],
+        ['critical' => 'major'   ],
+        ['blocker'  => 'critical'],
+    ) {
+        my ($old, $new) = @{$item};
+        $TRAC_ADMIN->(
+            "changing priority $old to $new", qw{priority change}, $old, $new,
+        );
+    }
+    $TRAC_ADMIN->(
+        "adding admin permission", qw{permission add admin TRAC_ADMIN},
+    );
+    my @admin_users = shellwords($CONFIG->get_trac_admin_users());
+    for my $item (@admin_users) {
+        $TRAC_ADMIN->(
+            "adding admin user $item", qw{permission add}, $item, q{admin},
+        );
+    }
+    $TRAC_ADMIN->(
+        "adding TICKET_EDIT_CC permission to authenticated",
+        qw{permission add}, 'authenticated', qw{TICKET_EDIT_CC},
+    );
+    $TRAC_ADMIN->(
+        "adding TICKET_EDIT_DESCRIPTION permission to authenticated",
+        qw{permission add}, 'authenticated', qw{TICKET_EDIT_DESCRIPTION},
+    );
+    eval {$TRAC_ADMIN->(
+        "adding TICKET_EDIT_COMMENT permission to authenticated",
+        qw{permission add}, 'authenticated', qw{TICKET_EDIT_COMMENT},
+    )};
+    if ($@) {
+        # Expected to fail for Trac < 0.12
+        $@ = undef;
+    }
+    $RUN->(
+        "adding names and emails of users",
+        sub {manage_users_in_trac_db_of($project, {get_users()})},
+    );
+    $RUN->(
+        "updating configuration file",
+        sub {
+            my $trac_ini_path = $project->get_trac_live_ini_path();
+            my $trac_ini = Config::IniFiles->new(q{-file} => $trac_ini_path);
+            if (!$trac_ini) {
+                die("$trac_ini_path: cannot open.\n");
+            }
+            for (
+                #section    #key        #value
+                ['inherit', 'file'    , '../../trac.ini,../../intertrac.ini'],
+                ['project', 'descr'   , $project->get_name()                ],
+                ['trac'   , 'base_url', $project->get_trac_live_url()       ],
+            ) {
+                my ($section, $key, $value) = @{$_};
+                if (!$trac_ini->SectionExists($section)) {
+                    $trac_ini->AddSection($section);
+                }
+                if (!$trac_ini->newval($section, $key, $value)) {
+                    die("$trac_ini_path: $section:$key: cannot set value.\n");
+                }
+            }
+            return $trac_ini->RewriteConfig();
+        },
+    );
+    $RUN->(
+        "updating InterTrac",
+        sub {
+            my $ini_path = catfile(
+                $CONFIG->get_trac_live_dir(),
+                'intertrac.ini',
+            );
+            if (!-e $ini_path) {
+                open(my $handle, '>', $ini_path) || die("$ini_path: $!\n");
+                close($handle) || die("$ini_path: $!\n");
+            }
+            my $trac_ini = Config::IniFiles->new(
+                q{-allowempty} => 1,
+                q{-file} => $ini_path,
+            );
+            if (!defined($trac_ini)) {
+                die("$ini_path: cannot open.\n");
+            }
+            if (!$trac_ini->SectionExists(q{intertrac})) {
+                $trac_ini->AddSection(q{intertrac});
+            }
+            my $name = $project->get_name();
+            for (
+                [q{title} , $name                        ],
+                [q{url}   , $project->get_trac_live_url()],
+                [q{compat}, 'false'                      ],
+            ) {
+                my ($key, $value) = @{$_};
+                my $option = lc($name) . q{.} . $key;
+                if (!$trac_ini->newval(q{intertrac}, $option, $value)) {
+                    die("$ini_path: intertrac:$option: cannot set value.\n");
+                }
+            }
+            return $trac_ini->RewriteConfig();
+        },
+    );
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Backup the SVN repository of a project.
+sub backup_svn_repository {
+    my ($option_hash_ref, $project) = @_;
+    my $RUN = sub {FCM::Admin::Runner->instance()->run(@_)};
+    if (!exists($option_hash_ref->{'no-pack'})) {
+        $RUN->(
+            sprintf("packing %s", $project->get_svn_live_path()),
+            sub {!system(qw{svnadmin pack}, $project->get_svn_live_path())},
+        );
+    }
+    my $base_name = $project->get_svn_base_name();
+    run_mkpath($CONFIG->get_svn_backup_dir());
+    my $work_dir = tempdir(
+        "$base_name.backup.XXXXXX",
+        DIR => $CONFIG->get_svn_backup_dir(),
+        CLEANUP => 1,
+    );
+    my $work_path = catfile($work_dir, $base_name);
+    $RUN->(
+        sprintf(
+            "hotcopying %s to %s", $project->get_svn_live_path(), $work_path,
+        ),
+        sub {!system(
+            qw{svnadmin hotcopy}, $project->get_svn_live_path(), $work_path,
+        )},
+        # Note: "hotcopy" is not yet possible via SVN::Repos
+    );
+    if (!exists($option_hash_ref->{'no-verify-integrity'})) {
+        my $VERIFIED_REVISION_REGEX = qr{\A\*\s+Verified\s+revision\s+\d+\.}xms;
+        $RUN->(
+            "verifying integrity of SVN repository of $project",
+            sub {
+                my $pipe = IO::Pipe->new();
+                $pipe->reader(sprintf(
+                    qq{svnadmin verify %s 2>&1}, $work_path,
+                ));
+                while (my $line = $pipe->getline()) {
+                    if ($line !~ $VERIFIED_REVISION_REGEX) { # don't print
+                        print($line);
+                    }
+                }
+                return $pipe->close();
+                # Note: "verify" is not yet possible via SVN::Repos
+            },
+        );
+    }
+    _create_backup_archive(
+        $work_path,
+        $CONFIG->get_svn_backup_dir(),
+        $project->get_svn_archive_base_name(),
+    );
+    if (!exists($option_hash_ref->{'no-housekeep-dumps'})) {
+        my $base_name = $project->get_svn_base_name();
+        my $dump_path = $CONFIG->get_svn_dump_dir();
+        my $youngest = _svnlook_youngest($work_path);
+        # Note: could use SVN::Repos for "youngest"
+        $RUN->(
+            "housekeeping $dump_path/$base_name-*.gz",
+            sub {
+                my @rev_dump_paths;
+                _get_files_from(
+                    $dump_path,
+                    sub {
+                        my ($dump_base_name, $path) = @_;
+                        my ($name, $rev)
+                            = $dump_base_name =~ qr{\A(.*)-(\d+)\.gz\z}msx;
+                        if (    !$name
+                            ||  !$rev
+                            ||  $name ne $base_name
+                            ||  $rev > $youngest
+                        ) {
+                            return;
+                        }
+                        push(@rev_dump_paths, $path);
+                    },
+                );
+                for my $rev_dump_path (@rev_dump_paths) {
+                    run_rmtree($rev_dump_path);
+                }
+                return 1;
+            }
+        );
+    }
+    run_rmtree($work_dir);
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Backup the Trac environment of a project.
+sub backup_trac_environment {
+    my ($option_hash_ref, $project) = @_;
+    my $trac_live_path = $project->get_trac_live_path();
+    my $base_name = $project->get_name();
+    run_mkpath($CONFIG->get_trac_backup_dir());
+    my $work_dir = tempdir(
+        "$base_name.backup.XXXXXX",
+        DIR => $CONFIG->get_trac_backup_dir(),
+        CLEANUP => 1,
+    );
+    my $work_path = catfile($work_dir, $base_name);
+    $RUNNER->run_with_retries(
+        sprintf(
+            qq{hotcopying %s to %s},
+            $project->get_trac_live_path(),
+            $work_path,
+        ),
+        sub {
+            return !system(
+                q{trac-admin},
+                $project->get_trac_live_path(),
+                q{hotcopy},
+                $work_path,
+            );
+        },
+    );
+    if (!exists($option_hash_ref->{'no-verify-integrity'})) {
+        my $db_path = catfile($work_path, qw{db trac.db});
+        my $db_name = catfile($project->get_name(), qw{db trac.db});
+        $RUNNER->run(
+            "checking $db_name for integrity",
+            sub {
+                my $db_handle
+                    = DBI->connect(qq{dbi:SQLite:dbname=$db_path}, q{}, q{});
+                if (!$db_handle) {
+                    return;
+                }
+                my $rc = defined($db_handle->do(q{pragma integrity_check;}));
+                $db_handle->disconnect();
+                return $rc;
+            },
+        );
+    }
+    _create_backup_archive(
+        $work_path,
+        $CONFIG->get_trac_backup_dir(),
+        $project->get_trac_archive_base_name(),
+    );
+    run_rmtree($work_dir);
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Backup misc files in the Trac live directory to the Trac backup directory.
+sub backup_trac_files {
+    # (no argument)
+    _copy_files($CONFIG->get_trac_live_dir(), $CONFIG->get_trac_backup_dir());
+}
+
+# ------------------------------------------------------------------------------
+# Distributes the central FCM working copy to standard locations.
+sub distribute_wc {
+    my $rc = 1;
+    my @RSYNC_OPTS = qw{--timeout=1800 --exclude=.*};
+    my @sources;
+    for my $source_key (shellwords($CONFIG->get_mirror_keys())) {
+        my $method = "get_$source_key";
+        if ($CONFIG->can($method)) {
+            push(@sources, $CONFIG->$method());
+        }
+    }
+    for my $dest (shellwords($CONFIG->get_mirror_dests())) {
+        $rc = $RUNNER->run_continue(
+            "distributing FCM to $dest",
+            sub {
+                run_rsync(
+                    \@sources, $dest,
+                    [@RSYNC_OPTS, qw{-a --delete-excluded}],
+                );
+            },
+        ) && $rc;
+    }
+    return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# Returns a filtered list of projects matching names in a list.
+sub filter_projects {
+    my ($project_list_ref, $filter_list_ref) = @_;
+    if (!@{$filter_list_ref}) {
+        return @{$project_list_ref};
+    }
+    my %project_of = map {($_->get_name(), $_)} @{$project_list_ref};
+    my @projects;
+    my @unmatched_names;
+    for my $name (@{$filter_list_ref}) {
+        if (exists($project_of{$name})) {
+            push(@projects, $project_of{$name});
+        }
+        else {
+            push(@unmatched_names, $name);
+        }
+    }
+    if (@unmatched_names) {
+        die("@unmatched_names: not found\n");
+    }
+    return @projects;
+}
+
+# ------------------------------------------------------------------------------
+# Returns a list of projects by searching the backup SVN directory.
+sub get_projects_from_svn_backup {
+    # (no dummy argument)
+    my $SVN_PROJECT_SUFFIX = $CONFIG->get_svn_project_suffix();
+    my @projects;
+    _get_files_from(
+        $CONFIG->get_svn_backup_dir(),
+        sub {
+            my ($base_name, $path) = @_;
+            my $name = $base_name;
+            if ($name !~ s{$SVN_PROJECT_SUFFIX\.tgz\z}{}xms) {
+                return;
+            }
+            if (!-f $path) {
+                return;
+            }
+            push(@projects, FCM::Admin::Project->new({name => $name}));
+        },
+    );
+    return @projects;
+}
+
+# ------------------------------------------------------------------------------
+# Returns a list of projects by searching the live SVN directory.
+sub get_projects_from_svn_live {
+    # (no dummy argument)
+    my $SVN_PROJECT_SUFFIX = $CONFIG->get_svn_project_suffix();
+    my @projects;
+    _get_files_from(
+        $CONFIG->get_svn_live_dir(),
+        sub {
+            my ($base_name, $path) = @_;
+            my $name = $base_name;
+            $name =~ s{$SVN_PROJECT_SUFFIX\z}{}xms;
+            if (!-d $path) {
+                return;
+            }
+            push(@projects, FCM::Admin::Project->new({name => $name}));
+        },
+    );
+    return @projects;
+}
+
+# ------------------------------------------------------------------------------
+# Returns a list of projects by searching the backup Trac directory.
+sub get_projects_from_trac_backup {
+    # (no dummy argument)
+    my @projects;
+    _get_files_from(
+        $CONFIG->get_trac_backup_dir(),
+        sub {
+            my ($base_name, $path) = @_;
+            my $name = $base_name;
+            if ($name !~ s{\.tgz\z}{}xms) {
+                return;
+            }
+            if (!-f $path) {
+                return;
+            }
+            push(@projects, FCM::Admin::Project->new({name => $name}));
+        },
+    );
+    return @projects;
+}
+
+# ------------------------------------------------------------------------------
+# Returns a list of projects by searching the live Trac directory.
+sub get_projects_from_trac_live {
+    # (no dummy argument)
+    my @projects;
+    _get_files_from(
+        $CONFIG->get_trac_live_dir(),
+        sub {
+            my ($name, $path) = @_;
+            if (!-d $path) {
+                return;
+            }
+            push(@projects, FCM::Admin::Project->new({name => $name}));
+        },
+    );
+    return @projects;
+}
+
+# ------------------------------------------------------------------------------
+# Return a HASH of valid users. If @only_users, then return only users matching
+# these IDs.
+sub get_users {
+    my @only_users = @_;
+    if (!defined($USER_INFO_TOOL)) {
+        my $name = $CONFIG->get_user_info_tool();
+        my $class = $UTIL->class_load($USER_INFO_TOOL_OF{$name});
+        $USER_INFO_TOOL = $class->new({util => $UTIL});
+    }
+    return $USER_INFO_TOOL->get_users_info(@only_users);
+}
+
+# ------------------------------------------------------------------------------
+# Housekeep logs generated by hook scripts of a SVN project.
+sub housekeep_svn_hook_logs {
+    my ($project) = @_;
+    my $project_path = $project->get_svn_live_path();
+    my $hook_source_dir = catfile($CONFIG->get_fcm_home(), 'etc', 'svn-hooks');
+    my $today = strftime("%Y%m%d", gmtime());
+    my $date_p1w = strftime("%Y%m%d", gmtime(time() - 604800)); # 1 week ago
+    my $date_p4w = strftime("%Y%m%d", gmtime(time() - 2419200)); # 4 weeks ago
+    my @hook_names = map {basename($_)} glob(catfile($hook_source_dir, q{*}));
+    for my $hook_name (sort @hook_names) {
+        my $log_path = catfile($project_path, 'log', $hook_name . '.log');
+        my $log_path_cur;
+        # Determine whether log file is more than a week old
+        if (    -l $log_path
+            &&  index(readlink($log_path), $hook_name . '.log.') == 0
+        ) {
+            my $path = readlink($log_path);
+            my ($date) = $path =~ qr{\.log\.(\d{8}\d*)\z}msx;
+            if ($date && $date > $date_p1w) {
+                $log_path_cur = catfile($project_path, 'log', $path);
+            }
+        }
+        # Create latest log, if necessary
+        if (!$log_path_cur) {
+            $log_path_cur = "$log_path.$today";
+            write_file($log_path_cur);
+        }
+        if (    !-e $log_path
+            ||  !-l $log_path
+            ||  readlink($log_path) ne basename($log_path_cur)
+        ) {
+            run_rmtree($log_path);
+            run_symlink(basename($log_path_cur), $log_path);
+        }
+        # Remove logs older than $keep_threshold
+        for my $path (
+            sort glob(catfile($project_path, 'log', $hook_name . '*.log.*'))
+        ) {
+            my ($date, $dot_gz) = $path =~ qr{\.log\.(\d{8}\d*)(\.gz)?\z}msx;
+            if (    $date && $date <= $date_p4w
+                ||  $date && $date <= $date_p1w && !-s $path
+            ) {
+                run_rmtree($path);
+            }
+            elsif ($date && $date <= $date_p1w && !$dot_gz) {
+                $RUNNER->run(
+                    "gzip $path",
+                    sub {gzip($path, "$path.gz") && unlink($path)},
+                );
+            }
+        }
+    }
+    my $group = $CONFIG->get_svn_group();
+    if ($group) {
+        _chgrp_and_chmod(catfile($project_path, 'log'), $group);
+    }
+}
+
+# ------------------------------------------------------------------------------
+# Installs hook scripts to a SVN project.
+sub install_svn_hook {
+    my ($project, $clean_mode) = @_;
+    my %path_of;
+    for (
+        [$CONFIG->get_fcm_site_home(), 'svn-hooks', $project->get_name()],
+        [$CONFIG->get_fcm_home(), 'etc', 'svn-hooks'],
+    ) {
+        my $hook_source_dir = catfile(@{$_});
+        _get_files_from(
+            $hook_source_dir,
+            sub {
+                my ($base_name, $path) = @_;
+                if (index($base_name, q{.}) == 0 || -d $path) {
+                    return;
+                }
+                $path_of{$base_name} = $path;
+            },
+        );
+    }
+    # Write hook environment configuration
+    my $project_path = $project->get_svn_live_path();
+    my $conf_dest = catfile($project_path, qw{conf hooks-env});
+    write_file(
+        $conf_dest,
+        "[default]\n",
+        map {sprintf("%s=%s\n", @{$_});}
+        grep {$_->[1];} (
+            ['FCM_HOME', $CONFIG->get_fcm_home()],
+            ['FCM_SVN_HOOK_ADMIN_EMAIL', $CONFIG->get_admin_email()],
+            ['FCM_SVN_HOOK_COMMIT_DUMP_DIR', $CONFIG->get_svn_dump_dir()],
+            ['FCM_SVN_HOOK_NOTIFICATION_FROM', $CONFIG->get_notification_from()],
+            ['FCM_SVN_HOOK_REPOS_SUFFIX', $CONFIG->get_svn_project_suffix()],
+            ['FCM_SVN_HOOK_TRAC_ROOT_DIR', $CONFIG->get_trac_live_dir()],
+            ['PATH', $CONFIG->get_svn_hook_path_env()],
+            ['TZ', 'UTC'],
+        )
+    );
+    # Install hook scripts and associated files
+    for my $key (sort keys(%path_of)) {
+        my $hook_source = $path_of{$key};
+        my $hook_dest = catfile($project->get_svn_live_hook_path(), $key);
+        run_copy($hook_source, $hook_dest);
+    }
+    # Install hook configurations from repository root, e.g. svnperms.conf
+    for my $line (qx{svnlook tree -N $project_path}) {
+        chomp($line);
+        my ($name) = $line =~ qr{\A\s*(.*)\z}msx;
+        if (grep {$_ eq $name} @SVN_REPOS_ROOT_HOOK_ITEMS) {
+            my $dest = catfile($project->get_svn_live_hook_path(), $name);
+            $RUNNER->run(
+                "install $dest <- ^/$name",
+                sub {
+                    my $source = "file://$project_path/$name";
+                    !system(qw{svn export -q --force}, $source, $dest)
+                        || die("\n");
+                    chmod((stat($dest))[2] | S_IRGRP | S_IROTH, $dest);
+                },
+            );
+            $path_of{$name} = "^/$name";
+        }
+    }
+    # Clean hook destination, if necessary
+    if ($clean_mode) {
+        my $hook_path = $project->get_svn_live_hook_path();
+        for my $path (sort glob(catfile($hook_path, q{*}))) {
+            if (!exists($path_of{basename($path)})) {
+                run_rmtree($path);
+            }
+        }
+    }
+    my $group = $CONFIG->get_svn_group();
+    if ($group) {
+        _chgrp_and_chmod($project->get_svn_live_hook_path(), $group);
+    }
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Updates the SVN password file.
+sub manage_users_in_svn_passwd {
+    my ($user_ref) = @_;
+    if (!$CONFIG->get_svn_passwd_file()) {
+        return 1;
+    }
+    my $svn_passwd_file = catfile(
+        $CONFIG->get_svn_live_dir(),
+        $CONFIG->get_svn_passwd_file(),
+    );
+    $RUNNER->run(
+        "updating $svn_passwd_file",
+        sub {
+            my $USERS_SECTION = q{users};
+            my $svn_passwd_ini;
+            my $is_changed;
+            if (-f $svn_passwd_file) {
+                $svn_passwd_ini
+                    = Config::IniFiles->new(q{-file} => $svn_passwd_file);
+            }
+            else {
+                $svn_passwd_ini = Config::IniFiles->new();
+                $svn_passwd_ini->SetFileName($svn_passwd_file);
+                $svn_passwd_ini->AddSection($USERS_SECTION);
+                $is_changed = 1;
+            }
+            for my $name (($svn_passwd_ini->Parameters($USERS_SECTION))) {
+                if (!exists($user_ref->{$name})) {
+                    $RUNNER->run(
+                        "removing $name from $svn_passwd_file",
+                        sub {
+                            return
+                                $svn_passwd_ini->delval($USERS_SECTION, $name);
+                        },
+                    );
+                    $is_changed = 1;
+                }
+            }
+            for my $user (values(%{$user_ref})) {
+                if (!defined($svn_passwd_ini->val($USERS_SECTION, "$user"))) {
+                    $RUNNER->run(
+                        "adding $user to $svn_passwd_file",
+                        sub {
+                            $svn_passwd_ini->newval(
+                                $USERS_SECTION, $user->get_name(), q{},
+                            ),
+                        },
+                    );
+                    $is_changed = 1;
+                }
+            }
+            return ($is_changed ? $svn_passwd_ini->RewriteConfig() : 1);
+        },
+    );
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Updates the Trac password file.
+sub manage_users_in_trac_passwd {
+    my ($user_ref) = @_;
+    if (!$CONFIG->get_trac_passwd_file()) {
+        return 1;
+    }
+    my $trac_passwd_file = catfile(
+        $CONFIG->get_trac_live_dir(),
+        $CONFIG->get_trac_passwd_file(),
+    );
+    $RUNNER->run(
+        "updating $trac_passwd_file",
+        sub {
+            my %old_names;
+            my %new_names = %{$user_ref};
+            if (-f $trac_passwd_file) {
+                read_file(
+                    $trac_passwd_file,
+                    sub {
+                        my ($line) = @_;
+                        chomp($line);
+                        if (
+                            !$line || $line =~ qr{\A\s*\z}xms # blank line
+                            || $line =~ qr{\A\s*\#}xms        # comment line
+                        ) {
+                            return;
+                        }
+                        my ($name, $passwd) = split(qr{\s*:\s*}xms, $line);
+                        if (exists($new_names{$name})) {
+                            delete($new_names{$name});
+                        }
+                        else {
+                            $old_names{$name} = 1;
+                        }
+                    },
+                ) || return;
+            }
+            else {
+                write_file($trac_passwd_file) || return;
+            }
+            if (%old_names || %new_names) {
+                for my $name (keys(%old_names)) {
+                    $RUNNER->run(
+                        "removing $name from $trac_passwd_file",
+                        sub {
+                            return !system(
+                                qw{htpasswd -D}, $trac_passwd_file, $name,
+                            );
+                        },
+                    );
+                }
+                for my $name (keys(%new_names)) {
+                    $RUNNER->run(
+                        "adding $name to $trac_passwd_file",
+                        sub {
+                            return !system(
+                                qw{htpasswd -b}, $trac_passwd_file, $name, q{},
+                            );
+                        },
+                    );
+                    sleep(1); # ensure the random seed for htpasswd is changed
+                }
+            }
+            return 1;
+        },
+        # Note: can use HTTPD::UserAdmin, if it is installed
+    );
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Manages the session* tables in the DB of a Trac environment.
+sub manage_users_in_trac_db_of {
+    my ($project, $user_ref) = @_;
+    return $RUNNER->run_with_retries(
+        sprintf(
+            qq{checking/updating %s},
+            $project->get_trac_live_db_path(),
+        ),
+        sub {return _manage_users_in_trac_db_of($project, $user_ref)},
+    );
+}
+
+# ------------------------------------------------------------------------------
+# Recovers a SVN repository from its backup.
+sub recover_svn_repository {
+    my ($project, $recover_dumps_option, $recover_hooks_option) = @_;
+    if (-e $project->get_svn_live_path()) {
+        die(sprintf(
+            "%s: live repository exists.\n",
+            $project->get_svn_live_path(),
+        ));
+    }
+    run_mkpath($CONFIG->get_svn_live_dir());
+    my $base_name = $project->get_svn_base_name();
+    my $work_dir = tempdir(
+        qq{$base_name.XXXXXX},
+        DIR => $CONFIG->get_svn_live_dir(),
+        CLEANUP => 1,
+    );
+    my $work_path = catfile($work_dir, $base_name);
+    _extract_backup_archive($project->get_svn_backup_path(), $work_path);
+    if ($recover_dumps_option) {
+        my $youngest = _svnlook_youngest($work_path);
+        my %dump_path_of;
+        _get_files_from(
+            $CONFIG->get_svn_dump_dir(),
+            sub {
+                my ($dump_base_name, $path) = @_;
+                my ($name, $rev) = $dump_base_name =~ qr{\A(.*)-(\d+)\.gz\z}msx;
+                if (    !$name
+                    ||  !$rev
+                    ||  $name ne $base_name
+                    ||  $rev <= $youngest
+                ) {
+                    return;
+                }
+                $dump_path_of{$rev} = $path;
+            },
+        );
+        for my $rev (sort {$a <=> $b} keys(%dump_path_of)) {
+            my $dump_path = $dump_path_of{$rev};
+            $RUNNER->run(
+                "loading $dump_path into $work_path",
+                sub {
+                    my $pipe = IO::Pipe->new();
+                    $pipe->writer(qw{svnadmin load}, $work_path);
+                    my $handle = IO::Zlib->new($dump_path, 'rb');
+                    if (!$handle) {
+                        die("$dump_path: $!\n");
+                    }
+                    while ($handle->read(my $buffer, $BUFFER_SIZE)) {
+                        $pipe->print($buffer);
+                    }
+                    $handle->close();
+                    return ($pipe->close());
+                },
+            );
+        }
+    }
+    run_rename($work_path, $project->get_svn_live_path());
+    my $group = $CONFIG->get_svn_group();
+    if ($group) {
+        _chgrp_and_chmod($project->get_svn_live_path(), $group);
+    }
+    if ($recover_hooks_option) {
+        install_svn_hook($project);
+        housekeep_svn_hook_logs($project);
+    }
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Recovers a Trac environment from its backup.
+sub recover_trac_environment {
+    my ($project) = @_;
+    if (-e $project->get_trac_live_path()) {
+        die(sprintf(
+            "%s: live environment exists.\n",
+            $project->get_trac_live_path(),
+        ));
+    }
+    run_mkpath($CONFIG->get_trac_live_dir());
+    my $base_name = $project->get_name();
+    my $work_dir = tempdir(
+        qq{$base_name.XXXXXX},
+        DIR => $CONFIG->get_trac_live_dir(),
+        CLEANUP => 1,
+    );
+    my $work_path = catfile($work_dir, $base_name);
+    _extract_backup_archive($project->get_trac_backup_path(), $work_path);
+    run_rename($work_path, $project->get_trac_live_path());
+    my $group = $CONFIG->get_trac_group();
+    if ($group) {
+        _chgrp_and_chmod($project->get_trac_live_path(), $group);
+    }
+}
+
+# ------------------------------------------------------------------------------
+# Recover a file from the Trac backup directory to the Trac live directory.
+sub recover_trac_files {
+    # (no argument)
+    _copy_files(
+        $CONFIG->get_trac_backup_dir(),
+        $CONFIG->get_trac_live_dir(),
+        $NO_OVERWRITE,
+        qr{\.tgz\z}msx,
+    );
+}
+
+# ------------------------------------------------------------------------------
+# Vacuum the database of a Trac environment.
+sub vacuum_trac_env_db {
+    my ($project) = @_;
+    $RUNNER->run(
+        "performing vacuum on database of Trac environment for $project",
+        sub {
+            my $db_handle = _get_trac_db_handle_for($project);
+            if (!$db_handle) {
+                return;
+            }
+            $db_handle->do(q{vacuum;}) && $db_handle->disconnect();
+        },
+    );
+}
+
+# ------------------------------------------------------------------------------
+# Verify users. Return a list of bad users from @users.
+sub verify_users {
+    my @users = @_;
+    if (!defined($USER_INFO_TOOL)) {
+        my $name = $CONFIG->get_user_info_tool();
+        my $class = $UTIL->class_load($USER_INFO_TOOL_OF{$name});
+        $USER_INFO_TOOL = $class->new({util => $UTIL});
+    }
+    return $USER_INFO_TOOL->verify_users(@users);
+}
+
+# ------------------------------------------------------------------------------
+# Changes/restores ownership and permission of a given $path to a given $group.
+sub _chgrp_and_chmod {
+    my ($path, $group) = @_;
+    my $gid = $group ? scalar(getgrnam($group)) : -1;
+    find(
+        sub {
+            my $file = $File::Find::name;
+            $RUNNER->run(
+                "changing group ownership for $file",
+                sub {return chown(-1, $gid, $file)},
+            );
+            my $mode = (stat($file))[2] | S_IRGRP | S_IWGRP;
+            $RUNNER->run(
+                "adding group write permission for $file",
+                sub {return chmod($mode, $file)},
+            );
+        },
+        $path,
+    );
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Copies files immediately under $source to $target.
+sub _copy_files {
+    my ($source, $target, $no_overwrite, $re_skip) = @_;
+    my @bases;
+    opendir(my $handle, $source) || die("$source: $!\n");
+    while (my $base = readdir($handle)) {
+        if (-f catfile($source, $base)) {
+            if ($no_overwrite && -f catfile($target, $base)) {
+                warn("[SKIP] $base: already exists in $target.\n");
+            }
+            elsif (!$re_skip || ($base !~ $re_skip)) {
+                push(@bases, $base);
+            }
+        }
+    }
+    closedir($handle);
+    run_mkpath($target);
+    for my $base (@bases) {
+        run_copy(map {catfile($_, $base)} ($source, $target));
+    }
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Creates backup archive from a path.
+sub _create_backup_archive {
+    my ($source_path, $backup_dir, $archive_base_name) = @_;
+    my $source_dir = dirname($source_path);
+    my $source_base_name = basename($source_path);
+    run_mkpath($backup_dir);
+    my ($fh, $work_backup_path)
+        = tempfile(qq{$archive_base_name.XXXXXX}, DIR => $backup_dir);
+    close($fh);
+    run_create_archive($work_backup_path, $source_dir, $source_base_name);
+    my $backup_path = catfile($backup_dir, $archive_base_name);
+    run_rename($work_backup_path, $backup_path);
+    my $mode = (stat($backup_path))[2] | S_IRGRP | S_IROTH;
+    return chmod($mode, $backup_path);
+}
+
+# ------------------------------------------------------------------------------
+# Extracts from a backup archive to a work path.
+sub _extract_backup_archive {
+    my ($archive_path, $work_path) = @_;
+    run_extract_archive($archive_path, dirname($work_path));
+    if (! -e $work_path) {
+        my ($base_name) = basename($work_path);
+        die("$base_name: does not exist in archive $archive_path.\n");
+    }
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Searches a directory for files and invokes a callback on each file.
+sub _get_files_from {
+    my ($dir_path, $callback_ref) = @_;
+    my $dir_handle = IO::Dir->new($dir_path);
+    if (!defined($dir_handle)) {
+        return;
+    }
+    BASE_NAME:
+    while (my $base_name = $dir_handle->read()) {
+        my $path = catfile($dir_path, $base_name);
+        if (index($base_name, q{.}) == 0) {
+            next BASE_NAME;
+        }
+        $callback_ref->($base_name, $path);
+    }
+    return $dir_handle->close();
+}
+
+# ------------------------------------------------------------------------------
+# Returns a database handle for the database of a Trac environment.
+sub _get_trac_db_handle_for {
+    my ($project) = @_;
+    my $db_path = $project->get_trac_live_db_path();
+    return DBI->connect(qq{dbi:SQLite:dbname=$db_path}, q{}, q{});
+}
+
+# ------------------------------------------------------------------------------
+# Manages the session* tables in the DB of a Trac environment.
+sub _manage_users_in_trac_db_of {
+    my ($project, $user_ref) = @_;
+    my $db_handle = _get_trac_db_handle_for($project);
+    if (!$db_handle) {
+        return;
+    }
+    SESSION: {
+        my $session_select_statement = $db_handle->prepare(
+            "SELECT sid FROM session WHERE authenticated == 1",
+        );
+        my $session_insert_statement = $db_handle->prepare(
+            "INSERT INTO session VALUES (?, 1, 0)",
+        );
+        my $session_delete_statement = $db_handle->prepare(
+            "DELETE FROM session WHERE sid == ?",
+        );
+        $session_select_statement->execute();
+        my $is_changed = 0;
+        my %session_old_users;
+        while (my ($sid) = $session_select_statement->fetchrow_array()) {
+            if (exists($user_ref->{$sid})) {
+                $session_old_users{$sid} = 1;
+            }
+            else {
+                $RUNNER->run(
+                    "session: removing $sid",
+                    sub{return $session_delete_statement->execute($sid)},
+                );
+                $is_changed = 1;
+            }
+        }
+        for my $sid (keys(%{$user_ref})) {
+            if (!exists($session_old_users{$sid})) {
+                $RUNNER->run(
+                    "session: adding $sid",
+                    sub {return $session_insert_statement->execute($sid)},
+                );
+                $is_changed = 1;
+            }
+        }
+        $session_select_statement->finish();
+        $session_insert_statement->finish();
+        $session_delete_statement->finish();
+    }
+    SESSION_ATTRIBUTE: {
+        my $attribute_select_statement = $db_handle->prepare(
+            "SELECT sid,name,value FROM session_attribute "
+                . "WHERE authenticated == 1 AND (name == ? OR name == ?)",
+        );
+        my $attribute_insert_statement = $db_handle->prepare(
+            "INSERT INTO session_attribute VALUES (?, 1, ?, ?)",
+        );
+        my $attribute_update_statement = $db_handle->prepare(
+            "UPDATE session_attribute SET value = ? "
+                . "WHERE sid = ? AND authenticated == 1 AND name == ?",
+        );
+        my $attribute_delete_statement = $db_handle->prepare(
+            "DELETE FROM session_attribute WHERE sid == ?",
+        );
+        $attribute_select_statement->execute('name', 'email');
+        my %attribute_old_users;
+        ROW:
+        while (my @row = $attribute_select_statement->fetchrow_array()) {
+            my ($sid, $name, $value) = @row;
+            my $user = exists($user_ref->{$sid})? $user_ref->{$sid} : undef;
+            if (defined($user)) {
+                $attribute_old_users{$sid} = 1;
+                my $getter
+                    = $name eq 'name'  ? 'get_display_name'
+                    : $name eq 'email' ? 'get_email'
+                    :                    undef;
+                if (!defined($getter)) {
+                    next ROW;
+                }
+                if ($user->$getter() ne $value) {
+                    my $new_value = $user->$getter();
+                    $RUNNER->run(
+                        "session_attribute: updating $name: $sid: $new_value",
+                        sub {return $attribute_update_statement->execute(
+                            $new_value, $sid, $name,
+                        )},
+                    );
+                }
+            }
+            else {
+                $RUNNER->run(
+                    "session_attribute: removing $sid",
+                    sub {return $attribute_delete_statement->execute($sid)},
+                );
+            }
+        }
+        USER:
+        for my $sid (keys(%{$user_ref})) {
+            if (exists($attribute_old_users{$sid})) {
+                next USER;
+            }
+            my $user = $user_ref->{$sid};
+            my $display_name = $user->get_display_name();
+            my $email        = $user->get_email();
+            $RUNNER->run(
+                "session_attribute: adding name: $sid: $display_name",
+                sub {return $attribute_insert_statement->execute(
+                    $sid, 'name', $display_name,
+                )},
+            );
+            $RUNNER->run(
+                "session_attribute: adding email: $sid: $email",
+                sub {return $attribute_insert_statement->execute(
+                    $sid, 'email', $email,
+                )},
+            );
+        }
+        $attribute_select_statement->finish();
+        $attribute_insert_statement->finish();
+        $attribute_update_statement->finish();
+        $attribute_delete_statement->finish();
+    }
+    return $db_handle->disconnect();
+}
+
+# ------------------------------------------------------------------------------
+# Returns the youngest revision of a SVN repository.
+sub _svnlook_youngest {
+    my ($svn_repos_path) = @_;
+    my ($youngest) = qx{svnlook youngest $svn_repos_path};
+    chomp($youngest);
+    return $youngest;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::Admin::System
+
+=head1 SYNOPSIS
+
+    use FCM::Admin::System qw{ ... };
+    # ... see descriptions of individual functions for detail
+
+=head1 DESCRIPTION
+
+This module contains utility functions for the administration of Subversion
+repositories and Trac environments hosted by the FCM team.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item add_svn_repository($project_name)
+
+Creates a new Subversion repository.
+
+=item add_trac_environment($project_name)
+
+Creates a new Trac environment.
+
+=item backup_svn_repository(\%option,$project)
+
+Creates an archived hotcopy of $project's live SVN repository, and put it in the
+SVN backup directory. If $option{'no-verify-integrity'} does not exist, it
+verifies the integrity of the live repository before creating the hotcopy. If
+$option{'no-pack'} does not exist, it packs the live repository before creating
+the hotcopy. If $option{'no-housekeep-dumps'} does not exist, it housekeeps the
+revision dumps of $project following a successful backup.
+
+$project should be a L<FCM::Admin::Project|FCM::Admin::Project> object.
+
+=item backup_trac_environment(\%option,$project)
+
+Creates an archived hotcopy of $project's live Trac environment, and put it in
+the Trac backup directory. If $option{'no-verify-integrity'} does not exist, it
+verifies the integrity of the database of the live environment before creating
+the hotcopy.
+
+$project should be a L<FCM::Admin::Project|FCM::Admin::Project> object.
+
+=item backup_trac_files()
+
+Copies regular files immediately under the live Trac directory to the Trac
+backup directory.
+
+=item distribute_wc()
+
+Distributes the central FCM working copy to standard locations.
+
+=item filter_projects($project_list_ref,$filter_list_ref)
+
+Filters the project list in $project_list_ref using a list of names in
+$filter_list_ref. Returns a list of projects with names matching those in
+$filter_list_ref. Returns the full list if $filter_list_ref points to an empty
+list.
+
+=item get_projects_from_svn_backup()
+
+Returns a list of L<FCM::Admin::Project|FCM::Admin::Project> objects by
+searching the SVN backup directory. By default, all valid projects are returned.
+
+=item get_projects_from_svn_live()
+
+Similar to get_projects_from_svn_backup(), but it searches the SVN live
+directory.
+
+=item get_projects_from_trac_backup()
+
+Similar to get_projects_from_svn_backup(), but it searches the Trac backup
+directory.
+
+=item get_projects_from_trac_live()
+
+Similar to get_projects_from_svn_backup(), but it searches the Trac live
+directory.
+
+=item get_users(@only_users)
+
+Retrieves a list of users. Store results in a HASH, {user ID => user info, ...}
+where each user info is stored in an instance of
+L<FCM::Admin::System::User|FCM::Admin::System::User>.
+
+If no argument, return all valid users. If @only_users, return only those users
+with matching user ID in @only_users.
+
+=item housekeep_svn_hook_logs($project)
+
+Housekeep logs generated by the hook scripts of the $project's SVN live
+repository.
+
+$project should be a L<FCM::Admin::Project|FCM::Admin::Project> object.
+
+=item install_svn_hook($project, $clean_mode)
+
+Searches for hook scripts in the standard location and install them (as symbolic
+links) in the I<hooks> directory of the $project's SVN live repository.
+
+$project should be a L<FCM::Admin::Project|FCM::Admin::Project> object.
+
+If $clean_mode is specified and is true, remove any items in the I<hooks>
+directory that are not known to this install.
+
+=item manage_users_in_svn_passwd($user_ref)
+
+Using entries in the hash reference $user_ref, sets up or updates the SVN and
+Trac password files. The $user_ref argument should be a reference to a hash, as
+returned by get_users().
+
+=item manage_users_in_trac_passwd($user_ref)
+
+Using entries in the hash reference $user_ref, sets up or updates the Trac
+password files. The $user_ref argument should be a reference to a hash, as
+returned by get_users().
+
+=item manage_users_in_trac_db_of($project, $user_ref)
+
+Using entries in $user_ref, sets up or updates the session/session_attribute
+tables in the databases of the live Trac environments. The $project argument
+should be a L<FCM::Admin::Project|FCM::Admin::Project> object
+and $user_ref should be a reference to a hash, as returned by get_users().
+
+=item recover_svn_repository($project,$recover_dumps_option,$recover_hooks_option)
+
+Recovers a project's SVN repository using its backup. If $recover_dumps_option
+is set to true, it will also attempt to load the latest revision dumps following
+a successful recovery. If $recover_hooks_option is set to true, it will also
+attempt to re-install the hook scripts following a successful recovery.
+
+$project should be a L<FCM::Admin::Project|FCM::Admin::Project> object.
+
+=item recover_trac_environment($project)
+
+Recovers a project's Trac environment using its backup.
+
+$project should be a L<FCM::Admin::Project|FCM::Admin::Project> object.
+
+=item recover_trac_files()
+
+Copies files immediately under the backup Trac directory to the Trac live
+directory (if the files do not already exist).
+
+=item vacuum_trac_env_db($project)
+
+Connects to the database of a project's Trac environment, and issues the
+"VACUUM" SQL command.
+
+$project should be a L<FCM::Admin::Project|FCM::Admin::Project> object.
+
+=back
+
+=head1 SEE ALSO
+
+L<FCM::Admin::Config|FCM::Admin::Config>,
+L<FCM::Admin::Project|FCM::Admin::Project>,
+L<FCM::Admin::Runner|FCM::Admin::Runner>,
+L<FCM::Admin::User|FCM::Admin::User>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Admin/User.pm b/lib/FCM/Admin/User.pm
new file mode 100644
index 0000000..2db3303
--- /dev/null
+++ b/lib/FCM/Admin/User.pm
@@ -0,0 +1,90 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+package FCM::Admin::User;
+use base qw{FCM::Class::HASH};
+use overload q{""} => \&get_name;
+
+__PACKAGE__->class({
+    name         => '$',
+    display_name => '$',
+    email        => '$',
+});
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::Admin::User
+
+=head1 SYNOPSIS
+
+    use FCM::Admin::User;
+    $user = FCM::Admin::User->new({name => 'bob'});
+    $user->set_display_name('Robert Smith');
+    $user->set_email('robert.smith at somewhere.org');
+
+=head1 DESCRIPTION
+
+An object of this class is used to store the data model of a user.
+
+=head1 METHODS
+
+=over 4
+
+=item FCM::Admin::User->new(\%arguments)
+
+Creates a new instance. The keys of the %argument hash may contain "name",
+"display_name", and/or "email".
+
+=item $user->get_name()
+
+Returns the name/ID of the user.
+
+=item $user->get_display_name()
+
+Returns the display name of the user.
+
+=item $user->get_email()
+
+Returns the e-mail address of the user.
+
+=item $user->set_name($value)
+
+Sets the name/ID of the user.
+
+=item $user->set_display_name($value)
+
+Sets the display name of the user.
+
+=item $user->set_email($value)
+
+Sets the e-mail address of the user.
+
+=back
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Admin/Users/LDAP.pm b/lib/FCM/Admin/Users/LDAP.pm
new file mode 100644
index 0000000..a08ad17
--- /dev/null
+++ b/lib/FCM/Admin/Users/LDAP.pm
@@ -0,0 +1,149 @@
+
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+package FCM::Admin::Users::LDAP;
+use base qw{FCM::Class::CODE};
+
+use FCM::Admin::Config;
+use FCM::Admin::User;
+use Net::LDAP;
+use Text::ParseWords qw{shellwords};
+
+my %ACTION_OF = (
+    get_users_info => \&_get_users_info,
+    verify_users   => \&_verify_users,
+);
+
+__PACKAGE__->class({util => '&'}, {action_of => {%ACTION_OF}});
+
+my $CONFIG = FCM::Admin::Config->instance();
+
+# Gets a HASH of users via LDAP.
+# %user_of = ($name => <FCM::Admin::User instance>, ...)
+sub _get_users_info {
+    my ($attrib_ref, @only_users) = @_;
+    my $res = _ldap_search($attrib_ref, undef, @only_users);
+    my ($uid_attr, $cn_attr, $mail_attr)
+        = shellwords($CONFIG->get_ldap_attrs());
+    my %user_of;
+    for my $entry ($res->entries()) {
+        my $name = $entry->get_value($uid_attr);
+        my $display_name = $entry->get_value($cn_attr);
+        my $email = $entry->get_value($mail_attr);
+        if ($display_name && $email) {
+            $user_of{$name} = FCM::Admin::User->new({
+                name         => $name,
+                display_name => $display_name,
+                email        => $email,
+            });
+        }
+    }
+    return (wantarray() ? %user_of : \%user_of);
+}
+
+# Return a list of bad users.
+sub _verify_users {
+    my ($attrib_ref, @users) = @_;
+    my $res = _ldap_search($attrib_ref, 0, @users); # 0 == $uid_attr
+    my ($uid_attr, $cn_attr, $mail_attr)
+        = shellwords($CONFIG->get_ldap_attrs());
+    my %bad_users = map {($_ => 1)} @users;
+    for my $entry ($res->entries()) {
+        my $name = $entry->get_value($uid_attr);
+        if (exists($bad_users{$name})) {
+            delete($bad_users{$name});
+        }
+    }
+    return sort(keys(%bad_users));
+}
+
+# Bind to the LDAP server. Return a Net::LDAP instance.
+sub _ldap_search {
+    my ($attrib_ref, $attr_index, @users) = @_;
+
+    my $ldap_uri = $CONFIG->get_ldap_uri();
+    my $ldap = Net::LDAP->new($CONFIG->get_ldap_uri());
+    my $password_file = $CONFIG->get_ldappw();
+    $password_file = $attrib_ref->{util}->file_tilde_expand($password_file);
+    my $password = $password_file
+        ? $attrib_ref->{util}->file_load($password_file)
+        : undef;
+    my %ldap_options = $password ? (password => $password) : ();
+    $ldap->bind($CONFIG->get_ldap_binddn(), %ldap_options);
+
+    my @attrs = shellwords($CONFIG->get_ldap_attrs());
+    my ($uid_attr) = @attrs;
+    my $filter = @users
+        ? "(|($uid_attr=" . join(")($uid_attr=", @users) . '))'
+        : "(&($uid_attr=*))";
+    my $ldap_filter_more = $CONFIG->get_ldap_filter_more();
+    if ($ldap_filter_more) {
+        $filter = '(&' . $filter . $ldap_filter_more . ')';
+    }
+    my $res = $ldap->search(
+        base   => $CONFIG->get_ldap_basedn(),
+        filter => $filter,
+        attrs  => [$attr_index ? ($attrs[$attr_index]) : @attrs],
+    );
+    $ldap->unbind();
+    return $res;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::Admin::Users::LDAP
+
+=head1 SYNOPSIS
+
+    use FCM::Admin::Users::LDAP;
+    my $users_info_util = FCM::Admin::Users::LDAP->new();
+    $users_info_util->get_users();
+
+=head1 DESCRIPTION
+
+Utility for obtaining user information via LDAP.
+
+=head1 METHODS
+
+=over 4
+
+=item $util->get_users_info()
+
+Return a HASH (in list context) or a reference to a HASH (in scalar context)
+{name => <FCM::Admin::User instance>, ...}. The HASH should contain all entries
+in the passwd database that appear to be real users.
+
+=item $util->verify_users(@users)
+
+Return a list of bad users in @users.
+
+=back
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Admin/Users/Passwd.pm b/lib/FCM/Admin/Users/Passwd.pm
new file mode 100644
index 0000000..769f799
--- /dev/null
+++ b/lib/FCM/Admin/Users/Passwd.pm
@@ -0,0 +1,141 @@
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+package FCM::Admin::Users::Passwd;
+use base qw{FCM::Class::CODE};
+
+use FCM::Admin::Config;
+use FCM::Admin::User;
+use Text::ParseWords qw{shellwords};
+
+my %ACTION_OF = (
+    get_users_info => \&_get_users_info,
+    verify_users   => \&_verify_users,
+);
+
+__PACKAGE__->class({}, {action_of => {%ACTION_OF}});
+
+my $CONFIG = FCM::Admin::Config->instance();
+
+# Gets a HASH of users using the POSIX password DB.
+# %user_of = ($name => <FCM::Admin::User instance>, ...)
+sub _get_users_info {
+    my ($attrib_ref, @only_users) = @_;
+    if (@only_users) {
+        return _get_only_users_info($attrib_ref, @only_users);
+    }
+    my $domain = $CONFIG->get_passwd_email_domain() || q{};
+    if ($domain) {
+        $domain = '@' . $domain;
+    }
+    my $gid_max = $CONFIG->get_passwd_gid_max();
+    my $uid_max = $CONFIG->get_passwd_uid_max();
+    my $gid_min = $CONFIG->get_passwd_gid_min();
+    my $uid_min = $CONFIG->get_passwd_uid_min();
+    my %user_of;
+    USER:
+    while (my ($name, $uid, $gid, $gecos) = (getpwent())[0, 2, 3, 6]) {
+        if (    exists($user_of{$name})
+            ||  defined($uid_max) && $uid > $uid_max || $uid < $uid_min
+            ||  defined($gid_max) && $gid > $gid_max || $gid < $gid_min
+            ||  !$gecos
+            ||  (@only_users && grep {$_ eq $name} @only_users)
+        ) {
+            next USER;
+        }
+        $user_of{$name} = FCM::Admin::User->new({
+            name         => $name,
+            display_name => $gecos,
+            email        => $gecos . $domain,
+        });
+    }
+    endpwent();
+    return (wantarray() ? %user_of : \%user_of);
+}
+
+# Gets a HASH of users matching @only_users using the POSIX password DB.
+# %user_of = ($name => <FCM::Admin::User instance>, ...)
+sub _get_only_users_info {
+    my ($attrib_ref, @only_users) = @_;
+    my $domain = $CONFIG->get_passwd_email_domain() || q{};
+    if ($domain) {
+        $domain = '@' . $domain;
+    }
+    my @ok_uids = shellwords($CONFIG->get_passwd_ok_uids());
+    my %user_of;
+    for my $user (@only_users) {
+        my ($name, $gecos) = (getpwnam($user))[0, 6];
+        if ($name && $gecos && $gecos =~ qr{\A[\w\.\-]+\.[\w\.\-]+\z}msx) {
+            $user_of{$name} = FCM::Admin::User->new({
+                name         => $name,
+                display_name => $gecos,
+                email        => $gecos . $domain,
+            });
+        }
+    }
+    return (wantarray() ? %user_of : \%user_of);
+}
+
+# Return a list of bad users in @users.
+sub _verify_users {
+    my ($attrib_ref, @users) = @_;
+    grep {!getpwnam($_)} @users;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::Admin::Users::Passwd
+
+=head1 SYNOPSIS
+
+    use FCM::Admin::Users::Passwd;
+    my $users_info_util = FCM::Admin::Users::Passwd->new();
+    $users_info_util->get_users();
+
+=head1 DESCRIPTION
+
+Utility for obtaining user information from passwd information.
+
+=head1 METHODS
+
+=over 4
+
+=item $util->get_users_info()
+
+Return a HASH (in list context) or a reference to a HASH (in scalar context)
+{name => <FCM::Admin::User instance>, ...}. The HASH should contain all entries
+in the passwd database that appear to be real users.
+
+=item $util->verify_users(@users)
+
+Return a list of bad users in @users.
+
+=back
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Admin/Util.pm b/lib/FCM/Admin/Util.pm
new file mode 100644
index 0000000..524339d
--- /dev/null
+++ b/lib/FCM/Admin/Util.pm
@@ -0,0 +1,386 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+package FCM::Admin::Util;
+
+use Exporter qw{import};
+use FCM::Admin::Config;
+use FCM::Admin::Runner;
+use File::Basename qw{dirname};
+use File::Copy qw{copy};
+use File::Path qw{mkpath rmtree};
+use IO::File;
+use SVN::Client;
+
+our @EXPORT_OK = qw{
+    option2config
+    read_file
+    run_copy
+    run_create_archive
+    run_extract_archive
+    run_mkpath
+    run_rename
+    run_rmtree
+    run_rsync
+    run_svn_info
+    run_svn_update
+    run_symlink
+    sed_file
+    write_file
+};
+
+my @HTML2PS = qw{html2ps -n -U -W b};
+my @PS2PDF  = qw{
+    ps2pdf
+    -dMaxSubsetPct=100
+    -dCompatibilityLevel=1.3
+    -dSubsetFonts=true
+    -dEmbedAllFonts=true
+    -dAutoFilterColorImages=false
+    -dAutoFilterGrayImages=false
+    -dColorImageFilter=/FlateEncode
+    -dGrayImageFilter=/FlateEncode
+    -dMonoImageFilter=/FlateEncode
+    -sPAPERSIZE=a4
+};
+
+# ------------------------------------------------------------------------------
+# Loads values of an option hash into the configuration.
+sub option2config {
+    my ($option_ref) = @_;
+    my $config = FCM::Admin::Config->instance();
+    for my $key (keys(%{$option_ref})) {
+        my $method = $key;
+        $method =~ s{-}{_}gxms;
+        $method = "set_$method";
+        if ($config->can($method)) {
+            $config->$method($option_ref->{$key});
+        }
+    }
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Reads lines from a file.
+sub read_file {
+    my ($path, $sub_ref) = @_;
+    my $file = IO::File->new($path);
+    if (!defined($file)) {
+        die("$path: cannot open for reading ($!).\n");
+    }
+    while (my $line = $file->getline()) {
+        $sub_ref->($line);
+    }
+    $file->close() || die("$path: cannot close for reading ($!).\n");
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Runs copy with checks and diagnostics.
+sub run_copy {
+    my ($source_path, $dest_path) = @_;
+    FCM::Admin::Runner->instance()->run(
+        "copy $source_path to $dest_path",
+        sub {
+            my $mode = (stat($source_path))[2];
+            my $rc = copy($source_path, $dest_path) && chmod($mode, $dest_path);
+            if (!$rc) {
+                die($!);
+            }
+            return $rc;
+        },
+    );
+}
+
+# ------------------------------------------------------------------------------
+# Creates a TAR-GZIP archive.
+sub run_create_archive {
+    my ($archive_path, $work_dir, @base_names) = @_;
+    FCM::Admin::Runner->instance()->run(
+        "creating archive $archive_path",
+        sub {
+            return !system(
+                qw{tar -c -z},
+                q{-C} => $work_dir,
+                q{-f} => $archive_path,
+                @base_names,
+            );
+            # Note: can use Archive::Tar, but "tar" is much faster.
+        },
+    );
+}
+
+# ------------------------------------------------------------------------------
+# Extracts from a TAR-GZIP archive.
+sub run_extract_archive {
+    my ($archive_path, $work_dir) = @_;
+    FCM::Admin::Runner->instance()->run(
+        "extracting archive $archive_path",
+        sub {
+            return !system(
+                qw{tar -x -z},
+                q{-C} => $work_dir,
+                q{-f} => $archive_path,
+            );
+            # Note: can use Archive::Tar, but "tar" is much faster.
+        },
+    );
+}
+
+# ------------------------------------------------------------------------------
+# Runs mkpath with checks and diagnostics.
+sub run_mkpath {
+    my ($path) = @_;
+    if (!-d $path) {
+        FCM::Admin::Runner->instance()->run(
+            "creating $path",
+            sub {return mkpath($path)},
+        );
+    }
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Runs rename with checks and diagnostics.
+sub run_rename {
+    my ($source_path, $dest_path) = @_;
+    FCM::Admin::Runner->instance()->run(
+        "renaming $source_path to $dest_path",
+        sub {
+            run_mkpath(dirname($dest_path));
+            my $rc = rename($source_path, $dest_path);
+            if (!$rc) {
+                die($!);
+            }
+            return $rc;
+        },
+    );
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Runs rmtree with checks and diagnostics.
+sub run_rmtree {
+    my ($path) = @_;
+    if (-e $path) {
+        FCM::Admin::Runner->instance()->run(
+            "removing $path",
+            sub {
+                rmtree($path);
+                return !-e $path;
+            },
+        );
+    }
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Runs rsync.
+sub run_rsync {
+    my ($sources_ref, $dest_path, $option_list_ref) = @_;
+    FCM::Admin::Runner->instance()->run(
+        sprintf('mirroring %s <- %s', $dest_path, join(q{ }, @{$sources_ref})),
+        sub {return !system(
+            q{rsync},
+            ($option_list_ref ? @{$option_list_ref} : ()),
+            @{$sources_ref},
+            $dest_path,
+        )},
+    );
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Runs "svn info".
+sub run_svn_info {
+    my ($path) = @_;
+    my $return;
+    my $ctx = SVN::Client->new();
+    $ctx->info($path, undef, 'WORKING', sub {$return = $_[1]}, 0);
+    return $return;
+}
+
+# ------------------------------------------------------------------------------
+# Runs "svn update".
+sub run_svn_update {
+    my ($path) = @_;
+    my @return;
+    my $ctx = SVN::Client->new(
+        notify => sub {
+            if ($path ne $_[0]) {
+                push(@return, $_[0]);
+            }
+        }
+    );
+    $ctx->update($path, 'HEAD', 1);
+    return @return;
+}
+
+# ------------------------------------------------------------------------------
+# Runs symlink with checks and diagnostics.
+sub run_symlink {
+    my ($source_path, $dest_path) = @_;
+    FCM::Admin::Runner->instance()->run(
+        "creating symlink: $source_path -> $dest_path",
+        sub {
+            my $rc = symlink($source_path, $dest_path);
+            if (!$rc) {
+                die($!);
+            }
+            return $rc;
+        },
+    );
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# Edits content of a file.
+sub sed_file {
+    my ($path, $sub_ref) = @_;
+    my @lines;
+    read_file(
+        $path,
+        sub {
+            my ($line) = @_;
+            $line = $sub_ref->($line);
+            push(@lines, $line);
+        },
+    );
+    write_file($path, @lines);
+}
+
+# ------------------------------------------------------------------------------
+# Writes content to a file.
+sub write_file {
+    my ($path, @contents) = @_;
+    mkpath(dirname($path));
+    my $file = IO::File->new($path, q{w});
+    if (!defined($file)) {
+        die("$path: cannot open for writing ($!).\n");
+    }
+    for my $content (@contents) {
+        $file->print($content);
+    }
+    $file->close() || die("$path: cannot close for writing ($!).\n");
+    return 1;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::Admin::Util
+
+=head1 SYNOPSIS
+
+    use FCM::Admin::Util qw{ ... };
+    # ... see descriptions of individual functions for detail
+
+=head1 DESCRIPTION
+
+This module contains utility functions for the administration of Subversion
+repositories and Trac environments hosted by the FCM team.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item option2config($option_ref)
+
+Loads the values of an option hash into
+L<FCM::Admin::Config|FCM::Admin::Config>.
+
+=item read_file($path,$sub_ref)
+
+Reads from $path. For each $line the file, calls $sub_ref->($line).
+
+=item run_copy($source_path,$dest_path)
+
+Copies $source_path to $dest_path, with diagnostic.
+
+=item run_create_archive($archive_path,$work_dir, at base_names)
+
+Creates a TAR-GZIP archive at $archive_path using $work_dir as the working
+directory and @base_names as members of the archive. Depends on GNU "tar" or
+compatible.
+
+=item run_extract_archive($archive_path,$work_dir)
+
+Extracts a TAR-GZIP archive at $archive_path using $work_dir as the working
+directory. Depends on GNU "tar" or compatible.
+
+=item run_mkpath($path)
+
+Creates $path if it does not already exist, with diagnostic.
+
+=item run_rename($source_path,$dest_path)
+
+Same as the core I<rename>, but with diagnostic.
+
+=item run_rmtree($path)
+
+Removes $path, with diagnostic.
+
+=item run_rsync(\@sources,$dest_path,$option_list_ref)
+
+Invokes the "rsync" shell command with diagnostics to mirror the paths in
+ at sources to $dest_path. Command line options can be specified in a list with
+$option_list_ref. Depends on "rsync".
+
+=item run_svn_info($path)
+
+Wrapper of the info() method of L<SVN::Client|SVN::Client>. Expects $path to be
+a Subversion working copy. Returns the C<svn_info_t> object as described by the
+info() method of L<SVN::Client|SVN::Client>.
+
+=item run_svn_update($path)
+
+Wrapper of the update() method of L<SVN::Client|SVN::Client>. Expects $path to be
+a Subversion working copy. Returns a list of updated paths.
+
+=item run_symlink($source_path,$dest_path)
+
+Same as the core I<symlink>, but with diagnostic.
+
+=item sed_file($path,$sub_ref)
+
+For each $line in $path, runs $line = $sub_ref->($line). Writes results back to
+$path.
+
+=item write_file($path,$content)
+
+Writes $content to $path.
+
+=back
+
+=head1 SEE ALSO
+
+L<FCM::Admin::Config|FCM::Admin::Config>,
+L<FCM::Admin::Runner|FCM::Admin::Runner>,
+L<SVN::Client|SVN::Client>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/CLI.pm b/lib/FCM/CLI.pm
new file mode 100644
index 0000000..c4fd6fd
--- /dev/null
+++ b/lib/FCM/CLI.pm
@@ -0,0 +1,291 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::CLI;
+use base qw{FCM::Class::CODE};
+
+use FCM::CLI::Exception;
+use FCM::CLI::Parser;
+use FCM::Context::Event;
+use FCM::System;
+use FindBin;
+use File::Basename        qw{dirname};
+use File::Spec::Functions qw{catfile rel2abs};
+use Pod::Usage            qw{pod2usage};
+
+my $E = 'FCM::CLI::Exception';
+our $EVENT;
+our $S;
+our %ACTION_OF = (
+    # Commands handled by FCM
+    'add'           => _opt_func('check', sub {$S->cm_check_unknown(@_)}),
+    'branch'        => \&_branch,
+    'branch-create' => _sys_func(sub {$S->cm_branch_create(@_)}),
+    'branch-delete' => _sys_func(sub {$S->cm_branch_delete(@_)}),
+    'branch-diff'   => _sys_func(sub {$S->cm_branch_diff(@_)}),
+    'branch-info'   => _sys_func(sub {$S->cm_branch_info(@_)}),
+    'branch-list'   => _sys_func(sub {$S->cm_branch_list(@_)}),
+    'browse'        => _sys_func(sub {$S->browse(@_)}),
+    'build'         => _sys_func(sub {$S->build(@_)}),
+    'cfg-print'     => _sys_func(sub {$S->config_parse(@_)}),
+    'checkout'      => _sys_func(sub {$S->cm_checkout(@_)}),
+    'cmp-ext-cfg'   => _sys_func(sub {$S->config_compare(@_)}),
+    'commit'        => _sys_func(sub {$S->cm_commit(@_)}),
+    'conflicts'     => _sys_func(sub {$S->cm_resolve_conflicts(@_)}),
+    'delete'        => _opt_func('check', sub {$S->cm_check_missing(@_)}),
+    'diff'          => _opt_func(
+        'branch', sub {$S->cm_branch_diff(@_)}, sub {$S->cm_diff(@_)},
+    ),
+    'export-items'  => _sys_func(sub {$S->export_items(@_)}),
+    'extract'       => _sys_func(sub {$S->extract(@_)}),
+    'gui'           => \&_gui,
+    'help'          => \&_help,
+    'keyword-print' => _sys_func(sub {$S->keyword_find(@_)}),
+    'loc-layout'    => _sys_func(sub {$S->cm_loc_layout(@_)}),
+    'merge'         => _sys_func(sub {$S->cm_merge(@_)}),
+    'mkpatch'       => _sys_func(sub {$S->cm_mkpatch(@_)}),
+    'make'          => _sys_func(sub {$S->make(@_)}),
+    'project-create'=> _sys_func(sub {$S->cm_project_create(@_)}),
+    'switch'        => _opt_func(
+        'relocate', sub {$S->svn(@_)}, sub {$S->cm_switch(@_)},
+    ),
+    'test-battery'  => \&_test_battery,
+    'update'        => _sys_func(sub {$S->cm_update(@_)}),
+    'version'       => _sys_func(sub {$S->version(@_)}),
+    # Commands passed directly to "svn"
+    map {($_ => _sys_func())} qw{
+        blame
+        cat
+        cleanup
+        copy
+        export
+        import
+        info
+        list
+        lock
+        log
+        mergeinfo
+        mkdir
+        move
+        patch
+        propdel
+        propedit
+        propget
+        proplist
+        propset
+        relocate
+        resolve
+        resolved
+        revert
+        status
+        unlock
+        upgrade
+    },
+);
+# List of overridden subcommands that need to display "svn help"
+our %CLI_MORE_HELP_FOR = map {($_, 1)} (qw{add delete diff switch update});
+
+# Creates the class.
+__PACKAGE__->class(
+    {'gui' => '$', 'parser' => 'FCM::CLI::Parser', 'system' => 'FCM::System'},
+    {   init => sub {
+            my $attrib_ref = shift();
+            $attrib_ref->{parser} ||= FCM::CLI::Parser->new();
+            $attrib_ref->{system}
+                ||= FCM::System->new({'gui' => $attrib_ref->{'gui'}});
+        },
+        action_of => {main => \&_main},
+    },
+);
+
+# The main CLI action.
+sub _main {
+    my ($attrib_ref, @argv) = @_;
+    local($EVENT) = sub {$attrib_ref->{system}->util()->event(@_)};
+    my ($app, $option_ref, @args) = eval {$attrib_ref->{parser}->parse(@argv)};
+    if (my $e = $@) {
+        _err($attrib_ref, \@argv, $e);
+    }
+    if (!$app || $option_ref->{help}) {
+        return _help($attrib_ref, $app);
+    }
+    $option_ref ||= {};
+    my $q = $option_ref->{quiet}   || 0;
+    my $v = $option_ref->{verbose} || 0;
+    my $reporter = $attrib_ref->{system}->util()->util_of_report();
+    my $verbosity = $reporter->DEFAULT + $v - $q;
+    if (exists($ENV{FCM_DEBUG}) && $ENV{FCM_DEBUG} eq 'true') {
+        $verbosity = $reporter->DEBUG;
+    }
+    $reporter->get_ctx_of_stderr()->set_verbosity($verbosity);
+    $reporter->get_ctx_of_stdout()->set_verbosity($verbosity);
+    my @context = eval {
+        if (!exists($ACTION_OF{$app})) {
+            return $E->throw($E->APP, \@argv);
+        }
+        $ACTION_OF{$app}->($attrib_ref, $app, $option_ref, @args);
+    };
+    if (my $e = $@) {
+        return _err($attrib_ref, \@argv, $e);
+    }
+}
+
+# "fcm branch".
+sub _branch {
+    my ($attrib_ref, $app, $option_ref, @args) = @_;
+    my $method
+        = exists($option_ref->{create}) ? 'cm_branch_create'
+        : exists($option_ref->{delete}) ? 'cm_branch_delete'
+        : exists($option_ref->{list})   ? 'cm_branch_list'
+        :                                 'cm_branch_info'
+        ;
+    if ($option_ref->{create}) {
+        if (!$option_ref->{name}) {
+            return $E->throw($E->OPT, [$app, @args]);
+        }
+        my $name = delete($option_ref->{name});
+        unshift(@args, $name);
+    }
+    $attrib_ref->{system}->($method, $option_ref, @args);
+}
+
+# Handles FCM::Exception.
+sub _err {
+    my ($attrib_ref, $argv_ref, $e) = @_;
+    $EVENT->(FCM::Context::Event->E, $e) || die($e);
+    die("\n");
+}
+
+# "fcm gui".
+sub _gui {
+    my ($attrib_ref, $app, $option_ref, @args) = @_;
+    exec("$FindBin::Bin/fcm_gui", @args);
+}
+
+# Implements "fcm help" and usage.
+sub _help {
+    my ($attrib_ref, $app, $option_ref, @args) = @_;
+    $app ||= 'help';
+    my @keys = ($app eq 'help' && @args) ? @args : (q{});
+    for my $key (@keys) {
+        if (exists($FCM::CLI::Parser::PREF_NAME_OF{$key})) {
+            $key = $FCM::CLI::Parser::PREF_NAME_OF{$key};
+        }
+        my $pod
+            = $key ? catfile(dirname($INC{'FCM/CLI.pm'}), 'CLI', "fcm-$key.pod")
+            :        $0
+            ;
+        if ($pod eq $0) {
+            # Read fcm-version.js file
+            my $version = $attrib_ref->{system}->util()->version();
+            my $bin = rel2abs($0);
+            $EVENT->(FCM::Context::Event->OUT, "$version ($bin)\n");
+        }
+        my $has_pod = -f $pod;
+        if ($has_pod) {
+            my $reporter = $attrib_ref->{system}->util()->util_of_report();
+            my $verbosity = $reporter->get_ctx_of_stdout()->get_verbosity();
+            pod2usage({
+                '-exitval' => 'NOEXIT',
+                '-input'   => $pod,
+                '-verbose' => $verbosity,
+            });
+        }
+        if (!$has_pod || exists($CLI_MORE_HELP_FOR{$key})) {
+            $attrib_ref->{system}->svn('help', {}, $key ? $key : ())
+        }
+    }
+    return;
+}
+
+# "fcm test-battery".
+sub _test_battery {
+    my ($attrib_ref, $app, $option_ref, @args) = @_;
+    exec("$FindBin::Bin/fcm_test_battery", @args);
+}
+
+# Returns a function that select the alternate handler for the application. The
+# handler is either $method_id (if $opt_id is set) or "svn".
+sub _opt_func {
+    my ($opt_id, $code0_ref, $code1_ref) = @_;
+    $code0_ref = _sys_func($code0_ref);
+    $code1_ref = _sys_func($code1_ref);
+    sub {
+        my ($attrib_ref, $app, $option_ref, @args) = @_;
+        my $code_ref = exists($option_ref->{$opt_id}) ? $code0_ref : $code1_ref;
+        $code_ref->($attrib_ref, $app, $option_ref, @args);
+    };
+}
+
+# Invokes a system function.
+sub _sys_func {
+    my ($code_ref) = @_;
+    sub {
+        my ($attrib_ref, $app, @args) = @_;
+        local($S) = $attrib_ref->{system};
+        defined($code_ref) ? $code_ref->(@args) : $S->svn($app, @args);
+    };
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::CLI
+
+=head1 SYNOPSIS
+
+    my $cli = FCM::CLI->new();
+    $cli->(@ARGV);
+
+=head1 DESCRIPTION
+
+An implementation of the FCM command line interface.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new()
+
+Returns a new instance.
+
+=item $cli->(@ARGV)
+
+Determines the application using the first element in @ARGV, parses the options
+and arguments according to the application, and invokes the application.
+
+=back
+
+=head1 DIAGNOSTICS
+
+=head2 FCM::CLI::Exception
+
+This exception is thrown when the CLI fails to invoke an application.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/CLI/Exception.pm b/lib/FCM/CLI/Exception.pm
new file mode 100644
index 0000000..85631ef
--- /dev/null
+++ b/lib/FCM/CLI/Exception.pm
@@ -0,0 +1,57 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::CLI::Exception;
+use base qw{FCM::Exception};
+
+use constant {
+    APP => 'APP',
+    OPT => 'OPT',
+};
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::CLI::Exception
+
+=head1 SYNOPSIS
+
+    use FCM::CLI::Exception;
+    FCM::CLI::Exception->throw(FCM::CLI::Exception->APP, \@argv, $e);
+    FCM::CLI::Exception->throw(FCM::CLI::Exception->OPT, \@argv, $e);
+
+=head1 DESCRIPTION
+
+An exception associated with the FCM CLI. It is a sub-class of
+L<FCM::Exception|FCM::Exception>. The $e->get_ctx() method returns an ARRAY
+reference containing the argument list. The $e->get_code() method may return
+either $e->APP (if an unknown application is specified) or $e->OPT (if an
+unknown option is specified, i.e. the option parser returns some errors).
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/CLI/Parser.pm b/lib/FCM/CLI/Parser.pm
new file mode 100644
index 0000000..a85875a
--- /dev/null
+++ b/lib/FCM/CLI/Parser.pm
@@ -0,0 +1,459 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::CLI::Parser;
+use base qw{FCM::Class::CODE};
+
+use FCM::CLI::Exception;
+use Getopt::Long qw{GetOptions :config bundling};
+
+use constant {
+    OPT_INCR => q{+},   # no argument, but incremental
+    OPT_BOOL => q{},    # no argument
+    OPT_SCAL => q{=s},  # single argument
+    OPT_LIST => q{=s@}, # multiple argument
+};
+
+# Option hash, key = preferred name of option, value = HASH reference where:
+# arg     => argument flag
+# letters => ARRAY reference of a list of option letters
+# names   => ARRAY reference of a list of names
+our %OPTION_OF = map {
+    ($_->[0][0], {arg => $_->[2], letters => $_->[1], names => $_->[0]});
+} (
+    [['archive'            ,            ], ['a'], OPT_BOOL],
+    [['auto-log'           ,            ], [   ], OPT_BOOL],
+    [['branch'             ,            ], ['b'], OPT_BOOL],
+    [['branch-of-branch'   ,            ], [   ], OPT_BOOL],
+    [['browser'            ,            ], ['b'], OPT_SCAL],
+    [['check'              ,            ], ['c'], OPT_BOOL],
+    [['clean'              ,            ], [   ], OPT_BOOL],
+    [['create'             ,            ], ['c'], OPT_BOOL],
+    [['config-file'        , 'file'     ], ['f'], OPT_LIST],
+    [['config-file-path'   ,            ], ['F'], OPT_LIST],
+    [['custom'             ,            ], [   ], OPT_BOOL],
+    [['delete'             ,            ], ['d'], OPT_BOOL],
+    [['diff-cmd'           ,            ], [   ], OPT_SCAL],
+    [['directory'          ,            ], ['C'], OPT_SCAL],
+    [['dry-run'            ,            ], [   ], OPT_BOOL],
+    [['exclude'            ,            ], [   ], OPT_LIST],
+    [['extensions'         ,            ], ['x'], OPT_SCAL],
+    [['graphical'          ,            ], ['g'], OPT_BOOL],
+    [['fcm1'               ,            ], ['1'], OPT_BOOL],
+    [['full'               ,            ], ['f'], OPT_BOOL],
+    [['help'               , 'usage'    ], ['h'], OPT_BOOL],
+    [['ignore-lock'        ,            ], [   ], OPT_BOOL],
+    [['info'               ,            ], ['i'], OPT_BOOL],
+    [['jobs'               ,            ], ['j'], OPT_SCAL],
+    [['list'               ,            ], ['l'], OPT_BOOL],
+    [['name'               ,            ], ['n'], OPT_SCAL],
+    [['new'                ,            ], ['n'], OPT_BOOL],
+    [['non-interactive'    ,            ], [   ], OPT_BOOL],
+    [['only'               ,            ], [   ], OPT_LIST],
+    [['organisation'       ,            ], [   ], OPT_SCAL],
+    [['password'           ,            ], [   ], OPT_SCAL],
+    [['quiet'              ,            ], ['q'], OPT_INCR],
+    [['relocate'           ,            ], [   ], OPT_BOOL],
+    [['reverse'            ,            ], [   ], OPT_BOOL],
+    [['revision'           ,            ], ['r'], OPT_SCAL],
+    [['rev-flag'           ,            ], [   ], OPT_SCAL],
+    [['show-all'           ,            ], ['a'], OPT_BOOL],
+    [['show-children'      ,            ], [   ], OPT_BOOL],
+    [['show-other'         ,            ], [   ], OPT_BOOL],
+    [['show-siblings'      ,            ], [   ], OPT_BOOL],
+    [['stage'              ,            ], ['s'], OPT_SCAL],
+    [['summarize'          , 'summarise'], [   ], OPT_BOOL],
+    [['svn-non-interactive',            ], [   ], OPT_BOOL],
+    [['switch'             ,            ], ['s'], OPT_BOOL],
+    [['targets'            ,            ], ['t'], OPT_LIST],
+    [['ticket'             ,            ], ['k'], OPT_LIST],
+    [['trac'               ,            ], ['t'], OPT_BOOL],
+    [['type'               ,            ], ['t'], OPT_SCAL],
+    [['url'                ,            ], [   ], OPT_BOOL],
+    [['user'               ,            ], ['u'], OPT_LIST],
+    [['verbose'            ,            ], ['v'], OPT_INCR],
+    [['verbosity'          ,            ], ['v'], OPT_SCAL],
+    [['wiki'               ,            ], ['w'], OPT_BOOL],
+    [['wiki-format'        , 'wiki'     ], ['w'], OPT_SCAL],
+    [['xml'                ,            ], [   ], OPT_BOOL],
+);
+# Hook command before parsing the options
+our %HOOK_BEFORE_FOR = (
+    'add'    => _get_code_to_match($OPTION_OF{check}),
+    'delete' => _get_code_to_match($OPTION_OF{check}),
+    'diff'   => sub {
+        _get_code_to_replace(
+            $OPTION_OF{graphical}, [qw{--diff-cmd fcm_graphic_diff}]
+        )->(@_);
+        _get_code_to_replace($OPTION_OF{summarize}, ['--summarize'])->(@_);
+        _get_code_to_match($OPTION_OF{branch})->(@_);
+    },
+    'switch' => sub {!_get_code_to_match($OPTION_OF{relocate})->(@_)},
+);
+our $HELP_APP = 'help';
+# Options for known applications
+our %OPTIONS_FOR = (
+    'add'           => [$OPTION_OF{check}],
+    'branch'        => [@OPTION_OF{
+        qw{ branch-of-branch create delete info list name non-interactive
+            password quiet revision rev-flag show-all show-children
+            show-siblings svn-non-interactive ticket type user verbose
+        }
+    }],
+    'branch-create' => [@OPTION_OF{
+        qw{ branch-of-branch non-interactive password rev-flag
+            svn-non-interactive switch ticket type
+        }
+    }],
+    'branch-delete' => [@OPTION_OF{
+        qw{ non-interactive password quiet show-all show-children show-siblings
+            svn-non-interactive switch verbose
+        }
+    }],
+    'branch-diff'   => [@OPTION_OF{
+        qw{diff-cmd graphical extensions summarize trac wiki xml}
+    }],
+    'branch-info'   => [@OPTION_OF{
+        qw{quiet show-all show-children show-siblings verbose}
+    }],
+    'branch-list'   => [@OPTION_OF{
+        qw{only quiet show-all url user verbose}
+    }],
+    'browse'        => [$OPTION_OF{browser}],
+    'build'         => [@OPTION_OF{
+        qw{archive clean full ignore-lock jobs stage targets verbosity}
+    }],
+    'cfg-print'     => [$OPTION_OF{fcm1}],
+    'cmp-ext-cfg'   => [@OPTION_OF{qw{quiet verbose wiki-format}}],
+    'commit'        => [@OPTION_OF{
+        qw{dry-run password svn-non-interactive}
+    }],
+    'conflicts'     => [],
+    'delete'        => [$OPTION_OF{check}],
+    'diff'          => [@OPTION_OF{
+        qw{branch diff-cmd extensions summarize trac wiki}
+    }],
+    'export-items'  => [@OPTION_OF{qw{directory config-file new}}],
+    'extract'       => [@OPTION_OF{qw{clean full ignore-lock verbosity}}],
+    'gui'           => [],
+    $HELP_APP       => [@OPTION_OF{qw{quiet verbose}}],
+    'keyword-print' => [@OPTION_OF{qw{verbose}}],
+    'loc-layout'    => [@OPTION_OF{qw{verbose}}],
+    'merge'         => [@OPTION_OF{
+        qw{auto-log custom dry-run non-interactive quiet reverse revision verbose}
+    }],
+    'mkpatch'       => [@OPTION_OF{qw{exclude organisation revision}}],
+    'make'          => [@OPTION_OF{
+        qw{ directory ignore-lock jobs config-file config-file-path new quiet
+            verbose
+        }
+    }],
+    'project-create'=> [@OPTION_OF{
+        qw{non-interactive password svn-non-interactive}
+    }],
+    'switch'        => [@OPTION_OF{qw{non-interactive revision quiet verbose}}],
+    'update'        => [@OPTION_OF{qw{non-interactive revision quiet verbose}}],
+);
+# Preferred names of known applications with aliases
+our %PREF_NAME_OF = (
+    'ann'      => 'blame',
+    'annotate' => 'blame',
+    'bcreate'  => 'branch-create',
+    'bc'       => 'branch-create',
+    'bdel'     => 'branch-delete',
+    'bdelete'  => 'branch-delete',
+    'bdi'      => 'branch-diff',
+    'bdiff'    => 'branch-diff',
+    'binfo'    => 'branch-info',
+    'bld'      => 'build',
+    'blist'    => 'branch-list',
+    'bls'      => 'branch-list',
+    'br'       => 'branch',
+    'brm'      => 'branch-delete',
+    'cfg'      => 'cfg-print',
+    'ci'       => 'commit',
+    'cf'       => 'conflicts',
+    'co'       => 'checkout',
+    'cp'       => 'copy',
+    'del'      => 'delete',
+    'di'       => 'diff',
+    'ext'      => 'extract',
+    'h'        => $HELP_APP,
+    'kp'       => 'keyword-print',
+    'ls'       => 'list',
+    'mv'       => 'move',
+    'pd'       => 'propdel',
+    'pdel'     => 'propdel',
+    'pe'       => 'propedit',
+    'pedit'    => 'propedit',
+    'pg'       => 'propget',
+    'pget'     => 'propget',
+    'pl'       => 'proplist',
+    'plist'    => 'proplist',
+    'praise'   => 'blame',
+    'ps'       => 'propset',
+    'pset'     => 'propset',
+    'ren'      => 'move',
+    'rename'   => 'move',
+    'rm'       => 'delete',
+    'remove'   => 'delete',
+    'st'       => 'status',
+    'sw'       => 'switch',
+    'stat'     => 'status',
+    'trac'     => 'browse',
+    'up'       => 'update',
+    'usage'    => $HELP_APP,
+    'www'      => 'browse',
+    '?'        => $HELP_APP,
+    '-V'       => 'version',
+    '--help'   => $HELP_APP,
+    '--usage'  => $HELP_APP,
+    '--version'=> 'version',
+);
+
+# Creates the class.
+__PACKAGE__->class(
+    {   help_app        => {isa => '$', default => $HELP_APP            },
+        help_option     => {isa => '%', default => {%{$OPTION_OF{help}}}},
+        hook_before_for => {isa => '%', default => {%HOOK_BEFORE_FOR}   },
+        options_for     => {isa => '%', default => {%OPTIONS_FOR}       },
+        pref_name_of    => {isa => '%', default => {%PREF_NAME_OF}      },
+    },
+    {action_of => {parse => \&_parse}},
+);
+
+# Parses the options and arguments.
+sub _parse {
+    my ($attrib_ref, @argv) = @_;
+    my @args = @argv;
+    my $option_hash_ref = {};
+    if (!@args) {
+        return ($attrib_ref->{help_app}, $option_hash_ref);
+    }
+    my $app = shift(@args);
+    if (exists($attrib_ref->{pref_name_of}{$app})) {
+        $app = $attrib_ref->{pref_name_of}{$app};
+    }
+    if (_get_code_to_match($attrib_ref->{help_option})->(\@args)) {
+        return ($attrib_ref->{help_app}, {}, $app);
+    }
+    if (exists($attrib_ref->{hook_before_for}{$app})) {
+        if (!$attrib_ref->{hook_before_for}{$app}->(\@args)) {
+            return ($app, $option_hash_ref, @args);
+        }
+    }
+    if (!exists($attrib_ref->{options_for}{$app})) {
+        return ($app, $option_hash_ref, @args);
+    }
+    my @option_strings = map {
+        join('|', @{$_->{names}}, @{$_->{letters}}) . $_->{arg};
+    } @{$attrib_ref->{options_for}{$app}};
+    local(@ARGV) = @args;
+    my @warnings;
+    local($SIG{__WARN__}) = sub {push(@warnings, @_)};
+    if (!GetOptions($option_hash_ref, @option_strings)) {
+        my $E = 'FCM::CLI::Exception';
+        for (@warnings) {
+            chomp();
+        }
+        return $E->throw($E->OPT, \@argv, join('|', @warnings));
+    }
+    @args = @ARGV;
+    return ($app, $option_hash_ref, @args);
+}
+
+# Returns a CODE reference for matching a simple option to a string.
+sub _get_option_matcher {
+    my ($option_ref) = @_;
+    return sub {
+        grep {$_[0] eq $_} (
+            (map {"--$_"} @{$option_ref->{names}  }),
+            (map { "-$_"} @{$option_ref->{letters}}),
+        );
+    };
+}
+
+# Returns a CODE reference for matching a simple option to a string.
+sub _get_code_to_match {
+    my ($option_ref) = @_;
+    my $grepper = _get_option_matcher($option_ref);
+    return sub {grep {$grepper->($_)} @{$_[0]}};
+}
+
+# Returns a CODE reference to replace a simple option in the argument list.
+sub _get_code_to_replace {
+    my ($option_ref, $replacement) = @_;
+    my @replacements = ref($replacement) ? @{$replacement} : $replacement;
+    my $grepper = _get_option_matcher($option_ref);
+    return sub {
+        @{$_[0]} = map {($grepper->($_) ? @replacements : $_)} @{$_[0]};
+        return 1;
+    };
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::CLI::Parser
+
+=head1 SYNOPSIS
+
+    use FCM::CLI::Parser;
+    my $cli = FCM::CLI::Parser->new(\%attrib);
+    my ($app, $opt_hash_ref, @args) = $cli->(@ARGV);
+
+=head1 DESCRIPTION
+
+This class provides an option/argument parser for the FCM command line
+interface. The parser, when called with some arguments, returns a list. The 1st
+element is the name of the application, the 2nd element is a HASH reference
+containing the option names and their values. The remaining elements are the
+remaining arguments.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Returns a new instance. The %attrib HASH may contain the following elements:
+
+=over 4
+
+=item help_app
+
+The name of the I<help> application. Default = $FCM::CLI::Parser::HELP_APP. 
+
+=item help_option
+
+An option that represents I<help>. If this option is encountered in the command
+line, the CODE reference returns (help_app, {}, $app) regardless of the other
+command line options and arguments. Default =
+$FCM::CLI::Parser::OPTIONS_FOR{help}.
+
+=item hook_before_for
+
+Hook commands for the applications, which are executed before the option parser.
+See the L</CONFIGURATIONS> section for detail. Default =
+$FCM::CLI::Parser::HOOK_BEFORE_FOR.
+
+=item options_for
+
+The options for each application. See the L</CONFIGURATIONS> section for detail.
+Default = $FCM::CLI::Parser::OPTIONS_FOR.
+
+=item pref_name_of
+
+The preferred names for the applications. See the L</CONFIGURATIONS> section for
+detail. Default = $FCM::CLI::Parser::PREF_NAME_OF.
+
+=back
+
+=item $instance->(@args)
+
+=back
+
+=head1 CONFIGURATIONS
+
+The following should only be used as read-only variables. The
+$class->new(\%attrib) method should be used to configure a parser.
+
+=over 4
+
+=item $FCM::CLI::Parser::HELP_APP
+
+The name of the I<help> application.
+
+=item %FCM::CLI::Parser::HOOK_BEFORE_FOR
+
+A hash containing the hook commands, which are invoked before calling the option
+parser. The hash keys are names of the applications, and the values are CODE
+references to invoke. If a hook exists for an application, it is called as
+$hook->(\@args) where @args is the current command line arguments (with the
+first argument, i.e. the application name removed). If the hook returns a false
+value, the parser will return immediately.
+
+=item %FCM::CLI::Parser::OPTION_OF
+
+A hash containing the known options. The key is the preferred name of the
+option, and the value is a HASH reference, where C<names> (=> ARRAY reference)
+are the long names of the option, C<letters> (=> ARRAY reference) are the
+option letters, C<arg> (=> integer) is a flag. (See L</CONSTANTS> section for
+detail.)
+
+=item %FCM::CLI::Parser::OPTIONS_FOR
+
+A hash containing the known applications. The keys are the names of the
+applications and the values are ARRAY references, each pointing to
+a list of options (as described in %FCM::CLIParser::OPTION_OF) for the
+application.
+
+=item %FCM::CLI::Parser::PREF_NAME_OF
+
+A hash containing the preferred names of an application. The keys are the
+aliases and the values are the preferred names.
+
+=back
+
+=head1 CONSTANTS
+
+=over 4
+
+=item FCM::CLI::Parser->OPT_BOOL
+
+Option flag. Option is a boolean with no argument.
+
+=item FCM::CLI::Parser->OPT_INCR
+
+Option flag. Option has no argument but is incremental.
+
+=item FCM::CLI::Parser->OPT_LIST
+
+Option flag. Option has one or more arguments.
+
+=item FCM::CLI::Parser->OPT_SCAL
+
+Option flag. Option has a single argument.
+
+=back
+
+=head1 DIAGNOSTICS
+
+=over 4
+
+=item FCM::CLI::Parser::Exception
+
+This exception is raised if an invalid command option is given. It inherits from
+L<FCM::Exception>. There is no error code associated with this exception. The
+$e->get_ctx() method returns an ARRAY reference containing the original
+arguments.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/CLI/fcm-add.pod b/lib/FCM/CLI/fcm-add.pod
new file mode 100644
index 0000000..22279de
--- /dev/null
+++ b/lib/FCM/CLI/fcm-add.pod
@@ -0,0 +1,22 @@
+=head1 NAME
+
+fcm add
+
+=head1 SYNOPSIS
+
+    1. fcm add --check [PATH ...]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --check, -c
+
+Check for any files or directories reported by "L<svn|svn> status" as not under
+version control and add them.
+
+=back
+
+For other options, see output of "L<svn|svn> help add".
+
+=cut
diff --git a/lib/FCM/CLI/fcm-branch-create.pod b/lib/FCM/CLI/fcm-branch-create.pod
new file mode 100644
index 0000000..5cd802a
--- /dev/null
+++ b/lib/FCM/CLI/fcm-branch-create.pod
@@ -0,0 +1,86 @@
+=head1 NAME
+
+fcm branch-create (bcreate, bc)
+
+=head1 SYNOPSIS
+
+Creates a branch.
+
+    fcm branch-create [OPTIONS] NAME [SOURCE]
+
+=head1 ARGUMENTS
+
+NAME is a short name for the branch, which should contain only characters in the
+set [A-Za-z0-9_\-\.]. If the --ticket=N option is not specified and NAME
+contains only a list of positive integers separated by [_-] (an underscore or a
+hyphen), the command will assume that NAME also specifies the related ticket
+numbers.
+
+SOURCE can be an URL or a Subversion working copy. Otherwise, the current
+working directory must be a working copy. The specified URL (or the URL of the
+working copy) must be URL under a standard FCM project.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --branch-of-branch
+
+If this option is specified and the SOURCE is a branch, it will create a new
+branch from the SOURCE branch. Otherwise, the branch is created from the trunk.
+
+=item --non-interactive
+
+Do no interactive prompting. This option implies --svn-non-interactive.
+
+=item --password=PASSWORD
+
+Specify a password for write access to the repository.
+
+=item --rev-flag=NONE|NORMAL|NUMBER
+
+Specify a flag for determining the prefix of the branch name. The flag can be
+the string "NORMAL", "NUMBER" or "NONE".  "NORMAL" is the default behaviour,
+in which the branch name will be prefixed with a Subversion revision number if
+the revision is not associated with a registered FCM revision keyword. If the
+revision is registered with a FCM revision keyword, the keyword will be used in
+place of the number. If "NUMBER" is specified, the branch name will always be
+prefixed with a Subversion revision number. If "NONE" is specified, the branch
+name will not be prefixed by a revision number or keyword.
+
+=item --svn-non-interactive
+
+Do no interactive prompting at commit time. This option is implied by
+--non-interactive.
+
+=item --switch
+
+C<fcm switch> the current working directory (if it contains a relevant working
+copy) to point to the newly created branch after the branch is created.
+
+=item --ticket=N, -k N
+
+Specify one (or more) Trac ticket. If specified, the ticket numbers will be
+included in the commit log message. Multiple tickets can be set by specifying
+this option multiple times, or by specifying the tickets in a comma-separated
+list.
+
+=item --type=TYPE, -t TYPE
+
+Specify the type of the branch to be created. It must be one of the following:
+
+ DEV::USER [DEV, USER] - a development branch for the user
+ DEV::SHARE [SHARE]    - a shared development branch
+ TEST::USER [TEST]     - a test branch for the user
+ TEST::SHARE           - a shared test branch
+ PKG::USER [PKG]       - a package branch for the user
+ PKG::SHARE            - a shared package branch
+ PKG::CONFIG [CONFIG]  - a configuration branch
+ PKG::REL [REL]        - a release branch
+
+If not specified, the default is to create a development branch for the current
+user, i.e.  DEV::USER.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-branch-delete.pod b/lib/FCM/CLI/fcm-branch-delete.pod
new file mode 100644
index 0000000..b9c8ba1
--- /dev/null
+++ b/lib/FCM/CLI/fcm-branch-delete.pod
@@ -0,0 +1,61 @@
+=head1 NAME
+
+fcm branch-delete (bdelete bdel brm)
+
+=head1 SYNOPSIS
+
+Deletes a branch.
+
+    fcm branch-delete [OPTIONS] [TARGET]
+
+=head1 ARGUMENTS
+
+TARGET can be an URL or a Subversion working copy. Otherwise, the current
+working directory must be a working copy. The specified URL (or the URL of the
+working copy) must be a URL under a valid branch in a standard FCM project.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --verbose, -v
+
+Print extra information.
+
+=item --show-all, -a
+
+Set --show-children, --show-other and --show-siblings.
+
+=item --show-children
+
+Report children of the current branch.
+
+=item --show-other
+
+Report custom/ reverse merges into the current branch.
+
+=item --show-siblings
+
+Report merges with siblings of the current branch.
+
+=item --non-interactive
+
+Do no interactive prompting. This option implies --svn-non-interactive.
+
+=item --password=PASSWORD
+
+Specify a password for write access to the repository.
+
+=item --svn-non-interactive
+
+Do no interactive prompting at commit time. This option is implied by
+--non-interactive.
+
+=item --switch
+
+If TARGET not specified in the argument list, C<fcm switch> the current working
+copy to point to the C<trunk> after the branch deletion.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-branch-diff.pod b/lib/FCM/CLI/fcm-branch-diff.pod
new file mode 100644
index 0000000..5c173f7
--- /dev/null
+++ b/lib/FCM/CLI/fcm-branch-diff.pod
@@ -0,0 +1,52 @@
+=head1 NAME
+
+fcm branch-diff (bdiff, bdi)
+
+=head1 SYNOPSIS
+
+Show differences relative to the base of the target branch, i.e. the changes
+available for merging from the target branch into its parent.
+
+    fcm branch-diff [OPTIONS] [TARGET]
+
+=head1 ARGUMENTS
+
+If TARGET is specified, it must either be a URL or a working copy. Otherwise,
+the target is the current directory which must be a working copy. The target
+URL must be a branch in a standard FCM project.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --diff-cmd=PATH
+
+Use PATH as I<diff> command.
+
+=item --graphical, -g
+
+Equivalent to C<--diff-cmd=fcm_graphic_diff>.
+
+=item --summarize, --summarise
+
+Show a summary of the results.
+
+=item --xml
+
+With --summarise, show a summary of the results in XML format.
+
+=item --trac, -t
+
+If TARGET is a URL, use Trac to display the diff.
+
+=item --wiki
+
+If TARGET is a URL, print Trac link for the diff.
+
+=item --extensions=ARG, -x ARG
+
+Options of the I<diff> command. See the help for "L<svn|svn> diff".
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-branch-info.pod b/lib/FCM/CLI/fcm-branch-info.pod
new file mode 100644
index 0000000..b28c211
--- /dev/null
+++ b/lib/FCM/CLI/fcm-branch-info.pod
@@ -0,0 +1,43 @@
+=head1 NAME
+
+fcm branch-info (binfo)
+
+=head1 SYNOPSIS
+
+Displays information of a branch.
+
+    fcm branch-info [OPTIONS] [TARGET]
+
+=head1 ARGUMENTS
+
+TARGET can be an URL or a Subversion working copy. Otherwise, the current
+working directory must be a working copy. The specified URL (or the URL of the
+working copy) must be a URL under a valid branch in a standard FCM project.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --verbose, -v
+
+Print extra information.
+
+=item --show-all, -a
+
+Set --show-children, --show-other and --show-siblings.
+
+=item --show-children
+
+Report children of the current branch.
+
+=item --show-other
+
+Report custom/ reverse merges into the current branch.
+
+=item --show-siblings
+
+Report merges with siblings of the current branch.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-branch-list.pod b/lib/FCM/CLI/fcm-branch-list.pod
new file mode 100644
index 0000000..267c1c1
--- /dev/null
+++ b/lib/FCM/CLI/fcm-branch-list.pod
@@ -0,0 +1,52 @@
+=head1 NAME
+
+fcm branch-list (blist, bls)
+
+=head1 SYNOPSIS
+
+Searches and lists branches in projects. By default, it lists only branches
+created by the current user.
+
+    fcm branch-list [OPTIONS] [TARGET ...]
+
+=head1 ARGUMENTS
+
+If no TARGET is specified, the current working directory is assumed to be the
+target. Each target must either be a URL[@REV] or a PATH[@REV] to a working copy
+of a standard FCM project.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --only=DEPTH:PATTERN, ...
+
+Specify a regular expression to match at various depth. E.g.  with the normal
+FCM branch naming convention, C<--only=1:dev --only=2:fred> will display only
+the development branches owned by user ID "fred". (This option is cumalative,
+and overrides the --show-all and --user=PATTERN options.)
+
+=item --quiet, -q
+
+Decreases verbosity. Only prints branches matching the search criteria.
+
+=item --show-all, -a
+
+Print branches of all users. (This option overrides the --user=USER option.)
+
+=item --url
+
+Displays Subversion URL instead of FCM location keywords.
+
+=item --user=PATTERN, -u PATTERN
+
+Equivalent to --only=2:^PATTERN$ for projects with the normal FCM branch naming
+convention. Lists branches created by the specified list of users instead of the
+current user. With the normal FCM branch naming convention, you can also list
+shared branches by specifying the user as "Share", configuration branches by
+specifying the user as "Config" and release branches by specifying the user as
+"Rel". (This option is cumalative.)
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-branch.pod b/lib/FCM/CLI/fcm-branch.pod
new file mode 100644
index 0000000..52d484e
--- /dev/null
+++ b/lib/FCM/CLI/fcm-branch.pod
@@ -0,0 +1,37 @@
+=head1 NAME
+
+fcm branch (br)
+
+=head1 SYNOPSIS
+
+    1. fcm branch --create --name NAME [OPTIONS] [SOURCE]
+       fcm branch -c           -n NAME [OPTIONS] [SOURCE]
+    2. fcm branch --delete             [OPTIONS] [TARGET]
+       fcm branch -d                   [OPTIONS] [TARGET]
+    3. fcm branch --info               [OPTIONS] [TARGET]
+       fcm branch                      [OPTIONS] [TARGET]
+    4. fcm branch --list               [OPTIONS] [TARGET]
+       fcm branch -l                   [OPTIONS] [TARGET]
+
+This command is deprecated. The 4 usages are replaced by:
+
+=over 4
+
+=item 1.
+
+C<fcm branch-create>: Type C<fcm help branch-create> for detail.
+
+=item 2.
+
+C<fcm branch-delete>: Type C<fcm help branch-delete> for detail.
+
+=item 3.
+
+C<fcm branch-info>: Type C<fcm help branch-info> for detail.
+
+=item 4.
+
+C<fcm branch-list>: Type C<fcm help branch-list> for detail.
+
+=back
+=cut
diff --git a/lib/FCM/CLI/fcm-browse.pod b/lib/FCM/CLI/fcm-browse.pod
new file mode 100644
index 0000000..c88c7f5
--- /dev/null
+++ b/lib/FCM/CLI/fcm-browse.pod
@@ -0,0 +1,30 @@
+=head1 NAME
+
+fcm browse (trac www)
+
+=head1 SYNOPSIS
+
+Invokes the browser for a version controlled target.
+
+    fcm browse [OPTIONS] [TARGET ...]
+
+=head1 ARGUMENTS
+
+If TARGET is specified, it must be a FCM URL keyword, a Subversion URL or the
+path to a local working copy. If not specified, the current working directory
+is assumed to be a working copy. If the --browser option is specified, the
+specified web browser command is used to launch the repository browser.
+Otherwise, it attempts to use the default browser from the configuration
+setting.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --browser=COMMAND
+
+Specify the web browser command.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-build.pod b/lib/FCM/CLI/fcm-build.pod
new file mode 100644
index 0000000..e897992
--- /dev/null
+++ b/lib/FCM/CLI/fcm-build.pod
@@ -0,0 +1,91 @@
+=head1 NAME
+
+fcm build (bld)
+
+=head1 SYNOPSIS
+
+Invokes the deprecated FCM 1 build system.
+
+    fcm build [OPTIONS] [CFGFILE]
+
+=head1 ARGUMENTS
+
+The path to a CFGFILE may be provided. Otherwise, the build system searches the
+default locations for a bld cfg file.
+
+=head1 OPTIONS
+
+If no option is specified, the options "-s 5 -t all -j 1 -v 1" are assumed.
+
+=over 4
+
+=item --archive, -a
+
+Archive sub-directories on success. If archive mode is switched on, build
+sub-directories that are only used in the build process will be archived to
+TAR-GZIP files.
+
+=item --clean
+
+Clean the destination.
+
+=item --full, -f
+
+Run in full mode. If the option for full build is specified, the
+sub-directories created by previous builds will be removed, so that the current
+build can start cleanly.
+
+=item --ignore-lock
+
+Ignore lock file. When a build is invoked, it sets up a lock file in the build
+root directory.  The lock is normally removed at the end of the build. While the
+lock file is in place, the build commands invoked in the same root directory
+will fail. If you need to bypass this check for whatever reason, you can invoke
+the build system with this option.
+
+=item --jobs=N, -j N
+
+Specify the number of parallel jobs.
+
+=item --stage=N, -s N
+
+Run command up to a named stage. The stages are:
+
+=over 4
+
+=item "1", "s" or "setup"
+
+Stage 1, setup.
+
+=item "2", "pp" or "pre_process"
+
+Stage 2, pre-process.
+
+=item "3", "gd" or "generate_dependency"
+
+Stage 3, generate dependency.
+
+=item "4", "gi" or "generate_interface"
+
+Stage 4, generate Fortran 9X interface.
+
+=item "5", "m", "make"
+
+Stage 5, make.
+
+=back
+
+=item --targets=TARGETS, -t TARGETS
+
+List of build targets, delimited by (:). If specified, the default targets
+declared in the configuration file will not be used.
+
+=item --verbose=N, -v N
+
+Specify the verbosity level. If specified, the verbosity level must be an
+integer greater than 0. Verbosity level 0 is the quiet mode. Increasing the
+verbosity level will increase the amount of diagnostic output.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-cfg-print.pod b/lib/FCM/CLI/fcm-cfg-print.pod
new file mode 100644
index 0000000..91dd4a8
--- /dev/null
+++ b/lib/FCM/CLI/fcm-cfg-print.pod
@@ -0,0 +1,23 @@
+=head1 NAME
+
+fcm cfg-print (cfg)
+
+=head1 SYNOPSIS
+
+Parses each FCM configuration file specified in the argument list, and prints
+the result to STDOUT.
+
+    fcm cfg-print [OPTIONS] [TARGET ...]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --fcm1, -1
+
+If specified, targets should be in FCM 1 format. Otherwise, they should be in
+FCM 2 format.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-changelist.pod b/lib/FCM/CLI/fcm-changelist.pod
new file mode 100644
index 0000000..7949dc8
--- /dev/null
+++ b/lib/FCM/CLI/fcm-changelist.pod
@@ -0,0 +1,9 @@
+=head1 NAME
+
+fcm changelist
+
+=head1 SYNOPSIS
+
+    svn changelist is not supported under fcm.
+
+=cut
diff --git a/lib/FCM/CLI/fcm-cmp-ext-cfg.pod b/lib/FCM/CLI/fcm-cmp-ext-cfg.pod
new file mode 100644
index 0000000..f5bb36f
--- /dev/null
+++ b/lib/FCM/CLI/fcm-cmp-ext-cfg.pod
@@ -0,0 +1,30 @@
+=head1 NAME
+
+fcm cmp-ext-cfg
+
+=head1 SYNOPSIS
+
+Compares the extract configurations of two similar FCM 1 extract configuration
+files CFGFILE1 and CFGFILE2. This application is deprecated.
+
+    fcm cmp-ext-cfg [OPTIONS] CFFILE1 CFGFILE2
+
+=head1 OPTIONS
+
+=over 4
+
+=item --verbose=N, -v N
+
+Specify the verbosity level. In normal mode with verbosity level 2 or above,
+displays the change log of each revision.
+
+=item --wiki-format=TARGET
+
+Print revision tables in wiki format. In wiki mode, print revision tables in
+wiki format. The argument to this option must be the Subversion URL or FCM URL
+keyword of a FCM project associated with the intended Trac system. The --verbose
+option has no effect in wiki mode.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-commit.pod b/lib/FCM/CLI/fcm-commit.pod
new file mode 100644
index 0000000..d7ed2d1
--- /dev/null
+++ b/lib/FCM/CLI/fcm-commit.pod
@@ -0,0 +1,31 @@
+=head1 NAME
+
+fcm commit (ci)
+
+=head1 SYNOPSIS
+
+Send changes from your working copy to the repository. Invoke your favourite
+editor to prompt you for a commit log message. Update your working copy
+following the commit.
+
+    fcm commit [OPTIONS] [PATH ...]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --dry-run
+
+Allows you to add to the commit message without committing.
+
+=item --svn-non-interactive
+
+Do no interactive prompting at commit time.
+
+=item --password=PASSWORD
+
+Specify a password ARG.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-conflicts.pod b/lib/FCM/CLI/fcm-conflicts.pod
new file mode 100644
index 0000000..d4a3453
--- /dev/null
+++ b/lib/FCM/CLI/fcm-conflicts.pod
@@ -0,0 +1,17 @@
+=head1 NAME
+
+fcm conflicts (cf)
+
+=head1 SYNOPSIS
+
+Use graphical tool to resolve any conflicts within your working copy.
+
+    fcm conflicts [PATH]
+
+=head1 ARGUMENTS
+
+Invoke a graphical merge tool to help you resolve conflicts in your working copy
+at PATH. It prompts you to run "L<svn|svn> resolved" each time you have resolved
+the conflicts in a text file.
+
+=cut
diff --git a/lib/FCM/CLI/fcm-delete.pod b/lib/FCM/CLI/fcm-delete.pod
new file mode 100644
index 0000000..d551e67
--- /dev/null
+++ b/lib/FCM/CLI/fcm-delete.pod
@@ -0,0 +1,22 @@
+=head1 NAME
+
+fcm delete (del, remove, rm)
+
+=head1 SYNOPSIS
+
+    fcm delete [OPTIONS] [TARGETS]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --check, -c
+
+Check for any files or directories reported by "L<svn|svn> status" as missing
+and schedule them for removal.
+
+=back
+
+For other options, see output of "L<svn|svn> help delete".
+
+=cut
diff --git a/lib/FCM/CLI/fcm-diff.pod b/lib/FCM/CLI/fcm-diff.pod
new file mode 100644
index 0000000..c73a00f
--- /dev/null
+++ b/lib/FCM/CLI/fcm-diff.pod
@@ -0,0 +1,45 @@
+=head1 NAME
+
+fcm diff (di)
+
+=head1 SYNOPSIS
+
+Display the differences between two revisions or paths.
+
+    1. fcm diff --branch [OPTIONS] [TARGET]
+       fcm diff -b                 [TARGET]
+    2. fcm diff [OPTIONS] [ARGS]
+
+=over 4
+
+=item 1.
+
+This usage is deprecated. It is replaced by C<fcm branch-diff>. Type
+C<fcm help branch-diff> for detail.
+
+=item 2.
+
+See the output of "L<svn|svn> help diff".
+
+=back
+
+=head1 OPTIONS
+
+The following are additional options supported by C<fcm diff>.
+
+=over 4
+
+=item --graphical, -g
+
+If this option is specified, the command uses a graphical tool to display the
+differences. This option can be used in combination with all other valid
+options except --diff-cmd and --extensions.
+
+=item --summarise
+
+This option is implemented in FCM as a wrapper to the Subversion --summarize
+option. It prints only a summary of the results.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-export-items.pod b/lib/FCM/CLI/fcm-export-items.pod
new file mode 100644
index 0000000..0a3ffda
--- /dev/null
+++ b/lib/FCM/CLI/fcm-export-items.pod
@@ -0,0 +1,57 @@
+=head1 NAME
+
+fcm export-items
+
+=head1 SYNOPSIS
+
+Exports directories in SOURCE as a list of versioned items.
+
+This command is used to support a legacy working practice, in which directories
+in a source tree are regarded as individual versioned items.
+
+    fcm export-items [OPTIONS] SOURCE
+
+=head1 ARGUMENTS
+
+The SOURCE should be the URL of a branch in a Subversion repository with the
+standard FCM layout.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --config-file=PATH, --file=PATH, -f PATH
+
+Specify the path to the configuration file.
+(default=$PWD/fcm-export-items.cfg)
+
+=item --directory=PATH, -C PATH
+
+Specify the path to the destination. (default=$PWD)
+
+=item --new
+
+Specify the new mode. In this mode, everything is re-exported. Otherwise, the
+system runs in incremental mode, in which the version directories are only
+updated if they do not already exist.
+
+=back
+
+=head1 CONFIGURATION
+
+The configuration file should be in the deprecated FCM 1 configuration format.
+The label in each entry should be a path relative to the source URL. If the path
+ends in * then the path is expanded recursively and any sub-directories
+containing regular files are added to the list of relative paths to export. The
+value may be empty, or it may be a list of space separated "conditions". Each
+condition is a conditional operator (>, >=, <, <=, == or !=) followed by a
+revision number. The command uses the revision log to determine the revisions at
+which the relative path has been updated in the source URL. If these revisions
+also satisfy the "conditions" set by the user, they will be considered in the
+export.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/CLI/fcm-extract.pod b/lib/FCM/CLI/fcm-extract.pod
new file mode 100644
index 0000000..3ccac10
--- /dev/null
+++ b/lib/FCM/CLI/fcm-extract.pod
@@ -0,0 +1,46 @@
+=head1 NAME
+
+fcm extract (ext)
+
+=head1 SYNOPSIS
+
+Invokes the deprecated FCM 1 extract system.
+
+    fcm extract [OPTIONS] [CFGFILE]
+
+=head1 ARGUMENTS
+
+The path to a CFG file may be provided. Otherwise, the extract system searches
+the default locations for an ext cfg file.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --clean
+
+Clean the destination.
+
+=item --full, -f
+
+Run in full mode. If the option for full extract is specified, the
+sub-directories created by previous extracts will be removed, so that the
+current extract can start cleanly.
+
+=item --ignore-lock
+
+Ignore lock file. When an extract is invoked, it sets up a lock file in the
+extract root directory.  The lock is normally removed at the end of the extract.
+While the lock file is in place, the extract commands invoked in the same root
+directory will fail. If you need to bypass this check for whatever reason, you
+can invoke the extract system with this option.
+
+=item --verbose=N, -v N
+
+Specify the verbosity level. If specified, the verbosity level must be an
+integer greater than 0. Verbosity level 0 is the quiet mode. Increasing the
+verbosity level will increase the amount of diagnostic output.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-gui.pod b/lib/FCM/CLI/fcm-gui.pod
new file mode 100644
index 0000000..30496a2
--- /dev/null
+++ b/lib/FCM/CLI/fcm-gui.pod
@@ -0,0 +1,12 @@
+=head1 NAME
+
+fcm gui
+
+=head1 SYNOPSIS
+
+Invokes the GUI wrapper for code management commands. The optional argument PATH
+modifies the initial working directory of the GUI.
+
+    fcm gui [PATH]
+
+=cut
diff --git a/lib/FCM/CLI/fcm-help.pod b/lib/FCM/CLI/fcm-help.pod
new file mode 100644
index 0000000..428cab7
--- /dev/null
+++ b/lib/FCM/CLI/fcm-help.pod
@@ -0,0 +1,25 @@
+=head1 NAME
+
+fcm help (h, usage, --help, --usage)
+
+=head1 SYNOPSIS
+
+Describes the usage of this program or its subcommands.
+
+    fcm help [OPTIONS] [SUBCOMMAND...]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --quiet, -q
+
+Decrease the verbosity level.
+
+=item --verbose, -v
+
+Increase the verbosity level.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-keyword-print.pod b/lib/FCM/CLI/fcm-keyword-print.pod
new file mode 100644
index 0000000..d0a8c9c
--- /dev/null
+++ b/lib/FCM/CLI/fcm-keyword-print.pod
@@ -0,0 +1,27 @@
+=head1 NAME
+
+fcm keyword-print (kp)
+
+=head1 SYNOPSIS
+
+Displays registered location and/or revision keywords.
+
+    fcm keyword-print [OPTIONS] [TARGET ...]
+
+=head1 ARGUMENTS
+
+If no argument is specified, prints registered location keywords. Otherwise,
+prints the location keyword and revision keywords for the specified target. If
+--verbose is specified, prints the implied location keywords as well.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --verbose, -v
+
+Increase the verbosity level.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-loc-layout.pod b/lib/FCM/CLI/fcm-loc-layout.pod
new file mode 100644
index 0000000..730d18b
--- /dev/null
+++ b/lib/FCM/CLI/fcm-loc-layout.pod
@@ -0,0 +1,25 @@
+=head1 NAME
+
+fcm loc-layout
+
+=head1 SYNOPSIS
+
+Parse the URL of a FCM/Subversion TARGET, and print its FCM layout information.
+
+    fcm keyword-print [OPTIONS] [TARGET ...]
+
+=head1 ARGUMENTS
+
+If no argument is specified, TARGET is the current working directory.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --verbose, -v
+
+Increase the verbosity level.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-make.pod b/lib/FCM/CLI/fcm-make.pod
new file mode 100644
index 0000000..fbf6a48
--- /dev/null
+++ b/lib/FCM/CLI/fcm-make.pod
@@ -0,0 +1,60 @@
+=head1 NAME
+
+fcm make
+
+=head1 SYNOPSIS
+
+Invokes the FCM make system. See the user guide for detail.
+
+    fcm make [OPTIONS] [DECLARATION ...]
+
+=head1 ARGUMENTS
+
+Each argument is considered to be a declaration line to append to the
+configuration file.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --config-file-path=PATH, -F PATH
+
+Specify paths for searching configuration files specified in relative paths.
+
+=item --config-file=PATH, --file=PATH, -f PATH
+
+Specify paths to the configuration files. (default = fcm-make.cfg in the
+current working directory)
+
+=item --directory=PATH, -C PATH
+
+Specify the path to the destination. (default = $PWD or whatever is specified
+in the "dest" setting in the configuration file)
+
+=item --ignore-lock
+
+Ignore lock file. When the system is invoked, it sets up a lock file in the
+destination.  The lock is normally removed when the system completes the make.
+While the lock file is in place, another make invoked in the same destination
+will fail. This option can be used to bypass this check.
+
+=item --jobs=N, -j N
+
+Specify the number of (child) processes that can be run simultaneously.
+
+=item --new
+
+Remove items in the destination created by the previous make, and starts a new
+make.
+
+=item --quiet, -q
+
+Decrease the verbosity level.
+
+=item --verbose, -v
+
+Increase the verbosity level.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-merge.pod b/lib/FCM/CLI/fcm-merge.pod
new file mode 100644
index 0000000..d49cc58
--- /dev/null
+++ b/lib/FCM/CLI/fcm-merge.pod
@@ -0,0 +1,76 @@
+=head1 NAME
+
+fcm merge
+
+=head1 SYNOPSIS
+
+Merge changes from a source into your working copy.
+
+    1. fcm merge SOURCE
+    2. fcm merge --custom  --revision N[:M] SOURCE
+       fcm merge --custom  URL[\@REV1] URL[\@REV2]
+    3. fcm merge --reverse --revision [M:]N
+
+=over 4
+
+=item 1.
+
+If neither --custom nor --reverse is specified, the command merges changes
+automatically from SOURCE into your working copy. SOURCE must be a valid
+URL[@REV] of a branch in a standard FCM project. The base of the merge will be
+calculated automatically based on the common ancestor and latest merge
+information between the SOURCE and the branch of the working copy.
+
+=item 2.
+
+If --custom is specified, the command can be used in two forms.
+  
+In the first form, it performs a custom merge from the specified changeset(s) of
+SOURCE into your working copy. SOURCE must be a valid URL[@REV] of a branch in
+a standard FCM project. If a single revision is specified, the merge delta is (N
+- 1):N of SOURCE. Otherwise, the merge delta, is N:M of SOURCE, where N < M.
+     
+In the second form, it performs a custom merge using the delta between the two
+specified branch URLs. For each URL, if a peg revision is not specified, the
+command will peg the URL with its last changed revision.
+
+=item 3.
+
+If --reverse is specified, the command performs a reverse merge of the
+changeset(s) specified by the --revision option. If a single revision is
+specified, the merge delta is N:(N - 1). Otherwise, the merge delta is M:N,
+where M > N. Note that you do not have to specify a SOURCE for a reverse merge,
+because the SOURCE should always be the branch your working copy is pointing to.
+  
+=back
+
+The command provide a commit log message template following the merge.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --auto-log
+
+In automatic mode, adds the log messages of the merged revisions in the commit
+log. Has no effect in other merge modes.
+
+=item --dry-run
+
+Try operation but make no changes.
+
+=item --non-interactive
+
+Do no interactive prompting.
+
+=item --revision=REV, -r REV
+
+Specify a (range of) revision number(s).
+
+=item --verbose, -v
+
+Print extra information.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-mkpatch.pod b/lib/FCM/CLI/fcm-mkpatch.pod
new file mode 100644
index 0000000..3d8c0f2
--- /dev/null
+++ b/lib/FCM/CLI/fcm-mkpatch.pod
@@ -0,0 +1,61 @@
+=head1 NAME
+
+fcm mkpatch
+
+=head1 SYNOPSIS
+
+Create patches from specified revisions of a URL
+
+    fcm mkpatch [OPTIONS] URL [OUTDIR]
+
+=head1 ARGUMENTS
+
+If OUTDIR is specified, the output is sent to OUTDIR. Otherwise, the output will
+be sent to a default location in the current directory ($PWD/fcm-mkpatch-out).
+The output directory will contain the patch for each revision as well as a
+script for importing the patch.
+
+A warning is given if the URL is not of a branch in a FCM project or if it is a
+sub-directory of a branch.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --exclude arg
+
+Exclude a path in the URL. Multiple paths can be specified by using a
+colon-separated list of paths, or by specifying this option multiple times.
+
+The specified path must be a relative path of the URL. Glob patterns such as *
+and ? are acceptable. Changes in an excluded path will not be considered in the
+patch. A changeset containing changes only in the excluded path will not be
+considered at all.
+
+=item --organisation arg
+
+This option can be used to specify the name of your organisation.
+
+The command will attempt to parse the commit log message for each revision in
+the patch. It will remove all merge templates, replace Trac links with a
+modified string, and add information about the original changeset. If you
+specify the name of your organisation, it will replace Trac links such as
+"ticket:123" with "$organisation_ticket:123", and report the original changeset
+with a message such as "$organisation_changeset:1000".  If the organisation
+name is not specified then it defaults to "original".
+
+=item --revision=arg, -r arg
+
+Specify a revision number or a revision number range.
+
+If a revision is specified with the --revision option, it will attempt to create
+a patch based on the changes at that revision. If a revision is not specified,
+it will attempt to create a patch based on the changes at the HEAD revision. If
+a revision range is specified, it will attempt to create a patch for each
+revision in that range (including the change in the lower range) where changes
+have taken place in the URL. No output will be written if there is no change in
+the given revision (range).
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-project-create.pod b/lib/FCM/CLI/fcm-project-create.pod
new file mode 100644
index 0000000..3fa71e5
--- /dev/null
+++ b/lib/FCM/CLI/fcm-project-create.pod
@@ -0,0 +1,34 @@
+=head1 NAME
+
+fcm project-create
+
+=head1 SYNOPSIS
+
+Creates a new project in a repository.
+
+    fcm project-create [OPTIONS] PROJECT-NAME REPOS-ROOT-URL
+
+=head1 ARGUMENTS
+
+PROJECT-NAME is a valid name of a project for the given repository.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --non-interactive
+
+Do no interactive prompting. This option implies --svn-non-interactive.
+
+=item --password=PASSWORD
+
+Specify a password for write access to the repository.
+
+=item --svn-non-interactive
+
+Do no interactive prompting at commit time. This option is implied by
+--non-interactive.
+
+=back
+
+=cut
diff --git a/lib/FCM/CLI/fcm-switch.pod b/lib/FCM/CLI/fcm-switch.pod
new file mode 100644
index 0000000..f4a2c20
--- /dev/null
+++ b/lib/FCM/CLI/fcm-switch.pod
@@ -0,0 +1,16 @@
+=head1 NAME
+
+fcm switch (sw)
+
+=head1 SYNOPSIS
+
+Update the working copy to a different URL.
+
+    1. fcm switch URL[@REV1] [PATH]
+    2. fcm switch --relocate FROM TO [PATH...]
+
+Note: if --relocate is not specified, "fcm switch" will only support the options
+--non-interactive, --revision (-r) and --quiet (-q). For detail, see the output
+of "L<svn|svn> help switch".
+
+=cut
diff --git a/lib/FCM/CLI/fcm-test-battery.pod b/lib/FCM/CLI/fcm-test-battery.pod
new file mode 100644
index 0000000..ce7ae8f
--- /dev/null
+++ b/lib/FCM/CLI/fcm-test-battery.pod
@@ -0,0 +1,25 @@
+=head1 NAME
+
+fcm test-battery (self-tests)
+
+=head1 SYNOPSIS
+
+Invokes the FCM self-tests.
+
+    fcm test-battery [PROVE_ARGS]
+
+=over 4
+
+=item 1. If an environment TEST_REMOTE_HOST is set, run CM tests using an
+ auto-generated Subversion server on $TEST_REMOTE_HOST.
+
+=item 2. If an environment variable TEST_PROJECT is set, run CM tests using a
+ project sub-hierarchy in the test repositories.
+
+=head1 ARGUMENTS
+
+PROVE_ARGS specifies override arguments for the 'prove' command. This is run
+from within the t/ subdirectory at the top of FCM's source tree.
+If not specified, the prove command will be run as 'prove -j 9 -r -s -f'.
+
+=cut
diff --git a/lib/FCM/CLI/fcm-update.pod b/lib/FCM/CLI/fcm-update.pod
new file mode 100644
index 0000000..39672cb
--- /dev/null
+++ b/lib/FCM/CLI/fcm-update.pod
@@ -0,0 +1,14 @@
+=head1 NAME
+
+fcm update (up)
+
+=head1 SYNOPSIS
+
+Bring changes from the repository into the working copy.
+
+    fcm update [PATH...]
+
+Note: "fcm update" only supports --non-interactive, --revision=REV (-r REV) and
+--quiet (-q). For detail, see the output of "L<svn|svn> help update".
+
+=cut
diff --git a/lib/FCM/CLI/fcm-version.pod b/lib/FCM/CLI/fcm-version.pod
new file mode 100644
index 0000000..1550ea8
--- /dev/null
+++ b/lib/FCM/CLI/fcm-version.pod
@@ -0,0 +1,11 @@
+=head1 NAME
+
+fcm version (--version, -V)
+
+=head1 SYNOPSIS
+
+Print FCM version.
+
+    fcm version
+
+=cut
diff --git a/lib/FCM/Class/CODE.pm b/lib/FCM/Class/CODE.pm
new file mode 100644
index 0000000..fda31dd
--- /dev/null
+++ b/lib/FCM/Class/CODE.pm
@@ -0,0 +1,307 @@
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+#-------------------------------------------------------------------------------
+package FCM::Class::CODE;
+use FCM::Class::Exception;
+use Scalar::Util qw{reftype};
+
+# Methods for working out the default value of an attribute.
+my %ATTRIB_DEFAULT_BY = (
+    default => sub {
+        my $opt_ref = shift();
+        my $ret = $opt_ref->{default};
+        return (ref($ret) && reftype($ret) eq 'CODE' ? $ret->() : $ret);
+    },
+    isa     => sub {
+        my $opt_ref = shift();
+        return
+              $opt_ref->{isa} eq 'ARRAY' ? []
+            : $opt_ref->{isa} eq 'HASH'  ? {}
+            : $opt_ref->{isa} eq 'CODE'  ? sub {}
+            :                              undef
+            ;
+    },
+);
+
+# Checks the value of an attribute.
+my $ATTRIB_CHECK = sub {
+    my ($class, $opt_ref, $key, $value, $caller_ref) = @_;
+    # Note: undef is always OK?
+    if (!defined($value)) {
+        return;
+    }
+    my $expected_isa = $opt_ref->{isa};
+    if (!$expected_isa || $expected_isa eq 'SCALAR' && !ref($value)) {
+        return;
+    }
+    if (!UNIVERSAL::isa($value, $expected_isa)) {
+        return FCM::Class::Exception->throw({
+            'code'    => FCM::Class::Exception->CODE_TYPE,
+            'caller'  => $caller_ref,
+            'package' => $class,
+            'key'     => $key,
+            'type'    => $expected_isa,
+            'value'   => $value,
+        });
+    }
+};
+
+# Creates the methods of the class.
+sub class {
+    my ($class, $attrib_opt_ref, $class_opt_ref) = @_;
+    my %class_opt = (
+        init        => sub {},
+        init_attrib => sub {@_},
+        action_of   => {},
+        (defined($class_opt_ref) ? %{$class_opt_ref} : ()),
+    );
+    if (!defined($attrib_opt_ref)) {
+        $attrib_opt_ref = {};
+    }
+    my %attrib_opt;
+    while (my ($key, $item) = each(%{$attrib_opt_ref})) {
+        my %option = (
+            r       => 1,     # readable?
+            w       => 1,     # writable?
+            default => undef, # default value or CODE to return it
+            isa     => undef, # attribute isa 
+            (     defined($item) && ref($item) ? %{$item}
+                : defined($item)               ? (isa => $item)
+                :                                ()
+            ),
+        );
+        if (defined($option{isa})) {
+            $option{isa}
+                = $option{isa} eq '$' ? 'SCALAR'
+                : $option{isa} eq '@' ? 'ARRAY'
+                : $option{isa} eq '%' ? 'HASH'
+                : $option{isa} eq '&' ? 'CODE'
+                : $option{isa} eq '*' ? 'GLOB'
+                :                       $option{isa}
+                ;
+        }
+        $attrib_opt{$key} = \%option;
+    }
+    my $main_ref = sub {
+        my ($attrib_ref, $key, @args) = @_;
+        if (!exists($class_opt{action_of}{$key})) {
+            return;
+        }
+        $class_opt{action_of}{$key}->($attrib_ref, @args);
+    };
+    no strict qw{refs};
+    # $class->new(\%attrib)
+    *{$class . '::new'} = sub {
+        my $class = shift();
+        my ($attrib_ref) = $class_opt{init_attrib}->(@_);
+        my $caller_ref = [caller()];
+        my %attrib = (defined($attrib_ref) ? %{$attrib_ref} : ());
+        while (my ($key, $value) = each(%attrib)) {
+            if (exists($attrib_opt{$key})) {
+                $ATTRIB_CHECK->(
+                    $class, $attrib_opt{$key}, $key, $value, $caller_ref,
+                );
+            }
+            #else {
+            #    delete($attrib{$key});
+            #}
+        }
+        my $self = bless(sub {$main_ref->(\%attrib, @_)}, $class);
+        KEY:
+        while (my ($key, $opt_ref) = each(%attrib_opt)) {
+            if (exists($attrib{$key})) {
+                next KEY;
+            }
+            for my $opt_name (qw{default isa}) {
+                if (defined($opt_ref->{$opt_name})) {
+                    $attrib{$key} = $ATTRIB_DEFAULT_BY{$opt_name}->($opt_ref);
+                    next KEY;
+                }
+            }
+        }
+        $class_opt{init}->(\%attrib, $self);
+        return $self;
+    };
+    # $instance->$key()
+    for my $key (keys(%{$class_opt{action_of}})) {
+        *{$class . '::' . $key}
+            = sub {my $self = shift(); $self->($key, @_)};
+    }
+    return 1;
+}
+
+#-------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Class::CODE
+
+=head1 SYNOPSIS
+
+    # Example
+    package Bar;
+    use base qw{FCM::Class::CODE};
+    __PACKAGE__->class(
+        {
+            # ...
+        },
+        {
+            action_of => {
+                bend => sub {
+                    my ($attrib_ref, @args) = @_;
+                    # ...
+                },
+                stretch => sub {
+                    my ($attrib_ref, @args) = @_;
+                    # ...
+                },
+            },
+        },
+    );
+    # Some time later...
+    $bar = Bar->new(\%attrib);
+    $bar->bend(@args);
+    $bar->stretch(@args);
+
+=head1 DESCRIPTION
+
+Provides a simple method to create CODE-based classes.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->class(\%attrib_opt,\%class_opt)
+
+Creates common methods for a CODE-based class.
+
+The %attrib_opt is used to configure the attributes of an instance of the class.
+The key of each element is the name of the attribute, and the value is a HASH
+containing the options of the attribute, or a SCALAR. (If a SCALAR is specified,
+it is equivalent to {isa => value}.) The options may contain:
+
+=over 4
+
+=item r
+
+(Default=true) If true, the attribute is readable.
+
+=item w
+
+(Default=true) If true, the attribute is writable.
+
+=item default
+
+(Default=undef) The default value of the attribute.
+
+If this option is defined, the attribute will be initialised to the specified
+value when the new() method is called. In the special case where the value of
+this option is a CODE reference, it will be invoked as $code->(\%attrib), and
+the default value will be the returned value of the CODE reference. This is
+useful, for example, if the default value needs to be a new instance of a class.
+If a genuine CODE reference is required as the default, this option should be
+set to a CODE reference that returns the required CODE reference itself.
+
+For example:
+
+    Foo->class({
+        foo => {default => 'foo'},          # 'foo'
+        bar => {default => sub {get_id()}}, # the next id
+        baz => {default => sub {\&code}},   # &code
+    });
+    {
+        my $id = 0;
+        sub get_id {$id++}
+    }
+
+If a default option is not defined, and if the attribute "isa" is ARRAY, HASH or
+CODE, then the default value is [], {} and sub {} respectively.
+
+=item isa
+
+(Default=undef) The expected type of the attribute. If this optioin is defined
+as $type, a new $value of the attribute is only accepted if $value is undef,
+UNIVERSAL::isa($value,$type) returns true or if $type is C<SCALAR> and the new
+value is not a reference.
+
+The attribute accepts $, @, %, & and * as aliases to SCALAR, ARRAY, HASH, CODE
+and GLOB.
+
+=back
+
+The %class_opt is used to configure what methods are created for the class, as
+well as other options for the $class->new() method. It may contain the
+following:
+
+=over 4
+
+=item init
+
+If $class_opt{init} is defined, it should be a CODE reference. If specified, it
+will be called once when $instance->new() is called, with the interface
+$init->(\%attrib,$self).
+
+=item init_attrib
+
+The value of this option must be a CODE. The $class->new() normally expects a
+single HASH reference argument. If an alternate interface to the $class->new()
+is required, this CODE can be used to turn the input argument list to the
+expected HASH reference.
+
+=item action_of
+
+This provides the actions of the class. It should be a HASH. Each $key in the
+HASH will be turned into a method implemented by the CODE reference in the
+corresponding $value: $instance->$key(@args) will call $instance->($key, at args),
+which will call $value->(\%attrib, at args).
+
+=back
+
+=item $class->new(\%attrib)
+
+Creates a new instance with %attrib. Initial values of the attributes can be
+specified using %attrib. Otherwise, the method will attempt to assign the
+default values, as specified in the class() method, to the newly created
+instance.
+
+=item $instance->$key(@args)
+
+A method is created for each $key of the %{$attrib{action_of}}.
+
+=back
+
+=head1 DIAGNOSTICS
+
+L<FCM::Class::Exception|FCM::Class::Exception> is thrown on error.
+
+=head1 SEE ALSO
+
+Inspired by the standard module L<Class::Struct|Class::Struct> and CPAN modules
+such as L<Class::Accessor|Class::Accessor>.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Class/Exception.pm b/lib/FCM/Class/Exception.pm
new file mode 100644
index 0000000..0f54896
--- /dev/null
+++ b/lib/FCM/Class/Exception.pm
@@ -0,0 +1,134 @@
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+#-------------------------------------------------------------------------------
+package FCM::Class::Exception;
+
+use constant {
+    CODE_TYPE => 'CODE_TYPE',
+};
+
+sub caught {
+    my ($class, $e) = @_;
+    blessed($e) && $e->isa($class);
+}
+
+sub throw {
+    my ($class, $attrib_ref) = @_;
+    my %e = (
+        'caller'  => [],
+        'code'    => undef,
+        'key'     => undef,
+        'package' => undef,
+        'type'    => undef,
+        'value'   => undef,
+        (defined($attrib_ref) ? %{$attrib_ref} : ()),
+    );
+    die(bless(\%e, $class));
+}
+
+for my $key (qw{caller code key package type value}) {
+    no strict qw{refs};
+    *{"get_$key"} = sub {$_[0]->{$key}};
+}
+
+#-------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Class::Exception
+
+=head1 SYNOPSIS
+
+    eval {
+        FCM::Class::Exception->throw({
+            'caller'  => [caller()],
+            'code'    => $code,
+            'key'     => $key,
+            'package' => $package,
+            'type'    => $type,
+            'value'   => $value,
+        });
+    };
+    if (my $e = $@) {
+        if (FCM::Class::Exception->caught($e)) {
+            # ... handle this exception class
+        }
+        else {
+            # ... do something else
+        }
+    }
+
+=head1 DESCRIPTION
+
+This exception is thrown on incorrect usage of an instance of a sub-class. An
+instance of this exception has the following attributes, which can be accessed
+via $e->get_$attrib():
+
+=head1 ATTRIBUTES
+
+=over 4
+
+=item caller
+
+Returns an ARRAY reference containing the caller (as returned by the built-in
+function in ARRAY context) that triggers the exception. Note: for a CODE-based
+class, this is always the caller when the instance is created.
+
+=item code
+
+The error code, which can be one of the following:
+
+=over 4
+
+=item $e->CODE_TYPE
+
+Attempt to set the value of an attribute to an incorrect type.
+
+=back
+
+=item key
+
+The key of the attribute that triggers this exception.
+
+=item type
+
+The expected data type (for an attempt to set the value of an attribute to an
+incorrect type).
+
+=item value
+
+The value of the attribute that triggers this exception.
+
+=back
+
+=head1 SEE ALSO
+
+L<FCM::Class::CODE|FCM::Class::CODE>
+L<FCM::Class::HASH|FCM::Class::HASH>
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Class/HASH.pm b/lib/FCM/Class/HASH.pm
new file mode 100644
index 0000000..97cd37f
--- /dev/null
+++ b/lib/FCM/Class/HASH.pm
@@ -0,0 +1,340 @@
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+#-------------------------------------------------------------------------------
+package FCM::Class::HASH;
+use FCM::Class::Exception;
+use Scalar::Util qw{reftype};
+
+# Methods for working out the default value of an attribute.
+my %ATTRIB_DEFAULT_BY = (
+    default => sub {
+        my $opt_ref = shift();
+        my $ret = $opt_ref->{default};
+        return (ref($ret) && reftype($ret) eq 'CODE' ? $ret->() : $ret);
+    },
+    isa     => sub {
+        my $opt_ref = shift();
+        return
+              $opt_ref->{isa} eq 'ARRAY' ? []
+            : $opt_ref->{isa} eq 'HASH'  ? {}
+            : $opt_ref->{isa} eq 'CODE'  ? sub {}
+            :                              undef
+            ;
+    },
+);
+
+# Checks the value of an attribute.
+my $ATTRIB_CHECK = sub {
+    my ($class, $opt_ref, $key, $value, $caller_ref) = @_;
+    # Note: undef is always OK?
+    if (!defined($value)) {
+        return;
+    }
+    my $expected_isa = $opt_ref->{isa};
+    if (!$expected_isa || $expected_isa eq 'SCALAR' && !ref($value)) {
+        return;
+    }
+    if (!UNIVERSAL::isa($value, $expected_isa)) {
+        return FCM::Class::Exception->throw({
+            'code'    => FCM::Class::Exception->CODE_TYPE,
+            'caller'  => $caller_ref,
+            'package' => $class,
+            'key'     => $key,
+            'type'    => $expected_isa,
+            'value'   => $value,
+        });
+    }
+};
+
+# Creates the methods of the class.
+sub class {
+    my ($class, $attrib_opt_ref, $class_opt_ref) = @_;
+    my %class_opt = (
+        init        => sub {},
+        init_attrib => sub {@_},
+        (defined($class_opt_ref) ? %{$class_opt_ref} : ()),
+    );
+    if (!defined($attrib_opt_ref)) {
+        $attrib_opt_ref = {};
+    }
+    my %attrib_opt;
+    while (my ($key, $item) = each(%{$attrib_opt_ref})) {
+        my %option = (
+            r       => 1,     # readable?
+            w       => 1,     # writable?
+            add     => undef, # isa eq 'HASH' only, class of HASH element
+            default => undef, # default value or CODE to return it
+            isa     => undef, # attribute type
+            (     defined($item) && ref($item) ? %{$item}
+                : defined($item)               ? (isa => $item)
+                :                                ()
+            ),
+        );
+        if (defined($option{isa})) {
+            $option{isa}
+                = $option{isa} eq '$' ? 'SCALAR'
+                : $option{isa} eq '@' ? 'ARRAY'
+                : $option{isa} eq '%' ? 'HASH'
+                : $option{isa} eq '&' ? 'CODE'
+                : $option{isa} eq '*' ? 'GLOB'
+                :                       $option{isa}
+                ;
+        }
+        $attrib_opt{$key} = \%option;
+    }
+    no strict qw{refs};
+    # $class->new(\%attrib)
+    *{$class . '::new'} = sub {
+        my $class = shift();
+        my ($attrib_ref) = $class_opt{init_attrib}->(@_);
+        my $caller_ref = [caller()];
+        my %attrib = (defined($attrib_ref) ? %{$attrib_ref} : ());
+        while (my ($key, $value) = each(%attrib)) {
+            $ATTRIB_CHECK->($class, $attrib_opt{$key}, $key, $value, $caller_ref);
+        }
+        my $self = bless(\%attrib, $class);
+        KEY:
+        while (my ($key, $opt_ref) = each(%attrib_opt)) {
+            if (exists($self->{$key})) {
+                next KEY;
+            }
+            for my $opt_name (qw{default isa}) {
+                if (defined($opt_ref->{$opt_name})) {
+                    $self->{$key} = $ATTRIB_DEFAULT_BY{$opt_name}->($opt_ref);
+                    next KEY;
+                }
+            }
+        }
+        $class_opt{init}->($self);
+        return $self;
+    };
+    # $instance->$methods()
+    while (my ($key, $opt_ref) = each(%attrib_opt)) {
+        # $instance->get_$attrib()
+        # $instance->get_$attrib($name)
+        if ($opt_ref->{r}) {
+            *{$class . '::get_' . $key}
+                = defined($opt_ref->{isa}) && $opt_ref->{isa} eq 'HASH'
+                ? sub {
+                    my ($self, $name) = @_;
+                    if (!defined($name)) {
+                        return $self->{$key};
+                    }
+                    if (exists($self->{$key}{$name})) {
+                        return $self->{$key}{$name};
+                    }
+                    return;
+                }
+                : sub {$_[0]->{$key}}
+                ;
+        }
+        # $instance->set_$attrib($value)
+        if ($opt_ref->{w}) {
+            *{$class . '::set_' . $key} = sub {
+                my ($self, $value) = @_;
+                $ATTRIB_CHECK->(
+                    $class, $attrib_opt{$key}, $key, $value, [caller()],
+                );
+                $self->{$key} = $value;
+                return $self;
+            };
+        }
+        # $instance->add_$attrib($name,\%option)
+        if (   defined($opt_ref->{isa}) && $opt_ref->{isa} eq 'HASH'
+            && defined($opt_ref->{add})
+        ) {
+            *{$class . '::add_' . $key} = sub {
+                my ($self, $name, @args) = @_;
+                if (defined($self->{$key}{$name})) {
+                    return $self->{$key}{$name};
+                }
+                $self->{$key}{$name} = $opt_ref->{add}->new(@args);
+            };
+        }
+    }
+    return 1;
+}
+
+#-------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Class::HASH
+
+=head1 SYNOPSIS
+
+    package Breakfast;
+    use base qw{FCM::Class::HASH};
+    __PACKAGE__->class(
+        {
+            eggs  => {isa => '@'},
+            ham   => {isa => '%'},
+            bacon => '$',
+            # ...
+        },
+    );
+    # Some time later...
+    $breakfast = Breakfast->new(\%attrib);
+    @eggs = @{$breakfast->get_eggs()};
+    $breakfast->set_ham(\%ham);
+
+=head1 DESCRIPTION
+
+Provides a simple method to create HASH-based classes.
+
+The class() method creates the new() method for initiating a new instance. It
+also provides a get_$attrib() and set_$attrib() accessors for each attribute.
+Basic type checkings are performed on writing to the attributes to ensure
+correct usage.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->class(\%attrib_opt,\%class_opt)
+
+Creates the class, using the attribute options in %attrib_opt and %class_opt.
+
+The %attrib_opt is used to configure the attributes of an instance of the class.
+The key of each element is the name of the attribute, and the value is a HASH
+containing the options of the attribute, or a SCALAR. (If a SCALAR is specified,
+it is equivalent to {isa => value}.). The options may contain:
+
+=over 4
+
+=item r
+
+(Default=true) If true, the attribute is readable.
+
+=item w
+
+(Default=true) If true, the attribute is writable.
+
+=item add
+
+(Default=undef) This is only useful for a HASH attribute. If defined, it should
+be the name of a class (e.g. $attrib_class). The HASH attribute will receive an
+extra method $instance->add_$attrib($key, at args). The method will assign the
+$name element of the HASH attribute to the result of $attrib_class->new(@args).
+
+=item default
+
+(Default=undef) The default value of the attribute.
+
+If this option is defined, the attribute will be initialised to the specified
+value when the new() method is called. In the special case where the value of
+this option is a CODE reference, it will be invoked as $code->(\%attrib), and
+the default value will be the returned value of the CODE reference. This is
+useful, for example, if the default value needs to be a new instance of a class.
+If a genuine CODE reference is required as the default, this option should be
+set to a CODE reference that returns the required CODE reference itself.
+
+For example:
+
+    Foo->class({
+        foo => {default => 'foo'},          # 'foo'
+        bar => {default => sub {get_id()}}, # the next id
+        baz => {default => sub {\&code}},   # &code
+    });
+    {
+        my $id = 0;
+        sub get_id {$id++}
+    }
+
+If the default options is not defined, and if the attribute "isa" is ARRAY, HASH
+or CODE, then the default value is [], {} and sub {} respectively.
+
+=item isa
+
+(Default=undef) The expected type of the attribute. If this optioin is defined
+as $type, a new $value of the attribute is only accepted if $value is undef,
+UNIVERSAL::isa($value,$type) returns true or if $type is C<SCALAR> and the new
+value is not a reference.
+
+The attribute accepts $, @, %, & and * as aliases to SCALAR, ARRAY, HASH, CODE
+and GLOB.
+
+=back
+
+The argument %class_opt can have the following elements:
+
+=over 4
+
+=item init
+
+If $class_opt{init} is defined, it should be a CODE reference. If specified, it
+will be called just after the instance is blessed in the $class->new() method,
+with an interface $f->($instance) where $instance is the new instance.
+
+=item init_attrib
+
+The value of this option must be a CODE. The $class->new() normally expects a
+single HASH reference argument. If an alternate interface to the $class->new()
+is required, this CODE can be used to turn the input argument list to the
+expected HASH reference.
+
+=back
+
+=item $class->new(\%attrib)
+
+Creates a new instance with %attrib. Initial values of the attributes can be
+specified using %attrib. Otherwise, the method will attempt to assign the
+default values, as specified in the class() method, to the newly created
+instance.
+
+=item $instance->get_$attrib()
+
+Returns a readable attribute.
+
+=item $instance->get_$attrib($key)
+
+These are available for HASH attributes only. Returns the value of an element in
+a readable attribute.
+
+=item $instance->set_$attrib($value)
+
+Sets the value of a writable attribute. Returns $instance.
+
+=item $instance->add_$attrib($key, at args)
+
+These are available for HASH attributes (with the C<add> attribute option
+defined) only. Adds a new $key element to the HASH attribute. Returns the newly
+added element.
+
+=back
+
+=head1 DIAGNOSTICS
+
+L<FCM::Class::Exception|FCM::Class::Exception> is thrown on error.
+
+=head1 SEE ALSO
+
+Inspired by the standard module L<Class::Struct|Class::Struct> and CPAN modules
+such as L<Class::Accessor|Class::Accessor>.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Context/ConfigEntry.pm b/lib/FCM/Context/ConfigEntry.pm
new file mode 100644
index 0000000..090b82a
--- /dev/null
+++ b/lib/FCM/Context/ConfigEntry.pm
@@ -0,0 +1,160 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::Context::ConfigEntry;
+use base qw{FCM::Class::HASH};
+
+use Text::ParseWords qw{shellwords};
+
+__PACKAGE__->class({
+    label       => '$',
+    modifier_of => '%',
+    ns_list     => '@',
+    stack       => '@',
+    value       => '$',
+});
+
+# A shorthand for shellwords($entry->get_value()).
+sub get_values {
+    shellwords($_[0]->get_value());
+}
+
+# The config entry's left hand side of the equal sign.
+sub get_lhs {
+    my ($self) = @_;
+    my $modifier = join(
+        q{, },
+        (   map
+            {   my $value = $self->{modifier_of}{$_};
+                join(q{:}, $_, (($value && $value eq 1) ? () : $value));
+            }
+            sort keys(%{$self->{modifier_of}})
+        ),
+    );
+    my $ns = join(
+        q{ },
+        (map {my $s = $_; $s =~ s{(["'\s])}{\\$1}gxms; $s} @{$self->{ns_list}}),
+    );
+    sprintf(
+        '%s%s%s',
+        $self->{label},
+        ($modifier ? "{$modifier}" : q{}),
+        ($ns ? "[$ns]" : q{}),
+    );
+}
+
+# The config entry, as a string.
+sub as_string {
+    my ($self, $in_fcm1) = @_;
+    my $value = $self->{value};
+    $value ||= q{};
+    $value =~ s{(\\)+(\$)}{$1$1\\$2}gxms;
+    sprintf(($in_fcm1 ? '%s %s' : '%s = %s'), $self->get_lhs(), $value);
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::Context::ConfigEntry;
+
+=head1 SYNOPSIS
+
+    my $c_entry = FCM::Context::ConfigEntry->new({
+        label       => 'egg',
+        modifier_of => {fried => 1},
+        ns_list     => [qw{all day breakfast}],
+        stack       => [[$breakfast_menu, 10], [$menu, 20]],
+        value       => 2,
+    });
+
+    # ... some time later
+    $label       = $c_entry->get_label();
+    %modifier_of = %{$c_entry->get_modifier_of()};
+    @ns_list     = @{$c_entry->get_ns_list()};
+    @stack       = @{$c_entry->get_stack()};
+    $value       = $c_entry->get_value();
+
+    print($c_entry->as_string());
+    # should print: egg{fried: 1}[all day breakfast] = 2
+
+=head1 DESCRIPTION
+
+This class is based on L<FCM::Class::HASH|FCM::Class::HASH> for representing an
+entry in a FCM configuration file. All attributes can be read using the
+$instance->get_$attrib() methods.
+
+=head1 ATTRIBUTES
+
+=over 4
+
+=item label
+
+The label of the entry.
+
+=item modifier_of
+
+A HASH containing the modifiers of this entry.
+
+=item ns_list
+
+An ARRAY containing the namespaces of this entry.
+
+=item stack
+
+An ARRAY containing the locator stack that provides this entry. The first
+element represents the top of the stack. Each element should be a reference to a
+2-element array [RESOURCE, LINE_NUMBER].
+
+=item value
+
+The value of this entry.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item $instance->as_string($in_fcm1)
+
+Returns a string representation of the config entry. If the optional argument
+$in_fcm1 is specified, it will return the config entry in FCM 1 format.
+
+=item $instance->get_lhs()
+
+Returns a string representation of the left hand side of the config entry.
+
+=item $instance->get_values()
+
+A shorthand for shellwords($instance->get_value()).
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Context/Event.pm b/lib/FCM/Context/Event.pm
new file mode 100644
index 0000000..5e6a090
--- /dev/null
+++ b/lib/FCM/Context/Event.pm
@@ -0,0 +1,383 @@
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+use strict;
+use warnings;
+#-------------------------------------------------------------------------------
+
+package FCM::Context::Event;
+use base qw{FCM::Class::HASH};
+
+use constant {
+    CM_ABORT                      => 'CM_ABORT',
+    CM_BRANCH_CREATE_SOURCE       => 'CM_BRANCH_CREATE_SOURCE',
+    CM_BRANCH_LIST                => 'CM_BRANCH_LIST',
+    CM_COMMIT_MESSAGE             => 'CM_COMMIT_MESSAGE',
+    CM_CONFLICT_TEXT              => 'CM_CONFLICT_TEXT',
+    CM_CONFLICT_TEXT_SKIP         => 'CM_CONFLICT_TEXT_SKIP',
+    CM_CONFLICT_TREE              => 'CM_CONFLICT_TREE',
+    CM_CONFLICT_TREE_SKIP         => 'CM_CONFLICT_TREE_SKIP',
+    CM_CONFLICT_TREE_TIME_WARN    => 'CM_CONFLICT_TREE_TIME_WARN',
+    CM_CREATE_TARGET              => 'CM_CREATE_TARGET',
+    CM_LOG_EDIT                   => 'CM_LOG_EDIT',
+    #CM_WC_STATUS                  => 'CM_WC_STATUS',
+    #CM_WC_STATUS_PATH             => 'CM_WC_STATUS_PATH',
+    CONFIG_OPEN                   => 'CONFIG_OPEN',
+    CONFIG_ENTRY                  => 'CONFIG_ENTRY',
+    CONFIG_VAR_UNDEF              => 'CONFIG_VAR_UNDEF',
+    E                             => 'E',
+    EXPORT_ITEM_CREATE            => 'EXPORT_ITEM_CREATE',
+    EXPORT_ITEM_DELETE            => 'EXPORT_ITEM_DELETE',
+    FCM_VERSION                   => 'FCM_VERSION',
+    KEYWORD_ENTRY                 => 'KEYWORD_ENTRY',
+    OUT                           => 'OUT',
+    MAKE_BUILD_SHELL_OUT          => 'MAKE_BUILD_SHELL_OUT',
+    MAKE_BUILD_SOURCE_ANALYSE     => 'MAKE_BUILD_SOURCE_ANALYSE',
+    MAKE_BUILD_SOURCE_SUMMARY     => 'MAKE_BUILD_SOURCE_SUMMARY',
+    MAKE_BUILD_TARGET_DONE        => 'MAKE_BUILD_TARGET_DONE',
+    MAKE_BUILD_TARGET_FAIL        => 'MAKE_BUILD_TARGET_FAIL',
+    MAKE_BUILD_TARGET_FROM_NS     => 'MAKE_BUILD_TARGET_FROM_NS',
+    MAKE_BUILD_TARGET_MISSING_DEP => 'MAKE_BUILD_TARGET_MISSING_DEP',
+    MAKE_BUILD_TARGET_SELECT      => 'MAKE_BUILD_TARGET_SELECT',
+    MAKE_BUILD_TARGET_SELECT_TIMER=> 'MAKE_BUILD_TARGET_SELECT_TIMER',
+    MAKE_BUILD_TARGET_STACK       => 'MAKE_BUILD_TARGET_STACK',
+    MAKE_BUILD_TARGET_SUMMARY     => 'MAKE_BUILD_TARGET_SUMMARY',
+    MAKE_BUILD_TARGET_TASK_SUMMARY=> 'MAKE_BUILD_TARGET_TASK_SUMMARY',
+    MAKE_BUILD_TARGETS_FAIL       => 'MAKE_BUILD_TARGETS_FAIL',
+    MAKE_DEST                     => 'MAKE_DEST',
+    MAKE_EXTRACT_PROJECT_TREE     => 'MAKE_EXTRACT_PROJECT_TREE',
+    MAKE_EXTRACT_RUNNER_SUMMARY   => 'MAKE_EXTRACT_RUNNER_SUMMARY',
+    MAKE_EXTRACT_SYMLINK          => 'MAKE_EXTRACT_SYMLINK',
+    MAKE_EXTRACT_TARGET           => 'MAKE_EXTRACT_TARGET',
+    MAKE_EXTRACT_TARGET_SUMMARY   => 'MAKE_EXTRACT_TARGET_SUMMARY',
+    MAKE_MIRROR                   => 'MAKE_MIRROR',
+    SHELL                         => 'SHELL',
+    TASK_WORKERS                  => 'TASK_WORKERS',
+    TIMER                         => 'TIMER',
+};
+
+__PACKAGE__->class({args => '@', code => '$'});
+
+#-------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Context::Event
+
+=head1 SYNOPSIS
+
+    use FCM::Context::Event;
+    my $event_ctx = FCM::Context::Event->new($code, @args);
+
+=head1 DESCRIPTION
+
+An instance of this class represents the context of an event. This class is a
+sub-class of L<FCM::Class::HASH|FCM::Class::HASH>.
+
+=head1 ATTRIBUTES
+
+=over 4
+
+=item args
+
+An ARRAY reference that represents the additional arguments/contexts of the
+event.
+
+=item code
+
+The event code. See below
+
+=back
+
+=head1 EVENTS
+
+The following is a list of event codes.
+
+=over 4
+
+=item FCM::Context::Event->CM_ABORT
+
+This event is raised when a code management command aborts. The 1st argument
+should be either "user" (user abort) or "null" (null command).
+
+=item FCM::Context::Event->CM_BRANCH_CREATE_SOURCE
+
+This event is raised to notify the source of a branch create. The 1st argument
+should be the expected source URL, and the 2nd argument is the specified peg
+revision.
+
+=item FCM::Context::Event->CM_BRANCH_LIST
+
+This event is raised when doing a branch listing. The 1st argument should be the
+project location and the rest of the arguments are the branches discovered.
+
+=item FCM::Context::Event->CM_COMMIT_MESSAGE
+
+This event is raised to notify the user the log message to be used for a commit.
+The 1st argument of the event should be an instance of
+FCM::System::CM::CommitMessage::State.
+
+=item FCM::Context::Event->CM_CONFLICT_TEXT
+
+This event is raised to notify the path of a file with a text conflict.
+
+=item FCM::Context::Event->CM_CONFLICT_TEXT_SKIP
+
+This event is raised to notify the path of a file with a text conflict that
+cannot be resolved using a merge tool. E.g. it may be a binary file.
+
+=item FCM::Context::Event->CM_CONFLICT_TREE
+
+This event is raised to notify the path of a node with a tree conflict.
+
+=item FCM::Context::Event->CM_CONFLICT_TREE_SKIP
+
+This event is raised to notify the path of a node with a tree conflict that
+cannot be resolved automatically under current functionality. For example, it
+may be a directory containing multiple conflicts.
+
+=item FCM::Context::Event->CM_CREATE_TARGET
+
+This event is raised to notify the target of a newly created URL. The 1st argument
+should be the target URL.
+
+=item FCM::Context::Event->CM_LOG_EDIT
+
+This event is raised before the system launches an editor to edit a commit log
+message. The 1st argument of the event should be the editor command.
+
+=item FCM::Context::Event->CONFIG_ENTRY
+
+This entry is raised to notify the reading of a configuration file entry. The
+1st argument should be a blessed reference of a
+L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry>. The second argument
+should be a boolean flag to indicate whether this entry is in FCM 1 format or
+not.
+
+=item FCM::Context::Event->CONFIG_OPEN
+
+This event is raised when a new configuration file is opened for reading. The
+1st argument of this event is an ARRAY that represents the include file stack,
+where the last element is the top of the stack. Each element of the stack is a
+2-element ARRAY reference, where the first element is a
+L<FCM::Context::Locator|FCM::Context::Locator> object and the second element is
+the line number. (At the top of the stack, the line number is set to 0.) The 2nd
+optional argument of this event is a number to adjust the verbosity level of the
+event.
+
+=item FCM::Context::Event->CONFIG_VAR_UNDEF
+
+This event is raised when a variable is undefined. The arguments of this event
+contain 2 elements. The 1st element is the configuration entry as a blessed
+reference of L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry>. The 2nd
+element is the name of the variable.
+
+=item FCM::Context::Event->E
+
+This event is raised when to notify an exception. The 1st argument of this event
+should be the exception.
+
+=item FCM::Context::Event->EXPORT_ITEM_CREATE
+
+This event is raised when the export-items system creates a link to an item.
+The 1st argument is the namespace of the item, the 2nd argument is the revision
+of the item and the 3rd argument is the name of the link.
+
+=item FCM::Context::Event->EXPORT_ITEM_DELETE
+
+This event is raised when the export-items system deletes a link to an item.
+The 1st argument is the namespace of the item, the 2nd argument is the revision
+of the item and the 3rd argument is the name of the link.
+
+=item FCM::Context::Event->FCM_VERSION
+
+This event is raised to notify the FCM version.
+
+=item FCM::Context::Event->KEYWORD_ENTRY
+
+This event is raised to notify a keyword entry. The 1st argument is the keyword
+entry as a blessed reference of FCM::Context::Keyword::Entry as described in
+L<FCM::Context::Keyword|FCM::Context::Keyword>.
+
+=item FCM::Context::Event->MAKE_BUILD_SHELL_OUT
+
+This event is raised to notify (shell command) output from make/build. The 1st
+argument should be the STDOUT, and the 2nd argument should be the STDERR.
+
+=item FCM::Context::Event->MAKE_BUILD_SOURCE_ANALYSE
+
+This event is raised when the make/build system has analysed a source file. The
+1st argument should be a blessed reference of FCM::Context::Make::Build::Source
+as described in L<FCM::Context::Make::Build|FCM::Context::Make::Build>. The 2nd
+argument should be the time it takes for the analysis.
+
+=item FCM::Context::Event->MAKE_BUILD_SOURCE_SUMMARY
+
+This event is raised when the make/build system has analysed all its source
+files. The 1st argument should be the total number of files. The 2nd argument
+should be the number analysed. The 3rd argument should be the elapsed time. The
+4th argument should be the total time, which may differ from the elapsed time if
+the analysis is run on more than 1 process.
+
+=item FCM::Context::Event->MAKE_BUILD_TARGET_DONE
+
+This event is raised when the make/build system has successfully updated a
+target or an update is unnecessary. The 1st argument is the target (as an
+instance of FCM::Context::Make::Build::Target), the 2nd argument is the elapsed
+time of the update, if relevant.
+
+=item FCM::Context::Event->MAKE_BUILD_TARGET_FAIL
+
+This event is raised when the make/build system failed to update a target or the
+target is failed by its dependencies. The 1st argument is the target (as an
+instance of FCM::Context::Make::Build::Target), the 2nd argument is the elapsed
+time, if the target is failed by its update.
+
+=item FCM::Context::Event->MAKE_BUILD_TARGET_FROM_NS
+
+This event is raised when the make/build system has generated a build target
+from a source or a source name-space. The arguments are: source/target
+namespace, target task, target category, and the target key.
+
+=item FCM::Context::Event->MAKE_BUILD_TARGET_MISSING_DEP
+
+This event is raised when the make/build system has discarded a missing
+dependency from a target. The 1st argument is the target ID, the 2nd argument is
+the dependency ID, and the 3rd argument is the dependency type.
+
+=item FCM::Context::Event->MAKE_BUILD_TARGET_SELECT
+
+This event is raised when the make/build system has selected a set of targets to
+build. The 1st argument is a HASH reference of the target set.
+
+=item FCM::Context::Event->MAKE_BUILD_TARGET_SELECT_TIMER
+
+This event is raised when the make/build system has completed the target select
+and dependency tree analysis. The only argument is the elapsed time.
+
+=item FCM::Context::Event->MAKE_BUILD_TARGET_STACK
+
+This event is raised when make/build system checks a target for cyclic
+dependency.  The 1st argument is the key of the task. The 2nd argument is rank
+of the task in the dependency hierarchy. The 3rd argument is the number of
+dependencies the task has if the task has already been checked, or undef if this
+is the first check for the task.
+
+=item FCM::Context::Event->MAKE_BUILD_TARGET_SUMMARY
+
+This event is raised when the make/build system has finished updating its
+targets, and is ready to give a total summary. The 1st argument is the number of
+modified targets, the 2nd argument is the number unchanged, the 3rd argument is
+the number failed, the 4th argument is the elapsed time.
+
+=item FCM::Context::Event->MAKE_BUILD_TARGET_TASK_SUMMARY
+
+This event is raised when the make/build system has finished updating its
+targets, and is ready to give a summary of each type of task. The 1st argument
+is the task type name, the 2nd argument is the number of modified targets, the
+3rd argument is the number unchanged, the 4th argument is the number failed, and
+the 5th argument is the total time spent on this task type.
+
+=item FCM::Context::Event->MAKE_BUILD_TARGETS_FAIL
+
+This event is raised when the make/build system has finished updating its
+targets, but some targets failed to update. The 1st argument is an ARRAY of
+FCM::Context::Make::Build::Target objects representing the failed targets.
+
+=item FCM::Context::Event->MAKE_DEST
+
+This event is raised when the make system sets up the destination. The 1st
+argument of this event is the make system context.
+
+=item FCM::Context::Event->MAKE_EXTRACT_PROJECT_TREE
+
+This event is raised after the make/extract system has finished gathering
+information for the source trees of each project. The 1st argument is a HASH of
+the (keys) project name-spaces and the (values) list (ARRAY) of source tree
+locators L<FCM::Context::Locator|FCM::Context::Locator> in the project.
+
+=item FCM::Context::Event->MAKE_EXTRACT_RUNNER_SUMMARY
+
+This event is raised after the make/extract system has finished using the task
+runner to perform some tasks. The 1st argument is an identifier for the tasks
+performed. The 2nd argument is the number of tasks. The 2nd argument is the
+elapsed time. The 3rd argument is the total time in all processes.
+
+=item FCM::Context::Event->MAKE_EXTRACT_SYMLINK
+
+This event is raised as the make/extract system ignores a source that is a
+symbolic link. The 1st argument of this event should be a blessed reference of
+FCM::Context::Make::Extract::Source as described in
+L<FCM::Context::Make::Extract|FCM::Context::Make::Extract>.
+
+=item FCM::Context::Event->MAKE_EXTRACT_TARGET
+
+This event is raised as the make/extract system updates a target destination. The
+1st argument of this event should be a blessed reference of
+FCM::Context::Make::Extract::Target as described in
+L<FCM::Context::Make::Extract|FCM::Context::Make::Extract>.
+
+=item FCM::Context::Event->MAKE_EXTRACT_TARGET_SUMMARY
+
+This event is raised after the make/extract system has updated all target
+destinations. The 1st argument of this event is a HASH reference, which contains
+2 keys: status and status_of_source, i.e. the destination status of the targets
+and the source status of the targets respectively. The values of both are HASH
+references. The keys are the names of the status, and the values are the number
+of targets with the corresponding status.
+
+=item FCM::Context::Event->MAKE_MIRROR
+
+This event is raised as the make/mirror system updates a target destination. The
+1st argument of this event should be the target URI. The remaining arguments
+should be the source paths.
+
+=item FCM::Context::Event->OUT
+
+This event is raised to notify (shell command) output. The 1st argument should
+be the STDOUT, and the 2nd argument should be the STDERR.
+
+=item FCM::Context::Event->SHELL
+
+This event is raised to notify the completion of a shell command. The 1st
+argument is an ARRAY reference of the shell command. The 2nd argument is an
+integer to override the verbosity level. The 3rd argument is the return code and
+the 4th argument is the elapsed time.
+
+=item FCM::Context::Event->TASK_WORKERS
+
+This event is raised on initialisation and destruction of worker processes for
+the utility task runner. The 1st argument should either be "init" or "destroy".
+The 2nd argument should be the number of workers initialised/destroyed.
+
+=item FCM::Context::Event->TIMER
+
+This event is raised at the start and end of the utility timer. The 1st
+argument is the name of the piece of code to time. The 2nd argument is the start
+the timer. The 3rd argument is the elapsed time at the end. If the 3rd argument
+is not specified, it is the start of the timer.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Context/Keyword.pm b/lib/FCM/Context/Keyword.pm
new file mode 100644
index 0000000..af1fcda
--- /dev/null
+++ b/lib/FCM/Context/Keyword.pm
@@ -0,0 +1,221 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::Context::Keyword;
+use base qw{FCM::Class::HASH};
+
+use constant {
+    BROWSER_CONFIG    => 'FCM::Context::Keyword::BrowserConfig',
+    ENTRY             => 'FCM::Context::Keyword::Entry',
+    ENTRY_OF_LOCATION => 'FCM::Context::Keyword::Entry::Location',
+};
+
+use Scalar::Util qw{blessed};
+
+__PACKAGE__->class({
+    entry_class    => {w => 0, isa => '$', default => ENTRY_OF_LOCATION},
+    entry_by_key   => {w => 0, isa => '%'},
+    entry_by_value => {w => 0, isa => '%'},
+});
+
+sub add_entry {
+    my $self = shift();
+    my ($key, $value, $entry);
+    if (blessed($_[0])) {
+        $entry = $_[0];
+        $key   = $entry->get_key();
+        $value = $entry->get_value();
+    }
+    else {
+        ($key, $value, my $attrib_ref) = @_;
+        $attrib_ref ||= {};
+        $entry = $self->get_entry_class()->new({
+            key => lc($key), value => $value, %{$attrib_ref},
+        });
+    }
+    $self->{entry_by_key}{lc($key)} = $entry;
+    $self->{entry_by_value}{$value} = $entry;
+    return $entry;
+}
+
+# ------------------------------------------------------------------------------
+package FCM::Context::Keyword::BrowserConfig;
+use base qw{FCM::Class::HASH};
+
+__PACKAGE__->class({comp_pat => undef, loc_tmpl => '$', rev_tmpl => '$'});
+
+# ------------------------------------------------------------------------------
+package FCM::Context::Keyword::Entry;
+use base qw{FCM::Class::HASH};
+
+__PACKAGE__->class({
+    key   => {w => 0, isa => '$'},
+    value => {w => 0, isa => '$'},
+});
+
+# ------------------------------------------------------------------------------
+package FCM::Context::Keyword::Entry::Location;
+use base qw{FCM::Context::Keyword::Entry};
+
+my $CTX = 'FCM::Context::Keyword';
+
+__PACKAGE__->class(
+    {   browser_config  => $CTX->BROWSER_CONFIG,
+        ctx_of_implied  => $CTX,
+        ctx_of_rev      => $CTX,
+        implied         => {isa => '$', default => 0},
+        key             => {isa => '$', w => 0},
+        loaded_rev_prop => '$',
+        type            => '$',
+        value           => {isa => '$', w => 0},
+    },
+    {   init => sub {
+            my ($self) = @_;
+            if (!$self->get_implied()) {
+                $self->{browser_config} = $CTX->BROWSER_CONFIG->new();
+                $self->{ctx_of_implied} = $CTX->new();
+                $self->{ctx_of_rev} = $CTX->new({entry_class => $CTX->ENTRY});
+            }
+        },
+    },
+);
+
+# Returns true if this is an implied entry
+sub is_implied {
+    $_[0]->{implied};
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Context::Keyword
+
+=head1 SYNOPSIS
+
+    use FCM::Context::Keyword;
+
+=head1 DESCRIPTION
+
+Provides a context object for the FCM keyword utility. All the classes described
+below are sub-classes of L<FCM::Class::HASH|FCM::Class::HASH>.
+
+=head1 OBJECTS
+
+=head2 FCM::Context::Keyword
+
+An object of this class is used to store a list of keyword entries. It has the
+following methods:
+
+=over 4
+
+=item $instance->add_entry($key,$value,\%attrib)
+
+Creates and adds a new entry.
+
+=item $instance->add_entry($entry)
+
+Adds a new entry.
+
+=item $instance->get_entry_class()
+
+Returns the class of the entry stored by this context.
+
+=item $instance->get_entry_by_key()
+
+Returns a HASH reference to map the entry keys with the entry objects.
+
+=item $instance->get_entry_by_value()
+
+Returns a HASH reference to map the entry values with the entry objects.
+
+=back
+
+=head2 FCM::Context::Keyword::BrowserConfig
+
+An object of this class is used to store the configuration for mapping a
+location to a browser URL. It has the following attributes:
+
+=over 4
+
+=item comp_pat
+
+The pattern for extracting components from a locator, for putting into the
+browser location template.
+
+=item loc_tmpl
+
+The browser location template.
+
+=item rev_tmpl
+
+The browser revision template.
+
+=back
+
+=head2 FCM::Context::Keyword::Entry
+
+This is used to store a simple keyword entry (e.g. for revision keywords). It
+has 2 attributes, the I<key> and the I<value>.
+
+=head2 FCM::Context::Keyword::Entry::Location
+
+This is a sub-class of FCM::Context::Keyword::Entry, and is used to store a
+location keyword entry. It has the following additional attributes:
+
+=over 4
+
+=item browser_config
+
+The configuration L</FCM::Context::Keyword::BrowserConfig> for mapping this
+location to a browser URL.
+
+=item ctx_of_implied
+
+The context </FCM::Context::Keyword> object of the implied entries, if this
+entry is a primary location.
+
+=item ctx_of_rev
+
+The context </FCM::Context::Keyword> object of the revision keywords.
+
+=item implied
+
+A flag to indicate that this is an entry implied by a primary location.
+
+=item loaded_rev_prop
+
+A flag to indicate that a previous attempt is made to load revision keywords
+from the I<property> of the location.
+
+=item type
+
+The location type.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Context/Locator.pm b/lib/FCM/Context/Locator.pm
new file mode 100644
index 0000000..5959aac
--- /dev/null
+++ b/lib/FCM/Context/Locator.pm
@@ -0,0 +1,144 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::Context::Locator;
+use base qw{FCM::Class::HASH};
+
+use constant {
+    L_INIT       => -1,
+    L_PARSED     =>  0,
+    L_NORMALISED =>  1,
+    L_INVARIANT  =>  2,
+};
+
+__PACKAGE__->class(
+    {   last_mod_rev  => '$',
+        last_mod_time => '$',
+        type          => '$',
+        value         => '$',
+        value_at_init => {isa => '$', i => 1, w => 0},
+        value_level   => {isa => '$', default => L_INIT},
+    },
+    {   init_attrib => sub {
+            my ($value, $attrib_ref) = @_;
+            return {
+                (defined($attrib_ref) ? %{$attrib_ref} : ()),
+                value         => $value,
+                value_at_init => $value,
+            };
+        },
+    }
+);
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Context::Locator
+
+=head1 SYNOPSIS
+
+    use FCM::Context::Locator;
+    $locator = FCM::Context::Locator->new($value_at_init, {type => $type});
+    $locator->set_value($value);
+    $locator->set_value_level($locator->L_INVARIANT);
+    print($locator->get_value(), "\n");
+
+=head1 DESCRIPTION
+
+A simple structure for storing the values of a FCM locator. It is based on
+L<FCM::Class::HASH|FCM::Class::HASH>.
+
+=head1 ATTRIBUTES
+
+An instance has the following attributes, all of which can be initialised and
+accessed via an $instance->get_$attrib() method:
+
+=over 4
+
+=item last_mod_rev
+
+The last modified revision.
+
+=item last_mod_time
+
+The last modified time (seconds since epoch).
+
+=item type
+
+The locator type.
+
+=item value
+
+The current value of the locator.
+
+=item value_at_init
+
+The value of the locator when the object is initialised.
+
+=item value_level
+
+The value level of the locator. It can be one of the L_* constants. A higher
+level indicates that the value is more processed.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new($value,\%attrib)
+
+Returns a new instance.
+
+=head1 CONSTANTS
+
+=over 4
+
+=item FCM::Context::Locator->L_INIT
+
+The lowest value level, i.e. the value has not been processed. (default)
+
+=item FCM::Context::Locator->L_PARSED
+
+The value level is between L_INIT and L_NORMALISED, i.e. where necessary, the
+FCM location keyword is substituted.
+
+=item FCM::Context::Locator->L_NORMALISED
+
+The value level is between L_PARSED and L_INVARIANT, i.e. where necessary, the
+FCM location and revision keywords are substituted and the value has been tidied
+(e.g. extra slashes in the path removed).
+
+=item FCM::Context::Locator->L_INVARIANT
+
+The highest value level, i.e. if the locator points to a version control
+resource, the value is expected to be tagged with a specific revision.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Context/Make.pm b/lib/FCM/Context/Make.pm
new file mode 100644
index 0000000..4551144
--- /dev/null
+++ b/lib/FCM/Context/Make.pm
@@ -0,0 +1,164 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::Context::Make;
+use base qw{FCM::Class::HASH};
+
+use constant {
+    ST_UNKNOWN =>  0,
+    ST_INIT    =>  1,
+    ST_OK      =>  2,
+    ST_FAILED  => -1,
+};
+
+__PACKAGE__->class({
+    ctx_of            => '%',
+    dest              => '$',
+    dest_lock         => '$',
+    error             => {},
+    inherit_ctx_list  => '@',
+    option_of         => '%',
+    prev_ctx          => __PACKAGE__,
+    status            => {isa => '$', default => ST_UNKNOWN},
+    steps             => '@',
+});
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Context::Make
+
+=head1 SYNOPSIS
+
+    use FCM::Context::Make;
+    my $ctx = FCM::Context::Make->new();
+
+=head1 DESCRIPTION
+
+Provides a context object for the FCM make system. It is a sub-class of
+L<FCM::Class::HASH|FCM::Class::HASH>.
+
+=head1 OBJECTS
+
+=head2 FCM::Context::Make
+
+An instance of this class represents a make. It has the following
+attributes:
+
+=over 4
+
+=item ctx_of
+
+A HASH containing the (keys) IDs and the (values) context objects of the make.
+
+=item dest
+
+The destination of this make.
+
+=item dest_lock
+
+The destination lock of this make.
+
+=item error
+
+This should be set to the value of the exception, if this make ends in one.
+
+=item inherit_ctx_list
+
+An ARRAY of contexts inherited by this make.
+
+=item option_of
+
+A HASH to store the options of this make. See L</OPTION> for detail.
+
+=item status
+
+The status of the make.
+
+=item steps
+
+The names of the steps to make.
+
+=back
+
+=head1 OPTION
+
+The C<option_of> attribute of a FCM::Context::Make object may contain the
+following elements:
+
+=over 4
+
+=item config-file
+
+An ARRAY of configuration file names.
+
+=item directory
+
+The working directory of the make.
+
+=item ignore-lock
+
+Ignores lock file in the destination.
+
+=item jobs
+
+The number of (child) threads that can be run simultaneously.
+
+=item new
+
+Performs a make in "new" mode (as opposed to the "incremental" mode).
+
+=back
+
+=head1 CONSTANTS
+
+=over 4
+
+=item FCM::Context::Make->ST_UNKNOWN
+
+The status of a make context or the context of a subsystem. Status is unknown.
+
+=item FCM::Context::Make->ST_INIT
+
+The status of a make context or the context of a subsystem. The make or the
+subsystem has initialised, but not completed.
+
+=item FCM::Context::Make->ST_OK
+
+The status of a make context or the context of a subsystem. The make or the
+subsystem has completed successfully.
+
+=item FCM::Context::Make->ST_FAILED
+
+The status of a make context or the context of a subsystem. The make or the
+subsystem has failed.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Context/Make/Build.pm b/lib/FCM/Context/Make/Build.pm
new file mode 100644
index 0000000..8489963
--- /dev/null
+++ b/lib/FCM/Context/Make/Build.pm
@@ -0,0 +1,481 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::Context::Make::Build;
+use base qw{FCM::Class::HASH};
+
+use FCM::Context::Make;
+
+use constant {
+    CTX_SOURCE  => 'FCM::Context::Make::Build::Source',
+    CTX_TARGET  => 'FCM::Context::Make::Build::Target',
+    ID_OF_CLASS => 'build',
+};
+
+my $ST_UNKNOWN = FCM::Context::Make->ST_UNKNOWN;
+
+__PACKAGE__->class(
+    {   dest             => '$',
+        dests            => '@',
+        id               => {isa => '$' , default => ID_OF_CLASS},
+        id_of_class      => {isa => '$' , default => ID_OF_CLASS},
+        input_ns_excl    => '@',
+        input_ns_incl    => '@',
+        input_source_of  => '%',
+        prop_of          => '%',
+        source_of        => '%',
+        status           => {isa => '$' , default => $ST_UNKNOWN},
+        target_of        => '%',
+        target_key_of    => '%',
+        target_select_by => '%',
+    },
+);
+
+# ------------------------------------------------------------------------------
+package FCM::Context::Make::Build::Source;
+use base qw{FCM::Class::HASH};
+
+__PACKAGE__->class({
+    checksum   => '$',
+    deps       => '@',
+    info_of    => '%',
+    ns         => '$',
+    path       => '$',
+    prop_of    => '%',
+    type       => '$',
+    up_to_date => '$',
+});
+
+# ------------------------------------------------------------------------------
+package FCM::Context::Make::Build::Target;
+use base qw{FCM::Class::HASH};
+
+use constant {
+    CT_BIN                  => 'bin',
+    CT_ETC                  => 'etc',
+    CT_INCLUDE              => 'include',
+    CT_LIB                  => 'lib',
+    CT_O                    => 'o',
+    CT_SRC                  => 'src',
+    POLICY_CAPTURE          => 'POLICY_CAPTURE',
+    POLICY_FILTER           => 'POLICY_FILTER',
+    POLICY_FILTER_IMMEDIATE => 'POLICY_FILTER_IMMEDIATE',
+    ST_FAILED               => 'ST_FAILED',
+    ST_MODIFIED             => 'ST_MODIFIED',
+    ST_OOD                  => 'ST_OOD',
+    ST_UNCHANGED            => 'ST_UNCHANGED',
+    ST_UNKNOWN              => 'ST_UNKNOWN',
+};
+
+__PACKAGE__->class(
+    {   category        => '$',
+        checksum        => '$',
+        deps            => '@',
+        dep_policy_of   => '%',
+        failed_by       => '@',
+        info_of         => '%',
+        key             => '$',
+        ns              => '$',
+        path            => '$',
+        path_of_prev    => '$',
+        path_of_source  => '$',
+        prop_of         => '%',
+        prop_of_prev_of => '%',
+        status          => {isa => '$', default => ST_UNKNOWN},
+        status_of       => '%',
+        task            => '$',
+        triggers        => '@',
+        type            => '$',
+    },
+);
+
+# Returns true if target has a usable dest status.
+sub can_be_source {
+    $_[0]->get_category() && $_[0]->get_category() eq CT_SRC;
+}
+
+# Returns true if target has an OK status.
+sub is_ok {
+    $_[0]->get_status() eq ST_MODIFIED || $_[0]->get_status() eq ST_UNCHANGED;
+}
+
+# Returns true if target has a failed status.
+sub is_failed {
+    $_[0]->get_status() eq ST_FAILED;
+}
+
+# Shorthand for $target->get_status() eq $target->ST_MODIFIED.
+sub is_modified {
+    $_[0]->get_status() eq ST_MODIFIED;
+}
+
+# Shorthand for $target->get_status() eq $target->ST_UNCHANGED.
+sub is_unchanged {
+    $_[0]->get_status() eq ST_UNCHANGED;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Context::Make::Build
+
+=head1 SYNOPSIS
+
+    use FCM::Context::Make::Build;
+    my $ctx = FCM::Context::Make::Build->new();
+
+=head1 DESCRIPTION
+
+Provides a context object for the FCM build system. All the classes described
+below are sub-classes of L<FCM::Class::HASH|FCM::Class::HASH>.
+
+=head1 OBJECTS
+
+=head2 FCM::Context::Make::Build
+
+An instance of this class represents a build. It has the following
+attributes:
+
+=over 4
+
+=item dest
+
+The destination of the build.
+
+=item dests
+
+An ARRAY containing the path for searching items in the current build.
+
+=item id
+
+The ID of the context. (default="build")
+
+=item id_of_class
+
+The class ID of the context. (default="build")
+
+=item input_source_of
+
+A HASH to map a name space to its ARRAY of input sources.
+
+=item input_ns_excl
+
+An ARRAY of source name-spaces to exclude.
+
+=item input_ns_incl
+
+An ARRAY of source name-spaces to include.
+
+=item prop_of
+
+A HASH containing the named properties (i.e. options and settings of named
+external tools). Expects a value to be an instance of
+L<FCM::Context::Make::Share::Property|FCM::Context::Make::Share::Property>.
+
+=item prop_of_prev_of
+
+A HASH containing the named properties (i.e. options and settings of named
+external tools) in the latest successful update of this target. Expects a value
+to be an instance of
+L<FCM::Context::Make::Share::Property|FCM::Context::Make::Share::Property>.
+
+=item source_of
+
+A HASH to map the namespace to the source contexts. Each element is expected to
+be an L</FCM::Context::Make::Build::Source> object.
+
+=item status
+
+The status of this context. See L<FCM::Context::Make|FCM::Context::Make> for the
+status constants.
+
+=item target_of
+
+A HASH to map the namespace to the target contexts. Each element is expected to
+be an L</FCM::Context::Make::Build::Target> object.
+
+=item target_key_of
+
+A HASH to map the automatic key of targets to their desired key.
+
+=item target_select_by
+
+A HASH to allow users to specify how to select from all the targets. The key can
+be "category", "key", "ns" or "task". Each value should be a HASH that
+represents the set of criteria.
+
+=back
+
+=head2 FCM::Context::Make::Build::Source
+
+An instance of this class represents an actual source of the build. It has the
+following attributes:
+
+=over 4
+
+=item checksum
+
+The MD5 checksum of the source file.
+
+=item deps
+
+An ARRAY to contain the dependencies of the source file. Each element of the
+ARRAY is expected to be a reference to a two-element ARRAY [$name, $type] where
+$name is the name of the dependency and $type is its type.
+
+=item info_of
+
+A HASH to contain the extra information of the source file. E.g. If the {main}
+element is true, the source contains a main program. If the {symbols} element
+is defined, it contains a reference to an ARRAY of program unit symbols that has
+been found in the source file.
+
+=item ns
+
+The name-space of the source file.
+
+=item path
+
+The path in the file system pointing to the source file.
+
+=item prop_of
+
+A HASH containing the keys and the values of the build properties (mainly on
+dependency settings) of the source.
+
+=item type
+
+The file type of the source file.
+
+=item up_to_date
+
+A flag to indicate whether the source file is up to date, compared with a
+previous build or the nearest inherited build.
+
+=back
+
+=head2 FCM::Context::Make::Build::Target
+
+An instance of this class represents a target of the build. It has the following
+attributes:
+
+=over 4
+
+=item category
+
+The target category, e.g. bin, etc, include, lib, o, src
+
+=item checksum
+
+The MD5 checksum of the target.
+
+=item deps
+
+An ARRAY containing the dependencies of the target. Each element of the
+ARRAY is expected to be a reference to a two-element ARRAY [$name, $type] where
+$name is the name of the dependency and $type is its type.
+
+=item dep_policy_of
+
+A HASH to contain a map between each relevant dependency type of this target and
+its policy to apply to the dependency type. The policy should take the value of
+POLICY_CAPTURE, POLICY_FILTER or POLICY_FILTER_IMMEDIATE.
+
+=item failed_by
+
+On failure, set to an ARRAY containing the names of the targets that cause the
+failure of this target.
+
+=item info_of
+
+A HASH to contain the extra information of the target. E.g. The {paths} => ARRAY
+reference of include/object search paths for the compile, link and preprocess
+tasks; and {deps}{o} => ARRAY reference and {deps}{o.special} => ARRAY
+reference of object dependency for the link tasks.
+
+=item key
+
+The key (i.e. the base name) of the target.
+
+=item ns
+
+The name-space (of the source file) associated with this target.
+
+=item path
+
+The path in the file system where the target can be located.
+
+=item path_of_prev
+
+The path in the file system where the target in a previous or inherited build
+can be located.
+
+=item path_of_source
+
+The path in the file system where the source file associated with the target can
+be located.
+
+=item prop_of
+
+A HASH containing the keys and the values of the build properties of the target.
+
+=item status
+
+The status of the target.
+
+=item status_of
+
+A HASH containing the status of dependency types that may be relevant to targets
+higher up in the dependency tree.
+
+=item task
+
+The target type, (i.e. the name of the task to update with the target).
+
+=item triggers
+
+An ARRAY reference of the keys of targets that should be automatically triggered
+by this target.
+
+=item type
+
+The type of the source that gives this target.
+
+=back
+
+In addition, an instance of FCM::Context::Make::Extract::Target has the
+following methods:
+
+=over 4
+
+=item $target->can_be_source()
+
+Returns true if the destination status indicates that the target is usable as a
+source file of a subsequent a make (step).
+
+=item $target->is_ok()
+
+Returns true if the target has a OK destination status.
+
+=item $target->is_failed()
+
+Returns true if the target has a failed destination status.
+
+=item $target->is_modified()
+
+Shorthand for $target->get_status() eq $target->ST_MODIFIED.
+
+=item $target->is_unchanged()
+
+Shorthand for $target->get_status() eq $target->ST_UNCHANGED.
+
+=back
+
+=head1 CONSTANTS
+
+The following is a list of constants:
+
+=over 4
+
+=item FCM::Context::Make::Build->CTX_INPUT
+
+Alias of FCM::Context::Make::Build::Input.
+
+=item FCM::Context::Make::Build->CTX_SOURCE
+
+Alias of FCM::Context::Make::Build::Source.
+
+=item FCM::Context::Make::Build->ID_OF_CLASS
+
+The default value of the "id" attribute (of an instance), and the ID of the
+functional class. ("build")
+
+=item FCM::Context::Make::Build::Target->CT_BIN
+
+Target category, "bin", executable.
+
+=item FCM::Context::Make::Build::Target->CT_ETC
+
+Target category, "etc", data and misc file.
+
+=item FCM::Context::Make::Build::Target->CT_INCLUDE
+
+Target category, "include", include file.
+
+=item FCM::Context::Make::Build::Target->CT_LIB
+
+Target category, "lib", program library.
+
+=item FCM::Context::Make::Build::Target->CT_O
+
+Target category, "o", compiled object file.
+
+=item FCM::Context::Make::Build::Target->CT_SRC
+
+Target category, "src", generated source file.
+
+=item FCM::Context::Make::Build::Target->POLICY_CAPTURE
+
+Indicates that the dependency type is relevant to the target, and the build
+engine should stop floating the dependency target up the dependency tree.
+
+=item FCM::Context::Make::Build::Target->POLICY_FILTER
+
+Indicates that the dependency type is relevant to the target, and the build
+engine may float the dependency target up the dependency tree as well.
+
+=item FCM::Context::Make::Build::Target->POLICY_FILTER_IMMEDIATE
+
+Indicates that the dependency type is relevant to the target but only if the
+dependency target is an immediate dependency of this target, and the build
+engine may float the dependency target up the dependency tree as well.
+
+=item FCM::Context::Make::Build::Target->ST_FAILED
+
+Indicates that the build has failed to update the target.
+
+=item FCM::Context::Make::Build::Target->ST_MODIFIED
+
+Indicates that the target is out of date and has been modified by the build.
+
+=item FCM::Context::Make::Build::Target->ST_OOD
+
+Indicates that the target is out of date.
+
+=item FCM::Context::Make::Build::Target->ST_UNCHANGED
+
+Indicates that the target is up to date and unchanged by the build.
+
+=item FCM::Context::Make::Build::Target->ST_UNKNOWN
+
+Indicates an unknown target status.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Context/Make/Extract.pm b/lib/FCM/Context/Make/Extract.pm
new file mode 100644
index 0000000..8e81b39
--- /dev/null
+++ b/lib/FCM/Context/Make/Extract.pm
@@ -0,0 +1,494 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::Context::Make::Extract;
+use base qw{FCM::Class::HASH};
+
+use FCM::Context::Make;
+
+use constant {
+    CTX_PROJECT => 'FCM::Context::Make::Extract::Project',
+    CTX_SOURCE  => 'FCM::Context::Make::Extract::Source',
+    CTX_TARGET  => 'FCM::Context::Make::Extract::Target',
+    CTX_TREE    => 'FCM::Context::Make::Extract::Tree',
+    ID_OF_CLASS => 'extract',
+    MIRROR      => 1,
+};
+
+__PACKAGE__->class({
+    dest         => '$',
+    id           => {isa => '$', default => ID_OF_CLASS},
+    id_of_class  => {isa => '$', default => ID_OF_CLASS},
+    ns_list      => '@',
+    project_of   => '%',
+    status       => {isa => '$', default => FCM::Context::Make->ST_UNKNOWN},
+    target_of    => '%',
+    prop_of      => '%',
+});
+
+# ------------------------------------------------------------------------------
+package FCM::Context::Make::Extract::Project;
+use base qw{FCM::Class::HASH};
+
+__PACKAGE__->class({
+    cache     => '$',
+    inherited => '$',
+    locator   => 'FCM::Context::Locator',
+    ns        => '$',
+    path_excl => '@',
+    path_incl => '@',
+    path_root => {isa => '$', default => q{}},
+    trees     => '@',
+});
+
+# ------------------------------------------------------------------------------
+package FCM::Context::Make::Extract::Tree;
+use base qw{FCM::Class::HASH};
+
+__PACKAGE__->class({
+    cache     => '$',
+    inherited => '$',
+    key       => '$',
+    locator   => 'FCM::Context::Locator',
+    ns        => '$',
+    sources   => '@',
+});
+
+# ------------------------------------------------------------------------------
+package FCM::Context::Make::Extract::Source;
+use base qw{FCM::Class::HASH};
+
+use constant {
+    ST_NORMAL    => 'ST_NORMAL',
+    ST_UNCHANGED => 'ST_UNCHANGED',
+    ST_MISSING   => 'ST_MISSING',
+};
+
+__PACKAGE__->class({
+    cache       => '$',
+    key_of_tree => '$',
+    locator     => 'FCM::Context::Locator',
+    ns          => '$',
+    ns_in_tree  => '$',
+    status      => {isa => '$', default => ST_NORMAL},
+});
+
+# Shorthand for $source->get_status() eq $source->ST_MISSING.
+sub is_missing {
+    $_[0]->get_status() eq ST_MISSING;
+}
+
+# Shorthand for $source->get_status() eq $source->ST_UNCHANGED.
+sub is_unchanged {
+    $_[0]->get_status() eq ST_UNCHANGED;
+}
+
+# ------------------------------------------------------------------------------
+package FCM::Context::Make::Extract::Target;
+use base qw{FCM::Class::HASH};
+
+use constant {
+    ST_ADDED      => 'ST_ADDED',
+    ST_DELETED    => 'ST_DELETED',
+    ST_MERGED     => 'ST_MERGED',
+    ST_MODIFIED   => 'ST_MODIFIED',
+    ST_O_ADDED    => 'ST_O_ADDED',
+    ST_O_DELETED  => 'ST_O_DELETED',
+    ST_UNCHANGED  => 'ST_UNCHANGED',
+    ST_UNKNOWN    => 'ST_UNKNOWN',
+    can_be_source => 1,
+};
+
+__PACKAGE__->class({
+    dests            => '@',
+    ns               => '$',
+    path             => '$',
+    source_of        => '%',
+    status           => {isa => '$', default => ST_UNKNOWN},
+    status_of_source => {isa => '$', default => ST_UNKNOWN},
+});
+
+# Returns true if target has an OK status.
+sub is_ok {
+    my ($self) = @_;
+    my $status = $self->get_status();
+    grep {$_ eq $status}
+        (ST_ADDED, ST_MERGED, ST_MODIFIED, ST_O_ADDED, ST_UNCHANGED);
+}
+
+# Shorthand for $target->get_status() eq $target->ST_UNCHANGED.
+sub is_unchanged {
+    $_[0]->get_status() eq ST_UNCHANGED;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Context::Make::Extract
+
+=head1 SYNOPSIS
+
+    use FCM::Context::Make::Extract;
+    my $ctx = FCM::Context::Make::Extract->new();
+
+=head1 DESCRIPTION
+
+Provides a context object for the FCM extract system. All the classes described
+below are sub-classes of L<FCM::Class::HASH|FCM::Class::HASH>.
+
+=head1 OBJECTS
+
+=head2 FCM::Context::Make::Extract
+
+An instance of this class represents an extract. It has the following
+attributes:
+
+=over 4
+
+=item dest
+
+The destination of the extract.
+
+=item id
+
+The ID of the current context. (default="extract")
+
+=item id_of_class
+
+The class ID of the current context. (default="extract")
+
+=item ns_list
+
+An ARRAY of name-spaces of the projects to extract.
+
+=item project_of
+
+A HASH to map (key) the name-spaces of the projects in this extract to (value)
+their corresponding contexts.
+
+=item prop_of
+
+A HASH containing the named properties (i.e. options and settings of named
+external tools). Expects a value to be an instance of
+L<FCM::Context::Make::Share::Property|FCM::Context::Make::Share::Property>.
+
+=item status
+
+The status of the extract. See L<FCM::Context::Make|FCM::Context::Make> for the
+status constants.
+
+=item target_of
+
+A HASH to map (key) the name-spaces of the targets in this extract to (value)
+their corresponding contexts.
+
+=back
+
+=head2 FCM::Context::Make::Extract::Project
+
+An instance of this class represents a project in an extract. It has the
+following attributes:
+
+=over 4
+
+=item cache
+
+The file system location (cache) of this project.
+
+=item inherited
+
+This project is inherited?
+
+=item locator
+
+An instance of L<FCM::Context::Locator|FCM::Context::Locator> that represents
+the locator of this project.
+
+=item ns
+
+The name-space of this project.
+
+=item path_excl
+
+An ARRAY of patterns to match the names of the paths that will be excluded in
+this project.
+
+=item path_incl
+
+An ARRAY of patterns to match the names of the paths that will always be
+included in this project.
+
+=item path_root
+
+The relative path in a project tree for the root name-space. If this is
+specified, the system will extract only files under this path, and their
+name-spaces will be adjusted to be relative to this path.
+
+=item trees
+
+An ARRAY of the tree contexts in this project. By convention, the 0th element is
+the base tree.
+
+=back
+
+=head2 FCM::Context::Make::Extract::Tree
+
+An instance of this class represents a tree in a project. It has the following
+attributes:
+
+=over 4
+
+=item cache
+
+The file system location (cache) of this tree.
+
+=item inherited
+
+A flag to indicate whether this tree is provided by an inherited extract.
+
+=item key
+
+The key of this tree. By convention, the base tree is the 0th key.
+
+=item locator
+
+An instance of L<FCM::Context::Locator|FCM::Context::Locator> that represents
+the locator of this tree.
+
+=item ns
+
+The name-space of the project in which this tree belongs.
+
+=item sources
+
+An ARRAY of source file contexts provided by this tree.
+
+=back
+
+=head2 FCM::Context::Make::Extract::Source
+
+An instance of this class represents a source file provided by a project tree.
+It has the following attributes:
+
+=over 4
+
+=item cache
+
+The file system location (cache) of this source file.
+
+=item key_of_tree
+
+The key of the tree that provides this source file.
+
+=item locator
+
+An instance of L<FCM::Context::Locator|FCM::Context::Locator> that represents
+the locator of the source file.
+
+=item ns
+
+The full (mapped) name-space of the source file, (including the leading project
+name-space).
+
+=item ns_in_tree
+
+The original name-space of the source file, relative to its path in the tree.
+
+=item status
+
+The status of the source file. It can take the value of one of the
+FCM::Context::Make::Extract::Source->ST_* constants. See </CONSTANTS> for detail.
+
+=back
+
+In addition, an instance of FCM::Context::Make::Extract::Source has the
+following methods:
+
+=over 4
+
+=item $source->is_missing()
+
+Shorthand for $source->get_status() eq $source->ST_MISSING.
+
+=item $source->is_unchanged()
+
+Shorthand for $source->get_status() eq $source->ST_UNCHANGED.
+
+=back
+
+=head2 FCM::Context::Make::Extract::Target
+
+An instance of this class represents an extract target. It has the following
+attributes:
+
+=over 4
+
+=item dests
+
+An ARRAY containing the destination search path of this target. The first
+element is the path to the destination of the current extract, and the rest are
+destinations to inherited extracts.
+
+=item ns
+
+The full name-space of this target.
+
+=item path
+
+Returns the actual destination path of this target.
+
+=item source_of
+
+A HASH for mapping (key) the keys of the trees to (value) the corresponding
+contexts of the source files provided by the trees to this target.
+
+=item status
+
+The status of the target destination. It can take the value of one of the
+FCM::Context::Make::Extract::Target->ST_* constants. See </CONSTANTS> for detail.
+
+=item status_of_source
+
+The status of the target, with respect to its sources. It can take the value of
+one of the FCM::Context::Make::Extract::Target->ST_* constants. See </CONSTANTS>
+for detail.
+
+=back
+
+In addition, an instance of FCM::Context::Make::Extract::Target has the
+following methods:
+
+=over 4
+
+=item $target->can_be_source()
+
+Returns true if the destination status indicates that the target is usable as a
+source file of a subsequent a make (step).
+
+=item $target->is_ok()
+
+Returns true if the target has a OK destination status.
+
+=item $target->is_unchanged()
+
+Shorthand for $target->get_status() eq $target->ST_UNCHANGED.
+
+=back
+
+=head1 CONSTANTS
+
+The following is a list of constants:
+
+=over 4
+
+=item FCM::Context::Make::Extract->CTX_PROJECT
+
+An alias to FCM::Context::Make::Extract::Project.
+
+=item FCM::Context::Make::Extract->CTX_SOURCE
+
+An alias to FCM::Context::Make::Extract::Source.
+
+=item FCM::Context::Make::Extract->CTX_TARGET
+
+An alias to FCM::Context::Make::Extract::Target.
+
+=item FCM::Context::Make::Extract->CTX_TREE
+
+An alias to FCM::Context::Make::Extract::Tree.
+
+=item FCM::Context::Make::Extract->ID_OF_CLASS
+
+The default value of the "id" attribute (of an instance), and the ID of the
+functional class. ("extract")
+
+=item FCM::Context::Make::Extract->MIRROR
+
+A flag to tell the mirror sub-system that the targets of this context can be
+used as inputs sources to subsequent steps for the configuration file in the
+mirror destination.
+
+=item FCM::Context::Make::Extract::Source->ST_NORMAL
+
+Source status: normal.
+
+=item FCM::Context::Make::Extract::Source->ST_UNCHANGED
+
+Source status: source is unchanged (against base).
+
+=item FCM::Context::Make::Extract::Source->ST_MISSING
+
+Source status: source is a placeholder in a target. It does not actually exist
+in the source tree.
+
+=item FCM::Context::Make::Extract::Target->ST_ADDED
+
+As destination status: new file in the target destination. As source status:
+added by a source in a diff tree.
+
+=item FCM::Context::Make::Extract::Target->ST_DELETED
+
+As destination status: file removed from the target destination. As source
+status: target removed by a diff tree.
+
+=item FCM::Context::Make::Extract::Target->ST_MERGED
+
+As source status: modified by 2 or more diff trees.
+
+=item FCM::Context::Make::Extract::Target->ST_MODIFIED
+
+As destination status: target destination is modified. As source status:
+modified by 1 diff tree.
+
+=item FCM::Context::Make::Extract::Target->ST_O_ADDED
+
+As destination status: new file in the target destination, overriding a file in
+an inherited destination.
+
+=item FCM::Context::Make::Extract::Target->ST_O_DELETED
+
+As destination status: target destination should be removed, but there is still
+a file in an inherited destination.
+
+=item FCM::Context::Make::Extract::Target->ST_UNCHANGED
+
+As destination status: target destination is unchanged. As source status:
+unchanged by a diff tree.
+
+=item FCM::Context::Make::Extract::Target->ST_UNKNOWN
+
+Status is unknown.
+
+=back
+
+=head1 SEE ALSO
+
+L<FCM::System::Make::Extract|FCM::System::Make::Extract>
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Context/Make/Mirror.pm b/lib/FCM/Context/Make/Mirror.pm
new file mode 100644
index 0000000..3f6110b
--- /dev/null
+++ b/lib/FCM/Context/Make/Mirror.pm
@@ -0,0 +1,119 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::Context::Make::Mirror;
+use base qw{FCM::Class::HASH};
+
+use FCM::Context::Make;
+
+use constant {ID_OF_CLASS => 'mirror'};
+
+__PACKAGE__->class({
+    dest           => '$',
+    id             => {isa => '$', default => ID_OF_CLASS},
+    id_of_class    => {isa => '$', default => ID_OF_CLASS},
+    prop_of        => '%',
+    status         => {isa => '$', default => FCM::Context::Make->ST_UNKNOWN},
+    target_logname => '$',
+    target_machine => '$',
+    target_path    => '$',
+});
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Context::Make::Mirror
+
+=head1 SYNOPSIS
+
+    use FCM::Context::Make::Mirror;
+    my $ctx = FCM::Context::Make::Mirror->new();
+    $ctx->set_dest_path($dest_path);
+    $ctx->set_source_path($source_path);
+    # ...
+
+=head1 DESCRIPTION
+
+Provides a context object for the mirror sub-system.
+
+=head1 ATTRIBUTES
+
+This class is based on L<FCM::Class::HASH|FCM::Class::HASH>. All attributes are
+accessible via $ctx->get_$attrib() and $ctx->set_$attrib($value) methods.
+
+=over 4
+
+=item dest
+
+The local working directory for the mirror sub-system.
+
+=item id
+
+The ID of the context. (default="mirror")
+
+=item id_of_class
+
+The class ID of the context. (default="mirror")
+
+=item prop_of
+
+A HASH containing the named properties (i.e. options and settings of named
+external tools). Expects a value to be an instance of
+L<FCM::Context::Make::Share::Property|FCM::Context::Make::Share::Property>.
+
+=item status
+
+The status of the context. See L<FCM::Context::Make|FCM::Context::Make> for the
+status constants.
+
+=item target_logname
+
+The logname part of the authority of the mirror destination.
+
+=item target_machine
+
+The machine part of the authority of the mirror destination.
+
+=item target_path
+
+The container path of the mirror destination (without the authority).
+
+=back
+
+=head1 CONSTANTS
+
+=over 4
+
+=item ID_OF_CLASS
+
+The default value of the "id" attribute (of an instance), and the ID of the
+functional class. ("mirror")
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Context/Make/Share/Property.pm b/lib/FCM/Context/Make/Share/Property.pm
new file mode 100644
index 0000000..cc81196
--- /dev/null
+++ b/lib/FCM/Context/Make/Share/Property.pm
@@ -0,0 +1,138 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::Context::Make::Share::Property;
+use base qw{FCM::Class::HASH};
+
+use constant {
+    CTX_VALUE  => 'FCM::Context::Make::Share::Property::Value',
+    NS_OF_ROOT => q{},
+};
+
+__PACKAGE__->class({ctx_of => '%', id => '$'});
+
+sub get_ctx {
+    $_[0]->get_ctx_of(NS_OF_ROOT);
+}
+
+sub set_ctx {
+    $_[0]->get_ctx_of()->{$_[0]->NS_OF_ROOT} = $_[1];
+}
+
+# ------------------------------------------------------------------------------
+package FCM::Context::Make::Share::Property::Value;
+use base qw{FCM::Class::HASH};
+
+__PACKAGE__->class({inherited => '$', value => '$'});
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Context::Make::Share::Property
+
+=head1 SYNOPSIS
+
+    use FCM::Context::Make::Share::Property;
+    $prop = FCM::Context::Make::Share::Property->new(\%attrib);
+
+=head1 DESCRIPTION
+
+Provides a context object to store the property of a named shell command.
+
+=head1 OBJECTS
+
+The classes described below are all sub-classes of
+L<FCM::Class::HASH|FCM::Class::HASH>.
+
+=head2 FCM::Context::Make::Share::Property
+
+This class represents a property. It has the following attributes:
+
+=over 4
+
+=item ctx_of
+
+A HASH to map (keys) the name-spaces to (values) the contexts of this property.
+Expects each context to be an instance of
+L</FCM::Context::Make::Share::Property::Value>.
+
+The context of a simple property is stored in the root (i.e. the empty string)
+name-space.
+
+=item id
+
+The ID of this property.
+
+=back
+
+An instance of FCM::Context::Make::Share::Property has 2 additional methods:
+
+=over 4
+
+=item $instance->get_ctx()
+
+Shorthand for:
+
+    $instance->get_ctx_of(q{}).
+
+=item $instance->set_ctx($ctx)
+
+Shorthand for:
+
+    $instance->get_ctx_of()->{q{}} = $ctx.
+
+=back
+
+=head2 FCM::Context::Make::Share::Property::Value
+
+This class represents a property value (associated with a name-space). It has
+the following attributes:
+
+=over 4
+
+=item inherited
+
+A flag, if true, indicates that this value is inherited.
+
+=item value
+
+The value.
+
+=back
+
+=head1 CONSTANTS
+
+=over 4
+
+=item FCM::Context::Make::Share::Property->NS_OF_ROOT
+
+The root name-space, an empty string.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Context/Task.pm b/lib/FCM/Context/Task.pm
new file mode 100644
index 0000000..d49fe6c
--- /dev/null
+++ b/lib/FCM/Context/Task.pm
@@ -0,0 +1,105 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::Context::Task;
+use base qw{FCM::Class::HASH};
+
+use constant {
+    ST_FAILED  => 'ST_FAILED',
+    ST_OK      => 'ST_OK',
+    ST_WORKING => 'ST_WORKING',
+};
+
+__PACKAGE__->class({
+    ctx      => {},
+    error    => {},
+    id       => '$',
+    elapse   => {isa => '$', default => 0},
+    state    => {isa => '$', default => ST_WORKING},
+});
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Context::Task
+
+=head1 SYNOPSIS
+
+    use FCM::Context::Task;
+    my $task = FCM::Context::Task->new(\%attrib);
+
+=head1 DESCRIPTION
+
+An instance of this class represents the generic context for a task for the
+L<FCM::Util->task_runner()|FCM::Util>. This class is a sub-class of
+L<FCM::Class::HASH|FCM::Class::HASH>.
+
+=head1 ATTRIBUTES
+
+=over 4
+
+=item ctx
+
+The specific context of the task, such as the inputs and the outputs.
+
+=item error
+
+If the task failed, the error/exception will be returned in this attribute.
+
+=item id
+
+The ID of the task.
+
+=item elapse
+
+The amount of time (in seconds) taken to run the task.
+
+=item state
+
+The state of the task. See L</CONSTANTS> for possible variables.
+
+=back
+
+=head1 CONSTANTS
+
+=over 4
+
+=item FCM::Context::Task->ST_FAILED
+
+A status to indicate that the task has failed.
+
+=item FCM::Context::Task->ST_OK
+
+A status to indicate that the task is completed successfully.
+
+=item FCM::Context::Task->ST_WORKING
+
+A status to indicate that the task is bing worked on.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Exception.pm b/lib/FCM/Exception.pm
new file mode 100644
index 0000000..edd45c2
--- /dev/null
+++ b/lib/FCM/Exception.pm
@@ -0,0 +1,113 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::Exception;
+
+use Data::Dumper qw{Dumper};
+use Scalar::Util qw{blessed};
+use overload (q{""} => \&Dumper);
+
+use constant {
+    DEFAULT => 'DEFAULT',
+};
+
+# Returns true if $e is a blessed instance of $class.
+sub caught {
+    my ($class, $e) = @_;
+    return (blessed($e) && $e->isa($class));
+}
+
+# Throws the exception.
+sub throw {
+    my ($class, $code, $ctx, $e) = @_;
+    if (defined($e) && !ref($e) && $e =~ qr{\A\s*\z}msx) {
+        $e = undef;
+    }
+    die(bless({code => $code, ctx => $ctx, exception => $e}, $class));
+}
+
+# Attribute accessors.
+for my $name (qw{code ctx exception}) {
+    no strict qw{refs};
+    *{"get_$name"} = sub {$_[0]->{$name}};
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Exception
+
+=head1 SYNOPSIS
+
+    use FCM::Exception;
+    my $E = 'FCM::Exception';
+    eval {
+        # ...
+        if ($some_error_condition) {
+            return $E->throw($code, $ctx);
+        }
+        # ...
+    };
+    if (my $e = $@) {
+        if ($E->caught($e)) {
+            # ...
+        }
+    }
+
+=head1 DESCRIPTION
+
+Exception associated with an FCM operation.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->caught($e)
+
+Returns true if $e is a blessed object of $class.
+
+=item $class->throw($code,$ctx,$e)
+
+Creates an instance and die() with it.
+
+=item $e->get_code()
+
+Returns the code associated with this exception.
+
+=item $e->get_ctx()
+
+Returns the context associated with this exception.
+
+=item $e->get_exception()
+
+Returns the exception that generates this exception.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System.pm b/lib/FCM/System.pm
new file mode 100644
index 0000000..5d06d0e
--- /dev/null
+++ b/lib/FCM/System.pm
@@ -0,0 +1,287 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+
+package FCM::System;
+use base qw{FCM::Class::CODE};
+
+use FCM::Util;
+use Scalar::Util qw{reftype};
+
+# Alias
+our $S;
+
+# The (keys) named actions of this class and (values) their implementations.
+our %ACTION_OF = (
+    browse               => _func('misc', sub {$S->browse(@_)}),
+    build                => _func('old' , sub {$S->build(@_)}),
+    config_compare       => _func('old' , sub {$S->config_compare(@_)}),
+    config_parse         => _func('misc', sub {$S->config_parse(@_)}),
+    cm_branch_create     => _func('cm'  , sub {$S->cm_branch_create(@_)}),
+    cm_branch_delete     => _func('cm'  , sub {$S->cm_branch_delete(@_)}),
+    cm_branch_diff       => _func('cm'  , sub {$S->cm_branch_diff(@_)}),
+    cm_branch_info       => _func('cm'  , sub {$S->cm_branch_info(@_)}),
+    cm_branch_list       => _func('cm'  , sub {$S->cm_branch_list(@_)}),
+    cm_commit            => _func('cm'  , sub {$S->cm_commit(@_)}),
+    cm_checkout          => _func('cm'  , sub {$S->cm_checkout(@_)}),
+    cm_check_missing     => _func('cm'  , sub {$S->cm_check_missing(@_)}),
+    cm_check_unknown     => _func('cm'  , sub {$S->cm_check_unknown(@_)}),
+    cm_diff              => _func('cm'  , sub {$S->cm_diff(@_)}),
+    cm_loc_layout        => _func('cm'  , sub {$S->cm_loc_layout(@_)}),
+    cm_merge             => _func('cm'  , sub {$S->cm_merge(@_)}),
+    cm_mkpatch           => _func('cm'  , sub {$S->cm_mkpatch(@_)}),
+    cm_project_create    => _func('cm'  , sub {$S->cm_project_create(@_)}),
+    cm_resolve_conflicts => _func('cm'  , sub {$S->cm_resolve_conflicts(@_)}),
+    cm_switch            => _func('cm'  , sub {$S->cm_switch(@_)}),
+    cm_update            => _func('cm'  , sub {$S->cm_update(@_)}),
+    export_items         => _func('misc', sub {$S->export_items(@_)}),
+    extract              => _func('old' , sub {$S->extract(@_)}),
+    keyword_find         => _func('misc', sub {$S->keyword_find(@_)}),
+    make                 => _func('make', sub {$S->main(@_)}),
+    svn                  => _func('cm'  , sub {$S->svn(@_)}),
+    util                 => sub {$_[0]->{util}},
+    version              => _func('misc', sub {$S->version(@_)}),
+);
+# The (keys) named system and their implementation classes.
+our %SYSTEM_CLASS_OF = (
+    cm   => 'FCM::System::CM',
+    old  => 'FCM::System::Old',
+    make => 'FCM::System::Make',
+    misc => 'FCM::System::Misc',
+);
+
+# Creates the class.
+__PACKAGE__->class(
+    {   gui             => '$',
+        system_class_of => {isa => '%', default => {%SYSTEM_CLASS_OF}},
+        system_of       => '%',
+        util            => '&',
+    },
+    {init => \&_init, action_of => {%ACTION_OF}},
+);
+
+# Initialises attributes.
+sub _init {
+    my $attrib_ref = shift();
+    $attrib_ref->{util} = FCM::Util->new();
+}
+
+# Generates main functions.
+sub _func  {
+    my ($name, $code_ref) = @_;
+    sub {
+        my ($attrib_ref, @args) = @_;
+        if (!defined($attrib_ref->{system_of}{$name})) {
+            my $class_name = $attrib_ref->{system_class_of}{$name};
+            $attrib_ref->{util}->class_load($class_name);
+            $attrib_ref->{system_of}{$name} = $class_name->new({
+                gui  => $attrib_ref->{gui},
+                util => $attrib_ref->{util},
+            });
+        }
+        local($S) = $attrib_ref->{system_of}{$name};
+        $code_ref->(@args);
+    };
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System
+
+=head1 SYNOPSIS
+
+    use FCM::System;
+    $fcm = FCM::System->new();
+    # ...
+    $fcm->make(\%option, @args);
+
+=head1 DESCRIPTION
+
+Provides a top level interface to access the functionalities of the FCM system.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Returns a new instance. It also initialises the utility and sub-system classes.
+The %attrib hash can be used configure the behaviour of the instance:
+
+=over 4
+
+=item event
+
+A CODE to handle event.
+
+=item gui
+
+The GUI geometry of "fcm gui-internal".
+
+=item system_class_of
+
+A HASH to map (keys) sub-system names to (values) their implementation classes.
+See %FCM::System::SYSTEM_CLASS_OF.
+
+=item system_of
+
+A HASH to map (keys) sub-system names to (values) their implementation instances.
+
+=item util
+
+An instance of L<FCM::Util|FCM::Util>.
+
+=back
+
+=item $fcm->browse(\%option, at args)
+
+Invokes a browser to browse the sources in @args.
+
+=item $fcm->build(\%option, at args)
+
+(Obsolete) Invokes the FCM 1 build system.
+
+=item $fcm->config_compare(\%option, at args)
+
+(Obsolete) Compares 2 FCM 1 extract configuration files.
+
+=item $fcm->config_parse(\%option, at args)
+
+Parses a configuration file.
+
+=item $fcm->cm_branch_create(\%option, at args)
+
+Creates of a branch in a project in a Subversion repository with a standard FCM
+layout.
+
+=item $fcm->cm_branch_delete(\%option, at args)
+
+Deletes of a branch in a project in a Subversion repository with a standard FCM
+layout.
+
+=item $fcm->cm_branch_diff(\%option, at args)
+
+Displays the changes between a branch and its parent in a project in a
+Subversion repository with a standard FCM layout.
+
+=item $fcm->cm_branch_info(\%option, at args)
+
+Displays information of a branch in a project in a Subversion repository with a
+standard FCM layout.
+
+=item $fcm->cm_branch_list(\%option, at args)
+
+Lists branches in a project in a Subversion repository with a standard FCM
+layout.
+
+=item $fcm->cm_commit(\%option, at args)
+
+Wraps C<svn commit>.
+
+=item $fcm->cm_checkout(\%option, at args)
+
+Wraps C<svn checkout>.
+
+=item $fcm->cm_check_missing(\%option, at args)
+
+Checks for missing status in a Subversion working copy.
+
+=item $fcm->cm_check_unknown(\%option, at args)
+
+Checks for unknown status in a Subversion working copy.
+
+=item $fcm->cm_diff(\%option, at args)
+
+Wraps C<svn diff>.
+
+=item $fcm->cm_loc_layout(\%option, at args)
+
+Parse and print layout information of each target in @args.
+
+=item $fcm->cm_merge(\%option, at args)
+
+Wraps C<svn merge>.
+
+=item $fcm->cm_mkpatch(\%option, at args)
+
+Creates FCM patches.
+
+=item $fcm->cm_project_create(\%option, at args)
+
+Create a new project in a Subversion repository.
+
+=item $fcm->cm_resolve_conflicts(\%option, at args)
+
+Invokes a graphic merge tool to resolve conflicts.
+
+=item $fcm->cm_switch(\%option, at args)
+
+Wraps C<svn switch>.
+
+=item $fcm->cm_update(\%option, at args)
+
+Wraps C<svn update>.
+
+=item $fcm->export_items(\%option, at args)
+
+Exports directories as versioned items in a branch of a project in a Subversion
+repository with the standard FCM layout.
+
+=item $fcm->extract(\%option, at args)
+
+(Obsolete) Invokes the FCM 1 extract system.
+
+=item $fcm->keyword_find(\%option, at args)
+
+If @args is empty, search for all known FCM location keyword entries. Otherwise,
+search for FCM location keyword entries matching the locations specified in
+ at args.
+
+=item $fcm->make(\%option, at args)
+
+Invokes the FCM make system.
+
+=item $fcm->svn(\%option, at args)
+
+Invokes C<svn> with @args. %option is ignored.
+
+=item $fcm->util()
+
+Returns the L<FCM::Util|FCM::Util> object.
+
+=back
+
+=head1 DIAGNOSTICS
+
+=head2 FCM::System::Exception
+
+This exception is a sub-class of L<FCM::Exception|FCM::Exception> and is thrown
+by methods of this class on error.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/CM.pm b/lib/FCM/System/CM.pm
new file mode 100644
index 0000000..51aba6a
--- /dev/null
+++ b/lib/FCM/System/CM.pm
@@ -0,0 +1,709 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::CM;
+use base qw{FCM::Class::CODE};
+
+use Cwd qw{cwd};
+use FCM1::Cm;
+use FCM1::Interactive;
+use FCM::Context::Event;
+use FCM::Context::Locator;
+use FCM::System::CM::CommitMessage;
+use FCM::System::CM::Prompt;
+use FCM::System::CM::ResolveConflicts qw{_cm_resolve_conflicts};
+use FCM::System::CM::SVN;
+use FCM::System::Exception;
+use FCM::Util::Exception;
+use File::Spec::Functions qw{catfile};
+use List::Util qw{first};
+use Storable qw{dclone};
+
+# The (keys) named actions of this class and (values) their implementations.
+our %ACTION_OF = (
+    cm_branch_create     => \&_cm_branch_create,
+    cm_branch_delete     => _fcm1_func(\&FCM1::Cm::cm_branch_delete),
+    cm_branch_diff       => _fcm1_func(\&FCM1::Cm::cm_branch_diff),
+    cm_branch_info       => _fcm1_func(\&FCM1::Cm::cm_branch_info),
+    cm_branch_list       => \&_cm_branch_list,
+    cm_commit            => _fcm1_func(\&FCM1::Cm::cm_commit),
+    cm_checkout          => \&_cm_checkout,
+    cm_check_missing     => _fcm1_func(
+        \&FCM1::Cm::cm_check_missing,
+        _opt_mod_st_check_handler_func('WC_STATUS_PATH'),
+    ),
+    cm_check_unknown     => _fcm1_func(
+        \&FCM1::Cm::cm_check_unknown,
+        _opt_mod_st_check_handler_func('WC_STATUS_PATH'),
+    ),
+    cm_diff              => \&_cm_diff,
+    cm_loc_layout        => \&_cm_loc_layout,
+    cm_merge             => _fcm1_func(\&FCM1::Cm::cm_merge),
+    cm_mkpatch           => _fcm1_func(\&FCM1::Cm::cm_mkpatch),
+    cm_project_create    => \&_cm_project_create,
+    cm_resolve_conflicts => \&_cm_resolve_conflicts,
+    cm_switch            => _fcm1_func(
+        \&FCM1::Cm::cm_switch, _opt_mod_st_check_handler_func('WC_STATUS'),
+    ),
+    cm_update            => _fcm1_func(
+        \&FCM1::Cm::cm_update, _opt_mod_st_check_handler_func('WC_STATUS'),
+    ),
+    svn                  => \&_svn,
+);
+
+# Alias
+my $E = 'FCM::System::Exception';
+
+# Creates the class.
+__PACKAGE__->class(
+    {   commit_message_util => '&',
+        gui                 => '$',
+        prompt              => '&',
+        svn                 => '&',
+        util                => '&',
+    },
+    {init => \&_init, action_of => \%ACTION_OF},
+);
+
+sub _init {
+    my ($attrib_ref) = @_;
+    if (!defined(FCM1::Keyword::get_util())) {
+        FCM1::Keyword::set_util($attrib_ref->{util});
+    }
+    if ($attrib_ref->{'gui'}) {
+        FCM1::Interactive::set_impl(
+            'FCM1::Interactive::InputGetter::GUI',
+            {geometry => $attrib_ref->{gui}},
+        );
+    }
+    $attrib_ref->{prompt} = FCM::System::CM::Prompt->new({
+        gui => $attrib_ref->{gui}, util => $attrib_ref->{util},
+    });
+    $attrib_ref->{commit_message_util} = FCM::System::CM::CommitMessage->new({
+        gui  => $attrib_ref->{gui},
+        util => $attrib_ref->{util},
+    });
+    $attrib_ref->{svn} = FCM::System::CM::SVN->new({util => $attrib_ref->{util}});
+    FCM1::Cm::set_util($attrib_ref->{util});
+    FCM1::Cm::set_commit_message_util($attrib_ref->{commit_message_util});
+    FCM1::Cm::set_svn_util($attrib_ref->{svn});
+}
+
+# Create a branch in a project.
+sub _cm_branch_create {
+    my ($attrib_ref, $option_ref, @args) = @_;
+    _parse_args($attrib_ref, $option_ref, \@args);
+    my ($name, $source) = @args;
+    # Check branch name
+    if (!$name || $name !~ qr{\A[\w\.\-/]+\z}msx) {
+        return $E->throw($E->CM_BRANCH_NAME, $name ? $name : q{});
+    }
+    # Determine ticket list with name
+    if (!$option_ref->{ticket} && $name =~ qr{\A[1-9]\d*([_\-][1-9]\d*)*\z}msx) {
+        $option_ref->{ticket} = [split(qr{[_\-]}msx, $name)];
+    }
+    # Check source
+    $source ||= cwd() . '@HEAD';
+    my $layout = $attrib_ref->{svn}->get_layout($source);
+    my $root = $layout->get_root();
+    my $source_rev = $layout->get_peg_rev();
+    my $project = $layout->get_project();
+    my $source_branch = $layout->get_branch();
+    if (!defined($project)) {
+        return $E->throw($E->CM_BRANCH_SOURCE, $source);
+    }
+    my @project_paths = split(qr{/}msx, $project);
+
+    # Determine whether to create a branch of a branch
+    if (!$option_ref->{'branch-of-branch'} || !$source_branch) {
+        $source_branch = 'trunk';
+    }
+    $source = join('/', $root, @project_paths, $source_branch)
+        . '@' . $source_rev;
+    my $source_commit_rev
+        = $attrib_ref->{svn}->get_info($source)->[0]->{'commit:revision'};
+    $source = join('/', $root, @project_paths, $source_branch)
+        . '@' . $source_commit_rev;
+    $attrib_ref->{util}->event(
+        FCM::Context::Event->CM_BRANCH_CREATE_SOURCE, $source, $source_rev,
+    );
+
+    # Handle multiple tickets
+    $option_ref->{ticket} ||= [];
+    $option_ref->{ticket} = [
+        sort
+            {$a <=> $b}
+        map
+            {s{\A#}{}msx; $_}
+        split(qr{,}msx, join(q{,}, @{$option_ref->{ticket}}))
+    ];
+
+    # Determine the sub-directory names of the branch
+    # FIXME: hard coded legacy!
+    my %layout_config = %{$layout->get_config()};
+    my @names;
+    if ($layout_config{'template-branch'}) {
+        my $template = $layout_config{'template-branch'};
+        if (    index($template, '{category}') >= 0
+            ||  index($template, '{owner}') >= 0
+        ) {
+            $option_ref->{type} ||= 'dev::user';
+            $option_ref->{type} = lc($option_ref->{type});
+            $option_ref->{type}
+                = $option_ref->{type} eq 'user'   ? 'dev::user'
+                : $option_ref->{type} eq 'share'  ? 'dev::share'
+                : $option_ref->{type} eq 'config' ? 'pkg::config'
+                : $option_ref->{type} eq 'rel'    ? 'pkg::rel'
+                : $option_ref->{type} eq 'dev'    ? 'dev::user'
+                : $option_ref->{type} eq 'test'   ? 'test::user'
+                : $option_ref->{type} eq 'pkg'    ? 'pkg::user'
+                :                                   $option_ref->{type}
+                ;
+            if (!grep {$option_ref->{type} eq $_} qw{
+                dev::share dev::user test::share test::user
+                pkg::config pkg::rel  pkg::share  pkg::user
+            }) {
+                return $E->throw($E->CM_OPT_ARG, ['type', $option_ref->{type}]);
+            }
+            my %set = map {$_ => 1} split('::', $option_ref->{type});
+            if (index($template, '{category}') >= 0) {
+                my $index = index($template, '{category}');
+                my $category = first {exists($set{$_})} qw{dev test pkg};
+                substr($template, $index, length('{category}'), $category);
+            }
+            if (index($template, '{owner}') >= 0) {
+                my $index = index($template, '{owner}');
+                my $owner = exists($set{user})
+                    ? $attrib_ref->{svn}->get_username($root)
+                    : first {exists($set{lc($_)})} qw{Share Config Rel};
+                substr($template, $index, length('{owner}'), $owner);
+            }
+        }
+        if (index($template, '{name_prefix}') >= 0) {
+            my $index = index($template, '{name_prefix}');
+            # Check revision flag is valid
+            $option_ref->{'rev-flag'} ||= 'normal';
+            $option_ref->{'rev-flag'} = lc($option_ref->{'rev-flag'});
+            if (!grep {$_ eq $option_ref->{'rev-flag'}} qw{normal number none}) {
+                return $E->throw(
+                    $E->CM_OPT_ARG, ['rev-flag', $option_ref->{'rev-flag'}]);
+            }
+            my $name_prefix = q{};
+            if ($option_ref->{'rev-flag'} ne 'none') {
+                $name_prefix = 'r' . $source_commit_rev;
+                if ($option_ref->{'rev-flag'} eq 'normal') {
+                    # Attempt to replace revision number with a keyword
+                    my $locator = FCM::Context::Locator->new($source);
+                    my $as_keyword = $attrib_ref->{util}->loc_as_keyword($locator);
+                    my ($u, $r) = $attrib_ref->{svn}->split_by_peg($as_keyword);
+                    if ($source_commit_rev ne $r) {
+                        $name_prefix = $r;
+                    }
+                }
+
+                # Add an underscore
+                $name_prefix .= '_';
+            }
+            substr($template, $index, length('{name_prefix}'), $name_prefix);
+        }
+        if (index($template, '{name}') >= 0) {
+            my $index = index($template, '{name}');
+            substr($template, $index, length('{name}'), $name);
+        }
+        push(@names, split(qr{/+}msx, $template));
+    }
+    else {
+        push(@names, split(qr{/+}msx, $name));
+    }
+    if ($layout_config{'depth-branch'} != scalar(@names)) {
+        return $E->throw($E->CM_BRANCH_NAME, join('/', @names));
+    }
+    if ($layout_config{'dir-branch'}) {
+        unshift(@names, $layout_config{'dir-branch'});
+    }
+    # Check whether the branch already exists
+    my $target = join('/', $root, @project_paths, @names);
+    my $target_url = eval {$attrib_ref->{svn}->get_info($target)->[0]->{url}};
+    $@ = undef;
+    if ($target_url) {
+        return $E->throw($E->CM_ALREADY_EXIST, $target_url);
+    }
+
+    # Message for the commit log
+    my @tickets = @{$option_ref->{ticket}};
+    my @message = sprintf('%sCreated %s from %s@%d.' . "\n",
+        (@tickets ? join(q{,}, map {'#' . $_} @tickets) . q{: } : q{}),
+        join('/', q{}, @project_paths, @names),
+        join('/', q{}, @project_paths, $source_branch), $source_commit_rev,
+    );
+
+    # Create a temporary file for the commit log message
+    my $commit_message_ctx = $attrib_ref->{commit_message_util}->ctx();
+    $commit_message_ctx->set_auto_part(join(q{}, @message));
+    $commit_message_ctx->set_info_part(sprintf("%s    %s\n", 'A', $target));
+    if (!$option_ref->{'non-interactive'}) {
+        $attrib_ref->{commit_message_util}->edit($commit_message_ctx);
+    }
+    $attrib_ref->{commit_message_util}->notify($commit_message_ctx);
+    my $temp_handle
+        = $attrib_ref->{commit_message_util}->temp($commit_message_ctx);
+
+    # Check with the user to see if he/she wants to go ahead
+    if (    !$option_ref->{'non-interactive'}
+        &&  !$attrib_ref->{prompt}->question('BRANCH_CREATE')
+    ) {
+        return;
+    }
+
+    # Create the branch
+    $attrib_ref->{svn}->call(
+        'copy',
+        '--file', $temp_handle->filename(),
+        '--parents',
+        ($option_ref->{'svn-non-interactive'} ? '--non-interactive' : ()),
+        (   defined($option_ref->{'password'})
+            ? ('--password', $option_ref->{'password'}) : ()
+        ),
+        $source,
+        $target,
+    );
+    $attrib_ref->{util}->event(FCM::Context::Event->CM_CREATE_TARGET, $target);
+
+    # Switch working copy to point to newly created branch
+    if ($option_ref->{'switch'}) {
+        $ACTION_OF{'cm_switch'}->($attrib_ref, $option_ref, $target);
+    }
+
+    $target;
+}
+
+# Filter lists branches in projects.
+sub _cm_branch_list {
+    my ($attrib_ref, $option_ref, @args) = @_;
+    _parse_args($attrib_ref, $option_ref, \@args);
+    if (!@args) {
+        @args = cwd() . '@HEAD';
+    }
+    my %common_patterns_at;
+    if ($option_ref->{'only'} && @{$option_ref->{'only'}}) {
+        for (@{$option_ref->{'only'}}) {
+            my ($depth, $pattern) = split(qr{:}msx, $_, 2);
+            $common_patterns_at{$depth} ||= [];
+            push(@{$common_patterns_at{$depth}}, $pattern);
+        }
+    }
+    my $UTIL = $attrib_ref->{'util'};
+    ARG:
+    for my $arg (@args) {
+        my %patterns_at = %{dclone(\%common_patterns_at)};
+        my %info = eval {%{$attrib_ref->{svn}->get_info($arg)->[0]}};
+        if ($@) {
+            return $E->throw($E->CM_ARG, $arg);
+        }
+        my $url = $info{'url'} . '@' . $info{'revision'};
+        my $layout = $attrib_ref->{svn}->get_layout($url);
+        my $root = $layout->get_root();
+        my $rev = $layout->get_peg_rev();
+        my $project = $layout->get_project();
+        if (!defined($project)) {
+            next ARG;
+        }
+        my $url_project = $root . ($project ? '/' . $project : q{});
+        my %layout_config = %{$layout->get_config()};
+        if ($layout_config{'level-owner-branch'} && !$option_ref->{'show-all'}) {
+            my $level = $layout_config{'level-owner-branch'};
+            if ($option_ref->{'user'} && @{$option_ref->{'user'}}) {
+                $patterns_at{$level} = [
+                    map {'^' . $_ . '$'}
+                    map {split(qr{[,:]}msx, $_)}
+                    @{$option_ref->{'user'}}
+                ];
+            }
+            elsif (!%patterns_at) {
+                my $owner = $attrib_ref->{svn}->get_username($root);
+                $patterns_at{$level} = ['^' . $owner . '$'];
+            }
+        }
+        my $url0 = $url_project;
+        if ($layout_config{'dir-branch'}) {
+            $url0 .= '/' . $layout_config{'dir-branch'};
+        }
+        else {
+            for my $key (qw{trunk tag}) {
+                if ($layout_config{"dir-$key"}) {
+                    $patterns_at{1} ||= [];
+                    push(
+                        @{$patterns_at{1}},
+                        '^(?!' . $layout_config{"dir-$key"} .  '$)',
+                    );
+                }
+            }
+        }
+        my @branches = $attrib_ref->{svn}->get_list(
+            $url0 . '@' . $rev,
+            sub {
+                my ($this_url, $this_name, $is_dir, $depth) = @_;
+                if (    exists($patterns_at{$depth})
+                    &&  !grep {$this_name =~ /$_/} @{$patterns_at{$depth}}
+                ) {
+                    return (0, 0);
+                }
+                my $can_return = $depth >= $layout_config{'depth-branch'};
+                ($can_return, ($is_dir && !$can_return));
+            },
+        );
+        if ($option_ref->{'url'}) {
+            $UTIL->event(
+                FCM::Context::Event->CM_BRANCH_LIST,
+                $url_project . '@' . $rev, @branches,
+            );
+        }
+        else {
+            $UTIL->event(
+                FCM::Context::Event->CM_BRANCH_LIST,
+                map {$UTIL->loc_as_keyword(FCM::Context::Locator->new($_))}
+                    ($url_project . '@' . $rev, @branches),
+            );
+        }
+    }
+}
+
+# Wraps "svn checkout".
+sub _cm_checkout {
+    my ($attrib_ref, $option_ref, @args) = @_;
+    _parse_args($attrib_ref, $option_ref, \@args);
+    my $target = @args && !$attrib_ref->{util}->uri_match($args[-1])
+        ? $args[-1] : cwd();
+    my $info_entry = eval {$attrib_ref->{svn}->get_info($target)->[0]};
+    if ($@) {
+        $@ = undef; # OK, not a working copy
+    }
+    elsif (grep {index($_, 'wc-info:') == 0} keys(%{$info_entry})) {
+        return $E->throw($E->CM_CHECKOUT, [$target, $info_entry->{url}]);
+    }
+    $attrib_ref->{svn}->call('checkout', @args);
+}
+
+# Wraps "svn diff".
+sub _cm_diff {
+    my ($attrib_ref, $option_ref, @args) = @_;
+    _parse_args($attrib_ref, $option_ref, \@args);
+    local(%ENV) = %ENV;
+    $ENV{FCM_GRAPHIC_DIFF}
+        ||= $attrib_ref->{util}->external_cfg_get('graphic-diff');
+    $attrib_ref->{svn}->call('diff', @args);
+}
+
+# Parse and print layout information of each target in @args.
+sub _cm_loc_layout {
+    my ($attrib_ref, $option_ref, @args) = @_;
+    _parse_args($attrib_ref, $option_ref, \@args);
+    if (!@args) {
+        @args = qw{.};
+    }
+    my $OUT = sub {
+        $attrib_ref->{util}->event(FCM::Context::Event->OUT, @_);
+    };
+    my $not_first;
+    for my $arg (@args) {
+        if ($not_first) {
+            $OUT->("\n");
+        }
+        $not_first = 1;
+        $OUT->("target: $arg\n");
+        my $layout = $attrib_ref->{svn}->get_layout($arg);
+        $OUT->($layout->as_string());
+    }
+}
+
+# Create a new project in a repository.
+sub _cm_project_create {
+    my ($attrib_ref, $option_ref, @args) = @_;
+    _parse_args($attrib_ref, $option_ref, \@args);
+    my ($name, $root_arg) = @args;
+    # Check project name
+    if (!$name || $name !~ qr{\A[\w\.\-/]+\z}msx) {
+        return $E->throw($E->CM_PROJECT_NAME, $name);
+    }
+    # Check root
+    if (!$root_arg) {
+        return $E->throw($E->CM_REPOSITORY, q{});
+    }
+    my $layout = $attrib_ref->{svn}->get_layout($root_arg);
+    my $root = $layout->get_root();
+    if (!$root) {
+        return $E->throw($E->CM_REPOSITORY, $root_arg);
+    }
+
+    # Check whether the depth of the project name is valid
+    my %layout_config = %{$layout->get_config()};
+    my @names = split(qr{/+}msx, $name);
+    my $depth_expected = $layout_config{'depth-project'};
+    if (defined($depth_expected) && $depth_expected != scalar(@names)) {
+        return $E->throw($E->CM_PROJECT_NAME, join('/', @names));
+    }
+    # Check whether the project (trunk) already exists
+    my $target = join('/', $root, @names, $layout_config{'dir-trunk'});
+    my $target_url = eval {$attrib_ref->{svn}->get_info($target)->[0]->{url}};
+    $@ = undef;
+    if ($target_url) {
+        return $E->throw($E->CM_ALREADY_EXIST, $target_url);
+    }
+
+    # Message for the commit log
+    my @message = sprintf("%s: new project.\n", join('/', @names));
+
+    # Create a temporary file for the commit log message
+    my $commit_message_ctx = $attrib_ref->{commit_message_util}->ctx();
+    $commit_message_ctx->set_auto_part(join(q{}, @message));
+    $commit_message_ctx->set_info_part(sprintf("%s    %s\n", 'A', $target));
+    if (!$option_ref->{'non-interactive'}) {
+        $attrib_ref->{commit_message_util}->edit($commit_message_ctx);
+    }
+    $attrib_ref->{commit_message_util}->notify($commit_message_ctx);
+    my $temp_handle
+        = $attrib_ref->{commit_message_util}->temp($commit_message_ctx);
+
+    # Check with the user to see if he/she wants to go ahead
+    if (    !$option_ref->{'non-interactive'}
+        &&  !$attrib_ref->{prompt}->question('PROJECT_CREATE')
+    ) {
+        return;
+    }
+
+    # Create the branch
+    $attrib_ref->{svn}->call(
+        'mkdir',
+        '--file', $temp_handle->filename(),
+        '--parents',
+        ($option_ref->{'svn-non-interactive'} ? '--non-interactive' : ()),
+        (   defined($option_ref->{'password'})
+            ? ('--password', $option_ref->{'password'}) : ()
+        ),
+        $target,
+    );
+    $attrib_ref->{util}->event(FCM::Context::Event->CM_CREATE_TARGET, $target);
+
+    $target;
+}
+
+# Returns a simple wrapper to FCM 1 FCM1::Cm functions.
+sub _fcm1_func {
+    my ($action_ref, $opt_mod_ref) = @_;
+    $opt_mod_ref ||= sub {};
+    sub {
+        my ($attrib_ref, $option_ref, @args) = @_;
+        _parse_args($attrib_ref, $option_ref, \@args);
+        local(@ARGV) = @args;
+        $opt_mod_ref->($option_ref);
+        eval {$action_ref->($option_ref, @args)};
+        if ($@) {
+            if (!FCM1::Cm::Abort->caught($@)) {
+                die($@);
+            }
+            if (!($@->get_code() eq $@->NULL || $@->get_code() eq $@->USER)) {
+                die($@);
+            }
+            $attrib_ref->{util}->event(
+                FCM::Context::Event->CM_ABORT, lc($@->get_code()),
+            );
+            $@ = undef;
+        }
+        return;
+    };
+}
+
+# Generate an option modifier to st_check_handler.
+sub _opt_mod_st_check_handler_func {
+    my $key = shift();
+    sub {
+        my $option_ref = shift();
+        if (!$option_ref->{'non-interactive'}) {
+            $option_ref->{st_check_handler} = $FCM1::Cm::CLI_HANDLER_OF{$key};
+        }
+    };
+}
+
+# Expands keywords in arguments.
+sub _parse_args {
+    my ($attrib_ref, $option_ref, $args_ref) = @_;
+    # Location keywords
+    my $UTIL = $attrib_ref->{util};
+    my $url;
+    for my $arg (@{$args_ref}) {
+        eval {
+            my $locator = FCM::Context::Locator->new($arg);
+            if ($UTIL->loc_what_type($locator) eq 'svn') {
+                my $new_arg = $UTIL->loc_as_normalised($locator);
+                my $SVN = $attrib_ref->{svn};
+                my ($new_arg_url, $new_arg_rev) = $SVN->split_by_peg($new_arg);
+                my (    $arg_url,     $arg_rev) = $SVN->split_by_peg($arg);
+                if (index($arg_url, $UTIL->loc_kw_prefix() . ':') == 0) {
+                    $arg_url = $new_arg_url;
+                }
+                if ($arg_rev && $new_arg_rev && $arg_rev ne $new_arg_rev) {
+                    $arg_rev = $new_arg_rev;
+                }
+                $arg = $arg_url . ($arg_rev ? '@' . $arg_rev : q{});
+                $url ||= $new_arg_url;
+            }
+        };
+        if (my $e = $@) {
+            if (    !FCM::Util::Exception->caught($e)
+                ||  index($e->get_code(), 'LOCATOR_') != 0
+            ) {
+                die($e);
+            }
+            $@ = undef;
+        }
+    }
+    # Revision keywords
+    $url ||= cwd();
+    my $in_opt_rev;
+    for my $arg (@{$args_ref}) {
+        my ($opt, $opt_arg);
+        if ($in_opt_rev) {
+            $in_opt_rev = 0;
+            ($opt, $opt_arg) = (q{}, $arg);
+        }
+        elsif (grep {$_ eq $arg} qw{-c --change -r --revision}) {
+            $in_opt_rev = 1;
+        }
+        else {
+            ($opt, $opt_arg)
+                = $arg =~ qr{\A(-[cr]|--(?:change|revision)=)(.*)\z}msx;
+        }
+        if ($opt_arg) {
+            $arg = $opt . _parse_args_rev($attrib_ref, $url, $opt_arg);
+        }
+    }
+    for my $key (grep {exists($option_ref->{$_})} qw{change revision}) {
+        $option_ref->{$key}
+            = _parse_args_rev($attrib_ref, $url, $option_ref->{$key});
+    }
+}
+
+# Expands revision keywords in an argument.
+sub _parse_args_rev {
+    my ($attrib_ref, $url, $arg) = @_;
+    my $UTIL = $attrib_ref->{util};
+    join(
+        ':',
+        map {
+            my $rev = $_;
+            my $locator = FCM::Context::Locator->new($url . '@' . $rev);
+            local($@);
+            my $value = eval{$UTIL->loc_as_normalised($locator)};
+            if ($value) {
+                (my $url, $rev) = $attrib_ref->{svn}->split_by_peg($value);
+            }
+            $rev;
+        } split(qr{:}msx, $arg, 2)
+    );
+}
+
+# Invokes a system "svn" call.
+sub _svn {
+    my ($attrib_ref, $app, $option_ref, @args) = @_;
+    _parse_args($attrib_ref, $option_ref, \@args);
+    $attrib_ref->{svn}->call($app, @args);
+}
+
+#-------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::CM
+
+=head1 SYNOPSIS
+
+    use FCM::System::CM;
+    my $system = FCM::System::CM->new(\%attrib);
+    my ($out, $err) = $system->svn({}, @args);
+
+=head1 DESCRIPTION
+
+The FCM code management sub-system. This is currently a thin adaptor of
+L<FCM1::Cm|FCM1::Cm>.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Returns a new instance. This class should normally be initialised by
+L<FCM::System|FCM::System>.
+
+=item $system->cm_branch_create(\%option, at args)
+
+Implement the C<fcm branch-create> command. On success, return the branch name
+created.
+
+=item $system->cm_branch_list(\%option, at args)
+
+Implement the C<fcm branch-list> command.
+
+=item $system->cm_checkout(\%option, at args)
+
+Thin wrapper of the C<svn checkout> command. Ensure checkout to clean location.
+
+=item $system->cm_diff(\%option, at args)
+
+Thin wrapper of the C<svn diff> command. Allow --graphical option.
+
+=item $system->cm_loc_layout(\%option, at args)
+
+Implement the C<fcm loc-layout> command.
+
+=item $system->cm_project_create(\%option, at args)
+
+Implement the C<fcm project-create> command.
+
+=item $system->cm_branch_delete(\%option, at args)
+=item $system->cm_branch_info(\%option, at args)
+=item $system->cm_commit(\%option, at args)
+=item $system->cm_check_missing(\%option, at args)
+=item $system->cm_check_unknown(\%option, at args)
+=item $system->cm_merge(\%option, at args)
+=item $system->cm_mkpatch(\%option, at args)
+=item $system->cm_resolve_conflicts(\%option, at args)
+=item $system->cm_switch(\%option, at args)
+=item $system->cm_update(\%option, at args)
+
+Thin adaptors for the corresponding code management functions in
+L<FCM1::Cm|FCM1::Cm>.
+
+=item $system->svn($app,\%option, at args)
+
+Invokes a system call to L<svn|svn> $app with @args. %option is not currently
+used, but is left in the argument list for compatibility with the other methods.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/CM/CommitMessage.pm b/lib/FCM/System/CM/CommitMessage.pm
new file mode 100644
index 0000000..a09fbb4
--- /dev/null
+++ b/lib/FCM/System/CM/CommitMessage.pm
@@ -0,0 +1,312 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# Utility to manipulate FCM commit messages.
+package FCM::System::CM::CommitMessage;
+use base qw{FCM::Class::CODE};
+
+use Cwd qw{cwd};
+use FCM::Context::Event;
+use FCM::System::Exception;
+use File::Spec::Functions qw{catfile};
+use File::Temp;
+use Text::ParseWords qw{shellwords};
+
+my $CTX = 'FCM::System::CM::CommitMessage::State';
+my $E = 'FCM::System::Exception';
+
+our $COMMIT_MESSAGE_BASE = '#commit_message#';
+our $DELIMITER_USER
+    = '--Add your commit message ABOVE - do not alter this line or those below--'
+    . "\n";
+our $DELIMITER_AUTO
+    = '--FCM message (will be inserted automatically)--'
+    . "\n";
+our $DELIMITER_INFO
+    = '--Change summary (not part of commit message)--'
+    . "\n";
+our $EDITOR = 'vi';
+our $GEDITOR = 'gedit';
+our $SUBVERSION_CONFIG_FILE = catfile((getpwuid($<))[7], qw{.subversion/config});
+
+__PACKAGE__->class({gui => '$', util => '&'},
+    {action_of => {
+        'ctx'       => sub {$CTX->new()},
+        'edit'      => \&_edit,
+        'load'      => \&_load,
+        'notify'    => \&_notify,
+        'path'      => \&_path,
+        'path_base' => sub {$COMMIT_MESSAGE_BASE},
+        'save'      => \&_save,
+        'temp'      => \&_temp,
+    }},
+);
+
+# Invokes an editor to edit the commit message context.
+sub _edit {
+    my ($attrib_ref, $commit_message_ctx) = @_;
+    my $UTIL = $attrib_ref->{'util'};
+    my $temp = File::Temp->new();
+    if ($commit_message_ctx->get_user_part()) {
+        print($temp $commit_message_ctx->get_user_part());
+    }
+    else {
+        print($temp "\n");
+    }
+    print($temp $DELIMITER_USER);
+    if ($commit_message_ctx->get_auto_part()) {
+        print($temp $DELIMITER_AUTO . $commit_message_ctx->get_auto_part());
+    }
+    print($temp $DELIMITER_INFO . $commit_message_ctx->get_info_part());
+    close($temp) || die("$temp: $!\n");
+    my $config_value;
+    my $editor_command
+        = $ENV{'SVN_EDITOR'} ? $ENV{'SVN_EDITOR'}
+        : ($config_value = _svn_config_get($attrib_ref, 'helpers', 'editor-cmd'))
+                             ? $config_value
+        : $ENV{'VISUAL'}     ? $ENV{'VISUAL'}
+        : $ENV{'EDITOR'}     ? $ENV{'EDITOR'}
+        : $attrib_ref->{gui} ? $GEDITOR
+        :                      $EDITOR
+        ;
+    $UTIL->event(FCM::Context::Event->CM_LOG_EDIT, $editor_command);
+    my @command = (shellwords($editor_command), $temp->filename());
+    !system(@command)
+        || return $E->throw($E->SHELL, {command_list => \@command, rc => $?});
+    # Note: cannot use FCM::Util->shell method for terminal based editor.
+    #my %value_of = %{$attrib_ref->{'util'}->shell_simple(\@command)};
+    #if ($value_of{'rc'}) {
+    #    return $E->throw($E->SHELL, {command_list => \@command, %value_of});
+    #}
+    my $user_part = _parse(
+        $attrib_ref,
+        scalar($UTIL->file_load($temp->filename())),
+        $DELIMITER_USER,
+    );
+    $commit_message_ctx->set_user_part($user_part);
+    if (($user_part . $commit_message_ctx->get_auto_part()) =~ qr{\A\s*\z}msx) {
+        return $E->throw($E->CM_LOG_EDIT_NULL);
+    }
+}
+
+# Reads a commit message file from $path or the standard location. Returns a
+# commit message context object.
+sub _load {
+    my ($attrib_ref, $path) = @_;
+    $path ||= _path($attrib_ref);
+    my ($user_part, $auto_part) = eval {
+        _parse($attrib_ref, scalar($attrib_ref->{'util'}->file_load($path)));
+    };
+    if (my $e = $@) {
+        $user_part = q{};
+        $auto_part = q{};
+        $@ = undef; # TODO: should raise a high verbosity event?
+    }
+    $CTX->new({'user_part' => $user_part, 'auto_part' => $auto_part});
+}
+
+# Raises an CM_COMMIT_MESSAGE event for the commit message.
+sub _notify {
+    my ($attrib_ref, $commit_message_ctx) = @_;
+    $attrib_ref->{util}->event(
+        FCM::Context::Event->CM_COMMIT_MESSAGE, $commit_message_ctx,
+    );
+}
+
+# Parses a commit message into the user and auto parts. Returns the user part in
+# scalar context. Returns (user_part, auto_part) in list context.
+sub _parse {
+    my ($attrib_ref, $message, $no_delimiter_user) = @_;
+    my @parts = (q{}, q{});
+    my $state = 0;
+    LINE:
+    for my $line (split("\n", $message)) {
+        if ($state && !wantarray()) {
+            last LINE;
+        }
+        $line .= "\n";
+        if ($line eq $DELIMITER_INFO) {
+            last LINE;
+        }
+        elsif ($line eq $DELIMITER_AUTO) {
+            $state = 1;
+            next LINE;
+        }
+        elsif ($line eq $DELIMITER_USER) {
+            $no_delimiter_user = undef;
+            $state = -1;
+            next LINE;
+        }
+        if ($state >= 0) {
+            $parts[$state] .= $line;
+        }
+    }
+    if ($no_delimiter_user) {
+        return $E->throw($E->CM_LOG_EDIT_DELIMITER, $DELIMITER_USER);
+    }
+    for my $part (@parts) {
+        $part =~ s{\A\s*(.*?)\s*\z}{$1}msx;
+        if ($part) {
+            $part .= "\n";
+        }
+    }
+    wantarray() ? @parts : $parts[0];
+}
+
+# Returns the path to the commit message file in the current working directory
+# or the commit message file in $dir if $dir is set.
+sub _path {
+    my ($attrib_ref, $dir) = @_;
+    catfile(($dir ? $dir : cwd()), $COMMIT_MESSAGE_BASE);
+}
+
+# Saves the commit message to $path or the standard location for later
+# retrieval.
+sub _save {
+    my ($attrib_ref, $commit_message_ctx, $path) = @_;
+    $path ||= _path($attrib_ref);
+    my $string = $commit_message_ctx->get_user_part();
+    if ($commit_message_ctx->get_auto_part()) {
+        $string .= $DELIMITER_AUTO . $commit_message_ctx->get_auto_part();
+    }
+    $attrib_ref->{'util'}->file_save($path, $string);
+}
+
+# Returns a File::Temp object containing a commit message ready for the VCS.
+sub _temp {
+    my ($attrib_ref, $commit_message_ctx) = @_;
+    my $temp = File::Temp->new();
+    print($temp $commit_message_ctx->get_user_part());
+    print($temp $commit_message_ctx->get_auto_part());
+    close($temp) || die("$temp: $!\n");
+    $temp;
+}
+
+# Loads a setting from $HOME/.subversion/config, and returns its value.
+sub _svn_config_get {
+    my ($attrib_ref, $section, $key) = @_;
+    # Note: can use Config::IniFiles, but best to avoid another dependency.
+    # Note: not very efficient logic here, but should not yet matter.
+    my $handle = $attrib_ref->{'util'}->file_load_handle($SUBVERSION_CONFIG_FILE);
+    my $is_in_section;
+    my $value;
+    LINE:
+    while (my $line = readline($handle)) {
+        chomp($line);
+        if ($line =~ qr{\A\s*(?:[#;]|\z)}msx) {
+            next LINE;
+        }
+        if ($line =~ qr{\A\s*\[\s*$section\s*\]\s*\z}msx) {
+            $is_in_section = 1;
+        }
+        elsif ($line =~ qr{\A\s*\[}msx) {
+            $is_in_section = 0;
+        }
+        elsif ($is_in_section) {
+            my ($rhs) = $line =~ qr{\A\s*$key\s*=\s*(.*)\z}msx;
+            if (defined($rhs)) {
+                $value = $rhs;
+            }
+        }
+    }
+    close($handle);
+    $value;
+}
+
+#-------------------------------------------------------------------------------
+package FCM::System::CM::CommitMessage::State;
+use base qw{FCM::Class::HASH};
+
+__PACKAGE__->class({
+    (map {($_ . '_part' => {isa => '$', default => q{}})} qw{auto info user}),
+});
+
+#-------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::CM::CommitMessage
+
+=head1 SYNOPSIS
+
+    use FCM::System::CM::CommitMessage;
+    my $commit_message_util = FCM::System::CM::CommitMessage->new(\%attrib);
+    my $commit_message_ctx = $commit_message_util->ctx();
+    $commit_message_util->edit($ctx);
+
+=head1 DESCRIPTION
+
+The commit message dumper, editor, loader, parser, etc for the FCM code
+management sub-system.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Return a new instance. This class should normally be initialised by
+L<FCM::System::CM|FCM::System::CM>.
+
+=item $commit_message_util->ctx()
+
+Return a new and empty commit message context.
+
+=item $commit_message_util->edit($commit_message_ctx)
+
+Invoke an editor to edit the commit message context.
+
+=item $commit_message_util->load($path)
+
+Load the content of a commit message file in $path, and return the result in a
+new commit message context.
+
+=item $commit_message_util->notify($commit_message_ctx)
+
+Raise a CM_COMMIT_MESSAGE event with the $commit_message_ctx.
+
+=item $commit_message_util->path($dir)
+
+Return the path to the commit message file in $dir or the current working
+directory if $dir is not specified.
+
+=item $commit_message_util->path($dir)
+
+Return the base name of the commit message file.
+
+=item $commit_message_util->save($commit_message_ctx, $path)
+
+Save the commit message to $path (or the standard location if $path is not
+specified).
+
+=item $commit_message_util->temp()
+
+Return a File::Temp object containing a commit message ready for the VCS.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/CM/Prompt.pm b/lib/FCM/System/CM/Prompt.pm
new file mode 100644
index 0000000..1217c4f
--- /dev/null
+++ b/lib/FCM/System/CM/Prompt.pm
@@ -0,0 +1,221 @@
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+#-------------------------------------------------------------------------------
+package FCM::System::CM::Prompt;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::Event;
+
+our $TYPE_YN = 'TYPE_YN';
+
+# Format string table
+my %S = (
+    'BRANCH_CREATE'     => 'Create the branch?',
+    'OVERWRITE'         => '%s: file exists, overwrite?',
+    'PROJECT_CREATE'    => 'Create the project?',
+    'RESOLVE'           => 'Run "svn resolve --accept working %s"?',
+    'TC'                => "Locally: %s.\n"
+                           . "Externally: %s.\n"
+                           . "Answer (y) to %s.\n"
+                           . "Answer (n) to %s.\n"
+                           . '%s'
+                           . 'Keep the local version?',
+    'TC_ACTION'         => 'accept the %s %s',
+    'TC_ACTION_ADD'     => 'keep the %s file filename',
+    'TC_ACTION_EDIT'    => 'keep the file',
+    'TC_FROM_LOC'       => 'local',
+    'TC_FROM_INC'       => 'external',
+    'TC_MERGE'          => "You can then merge in changes.\n",
+    'TC_ST_ADD'         => 'added',
+    'TC_ST_DELETE'      => 'deleted',
+    'TC_ST_EDIT'        => 'edited',
+    'TC_ST_RENAME'      => 'renamed to %s',
+);
+
+# Configuration for questions
+# KEY => {'format' => $|&, 'type' => $}
+my %Q_CONF = (
+    # Simple question prompts
+    (   map {($_ => {'format' => $S{$_}, 'type' => q{}})}
+            qw{BRANCH_CREATE OVERWRITE PROJECT_CREATE RESOLVE}
+    ),
+    # Tree conflicts prompts: TC_LxIy, for local x, incoming y
+    # where x and y correspond to:
+    # A => add,
+    # D => delete,
+    # E => edit,
+    # M => missing,
+    # R => rename
+    (   map {('TC_' . $_ => {'format' => \&_q_tree_conflict, 'type' => $TYPE_YN})}
+            qw(LAIA LDID LDIE LDIR LEID LEIR LRID LRIE LRIR)
+    ),
+);
+
+__PACKAGE__->class(
+    {gui => '$', util => '&'},
+    {init => \&_init, action_of => {question => \&_q}},
+);
+
+sub _init {
+    my $attrib_ref = shift();
+    my $class = $attrib_ref->{gui}
+        ? 'FCM::System::CM::Prompt::Zenity' : 'FCM::System::CM::Prompt::Simple';
+    $attrib_ref->{impl} = $class->new({util => $attrib_ref->{util}});
+}
+
+sub _q {
+    my ($attrib_ref, $key, @args) = @_;
+    my $format = $Q_CONF{$key}{'format'};
+    my $prompt = ref($format) ? $format->(@args) : sprintf($format, @args);
+    $attrib_ref->{'impl'}->question($Q_CONF{$key}{'type'}, $prompt);
+}
+
+# Tree conflict prompt.
+# $tree_key is the FCM::System::CM::TreeConflictKey for the conflict.
+# $rename_loc is the new local name for the conflict file, if any.
+# $rename_inc is the new incoming name for the conflict file, if any.
+sub _q_tree_conflict {
+    my ($tree_key, $rename_loc, $rename_inc) = @_;
+    my %opt_of = (
+        'loc' => {'key' => $tree_key->get_local()   , 'rename' => $rename_loc},
+        'inc' => {'key' => $tree_key->get_incoming(), 'rename' => $rename_inc},
+    );
+    sprintf($S{'TC'}, (
+        (   map {
+                my $opt = $_;
+                my $message = $S{'TC_ST_' . uc($opt->{'key'})};
+                if ($opt->{'key'} eq 'rename') {
+                    $message = sprintf($message, $opt->{'rename'});
+                }
+                $message;
+            }
+            @opt_of{'loc', 'inc'}
+        ),
+        (   map {
+                my $location_key = $_;
+                my $from = $S{'TC_FROM_' . uc($location_key)};
+                my $key = $opt_of{$location_key}->{'key'};
+                  $key eq 'add'     ? sprintf($S{'TC_ACTION_ADD'}, $from)
+                : $key eq 'edit'    ? $S{'TC_ACTION_EDIT'}
+                :                     sprintf($S{'TC_ACTION'}, $from, $key)
+                ;
+            }
+            ('loc', 'inc')
+        ),
+        (   (        (grep {$opt_of{'loc'}{'key'} eq $_} qw{rename edit})
+                &&   (grep {$opt_of{'inc'}{'key'} eq $_} qw{rename edit})
+            )
+            ? $S{'TC_MERGE'} : q{}
+        ),
+    ));
+}
+
+#-------------------------------------------------------------------------------
+package FCM::System::CM::Prompt::Simple;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::Event;
+
+our %SETTING_OF = (
+    q{}       => {'choices' => [qw{y n}], 'default' => 'n', 'positive' => 'y'},
+    'TYPE_YN' => {'choices' => [qw{y n}], 'default' => 'n', 'positive' => 'y'},
+);
+
+__PACKAGE__->class({util => '&'}, {action_of => {question => \&_question}});
+
+sub _question {
+    my ($attrib_ref, $type, $question) = @_;
+    my %setting = %{$SETTING_OF{$type}};
+    _prompt($attrib_ref, $question, $setting{'choices'}, $setting{'default'})
+        eq $setting{'positive'};
+}
+
+sub _prompt {
+    my ($attrib_ref, $question, $choices_ref, $default) = @_;
+    my ($tail, @heads) = reverse(@{$choices_ref});
+    my $prompt
+        = $question . "\n"
+        . sprintf('Enter "%s" or "%s"', join(q{, }, reverse(@heads)), $tail)
+        . sprintf(' (or just press <return> for "%s") ', $default);
+    my $answer;
+    while (!defined($answer)) {
+        $attrib_ref->{util}->event(FCM::Context::Event->OUT, $prompt);
+        $answer = readline(STDIN);
+        chomp($answer);
+        if (!$answer) {
+            $answer = $default;
+        }
+        if (!grep {$_ eq $answer} @{$choices_ref}) {
+            $answer = undef;
+        }
+    }
+    return $answer;
+}
+
+#-------------------------------------------------------------------------------
+package FCM::System::CM::Prompt::Zenity;
+use base qw{FCM::Class::CODE};
+
+our %OPTIONS_OF = (
+    q{}       => [],
+    'TYPE_YN' => ['--ok-label=_Yes', '--cancel-label=_No'],
+);
+
+__PACKAGE__->class({util => '&'}, {action_of => {question => \&_question}});
+
+sub _question {
+    my ($attrib_ref, $type, $question) = @_;
+    _zenity($attrib_ref, qw{--question --text}, $question, @{$OPTIONS_OF{$type}});
+}
+
+sub _zenity {
+    my ($attrib_ref, @args) = @_;
+    my @command = ('zenity', @args);
+    my %value_of = %{$attrib_ref->{util}->shell_simple(\@command)};
+    !$value_of{rc};
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::CM::Prompt
+
+=head1 SYNOPSIS
+
+    use FCM::System::CM::Prompt;
+    my $prompt = FCM::System::CM::Prompt->new(\%attrib);
+    if ($prompt->question($key, @args)) {
+        # do something
+    }
+
+=head1 DESCRIPTION
+
+Helper module for prompts in the FCM code management sub-system.
+See L<FCM::System::CM|FCM::System::CM> for detail.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/CM/ResolveConflicts.pm b/lib/FCM/System/CM/ResolveConflicts.pm
new file mode 100644
index 0000000..da9e96a
--- /dev/null
+++ b/lib/FCM/System/CM/ResolveConflicts.pm
@@ -0,0 +1,669 @@
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+#-------------------------------------------------------------------------------
+package FCM::System::CM::ResolveConflicts;
+use base qw{Exporter};
+our @EXPORT_OK = qw{_cm_resolve_conflicts};
+
+use Cwd qw{cwd};
+use FCM::Context::Event;
+use FCM::System::Exception;
+use File::Basename qw{basename dirname};
+use File::Copy qw{copy};
+use File::Spec::Functions qw{abs2rel catfile rel2abs};
+use File::Temp;
+
+# LxIy stands for local x, incoming y in the tree conflict description.
+# The letters of x and y correspond to:
+# A => add,
+# D => delete,
+# E => edit,
+# M => missing,
+# R => rename,
+# although the 'rename' has to be detected by our code below.
+
+our %TREE_CONFLICT_GET_GRAPHIC_SOURCES_FUNC_FOR = (
+    LEIR => \&_cm_tree_conflict_get_graphic_sources_for_leir,
+    LRIE => \&_cm_tree_conflict_get_graphic_sources_for_lrie,
+    LRIR => \&_cm_tree_conflict_get_graphic_sources_for_lrir,
+);
+
+# A tree conflict key must be present here for auto-resolving.
+our %TREE_CONFLICT_GET_FINAL_ACTIONS_FUNC_FOR = (
+    LAIA => \&_cm_tree_conflict_get_actions_for_laia,
+    LDID => sub {},
+    LDIE => \&_cm_tree_conflict_get_actions_for_ldie,
+    LDIR => \&_cm_tree_conflict_get_actions_for_ldir,
+    LEID => \&_cm_tree_conflict_get_actions_for_leid,
+    LEIR => \&_cm_tree_conflict_get_actions_for_leir,
+    LRID => \&_cm_tree_conflict_get_actions_for_lrid,
+    LRIE => \&_cm_tree_conflict_get_actions_for_lrie,
+    LRIR => \&_cm_tree_conflict_get_actions_for_lrir,
+);
+
+# Handle aliases for actions.
+our %TREE_CONFLICT_GET_UNALIAS_FOR = (
+    'obstruction' => 'add',
+    'missing' => 'delete',
+);
+
+# Number of renamed files that triggers a time warning.
+our $TREE_CONFLICT_WARN_FILES_THRESHOLD = 10;
+
+my $E = 'FCM::System::Exception';
+# Regular expressions
+my %RE = (
+    # determines if a file was copied, i.e. added with history from "svn status"
+    ST_COPIED => qr{^A..\+....(.*)}msx,
+);
+
+# Resolve conflicts.
+sub _cm_resolve_conflicts {
+    my ($attrib_ref, $option_ref, @args) = @_;
+    my $UTIL = $attrib_ref->{util};
+    my $pwd = cwd();
+    if (!@args) {
+        push(@args, '.');
+    }
+    for my $arg (@args) {
+        if (!-e $arg) {
+            die("$arg: $!\n");
+        }
+        chdir($attrib_ref->{svn}->get_wc_root($arg)) || die("$arg: $!\n");
+        my @command = qw{svn status};
+        my %value_of = %{$UTIL->shell_simple(\@command)};
+        if ($value_of{rc}) {
+            return $E->throw($E->SHELL, {command_list => \@command, %value_of});
+        }
+        my @status_lines = grep {$_} split("\n", $value_of{o});
+        local(%ENV) = %ENV;
+        $ENV{FCM_GRAPHIC_MERGE} ||= $UTIL->external_cfg_get('graphic-merge');
+        for my $path (map {($_ =~ qr{\AC.{6}\s(.*)\z}msx)} @status_lines) {
+            _cm_resolve_text_conflict(
+                $attrib_ref,
+                $option_ref,
+                $path,
+                @status_lines,
+            );
+        }
+        for my $path (map {($_ =~ qr{\A.{6}C\s(.*)\z}msx)} @status_lines) {
+            _cm_resolve_tree_conflict(
+                $attrib_ref,
+                $option_ref,
+                $path,
+                @status_lines,
+            );
+        }
+    }
+    chdir($pwd);
+}
+
+# Helper for _cm_resolve_conflicts, launch graphic merge tool.
+sub _cm_graphic_merge {
+    my $attrib_ref = shift();
+    my @command = ('fcm_graphic_merge', @_);
+    my $UTIL = $attrib_ref->{util};
+    my %value_of = %{$UTIL->shell_simple(\@command)};
+    # rc==0: all conflicts resovled
+    # rc==1: some conflicts not resolved
+    # rc==2: trouble
+    if (!grep {$_ eq $value_of{rc}} (0, 1)) {
+        return $E->throw(
+            $E->SHELL, {command_list => \@command, %value_of}, $value_of{e},
+        );
+    }
+    $UTIL->event(FCM::Context::Event->OUT, $value_of{o});
+    $value_of{rc};
+}
+
+# Resolve a text conflict.
+sub _cm_resolve_text_conflict {
+    my ($attrib_ref, $option_ref, $path) = @_;
+    my $PROMPT = $attrib_ref->{prompt};
+    my $UTIL = $attrib_ref->{util};
+    if (-B $path) {
+        $UTIL->event(FCM::Context::Event->CM_CONFLICT_TEXT_SKIP, $path);
+        return;
+    }
+    $UTIL->event(FCM::Context::Event->CM_CONFLICT_TEXT, $path);
+
+    # Get conflicts markers files
+    my %info = %{$attrib_ref->{svn}->get_info($path)->[0]};
+    my @keys = map {"conflict:$_-file"} qw{prev-wc prev-base cur-base};
+    # Subversion 1.6: conflict filenames are relative paths.
+    # Subversion 1.8: conflict filenames are absolute paths.
+    my ($mine, $older, $yours) = map {
+        rel2abs($_, rel2abs(dirname($path)))
+    } @info{@keys};
+
+    # If $path is newer (by more than a second), it may contain saved changes.
+    if (    -f $path && (stat($path))[9] > (stat($mine))[9] + 1
+        &&  !$PROMPT->question('OVERWRITE', $path)
+    ) {
+        return;
+    }
+
+    # Launch graphic merge tool
+    if (_cm_graphic_merge($attrib_ref, $path, $mine, $older, $yours)) {
+        return; # rc==1, some conflicts not resolved
+    }
+
+    # Prompt user to run "svn resolve --accept working" on the file
+    if ($PROMPT->question('RESOLVE', $path)) {
+        $attrib_ref->{svn}->call(qw{resolve --accept working}, $path);
+    }
+}
+
+# Resolve a tree conflict.
+sub _cm_resolve_tree_conflict {
+    my ($attrib_ref, $option_ref, $path, @status_lines) = @_;
+    my $PROMPT = $attrib_ref->{prompt};
+    my $UTIL = $attrib_ref->{util};
+
+    # Skip directories - too complex for now.
+    if (-d $path) {
+        $UTIL->event(FCM::Context::Event->CM_CONFLICT_TREE_SKIP, $path);
+        return;
+    }
+
+    # Get basic information about the tree conflict, and the filename.
+    my %info = %{$attrib_ref->{svn}->get_info($path)->[0]};
+
+    # Skip non-existent or unhandled tree conflicts.
+    if (!exists($info{'tree-conflict:operation'})) {
+        return
+    }
+    if ($info{'tree-conflict:operation'} ne 'merge') {
+        $UTIL->event(FCM::Context::Event->CM_CONFLICT_TREE_SKIP, $path);
+        return;
+    }
+    
+    my $tree_reason = $info{'tree-conflict:reason'};
+    if (grep {$tree_reason eq $_} keys(%TREE_CONFLICT_GET_UNALIAS_FOR)) {
+        $tree_reason = $TREE_CONFLICT_GET_UNALIAS_FOR{$tree_reason};
+    }
+    my $tree_key = FCM::System::CM::TreeConflictKey->new(
+        {   'local'    => $tree_reason,
+            'incoming' => $info{'tree-conflict:action'},
+            'type'     => $info{'tree-conflict:operation'},
+        },
+    );
+    my $tree_filename = $info{'path'};
+
+    my %wc_info = %{$attrib_ref->{svn}->get_info()->[0]};
+
+    my $repos_root = $wc_info{'repository:root'};
+    my $wc_branch = substr($wc_info{'url'}, length($repos_root) + 1);
+
+    my $tree_full_filename = '/' . $wc_branch . '/' . $tree_filename;
+
+    # Check for external renaming, by examining files added with history
+    my $ext_renamed_file = '';
+    my $ext_branch = '';
+    COPIED_FILE:
+    for my $copied_file (map {($_ =~ $RE{ST_COPIED})} @status_lines) {
+        my %copy_info = %{$attrib_ref->{svn}->get_info($copied_file)->[0]};
+        my $url = (
+              $copy_info{'wc-info:copy-from-url'}
+            . '@' . $copy_info{'wc-info:copy-from-rev'}
+        );
+        my $copy_log_ref = $attrib_ref->{svn}->get_log($url);
+        if (!$ext_branch) {
+            my $copy_full_path = substr(
+                $copy_info{'wc-info:copy-from-url'},
+                length($repos_root) + 1,
+            );
+            $ext_branch = substr(
+                $copy_full_path,
+                0,
+                -length($copied_file) - 1,
+            );
+        }
+        my $copied_full_filename = '/' . $ext_branch . '/' . $copied_file;
+        my $tree_ext_name
+            = ('/' . $info{'tree-conflict:source-right:path-in-repos'});
+        my $search_name = $tree_ext_name;
+        for my $log_entry_ref (reverse(@{$copy_log_ref})) {
+            for my $path_entry (@{$log_entry_ref->{'paths'}}) {
+                if (    exists $path_entry->{'copyfrom-path'}
+                    &&  $path_entry->{'copyfrom-path'} eq $search_name
+                ) {
+                    $search_name = $path_entry->{'path'};
+                    if ($search_name eq $copied_full_filename) {
+                        $ext_renamed_file = $copied_file;
+                        last COPIED_FILE;
+                    }
+                }
+            }
+        }
+    }
+
+    # Check for local renaming of the tree conflict file
+    my $local_renamed_file = '';
+    if ($tree_reason eq 'delete') {
+        $local_renamed_file = _cm_tree_conflict_get_local_rename(
+            $attrib_ref,
+            $wc_branch,
+            $tree_full_filename,
+            $ext_renamed_file
+        );
+    }
+
+    # The tree conflict identifier (tree_key) needs to be adjusted for reality
+    if ($local_renamed_file) {
+        $tree_key->set_local('rename');
+    }
+    if ($ext_renamed_file) {
+        $tree_key->set_incoming('rename');
+    }
+
+    # Skip and return if the tree key does not match a key in final cmds.
+    my $cmds_getter
+        = $TREE_CONFLICT_GET_FINAL_ACTIONS_FUNC_FOR{$tree_key->as_string()};
+    if (!$cmds_getter) {
+        $UTIL->event(FCM::Context::Event->CM_CONFLICT_TREE_SKIP, $path);
+        return;
+    }
+
+    # Print the tree conflict event message
+    $UTIL->event(FCM::Context::Event->CM_CONFLICT_TREE, $path);
+
+    # These are the relevant files for this tree conflict.
+    my @file_args = grep {$_} ($path, $local_renamed_file, $ext_renamed_file);
+
+    # Prompt which version of events to accept - local or incoming.
+    my $keep_local = $PROMPT->question(
+        'TC_' . $tree_key->as_string(),
+        $tree_key, $local_renamed_file, $ext_renamed_file,
+    );
+
+    # Add any graphic merge commands
+    my @cmds = _cm_tree_conflict_get_graphic_cmds(
+        $attrib_ref, $tree_key->as_string(), $keep_local, \@file_args,
+    );
+    # Now load any miscellaneous actions or commands - for example 'svn delete'
+    if ($tree_key->get_local() eq 'add' && $tree_key->get_incoming() eq 'add') {
+        # We need to generate a new filename and a temporary one in this case.
+        @file_args = ($path);
+        my $tree_dir = dirname($path);
+        my $newfile_handle = File::Temp->new(
+            DIR => $tree_dir,
+            TEMPLATE => basename($path) . '.XXXX',
+            UNLINK => 0,
+        );
+        unlink("$newfile_handle");  # Delete it, or it will block the copy.
+        push(@file_args, "$newfile_handle");
+    }
+    push(@cmds, $cmds_getter->($attrib_ref, $keep_local, \@file_args));
+
+    # Run the actions, including any subroutine references.
+    for my $cmd_ref (@cmds) {
+        $cmd_ref->();
+    }
+    # svn resolve.
+    $attrib_ref->{svn}->call(qw{resolve --accept working}, $path);
+}
+
+# Tree conflicts: check if a file was renamed locally.
+sub _cm_tree_conflict_get_local_rename {
+    my ($attrib_ref, $wc_branch, $tree_full_filename, $ext_renamed_file) = @_;
+    my $UTIL = $attrib_ref->{util};
+
+    # Get the verbose log for the working copy.
+    # Find the revision where the file was deleted, and store any copied
+    # filenames at that revision, and since that revision.
+    my ($d_rev, @rev_copied_filenames, $found_delete);
+    my @since_copied_filenames;
+    my $wc_log_ref = $attrib_ref->{svn}->get_log();
+    ENTRY:
+    for my $log_entry_ref (@{$wc_log_ref}) {
+        $d_rev = $log_entry_ref->{'revision'};
+        @rev_copied_filenames = ();
+        for my $path_entry (@{$log_entry_ref->{'paths'}}) {
+            if ($path_entry->{'copyfrom-path'}) {
+                push(@rev_copied_filenames, $path_entry->{'path'});
+                push(@since_copied_filenames, $path_entry->{'path'});
+            }
+            if (    $path_entry->{'path'} eq $tree_full_filename
+                &&  $path_entry->{'action'} eq 'D'
+            ) {
+                $found_delete = 1;
+            }
+        }
+        if ($found_delete) {
+            last ENTRY;
+        }
+    }
+
+    # We need to detect a copy and a deletion of the file to continue.
+    if (!$found_delete || !@rev_copied_filenames) {
+        return;
+    }
+
+    # Get rid of the branch name in our copied files
+    for my $copied_file (@rev_copied_filenames) {
+        $copied_file =~ s{^/$wc_branch/}{}msx;
+    }
+    for my $copied_file (@since_copied_filenames) {
+        $copied_file =~ s{^/$wc_branch/}{}msx;
+    }
+
+    # Get the pre-existing working copy files.
+    my @wc_files
+        = grep {$_ && $_ =~ qr{[^/]$}msx}
+            $attrib_ref->{svn}->stdout(qw{svn ls -R});
+
+    # Examine the files added with history at the revision log.
+    # A single rename will be detected here.
+    my @result;
+    COPIED_AT_REV_FILE:
+    for my $file (@rev_copied_filenames) {
+        if (!grep {$_ eq $file} @wc_files) {
+            next COPIED_AT_REV_FILE;
+        }
+        my @log_entry_refs
+            = @{$attrib_ref->{svn}->get_log($file . '@' . $d_rev)};
+        my $search_name = $tree_full_filename;
+        my $full_potential_rename = '/' . $wc_branch . '/' . $file;
+        for my $log_entry_ref (@log_entry_refs) {
+            for my $path_entry (@{$log_entry_ref->{'paths'}}) {
+                if (    exists($path_entry->{'copyfrom-path'})
+                    &&  $path_entry->{'copyfrom-path'} eq $tree_full_filename
+                    &&  $path_entry->{'action'} eq 'A'
+                    &&  $path_entry->{'path'} eq $full_potential_rename
+                ) {
+                    return $file;
+                }
+            }
+        }
+    }
+
+    # If no rename was detected, there may have been more than one.
+    # Get the logs for all current filenames that match copied filenames
+    # since the deletion, according to the working copy log.
+
+    # Warn if the number of files to be examined > the threshold.
+    if (    @wc_files > $TREE_CONFLICT_WARN_FILES_THRESHOLD
+        &&  @since_copied_filenames > $TREE_CONFLICT_WARN_FILES_THRESHOLD
+    ) {
+        my $tree_path = substr($tree_full_filename, length($wc_branch) + 2);
+        $UTIL->event(
+            FCM::Context::Event->CM_CONFLICT_TREE_TIME_WARN,
+            $tree_path,
+        );
+    }
+    WC_FILE:
+    for my $file (@wc_files) {
+        if (!grep {$_ eq $file} @since_copied_filenames) {
+            next WC_FILE;
+        }
+        my @log_entry_refs = @{$attrib_ref->{svn}->get_log($file)};
+        my $search_name = $tree_full_filename;
+        my $full_potential_rename = '/' . $wc_branch . '/' . $file;
+        for my $log_entry_ref (reverse(@log_entry_refs)) {
+            my $revision = $log_entry_ref->{'revision'};
+            for my $path_entry (@{$log_entry_ref->{'paths'}}) {
+                if (    exists($path_entry->{'copyfrom-path'})
+                    &&  $path_entry->{'copyfrom-path'} eq $search_name
+                    &&  $path_entry->{'action'} eq 'A'
+                ) {
+                    $search_name = $path_entry->{'path'};
+                    if (   $search_name eq $full_potential_rename
+                        && $revision >= $d_rev
+                    ) {
+                        return $file;
+                    }
+                }
+            }
+        }
+    }
+    return;
+}
+
+# Return the tree conflict command related to fcm_graphic_merge.
+sub _cm_tree_conflict_get_graphic_cmds {
+    my ($attrib_ref, $key, $keep_local, $files_ref) = @_;
+    my ($cfile, @rename_args) = @{$files_ref};
+    if (!@rename_args) {
+        return;
+    }
+    # Get the source argument subroutine reference, if it exists.
+    my $get_srcs_func_ref = $TREE_CONFLICT_GET_GRAPHIC_SOURCES_FUNC_FOR{$key};
+    if (!$get_srcs_func_ref) {
+        return;
+    }
+    # Get the sources for the graphic merge files.
+    my ($older_src, $merge_src, $working_src, $base)
+        = $get_srcs_func_ref->($cfile, $keep_local, \@rename_args);
+
+    # Set up the filenames.
+    my ($older_url, $older_peg)
+        = _cm_tree_conflict_source($attrib_ref, 'left', $older_src);
+    my $mine = $base . '.working';
+    my $older = $base . '.merge-left.r' . $older_peg;
+    my ($merge_url, $merge_peg)
+        = _cm_tree_conflict_source($attrib_ref, 'right', $merge_src);
+    my $yours = $base . '.merge-right.r' . $merge_peg;
+    # Set up the conflict files as in a text conflict.
+    sub {
+        $attrib_ref->{svn}->call(qw{export -q}, $older_url, $older);
+        $attrib_ref->{svn}->call(qw{export -q}, $merge_url, $yours);
+        copy($working_src, $mine);
+        _cm_graphic_merge($attrib_ref, $base, $mine, $older, $yours);
+        unlink($mine, $older, $yours);
+    };
+}
+
+# Return the source-left or source-right url from svn info.
+sub _cm_tree_conflict_source {
+    my ($attrib_ref, $direction, $info_filename) = @_;
+    my %info = %{$attrib_ref->{svn}->get_info($info_filename)->[0]};
+    my ($source_url, $source_peg);
+    if ($info{"tree-conflict:source-$direction:repos-url"}) {
+        $source_url
+            = $info{"tree-conflict:source-$direction:repos-url"}
+            . '/'
+            . $info{"tree-conflict:source-$direction:path-in-repos"};
+        $source_peg = $info{"tree-conflict:source-$direction:revision"};
+    }
+    elsif ($direction eq 'right') {
+        $source_url = $info{'wc-info:copy-from-url'};
+        $source_peg = $info{'wc-info:copy-from-rev'};
+    }
+    ($source_url . '@' . $source_peg, $source_peg);
+}
+
+# Select the files needed for the xxdiff (local edit, incoming rename)
+sub _cm_tree_conflict_get_graphic_sources_for_leir {
+    my ($cfile, $keep_local, $renames) = @_;
+    my $ext_rename = shift(@{$renames});
+    (   $cfile,
+        $ext_rename,
+        $cfile,
+        ($keep_local ? $cfile : $ext_rename),
+    );
+}
+
+# Select the files needed for the xxdiff (local rename, incoming edit)
+sub _cm_tree_conflict_get_graphic_sources_for_lrie {
+    my ($cfile, $keep_local, $renames) = @_;
+    my $local_rename = shift(@{$renames});
+    (   $cfile,
+        $cfile,
+        $local_rename,
+        $local_rename,
+    );
+}
+
+# Select the files needed for the xxdiff (local rename, incoming rename)
+sub _cm_tree_conflict_get_graphic_sources_for_lrir {
+    my ($cfile, $keep_local, $renames) = @_;
+    my ($local_rename, $ext_rename) = @{$renames};
+    (   $cfile,
+        $ext_rename,
+        $local_rename,
+        ($keep_local ? $local_rename : $ext_rename),
+    );
+}
+
+# Return the actions needed to resolve 'local add, incoming add'
+sub _cm_tree_conflict_get_actions_for_laia {
+    my ($attrib_ref, $keep_local, $files_ref) = @_;
+    my ($cfile, $new_name) = @{$files_ref};
+    my ($url, $url_peg) = _cm_tree_conflict_source($attrib_ref, 'right', $cfile);
+    my ($basename) = basename($cfile);
+    my $cdir = dirname($cfile);
+    sub {
+        if (!$keep_local) {
+            my $content = $attrib_ref->{svn}->stdout(qw{svn cat}, $url);
+            $attrib_ref->{util}->file_save($cfile, $content);
+        }
+    };
+}
+
+
+# Return the actions needed to resolve 'local missing, incoming edit'
+sub _cm_tree_conflict_get_actions_for_ldie {
+    my ($attrib_ref, $keep_local, $files_ref) = @_;
+    my ($cfile) = @{$files_ref};
+    my ($url, $url_peg) = _cm_tree_conflict_source($attrib_ref, 'right', $cfile);
+    my $cdir = dirname($cfile);
+    sub {
+        if (!$keep_local) {
+            $attrib_ref->{svn}->call('copy', $url, "$cdir/");
+        }
+    };
+}
+
+# Return the actions needed to resolve 'local delete, incoming rename'
+sub _cm_tree_conflict_get_actions_for_ldir {
+    my ($attrib_ref, $keep_local, $files_ref) = @_;
+    my ($cfile, $ext_rename) = @{$files_ref};
+    sub {
+        if ($keep_local) {
+            $attrib_ref->{svn}->call('revert', $ext_rename);
+            unlink($ext_rename);
+        }
+    };
+}
+
+# Return the actions needed to resolve 'local edit, incoming delete'
+sub _cm_tree_conflict_get_actions_for_leid {
+    my ($attrib_ref, $keep_local, $files_ref) = @_;
+    my ($cfile) = @{$files_ref};
+    sub {
+        if (!$keep_local) {
+            $attrib_ref->{svn}->call('delete', $cfile);
+        }
+    };
+}
+
+# Return the actions needed to resolve 'local edit, incoming rename'
+sub _cm_tree_conflict_get_actions_for_leir {
+    my ($attrib_ref, $keep_local, $files_ref) = @_;
+    my ($cfile, $ext_rename) = @{$files_ref};
+    sub {
+        if ($keep_local) {
+            $attrib_ref->{svn}->call('revert', $ext_rename);
+            unlink($ext_rename);
+        }
+        else {
+            $attrib_ref->{svn}->call('delete', $cfile);
+        }
+    };
+}
+
+# Return the actions needed to resolve 'local rename, incoming delete'
+sub _cm_tree_conflict_get_actions_for_lrid {
+    my ($attrib_ref, $keep_local, $files_ref) = @_;
+    my ($cfile, $lcl_rename) = @{$files_ref};
+    sub {
+        if (!$keep_local) {
+            $attrib_ref->{svn}->call('delete', $lcl_rename);
+        }
+    };
+}
+
+# Return the actions needed to resolve 'local rename, incoming edit'
+sub _cm_tree_conflict_get_actions_for_lrie {
+    my ($attrib_ref, $keep_local, $files_ref) = @_;
+    my ($cfile, $lcl_rename) = @{$files_ref};
+    sub {
+        if (!$keep_local) {
+            $attrib_ref->{svn}->call('rename', $lcl_rename, $cfile)
+        }
+    };
+}
+
+# Return the actions needed to resolve 'local rename, incoming rename'
+sub _cm_tree_conflict_get_actions_for_lrir {
+    my ($attrib_ref, $keep_local, $files_ref) = @_;
+    my ($cfile, $lcl_rename, $ext_rename) = @{$files_ref};
+    sub {
+        if ($keep_local) {
+            $attrib_ref->{svn}->call('revert', $ext_rename);
+            unlink($ext_rename);
+        }
+        else {
+            $attrib_ref->{svn}->call('delete', $lcl_rename);
+        }
+    };
+}
+
+# -----------------------------------------------------------------------------
+# Stores the identifier of the type of tree conflict.
+package FCM::System::CM::TreeConflictKey;
+use base qw{FCM::Class::HASH};
+
+# Creates the class.
+# 'local' is the local change (e.g. edit or delete),
+# 'incoming' is the external change (e.g. add or rename),
+# 'type' is one of merge, switch, or update.
+__PACKAGE__->class({'local' => '$', 'incoming' => '$', 'type' => '$'});
+
+# Returns a label string of the form LXIY e.g. LEID for local edit,
+# incoming delete.
+sub as_string {
+    my ($self) = shift();
+    sprintf(
+        'L%sI%s',
+        uc(substr($self->get_local(), 0, 1)),
+        uc(substr($self->get_incoming(), 0, 1)),
+    );
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::CM::ResolveConflicts
+
+=head1 DESCRIPTION
+
+Part of L<FCM::System::CM|FCM::System::CM>.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/CM/SVN.pm b/lib/FCM/System/CM/SVN.pm
new file mode 100644
index 0000000..8e84e8f
--- /dev/null
+++ b/lib/FCM/System/CM/SVN.pm
@@ -0,0 +1,1003 @@
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+#-------------------------------------------------------------------------------
+package FCM::System::CM::SVN;
+use base qw{FCM::Class::CODE};
+
+use Cwd qw{cwd};
+use FCM::Context::Event;
+use FCM::Context::Locator;
+use FCM::System::Exception;
+use Memoize qw{memoize};
+use File::Basename qw{dirname};
+use File::Spec::Functions qw{catfile rel2abs};
+use Time::Piece;
+use XML::Parser;
+
+my $E = 'FCM::System::Exception';
+
+# Settings for the default repository layout
+our %LAYOUT_CONFIG = (
+    'depth-project' => undef,
+    'depth-branch' => 3,
+    'depth-tag' => 1,
+    'dir-trunk' => 'trunk',
+    'dir-branch' => 'branches',
+    'dir-tag' => 'tags',
+    'level-owner-branch' => 2,
+    'level-owner-tag' => undef,
+    'template-branch' => '{category}/{owner}/{name_prefix}{name}',
+    'template-tag' => undef,
+);
+
+# Layout configuration file basename
+our $LAYOUT_CFG_BASE = 'svn-repos-layout.cfg';
+
+# "svn log --xml" handlers.
+# -> element node start tag handlers
+my %SVN_LOG_ELEMENT_START_HANDLER_FOR = (
+#   tag        => handler
+    'logentry' => \&_get_log_handle_element_enter_logentry,
+    'path'     => \&_get_log_handle_element_enter_path,
+);
+# -> text node (after a start tag) handlers
+my %SVN_LOG_TEXT_HANDLER_FOR = (
+#   tag    => handler
+    'date' => \&_get_log_handle_text_date,
+    'path' => \&_get_log_handle_text_path,
+);
+
+our $SUBVERSION_SERVERS_CONF = catfile((getpwuid($<))[7], qw{.subversion/servers});
+
+my %ACTION_OF = (
+    'call'               => \&_call,
+    'get_info'           => \&_get_info,
+    'get_layout'         => \&_get_layout,
+    'get_layout_common'  => \&_get_layout_common,
+    'get_list'           => \&_get_list,
+    'get_log'            => \&_get_log,
+    'get_username'       => \&_get_username,
+    'get_wc_root'        => \&_get_wc_root,
+    'load_layout_config' => \&_load_layout_config,
+    'split_by_peg'       => \&_split_by_peg,
+    'stdout'             => \&_stdout,
+);
+
+# Creates the class.
+__PACKAGE__->class(
+    {   layout_cfg_base => {isa => '$', default => $LAYOUT_CFG_BASE},
+        layout_config_of=> '%',
+        util            => '&',
+    },
+    {action_of => \%ACTION_OF},
+);
+
+# Calls "svn".
+sub _call {
+    my ($attrib_ref, @args) = @_;
+    my @command = ('svn', @args);
+    my $timer = $attrib_ref->{util}->timer();
+    my $rc = system(@command);
+    $attrib_ref->{util}->event(
+        FCM::Context::Event->SHELL, \@command, $rc, $timer->());
+    if ($rc) {
+        $rc = $? == -1 ? $!
+            : $? & 127 ? $? & 127
+            :            $? >> 8
+            ;
+        return $E->throw($E->SHELL, {command_list => \@command, rc => $rc});
+    }
+    return;
+}
+
+# Invokes "svn info --xml @paths", and returns a LIST of info entries.
+memoize('_get_info');
+sub _get_info {
+    my $attrib_ref = shift();
+    my %option = ('recursive' => undef, 'revision' => undef);
+    if (@_ && ref($_[0]) && ref($_[0]) eq 'HASH') {
+        %option = (%option, %{shift()});
+    }
+    my @paths = @_;
+    if (!@paths) {
+        @paths = (q{.});
+    }
+    my (@entries, @stack);
+    my $parser = XML::Parser->new(Handlers => {
+        'Start' => sub {_get_info_handle_element_enter(\@entries, \@stack, @_)},
+        'End'   => sub {_get_info_handle_element_leave(\@entries, \@stack, @_)},
+        'Char'  => sub {_get_info_handle_text(         \@entries, \@stack, @_)},
+    });
+    $parser->parse(scalar(_stdout(
+        $attrib_ref,
+        qw{svn info --xml},
+        ($option{'recursive'} ? '--recursive' : ()),
+        ($option{'revision'} ? ('--revision', $option{'revision'}) : ()),
+        @paths,
+    )));
+    \@entries;
+}
+
+# Helper for _get_info. Handle the start tag of an XML element.
+sub _get_info_handle_element_enter {
+    my ($entries_ref, $stack_ref, $expat, $tag, %attrib) = @_;
+    # "entry": create a new entry in the list
+    if ($tag eq 'entry') {
+        push(@{$entries_ref}, {});
+    }
+    # "tree-conflict:version": need to handle differently
+    if (    $tag eq 'version'
+        &&  @{$stack_ref}
+        &&  $stack_ref->[-1] eq 'tree-conflict'
+    ) {
+        my (undef, undef, @names) = @{$stack_ref};
+        push(@names, delete($attrib{side}));
+        while (my ($key, $value) = each(%attrib)) {
+            my $name = join(':', @names, $key);
+            $entries_ref->[-1]->{$name} = delete($attrib{$key});
+        }
+    }
+    # Add current tag to stack
+    push(@{$stack_ref}, $tag);
+    # Add attributes to current entry, if appropriate
+    if (@{$entries_ref} && @{$stack_ref} >= 2 && %attrib) {
+        my (undef, undef, @names) = @{$stack_ref};
+        while (my ($key, $value) = each(%attrib)) {
+            my $name = join(':', @names, $key);
+            $entries_ref->[-1]->{$name} = $value;
+        }
+    }
+}
+
+# Helper for _get_info. Handle the end tag of an XML element.
+sub _get_info_handle_element_leave {
+    my ($entries_ref, $stack_ref, $expat, $tag) = @_;
+    pop(@{$stack_ref}) eq $tag;
+}
+
+# Helper for _get_info. Handle an XML text node.
+sub _get_info_handle_text {
+    my ($entries_ref, $stack_ref, $expat, $text) = @_;
+    if (@{$stack_ref} <= 2 || !@{$entries_ref} || $text eq "\n") {
+        return;
+    }
+    my (undef, undef, @names) = @{$stack_ref};
+    my $name = join(':', @names);
+    $entries_ref->[-1]->{$name} .= $text;
+}
+
+# Return an object containing the repository layout information of a URL.
+sub _get_layout {
+    my ($attrib_ref, $url_arg) = @_;
+    my %info = %{_get_info($attrib_ref, $url_arg)->[0]};
+    my ($url, $root, $peg_rev) = @info{'url', 'repository:root', 'revision'};
+    my $path = substr($url, length($root));
+    my $layout = _get_layout_common($attrib_ref, $root, $peg_rev, $path);
+    $layout->set_url($root . $path . '@' . $peg_rev);
+    $layout->set_username(_get_username($attrib_ref, $root));
+    $layout;
+}
+
+# Return an object containing the repository layout information of a URL.
+sub _get_layout_common {
+    my ($attrib_ref, $root, $rev, $path, $is_local) = @_;
+
+    my %layout_config = _load_layout_config(
+        $attrib_ref, ($is_local ? 'file://' . $root : $root),
+    );
+    my ($project, $branch, $category, $owner, $sub_tree);
+    my @names = split(qr{/+}msx, $path);
+    shift(@names); # element 1 should be an empty string
+
+    # Search for the project
+    my $depth = $layout_config{'depth-project'};
+    if (defined($depth)) {
+        if (@names >= $depth) {
+            my @project_names = ();
+            for (1 .. $layout_config{'depth-project'}) {
+                push(@project_names, shift(@names));
+            }
+            $project = join('/', @project_names);
+        }
+    }
+    elsif (!grep {!defined($layout_config{"dir-$_"})} qw{trunk branch tag}) {
+        # trunk, branches and tags are ALL in specific sub-directories under
+        # the project
+        my @dirs = map {$layout_config{"dir-$_"}} qw{trunk branch tag};
+        my @head = ();
+        my @tail = @names;
+        while (my $name = shift(@tail)) {
+            if (grep {$_ eq $name} @dirs) {
+                $project = join('/', @head);
+                @names = ($name, @tail);
+                last;
+            }
+            push(@head, $name);
+        }
+        if (!defined($project)) {
+            # $path does not contain the specific sub-directories that
+            # contain the trunk, branches and tags, but $path itself may be
+            # the project
+            my $target = $path . '/' . $layout_config{'dir-trunk'};
+            if (_verify_path($attrib_ref, $root, $rev, $target, $is_local)) {
+                $project = join('/', @names);
+            }
+            @names = ();
+        }
+    }
+    else {
+        # Can only assume that trunk is in a specific sub-directory under the
+        # project
+        my @head = ();
+        my @tail = @names;
+        while (my $name = shift(@tail)) {
+            if ($name eq $layout_config{'dir-trunk'}) {
+                $project = join('/', @head);
+                @names = ($name, @tail);
+                last;
+            }
+            push(@head, $name);
+        }
+        if (!defined($project)) {
+            # $path does not contain the trunk sub-directory, need to search
+            # for it 
+            my @head = ();
+            my @tail = @names;
+            while (@head <= @names) {
+                my $target = join('/', @head, $layout_config{'dir-trunk'});
+                if (_verify_path($attrib_ref, $root, $rev, $target, $is_local)) {
+                    $project = join('/', @head);
+                    @names = @tail;
+                    last;
+                }
+                push(@head, shift(@tail));
+            }
+        }
+    }
+
+    # Search for the branch
+    if (defined($project) && @names) {
+        KEY:
+        for my $key (qw{trunk branch tag}) {
+            my @branch_names;
+            if ($layout_config{"dir-$key"}) {
+                if ($names[0] eq $layout_config{"dir-$key"}) {
+                    @branch_names = (shift(@names));
+                }
+                else {
+                    next KEY;
+                }
+            }
+            my $depth = $layout_config{"depth-$key"}
+                ? $layout_config{"depth-$key"} : 0;
+            if (@names >= $depth) {
+                for my $i (1 .. $depth) {
+                    my $name = shift(@names);
+                    push(@branch_names, $name);
+                    if (    $layout_config{"level-owner-$key"}
+                        &&  $layout_config{"level-owner-$key"} == $i
+                    ) {
+                        $owner = $name;
+                    }
+                }
+                $branch = join('/', @branch_names);
+                $category = $key;
+            }
+            last KEY;
+        }
+    }
+    # Remainder is the sub-tree under the branch
+    if (defined($branch)) {
+        $sub_tree = join('/', @names);
+    }
+    FCM::System::CM::SVN::Layout->new({
+        config          => \%layout_config,
+        root            => $root, 
+        path            => $path, 
+        peg_rev         => $rev,
+        project         => $project, 
+        branch          => $branch, 
+        branch_category => $category, 
+        branch_owner    => $owner, 
+        sub_tree        => $sub_tree,
+    });
+}
+
+# Return a (filtered) recursive listing of $url_arg.
+sub _get_list {
+    my ($attrib_ref, $url_arg, $filter_func) = @_;
+    my @list;
+    my ($url0, $rev) = _split_by_peg($attrib_ref, $url_arg);
+    my @items = ([$url0, 0]);
+    while (my $item = shift(@items)) {
+        my ($url, $depth) = @{$item};
+        ++$depth;
+        my @lines = _stdout($attrib_ref, qw{svn list}, $url . '@' . $rev);
+        for my $line (@lines) {
+            my ($this_name, $is_dir) = $line =~ qr{\A(.*?)(/?)\z};
+            my $this_url = $url . '/' . $this_name ;
+            my ($can_return, $can_recurse) = (1, $is_dir);
+            if (defined($filter_func)) {
+                ($can_return, $can_recurse)
+                    = $filter_func->($this_url, $this_name, $is_dir, $depth);
+            }
+            if ($can_return) {
+                push(@list, $this_url . '@' . $rev);
+            }
+            if ($can_recurse && $is_dir) {
+                push(@items, [$this_url, $depth]);
+            }
+        }
+    }
+    @list;
+}
+
+# Invokes "svn log --xml".
+sub _get_log {
+    my $attrib_ref = shift();
+    my %option = ('revision' => undef, 'stop-on-copy' => undef);
+    if (@_ && ref($_[0]) && ref($_[0]) eq 'HASH') {
+        %option = (%option, %{shift()});
+    }
+    my @paths = @_;
+    if (!@paths) {
+        @paths = (q{.});
+    }
+    my (@entries, @stack);
+    my $parser = XML::Parser->new(Handlers => {
+        'Start' => sub {_get_log_handle_element_enter(\@entries, \@stack, @_)},
+        'End'   => sub {_get_log_handle_element_leave(\@entries, \@stack, @_)},
+        'Char'  => sub {_get_log_handle_text(     \@entries, \@stack, @_)},
+    });
+    $parser->parse(scalar(_stdout(
+        $attrib_ref,
+        qw{svn log --xml -v},
+        ($option{'revision'} ? ('--revision', $option{'revision'}) : ()),
+        ($option{'stop-on-copy'} ? ('--stop-on-copy') : ()),
+        @paths,
+    )));
+    \@entries;
+}
+
+# Helper for "_get_log", handle beginning of an XML element.
+sub _get_log_handle_element_enter {
+    my ($entries_ref, $stack_ref, $expat, $tag, %attrib) = @_;
+    push(@{$stack_ref}, $tag);
+    if (exists($SVN_LOG_ELEMENT_START_HANDLER_FOR{$tag})) {
+        $SVN_LOG_ELEMENT_START_HANDLER_FOR{$tag}->(
+            $entries_ref,
+            $tag,
+            %attrib,
+        );
+    }
+}
+
+# Helper for "_get_log", handle beginning of the "logentry" element.
+sub _get_log_handle_element_enter_logentry {
+    my ($entries_ref, $tag, %attrib) = @_;
+    push(
+        @{$entries_ref},
+        {   'author'   => q{},
+            'date'     => q{},
+            'msg'      => q{},
+            'paths'    => [],
+            'revision' => $attrib{'revision'},
+        },
+    );
+}
+
+# Helper for "_get_log", handle beginning of the "path" element.
+sub _get_log_handle_element_enter_path {
+    my ($entries_ref, $tag, %attrib) = @_;
+    push(@{$entries_ref->[-1]->{'paths'}}, {%attrib, 'path' => q{}});
+}
+
+# Helper for "_get_log", handle end of an element.
+sub _get_log_handle_element_leave {
+    my ($entries_ref, $stack_ref, $expat, $tag) = @_;
+    pop(@{$stack_ref}) eq $tag;
+}
+
+# Helper for "_get_log", handle text node.
+sub _get_log_handle_text {
+    my ($entries_ref, $stack_ref, $expat, $text) = @_;
+    if (!exists($stack_ref->[-1])) {
+        return;
+    }
+    if (exists($SVN_LOG_TEXT_HANDLER_FOR{$stack_ref->[-1]})) {
+        $SVN_LOG_TEXT_HANDLER_FOR{$stack_ref->[-1]}->($entries_ref, $text);
+    }
+    elsif ( @{$entries_ref}
+        &&  exists($entries_ref->[-1]->{$stack_ref->[-1]})
+        &&  !ref($entries_ref->[-1]->{$stack_ref->[-1]})
+    ) {
+        $entries_ref->[-1]->{$stack_ref->[-1]} .= $text;
+    }
+}
+
+# Helper for "_get_log", handle text node in a "date" element.
+sub _get_log_handle_text_date {
+    my ($entries_ref, $text) = @_;
+    # "svn log --xml" may return a date with trailing spaces!
+    $text =~ s{\s+\z}{}gmsx;
+    my $head = Time::Piece->strptime(substr($text, 0, -8), '%Y-%m-%dT%H:%M:%S');
+    my $tail = substr($text, -8, -1);
+    $entries_ref->[-1]->{'date'} = $head->epoch() + $tail;
+}
+
+# Helper for "_get_log", handle text node in a "path" element.
+sub _get_log_handle_text_path {
+    my ($entries_ref, $text) = @_;
+    $entries_ref->[-1]->{'paths'}->[-1]->{'path'} .= $text;
+}
+
+# Return the username of the host of a given target URL.
+memoize('_get_username');
+sub _get_username {
+    my ($attrib_ref, $target) = @_;
+    my ($scheme, $sps) = $attrib_ref->{util}->uri_match($target);
+    my ($host) = $sps =~ qr{\A//([^/]+)(?:/|\z)}msx;
+    # Note: can use Config::IniFiles, but best to avoid another dependency.
+    # Note: not very efficient logic here, but should not yet matter.
+    my $subversion_servers_conf = exists($ENV{'FCM_SUBVERSION_SERVERS_CONF'})
+        ? $ENV{'FCM_SUBVERSION_SERVERS_CONF'} : $SUBVERSION_SERVERS_CONF;
+    my $handle
+        = $attrib_ref->{'util'}->file_load_handle($subversion_servers_conf);
+    my $is_in_section;
+    my $group;
+    LINE:
+    while (my $line = readline($handle)) {
+        chomp($line);
+        if ($line =~ qr{\A\s*(?:[#;]|\z)}msx) {
+            next LINE;
+        }
+        if ($line =~ qr{\A\s*\[\s*groups\s*\]\s*\z}msx) {
+            $is_in_section = 1;
+        }
+        elsif ($line =~ qr{\A\s*\[}msx) {
+            $is_in_section = 0;
+        }
+        elsif ($is_in_section) {
+            my ($lhs, $rhs) = $line =~ qr{\A\s*(\S+)\s*=\s*(\S+)\s*\z}msx;
+            if ($rhs) {
+                $rhs =~ s{[.]}{\\.}gmsx;
+                $rhs =~ s{[*]}{.*}gmsx;
+                $rhs =~ s{[?]}{.?}gmsx;
+                if ($host && $host =~ qr{\A$rhs\z}msx) {
+                    $group = $lhs;
+                    last LINE;
+                }
+            }
+        }
+    }
+    my $username = scalar(getpwuid($<)); # current user ID
+    if ($group) {
+        seek($handle, 0, 0);
+        LINE:
+        while (my $line = readline($handle)) {
+            chomp($line);
+            if ($line =~ qr{\A\s*(?:[#;]|\z)}msx) {
+                next LINE;
+            }
+            if ($line =~ qr{\A\s*\[\s*$group\s*\]\s*\z}msx) {
+                $is_in_section = 1;
+            }
+            elsif ($line =~ qr{\A\s*\[}msx) {
+                $is_in_section = 0;
+            }
+            elsif ($is_in_section) {
+                my ($rhs) = $line =~ qr{\A\s*username\s*=\s*(\S+)\s*\z}msx;
+                if ($rhs) {
+                    $username = $rhs;
+                    last LINE;
+                }
+            }
+        }
+    }
+    close($handle);
+    return $username;
+}
+
+# Return path to the root working copy directory of the argument.
+sub _get_wc_root {
+    my ($attrib_ref, $path) = @_;
+    $path ||= cwd();
+    my ($entries_ref) = _get_info($attrib_ref, $path);
+    if (    defined($entries_ref)
+        &&  @{$entries_ref}
+        &&  exists($entries_ref->[0]->{'wc-info:wcroot-abspath'})
+    ) {
+        return $entries_ref->[0]->{'wc-info:wcroot-abspath'};
+    }
+    if (-f $path) {
+        $path = dirname($path);
+    }
+    $path = rel2abs($path);
+    my $return;
+    if (-e catfile($path, qw{.svn entries})) {
+        while (   -e catfile($path, qw{.svn entries})
+               && $path ne dirname($path)
+        ) {
+            $return = $path;
+            $path = dirname($path);
+        }
+    }
+    else {
+        while (   !-e catfile($path, qw{.svn entries})
+               && $path ne dirname($path)
+        ) {
+            $path = dirname($path);
+            $return = $path;
+        }
+    }
+    return $return;
+}
+
+# Load layout related configuration for a given URL root.
+memoize('_load_layout_config');
+sub _load_layout_config {
+    my ($attrib_ref, $root) = @_;
+    if (exists($attrib_ref->{layout_config_of}{$root})) {
+        return %{$attrib_ref->{layout_config_of}{$root}};
+    }
+    my %site_layout_config;
+    if (exists($attrib_ref->{layout_config_of}{q{}})) {
+        %site_layout_config = %{$attrib_ref->{layout_config_of}{q{}}};
+    }
+    else {
+        %site_layout_config = %LAYOUT_CONFIG;
+        $attrib_ref->{util}->cfg_init(
+            $attrib_ref->{layout_cfg_base},
+            sub {
+                my $config_reader = shift();
+                my @unknown_entries;
+                while (defined(my $entry = $config_reader->())) {
+                    if (exists($site_layout_config{$entry->get_label()})) {
+                        my $value
+                            = $entry->get_value() ? $entry->get_value() : undef;
+                        $site_layout_config{$entry->get_label()} = $value;
+                    }
+                    else {
+                        push(@unknown_entries, $entry);
+                    }
+                }
+                if (@unknown_entries) {
+                    return $E->throw($E->CONFIG_UNKNOWN, \@unknown_entries);
+                }
+            },
+        );
+        $attrib_ref->{layout_config_of}{q{}} = {%site_layout_config};
+    }
+    $attrib_ref->{layout_config_of}{$root} = {%site_layout_config};
+    my @prop_lines = eval {
+        _stdout($attrib_ref, qw{svn propget fcm:layout}, $root);
+    };
+    if ($@) {
+        $@ = undef;
+    }
+    PROP_LINE:
+    while (defined(my $prop_line = shift(@prop_lines))) {
+        chomp($prop_line);
+        if ($prop_line =~ qr{\A\s*(?:\#|\z)}msx) { # comment line
+            next PROP_LINE;
+        }
+        ($prop_line) = $prop_line =~ qr{\A\s*(.+?)\s*\z}msx; # trim
+        my ($key, $value) = split(qr{\s*=\s*}msx, $prop_line, 2);
+        if (exists($attrib_ref->{layout_config_of}{$root}{$key})) {
+            $attrib_ref->{layout_config_of}{$root}{$key} = $value;
+        }
+    }
+    %{$attrib_ref->{layout_config_of}{$root}};
+}
+
+# Splits a URL at REV by the @.
+sub _split_by_peg {
+    my ($attrib_ref, $url) = @_;
+    $url =~ qr{\A(.*?)(?:@([^@/]+))?\z}msx;
+}
+
+# Calls "svn", return its standard output.
+sub _stdout {
+    my ($attrib_ref, @command) = @_;
+    my %value_of = %{$attrib_ref->{util}->shell_simple(\@command)};
+    if ($value_of{rc}) {
+        return $E->throw(
+            $E->SHELL,
+            {command_list => \@command, %value_of}
+        );
+    }
+    wantarray() ? split("\n", $value_of{o}) : $value_of{o};
+}
+
+# Return true if $path is in $repos for this $rev
+sub _verify_path {
+    my ($attrib_ref, $root, $rev, $path, $is_local) = @_;
+    if ($is_local) {
+        my $opt = $rev =~ qr{\A\d+\z}msx ? '-r' : '-t';
+        eval {
+            _stdout($attrib_ref, qw{svnlook tree -N}, $opt, $rev, $root, $path);
+        };
+        if ($@) {
+            $@ = q{};
+            return;
+        }
+        return ($root, $rev, $path);
+    }
+    else {
+        my $target = $root . '/' . $path . '@' . $rev;
+        my $url = eval {_get_info($attrib_ref, $target)->[0]->{url}};
+        if ($@ || !$url) {
+            $@ = q{};
+            return;
+        }
+        return ($root, $rev, $path);
+    }
+}
+
+#-------------------------------------------------------------------------------
+# Represent the layout information of a Subversion URL.
+package FCM::System::CM::SVN::Layout;
+use base qw{FCM::Class::HASH};
+
+__PACKAGE__->class({
+    config          => '%',
+    url             => '$',
+    root            => '$',
+    path            => '$',
+    peg_rev         => '$',
+    project         => '$',
+    branch          => '$',
+    branch_category => '$',
+    branch_owner    => '$',
+    sub_tree        => '$',
+    username        => {isa => '$', default => scalar(getpwuid($<))},
+});
+
+sub is_trunk {
+    $_[0]->{branch_category} && $_[0]->{branch_category} eq 'trunk';
+}
+
+sub is_branch {
+    $_[0]->{branch_category} && $_[0]->{branch_category} eq 'branch';
+}
+
+sub is_tag {
+    $_[0]->{branch_category} && $_[0]->{branch_category} eq 'tag';
+}
+
+sub is_owned_by_user {
+    my ($self, $user) = @_;
+    $user ||= $self->get_username();
+    $self->{branch_owner} && $self->{branch_owner} eq $user;
+}
+
+sub is_shared {
+    my ($self) = @_;
+    $self->{branch_owner}
+        && grep {$_ eq $self->{branch_owner}} qw{Share Config Rel};
+}
+
+sub as_string {
+    my ($self) = @_;
+    my $return = q{};
+    for my $key (qw{
+        url
+        root
+        path
+        peg_rev
+        project
+        branch
+        branch_category
+        branch_owner
+        sub_tree
+    }) {
+        my $value = $self->{$key};
+        if ($key ne 'config' && defined($value)) {
+            $return .= "$key: $value\n";
+        }
+    }
+    return $return;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::CM::SVN
+
+=head1 DESCRIPTION
+
+Part of L<FCM::System::CM|FCM::System::CM>. Provides an interface for common SVN
+functionalities used in the FCM CM sub-system.
+
+=head1 METHODS
+
+This is a sub-class of L<FCM::Class::CODE|FCM::Class::CODE>.
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Return a new instance of this class. %attrib accepts a single "util" key for an
+instance of an L<FCM::Util|FCM::Util> object.
+
+=item $instance->call(@args)
+
+Call the command line "svn" with a list of arguments in @args.
+
+=item $instance->get_info(@path)
+=item $instance->get_info(\%option, @path)
+
+Invokes "svn info --xml @paths", and returns a LIST of info entries. If @paths
+is not specified, use ("."). If %option is specified, it may contain the keys:
+
+=over 4
+
+=item recursive
+
+If value of this key is not undef, add --recursive to "svn info".
+
+=item revision
+
+If value of this key is not undef, add --revision VALUE to "svn info".
+
+=back
+
+Each info entry is a HASH with keys reflecting the tag or attribute name in an
+entry element. The original hierarchy below the entry element is delimited by a
+colon in the name. For example, a return structure may look like this:
+    [   {   'commit:author' => 'fred',
+            'commit:date' => '2011-11-09T15:41:14.514665Z',
+            'commit:revision' => '4549',
+            'kind' => 'dir',
+            'path' => 'trunk',
+            'revision' => '4552',
+            'repository:root' => 'svn://host/my-repos',
+            'repository:uuid' => '91f685bf-fbee-0310-99e6-f3aa9e660bd5'
+            'url' => 'svn://host/my-repos/FCM/trunk',
+        },
+    ]
+
+=item $instance->get_layout($url)
+
+Return an instance of L<FCM::System::CM::SVN::Layout|/FCM::System::CM::SVN::Layout>
+containing the repository layout information of $url.
+
+=item $instance->get_layout_common($root, $rev, $path, $is_local)
+
+Return an instance of L<FCM::System::CM::SVN::Layout|/FCM::System::CM::SVN::Layout>
+containing the repository layout information for $path in $root at $rev. If
+$is_local is true, use "svnlook" to verify the existence of $path in $root
+at $rev. Otherwise, it uses "svn info" instead. If $rev is assumed to be a
+transaction if it is not numeric.
+
+=item $instance->get_list($url_arg, $filter_func)
+
+Call "svn list" multiple times to obtain a recursive listing of files and
+directories under $url_arg. Return a list containing the listing. If
+$filter_func is defined, it should be a CODE reference, which would be invoked
+for each file/directory found. It should have the interface:
+
+    ($can_return, $can_recurse)
+        = $filter_func->($this_url, $this_name, $is_dir, $depth);
+
+where $this_url is the URL of the file/directory found, $this_name is the
+base name of the file/directory found, $is_dir is true if it is a directory,
+$depth is the directory depth of $this_url relative to $url_arg.
+
+The $filter_func CODE reference should return a 2-element list ($can_return,
+$can_recurse). The get_list method will only return $this_url in the listing
+if $can_return is set to true. If $is_dir is true and $can_recurse is true, the
+get_list method will go down to do more listing in $this_url.
+
+=item $instance->get_log(@path)
+=item $instance->get_log(\%option, @path)
+
+Invokes "svn log --xml".  If @paths is not specified, use ("."). If %option is
+specified, it may contain the keys:
+
+=over 4
+
+=item revision
+
+If value of this key is not undef, add --revision VALUE to "svn log".
+
+=item stop-on-copy
+
+If value of this key is not undef, add --stop-on-copy to "svn log".
+
+=back
+
+Returns an ARRAY reference. Each element is a data structure that represents a
+log entry. The data structure should look like:
+    [   {   'author'   => $author,
+            'date'     => $date, # seconds since epoch
+            'msg'      => $msg,
+            'paths'    => [
+                {   'path'          => $path,
+                    'action'        => $action,
+                    'copyfrom-path' => $p,
+                    'copyfrom-rev'  => $r,
+                },
+                # ...
+            ],
+            'revision' => $revision,
+        },
+    ]
+
+=item $instance->get_username($target)
+
+Return the user name associated with $target.
+
+=item $instance->get_wc_root($path)
+
+Return the path to the root working copy directory of the argument.
+
+=item $instance->load_layout_config($root)
+
+Return a HASH (not a reference) containing the layout configuration of $root.
+See %LAYOUT_CONFIG for default settings. $root should be the URL to a
+repository root.
+
+=item $instance->split_by_peg($location)
+
+Split a location string (either a URL at PEG or a PATH at PEG) and return a
+two-element list: either (URL, PEG) or (PATH, PEG).
+
+=item $instance->stdout(@command)
+
+Call a @command, capture and return the STDOUT on success. In scalar context,
+return the STDOUT as-is. In array context, return it as a list of lines with the
+new line characters removed.
+
+=back
+
+=head1 EXCEPTION
+
+Methods in this class may throw an
+L<FCM::System::Exception|FCM::System::Exception> on error.
+
+=head1 FCM::System::CM::SVN::Layout
+
+The FCM::System::CM::SVN::Layout class inherits from
+L<FCM::Class::HASH|FCM::Class::HASH>. An instance represents the layout
+information in a Subversion URL based on the default or specified FCM layout
+information. It has the following attributes:
+
+=over 4
+
+=item config
+
+is a HASH containing the layout configuration applied to this URL.
+Valid keys and their default values are:
+
+=over 4
+
+=item depth-project => undef
+Number of sub-directories used by the name of a project.
+
+=item depth-branch => 3
+Number of sub-directories (under "branches") used by the name of branch.
+
+=item depth-tag => 1
+Number of sub-directories (under "tags") used by the name of branch.
+
+=item dir-trunk => 'trunk'
+Name of the master/trunk directory.
+
+=item dir-branch => 'branches'
+Name of the directory where all branches live. May be empty.
+
+=item dir-tag => 'tags'
+Name of the directory where all tags live. May be empty.
+
+=item level-owner-branch => 2
+Sub-directory level in the name of a branch containing the its owner.
+
+=item level-owner-branch => undef
+Sub-directory level in the name of a tag containing the its owner.
+
+=item template-branch => '{category}/{owner}/{name_prefix}{name}'
+Branch name template.
+
+=item template-tag => undef
+Tag name template.
+
+=back
+
+=item url
+
+is the full URL at PEG.
+
+=item root
+
+is the repository root.
+
+=item path
+
+is the path below the repository root.
+
+=item peg_rev
+
+is the (peg) revision of the URL.
+
+=item project
+
+is the project name in the URL. It is undef if the URL does not contain a valid
+project name for the given repository. An empty string is possible, for example,
+if the layout means that the trunk is at the root level.
+
+=item branch
+
+is the "branch" name in the URL, (which may be the name of the master/trunk
+branch or the name of a tag). It is undef if the URL does not contain a valid
+branch name for the given repository.
+
+=item branch_category
+
+is the category (i.e. "trunk", "branch" or "tag") of the branch.
+
+=item branch_owner
+
+is the owner of the branch, if it can be derived from the URL.
+
+=item sub_tree
+
+is the path in the URL under the branch of a project tree. It is undef if the
+URL is not at or below the level of a branch of the project tree. An empty
+string means the that the URL is at root level of the project tree.
+
+=back
+
+An FCM::System::CM::SVN::Layout instance has the following convenient methods:
+
+=over 4
+
+=item $layout->is_trunk()
+
+The URL is in the trunk of a project.
+
+=item $layout->is_branch()
+
+The URL is in a branch of a project.
+
+=item $layout->is_tag()
+
+The URL is in a tag of a project.
+
+=item $layout->is_owned_by_user($user)
+
+The URL is in a branch owned by $user. If $user is not defined, it defaults to
+the current user ID.
+
+=item $layout->is_shared()
+
+The URL is in a shared branch.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Exception.pm b/lib/FCM/System/Exception.pm
new file mode 100644
index 0000000..b7a356e
--- /dev/null
+++ b/lib/FCM/System/Exception.pm
@@ -0,0 +1,421 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package FCM::System::Exception;
+use base qw{FCM::Exception};
+use Scalar::Util qw{blessed};
+
+use constant {
+    BUILD_SOURCE     => 'BUILD_SOURCE',
+    BUILD_SOURCE_SYN => 'BUILD_SOURCE_SYN',
+    BUILD_TARGET     => 'BUILD_TARGET',
+    BUILD_TARGET_BAD => 'BUILD_TARGET_BAD',
+    BUILD_TARGET_CYC => 'BUILD_TARGET_CYC',
+    BUILD_TARGET_DEP => 'BUILD_TARGET_DEP',
+    BUILD_TARGET_DUP => 'BUILD_TARGET_DUP',
+    CACHE_LOAD       => 'CACHE_LOAD',
+    CACHE_TYPE       => 'CACHE_TYPE',
+    CM_ALREADY_EXIST => 'CM_ALREADY_EXIST',
+    CM_ARG           => 'CM_ARG',
+    CM_BRANCH_NAME   => 'CM_BRANCH_NAME',
+    CM_BRANCH_SOURCE => 'CM_BRANCH_SOURCE',
+    CM_CHECKOUT      => 'CM_CHECKOUT',
+    CM_LOG_EDIT_DELIMITER => 'CM_LOG_EDIT_DELIMITER',
+    CM_LOG_EDIT_NULL => 'CM_LOG_EDIT_NULL',
+    CM_OPT_ARG       => 'CM_OPT_ARG',
+    CM_PROJECT_NAME  => 'CM_PROJECT_NAME',
+    CM_REPOSITORY    => 'CM_REPOSITORY',
+    CONFIG_CONFLICT  => 'CONFIG_CONFLICT',
+    CONFIG_INHERIT   => 'CONFIG_INHERIT',
+    CONFIG_MODIFIER  => 'CONFIG_MODIFIER',
+    CONFIG_NS        => 'CONFIG_NS',
+    CONFIG_NS_VALUE  => 'CONFIG_NS_VALUE',
+    CONFIG_UNKNOWN   => 'CONFIG_UNKNOWN',
+    CONFIG_VALUE     => 'CONFIG_VALUE',
+    COPY             => 'COPY',
+    DEST_CLEAN       => 'DEST_CLEAN',
+    DEST_CREATE      => 'DEST_CREATE',
+    DEST_LOCKED      => 'DEST_LOCKED',
+    EXPORT_ITEMS_SRC => 'EXPORT_ITEMS_SRC',
+    EXTRACT_LOC_BASE => 'EXTRACT_LOC_BASE',
+    EXTRACT_MERGE    => 'EXTRACT_MERGE',
+    EXTRACT_NS       => 'EXTRACT_NS',
+    MIRROR           => 'MIRROR',
+    MIRROR_NULL      => 'MIRROR_NULL',
+    MIRROR_SOURCE    => 'MIRROR_SOURCE',
+    MIRROR_TARGET    => 'MIRROR_TARGET',
+    MAKE             => 'MAKE',
+    MAKE_ARG         => 'MAKE_ARG',
+    MAKE_CFG         => 'MAKE_CFG',
+    MAKE_CFG_FILE    => 'MAKE_CFG_FILE',
+    MAKE_PROP_NS     => 'MAKE_PROP_NS',
+    MAKE_PROP_VALUE  => 'MAKE_PROP_VALUE',
+    SHELL            => 'SHELL',
+};
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Exception
+
+=head1 SYNOPSIS
+
+    eval {
+        # ...
+        FCM::System::Exception->throw($code, $ctx);
+        # ...
+        FCM::System::Exception->throw($code, $ctx, {exception => $e});
+        # ...
+    };
+    if (my $e = $@) {
+        if (FCM::System::Exception->caught($e)) {
+            # do something ...
+        }
+        else {
+            # do something else ...
+        }
+    }
+
+=head1 DESCRIPTION
+
+This exception represents an error condition in an FCM sub-system. It is a
+sub-class of L<FCM::Exception|FCM::Exception>.
+
+=head1 CONSTANTS
+
+The following are known error code:
+
+=over 4
+
+=item FCM::System::Exception->BUILD_SOURCE
+
+The build sub-system fails because a specified source does not exist. Expects
+$e->get_ctx() to return the source path.
+
+=item FCM::System::Exception->BUILD_SOURCE_SYN
+
+The build sub-system fails because a specified source has a syntax error.
+Expects $e->get_ctx() to return an ARRAY reference containing the source path
+and the line number where the error occurs.
+
+=item FCM::System::Exception->BUILD_TARGET
+
+The build sub-system fails because a target does not exist when it is supposed
+to be updated. Expects $e->get_ctx() to return an instance of
+L<FCM::Context::Make::Build::Target|FCM::Context::Make::Build/FCM::Context::Make::Build::Target>.
+
+=item FCM::System::Exception->BUILD_TARGET_BAD
+
+The build sub-system fails because the user has specified invalid targets.
+Expects $e->get_ctx() to return an ARRAY reference of the bad target keys.
+
+=item FCM::System::Exception->BUILD_TARGET_CYC
+
+The build sub-system fails due to cyclic dependency in a target. Expects
+$e->get_ctx() to return a HASH {$key => {'keys' => \@stack}, ...}, where each
+$key is the ID of a problematic target and the @stack is an ARRAY reference of a
+stack of target keys where the problem is detected.
+
+=item FCM::System::Exception->BUILD_TARGET_DEP
+
+The build sub-system fails because some targets have missing dependencies.
+Expects $e->get_ctx() to return a HASH
+{$key => {'keys' => \@stack, 'values' => [$dep_key, $dep_type]}, ...}, where each
+$key is the ID of a problematic target, the @stack is an ARRAY reference of a
+stack of target keys where the problem is detected, and the
+[$dep_key, $dep_type] ARRAY contains the key and type of the dependency.
+
+=item FCM::System::Exception->BUILD_TARGET_DUP
+
+The build sub-system fails because there are multiple versions of a build
+target. Expects $e->get_ctx() to return a HASH
+{$key => {'keys' => \@stack, 'values' => \@ns}, ...} where each $key is the ID of a
+problematic target, the @stack is an ARRAY
+reference of a stack of target keys where the problem is detected,
+and @ns contains the name-spaces of the sources that give the same target key.
+
+=item FCM::System::Exception->CACHE_LOAD
+
+The system is unable to load a cache from a make destination. Expects
+$e->get_ctx() to return the path it fails to load; and the $e->get_exception()
+to return the original exception that triggers this failure.
+
+=item FCM::System::Exception->CACHE_TYPE
+
+The system loaded a cache into a data structure, but the data structure is not
+the expected object type. Expects $e->get_ctx() to return the path to the cache.
+
+=item FCM::System::Exception->CM_ALREADY_EXIST
+
+Attempt to create a target that already exists. Expects $e->get_ctx() to return the
+target URL.
+
+=item FCM::System::Exception->CM_ARG
+
+Attempt to supply a bad argument. Expects $e->get_ctx() to return the bad value.
+
+=item FCM::System::Exception->CM_BRANCH_NAME
+
+Attempt to create a branch with a bad name. Expects $e->get_ctx() to return the
+bad name.
+
+=item FCM::System::Exception->CM_BRANCH_SOURCE
+
+Attempt to create a branch with an invalid source. Expects $e->get_ctx() to
+return the source.
+
+=item FCM::System::Exception->CM_CHECKOUT
+
+Attempt to checkout to an existing working copy. Expects $e->get_ctx() to return
+an ARRAY containing the target path and the URL it is pointing to.
+
+=item FCM::System::Exception->CM_LOG_EDIT_DELIMITER
+
+The commit message delimiter is modified after an edit.
+
+=item FCM::System::Exception->CM_LOG_EDIT_NULL
+
+The commit message is empty after an edit.
+
+=item FCM::System::Exception->CM_OPT_ARG
+
+Attempt to supply a bad argument to a valid option. Expects $e->get_ctx() to
+return the option key and the bad value.
+
+=item FCM::System::Exception->CM_PROJECT_NAME
+
+Attempt to create a project with a bad name. Expects $e->get_ctx() to return the
+bad name.
+
+=item FCM::System::Exception->CM_REPOSITORY
+
+Attempt to access an invalid repository. Expects $e->get_ctx() to return the
+bad repository.
+
+=item FCM::System::Exception->CONFIG_CONFLICT
+
+In a make configuration file, a declaration attempts to modify a value that is
+inherited from a previous make, and considered read-only. Expects $e->get_ctx()
+to return a L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry> object.
+
+=item FCM::System::Exception->CONFIG_INHERIT
+
+In a make configuration file, a declaration attempts to inherit from a make that
+is either incomplete or failed. Expects $e->get_ctx() to return a
+L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry> object.
+
+=item FCM::System::Exception->CONFIG_MODIFIER
+
+In a make configuration file, a modifier of in a declaration is incorrect.
+Expects $e->get_ctx() to return a
+L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry> object and
+$e->get_exception() to return (if any) the original exception that triggers this
+failure.
+
+=item FCM::System::Exception->CONFIG_NS
+
+In a make configuration file, a declaration is missing a required name-space, or
+the name-space declaration is incorrect. Expects $e->get_ctx() to return a
+L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry> object and
+$e->get_exception() to return (if any) the original exception that triggers this
+failure.
+
+=item FCM::System::Exception->CONFIG_NS_VALUE
+
+In a make configuration file, the name-space of a declaration is incompatible
+with the value. (E.g. the number of name-space elements does not match with the
+number of words in a value.) Expects $e->get_ctx() to return a
+L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry> object and
+$e->get_exception() to return (if any) the original exception that triggers this
+failure.
+
+=item FCM::System::Exception->CONFIG_UNKNOWN
+
+In a make configuration file, the label of a declaration is unrecognised by the
+system. Expects $e->get_ctx() to return a reference to an ARRAY containing
+L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry> objects.
+
+=item FCM::System::Exception->CONFIG_VALUE
+
+In a make configuration file, the value of a declaration is incorrect. Expects
+$e->get_ctx() to return a L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry>
+object and $e->get_exception() to return (if any) the original exception that
+triggers this failure.
+
+=item FCM::System::Exception->COPY
+
+The system fails to perform a file copy. Expects $e->get_ctx() to return a
+2-element ARRAY reference to represent the source and the target, and the
+$e->get_exception() to return the original exception that triggers this failure.
+
+=item FCM::System::Exception->DEST_CLEAN
+
+A destination path cannot be removed. Expects $e->get_ctx() to return the path
+that the system fails to remove, and $e->get_exception() to return the original
+exception that triggers this failure.
+
+=item FCM::System::Exception->DEST_CREATE
+
+The system is unable to create a path at a make destination. Expects
+$e->get_ctx() to return the path it fails to create; and the $e->get_exception()
+to return the original exception that triggers this failure.
+
+=item FCM::System::Exception->DEST_LOCKED
+
+A lock file exists at the destination. Expects $e->get_ctx() to return the
+path to the lock file.
+
+=item FCM::System::Exception->EXPORT_ITEMS_SRC
+
+The system fails because the source location is not specified.
+
+=item FCM::System::Exception->EXTRACT_LOC_BASE
+
+The system fails to determine the location of a base tree of a project. Expects
+$e->get_ctx() to return the name-space of the project.
+
+=item FCM::System::Exception->EXTRACT_MERGE
+
+The system fails to merge the sources of an extract target. Expects
+$e->get_ctx() to return a HASH reference with the following keys:
+
+=over 4
+
+=item target
+
+The FCM::Context::Make::Extract::Target object associated with this failure.
+
+=item output
+
+The path to a file containing the failed merge output.
+
+=item keys_done
+
+The keys of the source trees providing the source files for this target that
+have already been merged.
+
+=item key
+
+The key of the source tree providing the source file for this target that causes
+the merge conflict.
+
+=item keys_left
+
+The keys of the source trees providing the source files for this target that are
+yet to be merged.
+
+=back
+
+=item FCM::System::Exception->EXTRACT_NS
+
+The system fails because there are some extract declarations for the name-spaces
+but the settings are not used. Expects $e->get_ctx() to return an ARRAY of bad
+name-spaces.
+
+=item FCM::System::Exception->MIRROR
+
+The mirror operation failed. Expects $e->get_ctx() to return a reference of a
+2-element ARRAY containing the source and the target of the mirror, and
+$e->get_exception() to return the original exception that triggers this failure.
+
+=item FCM::System::Exception->MIRROR_NULL
+
+The mirror step failed because a target is not specified. The $e->get_ctx() is
+undefined.
+
+=item FCM::System::Exception->MIRROR_SOURCE
+
+The mirror step failed because the destination of a completed step in the make
+is not suitable for mirroring. Expects $e->get_ctx() to return an ARRAY
+reference containing the names of the unsuitable steps.
+
+=item FCM::System::Exception->MIRROR_TARGET
+
+The mirror step failed to create the target. Expects $e->get_ctx() to
+return the target of the mirror, and $e->get_exception() to return the original
+exception that triggers this failure.
+
+=item FCM::System::Exception->MAKE
+
+A named step in a make is not implemented. Expects $e->get_ctx() to return the
+name of the step.
+
+=item FCM::System::Exception->MAKE_ARG
+
+A make sub-system fails because of bad command line arguments. Expects
+$e->get_ctx() to return an ARRAY reference of something like this:
+
+    my @list = @{$e->get_ctx()};
+    for (@list) {
+        my ($arg_index, $arg) = @{$_};
+        warn("Argument $arg_index ($arg) is invalid\n");
+    }
+
+=item FCM::System::Exception->MAKE_CFG
+
+A make sub-system fails because it can find no configuration.
+
+=item FCM::System::Exception->MAKE_CFG_FILE
+
+A make sub-system fails because it cannot file a named configuration file.
+Expects $e->get_ctx() to return the configuration file name.
+
+=item FCM::System::Exception->MAKE_PROP_NS
+
+A make sub-system fails because a property is specified with an invalid
+name-space. Expects $e->get_ctx() to return an ARRAY reference of something like
+this:
+
+    my @list = @{$e->get_ctx()};
+    for (@list) {
+        my ($step_name, $prop_name, $ns, $value) = @{$_};
+        warn("{$prop_name}[$ns]: prop ns is invalid\n");
+    }
+
+=item FCM::System::Exception->MAKE_PROP_VALUE
+
+A make sub-system fails because a property is specified with an invalid
+value. Expects $e->get_ctx() to return an ARRAY reference of something like
+this:
+
+    my @list = @{$e->get_ctx()};
+    for (@list) {
+        my ($step_name, $prop_name, $ns, $value) = @{$_};
+        warn("{$prop_name}[$ns] = $value: prop value is bad\n");
+    }
+
+=item FCM::System::Exception->SHELL
+
+A shell command returns an error. Expects $e->get_ctx() to return a HASH
+reference containing {command_list}, an ARRAY reference representing the
+command; {rc}, the return code; {out}, the standard output of the command and
+{err}, the standard error of the command. Expects $e->get_exception() to return
+the standard error of the command.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make.pm b/lib/FCM/System/Make.pm
new file mode 100644
index 0000000..3e13021
--- /dev/null
+++ b/lib/FCM/System/Make.pm
@@ -0,0 +1,451 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::ConfigEntry;
+use FCM::Context::Event;
+use FCM::System::Exception;
+use FCM::System::Make::Build;
+use FCM::System::Make::Extract;
+use FCM::System::Make::Mirror;
+use FCM::System::Make::Preprocess;
+use FCM::System::Make::Share::Config;
+use FCM::System::Make::Share::Dest;
+use File::Path qw{rmtree};
+use File::Spec::Functions qw{catfile};
+use File::Copy qw{copy};
+use File::Temp;
+use POSIX qw{strftime};
+use Sys::Hostname qw{hostname};
+
+# Actions of the named common steps
+my %ACTION_OF = (
+    'config-parse' => \&_config_parse,
+    'dest-init'    => \&_dest_init   ,
+);
+# Alias to class name
+my $E = 'FCM::System::Exception';
+# The initial steps to run
+my @INIT_STEPS = (qw{config-parse dest-init});
+# The name of the system
+our $NAME = 'make';
+# Base name of common configuration file
+our $CFG_BASE = 'make.cfg';
+# A map of named helper utilities
+our %SHARED_UTIL_OF = (
+    'config' => 'FCM::System::Make::Share::Config',
+    'dest'   => 'FCM::System::Make::Share::Dest'  ,
+);
+# A map of named subsystems
+our %SUBSYSTEM_OF = (
+    'build'      => 'FCM::System::Make::Build'     ,
+    'extract'    => 'FCM::System::Make::Extract'   ,
+    'mirror'     => 'FCM::System::Make::Mirror'    ,
+    'preprocess' => 'FCM::System::Make::Preprocess',
+);
+
+# Creates the class.
+__PACKAGE__->class(
+    {   cfg_base       => {isa => '$', default => $CFG_BASE},
+        name           => {isa => '$', default => $NAME},
+        shared_util_of => '%',
+        subsystem_of   => '%',
+        util           => '&',
+    },
+    {init => \&_init, action_of => {main => \&_main}},
+);
+
+# Initialises an instance.
+sub _init {
+    my $attrib_ref = shift();
+    for (
+        ['shared_util_of', \%SHARED_UTIL_OF],
+        ['subsystem_of'  , \%SUBSYSTEM_OF  ],
+    ) {
+        my ($key, $hash_ref) = @{$_};
+        while (my ($id, $class) = each(%{$hash_ref})) {
+            if (!exists($attrib_ref->{$key}{$id})) {
+                $attrib_ref->{$key}{$id} = $class->new({
+                    'shared_util_of' => $attrib_ref->{'shared_util_of'},
+                    'subsystem_of'   => $attrib_ref->{'subsystem_of'},
+                    'util'           => $attrib_ref->{'util'},
+                });
+            }
+        }
+    }
+    $attrib_ref->{util}->cfg_init(
+        $attrib_ref->{cfg_base},
+        sub {
+            my $config_reader = shift();
+            my @unknown_entries;
+            while (defined(my $entry = $config_reader->())) {
+                my ($id, $label) = split(qr{\.}msx, $entry->get_label(), 2);
+                if (exists($attrib_ref->{subsystem_of}{$id})) {
+                    my $subsystem = $attrib_ref->{subsystem_of}{$id};
+                    if (!$subsystem->config_parse_class_prop($entry, $label)) {
+                        push(@unknown_entries, $entry);
+                    }
+                }
+                else {
+                    push(@unknown_entries, $entry);
+                }
+            }
+            if (@unknown_entries) {
+                return $E->throw($E->CONFIG_UNKNOWN, \@unknown_entries);
+            }
+        },
+    );
+}
+
+# Sets up the destination.
+sub _config_parse {
+    my ($attrib_ref, $m_ctx, @args) = @_;
+    my $entry_callback_ref = sub {
+        my ($entry) = @_;
+        print({$attrib_ref->{handle_cfg}} $entry->as_string(), "\n");
+    };
+    $attrib_ref->{shared_util_of}{config}->parse(
+        $entry_callback_ref, $m_ctx, @args,
+    );
+}
+
+# Sets up the destination.
+sub _dest_init {
+    my ($attrib_ref, $m_ctx) = @_;
+    $attrib_ref->{shared_util_of}{dest}->dest_init($m_ctx);
+
+    # Move temporary log file to destination
+    my $now = strftime("%Y%m%dT%H%M%S", gmtime());
+    my $log = $attrib_ref->{shared_util_of}{dest}->path($m_ctx, 'sys-log');
+    my $log_actual = sprintf("%s-%s", $log, $now);
+    _symlink($log_actual, $log);
+    (       close($attrib_ref->{handle_log})
+        &&  copy($attrib_ref->{handle_log}->filename(), $log)
+        &&  open(my $handle_log, '>>', $log)
+    ) || return $E->throw($E->DEST_CREATE, $log, $!);
+    _symlink(
+        $FCM::System::Make::Share::Dest::PATH_OF{'sys-log'},
+        $attrib_ref->{shared_util_of}{dest}->path($m_ctx, 'sys-log-symlink'),
+    );
+    my $log_ctx = $attrib_ref->{util}->util_of_report()->get_ctx($m_ctx);
+    $log_ctx->set_handle($handle_log);
+
+    # Saves as parsed config
+    my $cfg = $attrib_ref->{shared_util_of}{dest}->path(
+        $m_ctx, 'sys-config-as-parsed',
+    );
+    (       close($attrib_ref->{handle_cfg})
+        &&  copy($attrib_ref->{handle_cfg}->filename(), $cfg)
+    ) || return $E->throw($E->DEST_CREATE, $cfg, $!);
+    _symlink(
+        $FCM::System::Make::Share::Dest::PATH_OF{'sys-config-as-parsed'},
+        $attrib_ref->{shared_util_of}{dest}->path(
+            $m_ctx,
+            'sys-config-as-parsed-symlink',
+        ),
+    );
+}
+
+# The main function of an instance of this class.
+sub _main {
+    my ($attrib_ref, $option_hash_ref, @args) = @_;
+    my @bad_args;
+    for my $i (0 .. $#args) {
+        if (index($args[$i], "=") < 0) {
+            push(@bad_args, [$i, $args[$i]]);
+        }
+    }
+    if (@bad_args) {
+        return $E->throw($E->MAKE_ARG, \@bad_args);
+    }
+    # Starts the system
+    my $m_ctx = FCM::Context::Make->new({option_of => $option_hash_ref});
+    my $T = sub {_timer_wrap($attrib_ref, @_)};
+    eval {$T->(
+        sub {
+            my %attrib = (
+                %{$attrib_ref},
+                handle_log => File::Temp->new(),
+                handle_cfg => File::Temp->new(),
+            );
+            $attrib_ref->{util}->util_of_report()->add_ctx(
+                $m_ctx, # key
+                {   handle    => $attrib{handle_log},
+                    type      => undef,
+                    verbosity => $attrib_ref->{util}->util_of_report()->HIGH,
+                },
+            );
+            $attrib_ref->{util}->event(
+                FCM::Context::Event->FCM_VERSION,
+                $attrib_ref->{util}->version(),
+            );
+            for my $step (@INIT_STEPS) {
+                $T->(sub {$ACTION_OF{$step}->(\%attrib, $m_ctx, @args)}, $step);
+            }
+            my $prev_m_ctx = $m_ctx->get_prev_ctx();
+            if (defined($prev_m_ctx)) {
+                for my $step (keys(%{$prev_m_ctx->get_ctx_of()})) {
+                    if (!grep {$_ eq $step} @{$m_ctx->get_steps()}) {
+                        delete($prev_m_ctx->get_ctx_of()->{$step});
+                    }
+                }
+            }
+            for my $step (@{$m_ctx->get_steps()}) {
+                my $ctx = $m_ctx->get_ctx_of($step);
+                if (!defined($ctx)) {
+                    return $E->throw($E->MAKE, $step);
+                }
+                my $id_of_class = $ctx->get_id_of_class();
+                if (!exists($attrib_ref->{subsystem_of}{$id_of_class})) {
+                    return $E->throw($E->MAKE, $step);
+                }
+                my $impl = $attrib_ref->{subsystem_of}{$id_of_class};
+                $ctx->set_status($m_ctx->ST_INIT);
+                if ($ctx->can('set_dest')) {
+                    $ctx->set_dest(
+                        $attrib_ref->{shared_util_of}{dest}->path(
+                            $m_ctx, 'target', $ctx->get_id(),
+                        ),
+                    );
+                }
+                eval {$T->(sub {$impl->main($m_ctx, $ctx)}, $step)};
+                if (my $e = $@) {
+                    $ctx->set_status($m_ctx->ST_FAILED);
+                    die($e);
+                }
+                $ctx->set_status($m_ctx->ST_OK);
+                if (    defined($prev_m_ctx)
+                    &&  exists($prev_m_ctx->get_ctx_of()->{$step})
+                ) {
+                    delete($prev_m_ctx->get_ctx_of()->{$step});
+                }
+            }
+        },
+    )};
+    if (my $e = $@) {
+        $m_ctx->set_status($m_ctx->ST_FAILED);
+        $m_ctx->set_error($e);
+        $attrib_ref->{util}->event(FCM::Context::Event->E, $e);
+        _main_finally($attrib_ref, $m_ctx);
+        die("\n");
+    }
+    $m_ctx->set_status($m_ctx->ST_OK);
+    $attrib_ref->{shared_util_of}{dest}->save(
+        [$attrib_ref->{shared_util_of}{config}->unparse($m_ctx)],
+        $m_ctx,
+        'sys-config-on-success',
+    );
+    _symlink(
+        $FCM::System::Make::Share::Dest::PATH_OF{'sys-config-on-success'},
+        $attrib_ref->{shared_util_of}{dest}->path(
+            $m_ctx,
+            'sys-config-on-success-symlink',
+        ),
+    );
+    _main_finally($attrib_ref, $m_ctx);
+    return $m_ctx;
+}
+
+# Helper to run the "finally" part of "_main".
+sub _main_finally {
+    my ($attrib_ref, $m_ctx) = @_;
+    $m_ctx->set_inherit_ctx_list([]);
+    $m_ctx->set_prev_ctx(undef);
+    $attrib_ref->{shared_util_of}{dest}->dest_done($m_ctx);
+    my $log_ctx = $attrib_ref->{util}->util_of_report()->del_ctx($m_ctx);
+    close($log_ctx->get_handle());
+}
+
+# Wrap "symlink".
+sub _symlink {
+    my ($source, $target) = @_;
+    if (-l $target && readlink($target) eq $source) {
+        return;
+    }
+    if (-e $target || -l $target) {
+        rmtree($target);
+    }
+    symlink($source, $target) || return $E->throw($E->DEST_CREATE, $target, $!);
+}
+
+# Wraps a piece of code with timer events.
+sub _timer_wrap {
+    my ($attrib_ref, $code_ref, @names) = @_;
+    my @event_args = (
+        FCM::Context::Event->TIMER,
+        join(q{ }, $attrib_ref->{name}, @names),
+        time(),
+    );
+    $attrib_ref->{util}->event(@event_args);
+    my $timer = $attrib_ref->{util}->timer();
+    my $return = eval {wantarray() ? [$code_ref->()] : $code_ref->()};
+    my $e = $@;
+    $attrib_ref->{util}->event(@event_args, $timer->(), $e);
+    if ($e) {
+        die($e);
+    }
+    return (wantarray() ? @{$return} : $return);
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make;
+    my $system = FCM::System::Make->new(\%attrib);
+    $system->(\%option);
+
+
+=head1 DESCRIPTION
+
+Invokes the FCM make system.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Creates and returns a new instance. The %attrib may contain the following:
+
+=over 4
+
+=item cfg_base
+
+The base name of the common (site/user) configuration file. (default="make.cfg")
+
+=item name
+
+The name of this sub-system. (default="make")
+
+=item shared_util_of
+
+A HASH to map the names to the classes of the named helper utilities for the
+make system and its sub-systems. (default = %FCM::System::Make::SHARED_UTIL_OF)
+
+=item subsystem_of
+
+A HASH to map the names to the classes of the subsystems. (default =
+%FCM::System::Make::SUBSYSTEM_OF)
+
+=item util
+
+An instance of L<FCM::Util|FCM::Util>.
+
+=back
+
+=item $system->(\%option)
+
+Invokes a make. The %option may contain the following:
+
+=over 4
+
+=item config-file
+
+The path to the configuration file. (default = $PWD/fcm-make.cfg)
+
+=item ignore-lock
+
+This flag can be used to ignore the lock file. The system creates a lock file in
+the destination to prevent another command from running in the same destination.
+If this flag is set, the system will continue even if it encounters a lock file
+in the destination. (default = false)
+
+=item jobs
+
+The number of (child) jobs that can be used to run parallel tasks.
+
+=item new
+
+A flag to tell the system to perform a new make. (default = false, i.e.
+incremental make)
+
+=back
+
+Throws L<FCM::System::Exception|FCM::System::Exception> on error.
+
+=back
+
+=head1 SUBSYSTEMS
+
+A subsystem of the make system should be a CODE-based class that implements a
+particular set of methods. (Some of these methods can be imported from
+L<FCM::System::Make::Share::Subsystem|FCM::System::Make::Share::Subsystem>.) The
+methods that should be implemented are:
+
+=over 4
+
+=item $subsystem_class->new(\%attrib)
+
+Creates a new instance of the subsystem. The make system passes the
+I<shared_util_of>, I<subsystem_of> and I<util> attributes to this method.
+
+=item $subsystem->config_parse($ctx,$entry,$label)
+
+Reads the settings of $entry into the $ctx. The $label is the configuration
+entry label in the context of the subsystem. (This is normally the
+$entry->get_label() but with the context ID prefix removed.). Returns true on
+success.
+
+=item $subsystem->config_parse_inherit_hook($ctx,$i_ctx)
+
+This method is called when the make inherits from an existing make. The $ctx is
+the current subsystem context, and the $i_ctx is the inherited subsystem
+context. This method allows the subsystem to make use of the inherited settings
+in the current context.
+
+=item $subsystem->config_unparse($ctx)
+
+Returns a list of L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry> to
+represent the settings of the $ctx.
+
+=item $subsystem->ctx($id_of_class,$id)
+
+Returns a new context for the subsystem. The $id_of_class is the ID of the
+subsystem class. The $id is the step ID of the context.
+
+=item $subsystem->config_parse_class_prop($entry,$label)
+
+Reads a configuration $entry into the subsystem default property. The $label is
+the label of the $entry, but with the prefix (the subsystem ID plus a dot)
+removed.
+
+=item $subsystem->main($m_ctx,$ctx)
+
+Invokes the subsystem. The $m_ctx is the current context of the make (as a
+blessed reference of L<FCM::Context::Make|FCM::Context::Make>). The $ctx is the
+context of the subsystem.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build.pm b/lib/FCM/System/Make/Build.pm
new file mode 100644
index 0000000..8e55c03
--- /dev/null
+++ b/lib/FCM/System/Make/Build.pm
@@ -0,0 +1,1650 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build;
+use base qw{FCM::Class::CODE};
+
+use Cwd qw{cwd realpath};
+use FCM::Context::ConfigEntry;
+use FCM::Context::Event;
+use FCM::Context::Make::Build;
+use FCM::Context::Task;
+use FCM::System::Exception;
+use FCM::System::Make::Build::FileType::C;
+use FCM::System::Make::Build::FileType::CXX;
+use FCM::System::Make::Build::FileType::Data;
+use FCM::System::Make::Build::FileType::Fortran;
+use FCM::System::Make::Build::FileType::H;
+use FCM::System::Make::Build::FileType::NS;
+use FCM::System::Make::Build::FileType::Script;
+use FCM::System::Make::Share::Subsystem;
+use File::Basename qw{basename dirname};
+use File::Find qw{find};
+use File::Path qw{mkpath};
+use File::Spec::Functions qw{abs2rel catfile rel2abs splitdir splitpath};
+use Storable qw{dclone};
+use Text::ParseWords qw{shellwords};
+
+# Aliases
+our ($EVENT, $UTIL);
+my $E = 'FCM::System::Exception';
+my $STATE = 'FCM::System::Make::Build::State';
+
+# Classes for working with typed source files
+our @FILE_TYPE_UTILS = (
+    'FCM::System::Make::Build::FileType::C',
+    'FCM::System::Make::Build::FileType::CXX',
+    'FCM::System::Make::Build::FileType::Data',
+    'FCM::System::Make::Build::FileType::Fortran',
+    'FCM::System::Make::Build::FileType::H',
+    'FCM::System::Make::Build::FileType::NS',
+    'FCM::System::Make::Build::FileType::Script',
+);
+
+# Default target selection
+our %TARGET_SELECT_BY = (task => {});
+
+# Configuration parser label to action map
+our %CONFIG_PARSER_OF = (
+    'ns-excl' => _config_parse_ns_filter_func(sub {$_[0]->get_input_ns_excl()}),
+    'ns-incl' => _config_parse_ns_filter_func(sub {$_[0]->get_input_ns_incl()}),
+    'source'  => \&_config_parse_source,
+    'target'  => \&_config_parse_target,
+    'target-rename' => \&_config_parse_target_rename,
+);
+
+# Default properties
+our %PROP_OF = (
+    #                               [default       , ns-ok]
+    'ignore-missing-dep-ns'      => [q{}           , undef],
+    'no-step-source'             => [q{}           , undef],
+    'no-inherit-source'          => [q{}           , undef],
+    'no-inherit-target-category' => [q{bin etc lib}, undef],
+);
+
+# Creates the class.
+__PACKAGE__->class(
+    {   config_parser_of  => {isa => '%', default => {%CONFIG_PARSER_OF}},
+        file_type_utils   => {isa => '@', default => [@FILE_TYPE_UTILS]},
+        file_type_util_of => '%',
+        prop_of           => {isa => '%', default => {%PROP_OF}},
+        target_select_by  => {isa => '%', default => {%TARGET_SELECT_BY}},
+        util              => '&',
+    },
+    {   init => \&_init,
+        action_of => {
+            config_parse              => \&_config_parse,
+            config_parse_class_prop   => \&_config_parse_class_prop,
+            config_parse_inherit_hook => \&_config_parse_inherit_hook,
+            config_unparse            => \&_config_unparse,
+            config_unparse_class_prop => \&_config_unparse_class_prop,
+            ctx                       => \&_ctx,
+            main                      => \&_main,
+        },
+    },
+);
+
+# Initialises the helpers of the class.
+sub _init {
+    my ($attrib_ref) = @_;
+    # Initialises file type utilities, if necessary
+    for my $class (@{$attrib_ref->{file_type_utils}}) {
+        $attrib_ref->{util}->class_load($class);
+        my $file_type_util = $class->new({util => $attrib_ref->{util}});
+        my $id = $file_type_util->id();
+        if (!defined($attrib_ref->{file_type_util_of}{$id})) {
+            $attrib_ref->{file_type_util_of}{$id} = $file_type_util;
+        }
+    }
+    # Initialises properties derived from the file type utilities
+    # TBD: warn if a property is already set and is different from previous?
+    while (
+        my ($id, $file_type_util) = each(%{$attrib_ref->{file_type_util_of}})
+    ) {
+        # File name extension, name pattern and she-bang pattern
+        for my $key (qw{ext pat she}) {
+            my $method = 'file_' . $key;
+            if ($file_type_util->can($method)) {
+                my $value = $file_type_util->$method();
+                if (defined($value)) {
+                    $attrib_ref->{prop_of}{"file-$key.$id"} = [$value];
+                }
+            }
+        }
+        # Dependency types
+        if ($file_type_util->can('source_analyse_deps')) {
+            for my $name ($file_type_util->source_analyse_deps()) {
+                $attrib_ref->{prop_of}{"dep.$name"} = [q{}, 1];
+                $attrib_ref->{prop_of}{"no-dep.$name"} = [q{}, 1];
+            }
+        }
+        # Name-space dependency types
+        if ($file_type_util->can('ns_targets_deps')) {
+            for my $name ($file_type_util->ns_targets_deps()) {
+                $attrib_ref->{prop_of}{"ns-dep.$name"} = [q{}, 1];
+            }
+        }
+        # Target extensions
+        if ($file_type_util->can('target_file_ext_of')) {
+            while (my ($key, $value)
+                = each(%{$file_type_util->target_file_ext_of()})
+            ) {
+                $attrib_ref->{prop_of}{"file-ext.$key"} = [$value, 1];
+            }
+        }
+        # Target file naming options
+        if ($file_type_util->can('target_file_name_option_of')) {
+            while (my ($key, $value)
+                = each(%{$file_type_util->target_file_name_option_of()})
+            ) {
+                $attrib_ref->{prop_of}{"file-name-option.$key"} = [$value, 1];
+            }
+        }
+        # Task properties
+        my %task_of = %{$file_type_util->task_of()};
+        while (my ($name, $task) = each(%task_of)) {
+            if ($task->can('prop_of')) {
+                my %prop_of = %{$task->prop_of()};
+                while (my ($key, $value) = each(%prop_of)) {
+                    $attrib_ref->{prop_of}{$key} = [$value, 1];
+                }
+            }
+        }
+    }
+}
+
+# A hook command for the "inherit/use" declaration.
+sub _config_parse_inherit_hook {
+    my ($attrib_ref, $ctx, $i_ctx) = @_;
+    push(@{$ctx->get_input_ns_excl()}, @{$i_ctx->get_input_ns_excl()});
+    push(@{$ctx->get_input_ns_incl()}, @{$i_ctx->get_input_ns_incl()});
+    while (my ($key, $value) = each(%{$i_ctx->get_target_key_of()})) {
+        $ctx->get_target_key_of()->{$key} = $value;
+    }
+    while (my ($key, $value) = each(%{$i_ctx->get_target_select_by()})) {
+        $ctx->get_target_select_by()->{$key} = dclone($value);
+    }
+    _config_parse_inherit_hook_prop($attrib_ref, $ctx, $i_ctx);
+}
+
+# Returns a function to parse a build/preprocess.ns-??cl declaration.
+sub _config_parse_ns_filter_func {
+    my ($getter) = @_;
+    sub {
+        my ($attrib_ref, $ctx, $entry) = @_;
+        if (@{$entry->get_ns_list()}) {
+            return $E->throw($E->CONFIG_NS, $entry);
+        }
+        @{$getter->($ctx)} = map {$_ eq q{/} ? q{} : $_} $entry->get_values();
+    };
+}
+
+# Parses a build/preprocess.source declaration.
+sub _config_parse_source {
+    my ($attrib_ref, $ctx, $entry) = @_;
+    my ($ns) = @{$entry->get_ns_list()};
+    $ns ||= q{};
+    $ctx->get_input_source_of()->{$ns} = [$entry->get_values()];
+}
+
+# Parses a build/preprocess.target declaration.
+sub _config_parse_target {
+    my ($attrib_ref, $ctx, $entry) = @_;
+    my %modifier_of = %{$entry->get_modifier_of()};
+    if (!keys(%modifier_of)) {
+        %modifier_of = (key => 1);
+    }
+    while (my $name = each(%modifier_of)) {
+        if (!grep {$_ eq $name} qw{category key ns task}) {
+            return $E->throw($E->CONFIG_MODIFIER, $entry);
+        }
+        $ctx->get_target_select_by()->{$name}
+            = {map {$_ eq q{/} ? (q{} => 1) : ($_ => 1)} $entry->get_values()};
+    }
+}
+
+# Parses a build/preprocess.target-rename declaration.
+sub _config_parse_target_rename {
+    my ($attrib_ref, $ctx, $entry) = @_;
+    $ctx->set_target_key_of({
+        map {
+            my ($old, $new) = split(qr{:}msx, $_, 2);
+            if (!$old || !$new) {
+                return $E->throw($E->CONFIG_VALUE, $entry);
+            }
+            ($old => $new);
+        } ($entry->get_values()),
+    });
+}
+
+# Turns a context into a list of configuration entries.
+sub _config_unparse {
+    my ($attrib_ref, $ctx) = @_;
+    my %LABEL_OF
+        = map {($_ => $ctx->get_id() . q{.} . $_)} keys(%CONFIG_PARSER_OF);
+    (   (   @{$ctx->get_input_ns_excl()}
+            ? FCM::Context::ConfigEntry->new({
+                label => $LABEL_OF{'ns-excl'},
+                value => _config_unparse_join(
+                    map {$_ ? $_ : q{/}} @{$ctx->get_input_ns_excl()}
+                ),
+            })
+            : ()
+        ),
+        (   @{$ctx->get_input_ns_incl()}
+            ? FCM::Context::ConfigEntry->new({
+                label => $LABEL_OF{'ns-incl'},
+                value => _config_unparse_join(
+                    map {$_ ? $_ : q{/}} @{$ctx->get_input_ns_incl()}
+                ),
+            })
+            : ()
+        ),
+        (   map {
+                FCM::Context::ConfigEntry->new({
+                    label   => $LABEL_OF{source},
+                    ns_list => [$_],
+                    value   => _config_unparse_join(
+                        sort(@{$ctx->get_input_source_of()->{$_}})
+                    ),
+                })
+            }
+            sort keys(%{$ctx->get_input_source_of()})
+        ),
+        (   keys(%{$ctx->get_target_key_of()})
+            ? FCM::Context::ConfigEntry->new({
+                label => $LABEL_OF{'target-rename'},
+                value => _config_unparse_join(
+                    map {$_ . ':' . $ctx->get_target_key_of()->{$_}}
+                    sort keys(%{$ctx->get_target_key_of()})
+                ),
+            })
+            : ()
+        ),
+        (   map {
+                FCM::Context::ConfigEntry->new({
+                    label       => $LABEL_OF{'target'},
+                    modifier_of => {$_ => 1},
+                    value       => _config_unparse_join(
+                        keys(%{$ctx->get_target_select_by()->{$_}}),
+                    ),
+                });
+            }
+            sort keys(%{$ctx->get_target_select_by()})
+        ),
+        _config_unparse_prop($attrib_ref, $ctx),
+    );
+}
+
+# Returns a new context.
+sub _ctx {
+    my ($attrib_ref, $id_of_class, $id) = @_;
+    FCM::Context::Make::Build->new({
+        id               => $id,
+        id_of_class      => $id_of_class,
+        target_select_by => dclone($attrib_ref->{target_select_by}),
+    });
+}
+
+# The main function of the class.
+sub _main {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    local($UTIL) = $attrib_ref->{util};
+    local($EVENT) = sub {$UTIL->event(@_)};
+    for my $function (
+        \&_sources_locate,
+        \&_sources_type,
+        \&_sources_analyse,
+        \&_targets_update,
+    ) {
+        $function->($attrib_ref, $m_ctx, $ctx);
+    }
+}
+
+# Locates the actual source files, and determines their types.
+sub _sources_locate {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    # From inherited
+    my %NO_INHERIT_FROM
+        = map {($_ => 1)} _props($attrib_ref, 'no-inherit-source', $ctx);
+    if (!$NO_INHERIT_FROM{'*'}) {
+        for my $i_ctx (_i_ctx_list($m_ctx, $ctx)) {
+            while (my ($ns, $source) = each(%{$i_ctx->get_source_of()})) {
+                if (!exists($NO_INHERIT_FROM{$ns})) { # exact name-spaces only
+                    $ctx->get_source_of()->{$ns} = dclone($source);
+                }
+            }
+        }
+    }
+    # From specified input
+    while (my ($ns, $input_sources_ref) = each(%{$ctx->get_input_source_of()})) {
+        for my $input_source (@{$input_sources_ref}) {
+            my $path = realpath(rel2abs($input_source, $m_ctx->get_dest()));
+            _sources_locate_by_find($attrib_ref, $m_ctx, $ctx, $ns, $path);
+        }
+    }
+    # From completed make destinations
+    my %NO_SOURCE_FROM
+        = map {($_, 1)} _props($attrib_ref, 'no-step-source', $ctx);
+    for my $step (@{$m_ctx->get_steps()}) {
+        my $a_ctx = $m_ctx->get_ctx_of($step);
+        if (    !exists($NO_SOURCE_FROM{$step})
+            &&  defined($a_ctx)
+            &&  $a_ctx->get_status() eq $m_ctx->ST_OK
+            &&  $a_ctx->can('get_target_of')
+        ) {
+            my @target_list
+                = grep {$_->can_be_source()} values(%{$a_ctx->get_target_of()});
+            for my $target (@target_list) {
+                if ($target->is_ok() && -e $target->get_path()) {
+                    my $checksum;
+                    if ($target->can('get_checksum')) {
+                        $checksum = $target->get_checksum();
+                    }
+                    my $source = $ctx->CTX_SOURCE->new({
+                        checksum => $checksum,
+                        ns       => $target->get_ns(),
+                        path     => $target->get_path(),
+                    });
+                    $ctx->get_source_of()->{$target->get_ns()} = $source;
+                }
+                elsif (exists($ctx->get_source_of()->{$target->get_ns()})) {
+                    delete($ctx->get_source_of()->{$target->get_ns()});
+                }
+            }
+        }
+    }
+    # Applies filter
+    my %INPUT_NS_EXCL = map {($_, 1)} @{$ctx->get_input_ns_excl()};
+    my %INPUT_NS_INCL = map {($_, 1)} @{$ctx->get_input_ns_incl()};
+    if (keys(%INPUT_NS_EXCL) || keys(%INPUT_NS_INCL)) {
+        while (my ($ns, $source) = each(%{$ctx->get_source_of()})) {
+            my $ns_iter_ref = $UTIL->ns_iter($ns, $UTIL->NS_ITER_UP);
+            NS:
+            while (defined(my $head = $ns_iter_ref->())) {
+                if (exists($INPUT_NS_INCL{$head})) {
+                    last NS;
+                }
+                if (exists($INPUT_NS_EXCL{$head})) {
+                    delete($ctx->get_source_of()->{$ns});
+                    last NS;
+                }
+            }
+        }
+    }
+}
+
+# Locates the actual source files in $path.
+sub _sources_locate_by_find {
+    my ($attrib_ref, $m_ctx, $ctx, $key, $path) = @_;
+    if (!-e $path) {
+        return $E->throw($E->BUILD_SOURCE, $path, $!);
+    }
+    find(
+        sub {
+            my $path_found = $File::Find::name;
+            if (-d $path_found) {
+                return;
+            }
+            my ($vol, $dir_name, $base) = splitpath($path_found);
+            for my $name (splitdir($dir_name), $base) {
+                if (index($name, q{.}) == 0) {
+                    return; # ignore Unix hidden/system files
+                }
+            }
+            my $ns = abs2rel($path_found, $path);
+            if ($key) {
+                $ns = $UTIL->ns_cat($key, $ns);
+            }
+            $ctx->get_source_of()->{$ns}
+                = $ctx->CTX_SOURCE->new({ns => $ns, path => $path_found});
+        },
+        $path,
+    );
+}
+
+# Determines source types.
+sub _sources_type {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    my %INPUT_FILE_EXT_TO_TYPE_MAP;
+    my %INPUT_FILE_PAT_TO_TYPE_MAP;
+    my %INPUT_FILE_SHE_TO_TYPE_MAP;
+    for (
+        ['file-ext.', \%INPUT_FILE_EXT_TO_TYPE_MAP, 1],
+        ['file-pat.', \%INPUT_FILE_PAT_TO_TYPE_MAP, 0],
+        ['file-she.', \%INPUT_FILE_SHE_TO_TYPE_MAP, 0],
+    ) {
+        my ($prefix, $map_ref, $value_is_words) = @{$_};
+        for my $id (keys(%{$attrib_ref->{file_type_util_of}})) {
+            my $name = $prefix . $id;
+            my $value = _prop($attrib_ref, $name, $ctx);
+            if (defined($value)) {
+                for my $key (($value_is_words ? shellwords($value) : ($value))) {
+                    $map_ref->{$key} = $id;
+                }
+            }
+        }
+    }
+    my $type_func = sub {
+        my ($path) = @_;
+        # Try file name extension
+        my $extension = $UTIL->file_ext($path);
+        $extension = $extension ? q{.} . $extension : undef;
+        if ($extension && exists($INPUT_FILE_EXT_TO_TYPE_MAP{$extension})) {
+            return $INPUT_FILE_EXT_TO_TYPE_MAP{$extension};
+        }
+        # Try she-bang line
+        if (-T $path) {
+            my $line = $UTIL->file_head($path);
+            if ($line) {
+                while (my ($pattern, $type) = each(%INPUT_FILE_SHE_TO_TYPE_MAP)) {
+                    if (index($line, '#!') == 0) { # OK to hard code this
+                        keys(%INPUT_FILE_SHE_TO_TYPE_MAP); # reset iterator
+                        return $type;
+                    }
+                }
+            }
+        }
+        # Try file name pattern
+        my $base_name = basename($path);
+        while (my ($pattern, $type) = each(%INPUT_FILE_PAT_TO_TYPE_MAP)) {
+            if ($base_name =~ $pattern) {
+                keys(%INPUT_FILE_PAT_TO_TYPE_MAP); # reset iterator
+                return $type;
+            }
+        }
+        return q{};
+    };
+    while (my ($ns, $source) = each(%{$ctx->get_source_of()})) {
+        if (!defined($source->get_type())) {
+            $source->set_type($type_func->($source->get_path()));
+        }
+    }
+}
+
+# Reads source files to gather dependency and other information.
+sub _sources_analyse {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    my $timer = $UTIL->timer();
+    my %FILE_TYPE_UTIL_OF = %{$attrib_ref->{file_type_util_of}};
+    # Checksum
+    while (my ($ns, $source) = each(%{$ctx->get_source_of()})) {
+        if (    exists($FILE_TYPE_UTIL_OF{$source->get_type()})
+            &&  !defined($source->get_checksum())
+        ) {
+            $source->set_checksum($UTIL->file_md5($source->get_path()));
+        }
+    }
+    # Source information
+    my $n_jobs = $m_ctx->get_option_of('jobs');
+    my $runner = $UTIL->task_runner(
+        sub {_source_analyse($attrib_ref, @_)},
+        $n_jobs,
+    );
+    my $elapse_tasks = 0;
+    my $n = eval {
+        $runner->main(
+            _source_analyse_get_func($attrib_ref, $m_ctx, $ctx),
+            _source_analyse_put_func($attrib_ref, $m_ctx, $ctx, \$elapse_tasks),
+        );
+    };
+    my $e = $@;
+    $runner->destroy();
+    if ($e) {
+        die($e);
+    }
+    my $n_total = scalar(keys(%{$ctx->get_source_of()}));
+    $EVENT->(
+        FCM::Context::Event->MAKE_BUILD_SOURCE_SUMMARY,
+        $n_total, $n, $timer->(), $elapse_tasks,
+    );
+}
+
+# Reads a source to gather information.
+sub _source_analyse {
+    my ($attrib_ref, $source) = @_;
+    my $FILE_TYPE_UTIL
+        = $attrib_ref->{file_type_util_of}->{$source->get_type()};
+    if (!$FILE_TYPE_UTIL->can('source_analyse')) {
+        return;
+    }
+    $FILE_TYPE_UTIL->source_analyse($source);
+}
+
+# Generates an iterator for each source file requiring information gathering.
+sub _source_analyse_get_func {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    my $P_SOURCE_GETTER
+        = _prev_hash_item_getter($m_ctx, $ctx, sub {$_[0]->get_source_of()});
+    my %FILE_TYPE_UTIL_OF = %{$attrib_ref->{file_type_util_of}};
+    my $exhausted;
+    sub {
+        if ($exhausted) {
+            return;
+        }
+        SOURCE:
+        while (my ($ns, $source) = each(%{$ctx->get_source_of()})) {
+            my $type = $source->get_type();
+            if (!exists($FILE_TYPE_UTIL_OF{$type})) {
+                next SOURCE;
+            }
+            # Stores the current properties relevant to the source
+            for my $dep_type ($FILE_TYPE_UTIL_OF{$type}->source_analyse_deps()) {
+                for my $n (map {$_ . q{.} . $dep_type} qw{dep no-dep}) {
+                    $source->get_prop_of()->{$n}
+                        = _prop($attrib_ref, $n, $ctx, $ns);
+                }
+            }
+            # Compare with previous source, if possible
+            my $p_source = $P_SOURCE_GETTER->($ns);
+            if (defined($p_source)) {
+                $source->set_up_to_date(
+                    $p_source->get_checksum() eq $source->get_checksum());
+                if (    $source->get_up_to_date()
+                    &&  !$UTIL->hash_cmp(
+                            map {$_->get_prop_of()} ($source, $p_source)
+                        )
+                ) {
+                    $source->set_info_of(dclone($p_source->get_info_of()));
+                    $source->set_deps(   dclone($p_source->get_deps()   ));
+                    next SOURCE;
+                }
+            }
+            return FCM::Context::Task->new({ctx => $source, id  => $ns});
+        }
+        $exhausted = 1;
+        return;
+    };
+}
+
+# Generates a callback when a source read completes.
+sub _source_analyse_put_func {
+    my ($attrib_ref, $m_ctx, $ctx, $elapse_tasks_ref) = @_;
+    my %FILE_TYPE_UTIL_OF = %{$attrib_ref->{file_type_util_of}};
+    sub {
+        my ($task) = @_;
+        if ($task->get_state() eq $task->ST_FAILED) {
+            die($task->get_error());
+        }
+        my $ns = $task->get_id();
+        my $source = $ctx->get_source_of()->{$ns} = $task->get_ctx();
+        for my $type (
+            $FILE_TYPE_UTIL_OF{$source->get_type()}->source_analyse_deps()
+        ) {
+            # Note: "dep" property: use name-space value only
+            my $key = 'dep.' . $type;
+            push(
+                @{$source->get_deps()},
+                (map {[$_, $type]} _props($attrib_ref, $key, $ctx, $ns)),
+            );
+        }
+        ${$elapse_tasks_ref} += $task->get_elapse();
+        $EVENT->(
+            FCM::Context::Event->MAKE_BUILD_SOURCE_ANALYSE,
+            $source, $task->get_elapse(),
+        );
+    }
+}
+
+# Updates the targets.
+sub _targets_update {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    my $timer = $UTIL->timer();
+    # Creates and changes directory to the destination
+    eval {mkpath($ctx->get_dest())};
+    if ($@) {
+        return $E->throw($E->DEST_CREATE, $ctx->get_dest());
+    }
+    my $old_cwd = cwd();
+    chdir($ctx->get_dest()) || die(sprintf("%s: %s\n", $ctx->get_dest(), $!));
+    # Determines the destination search path
+    my $id = $ctx->get_id();
+    @{$ctx->get_dests()} = (
+        $ctx->get_dest(),
+        map {$_->get_ctx_of($id) ? @{$_->get_ctx_of($id)->get_dests()} : ()}
+            @{$m_ctx->get_inherit_ctx_list()}
+        ,
+    );
+    # Performs targets update
+    my %stat_of = ();
+    eval {
+        my $n_jobs = $m_ctx->get_option_of('jobs');
+        my $runner = $UTIL->task_runner(
+            sub {_target_update($attrib_ref, @_)},
+            $n_jobs,
+        );
+        eval {
+            my ($get_ref, $put_ref) = _targets_manager_funcs(
+                 $attrib_ref, $m_ctx, $ctx, \%stat_of,
+            );
+            $runner->main($get_ref, $put_ref);
+        };
+        my $e = $@;
+        $runner->destroy();
+        if ($e) {
+            die($e);
+        }
+    };
+    my $e = $@;
+    # Back to original working directory
+    chdir($old_cwd) || die(sprintf("%s: %s\n", $old_cwd, $!));
+    if ($e) {
+        die($e);
+    }
+    # Finally
+    my @targets = values(%{$ctx->get_target_of()});
+    for my $key (sort(keys(%stat_of))) {
+        $stat_of{$key}{n}{$ctx->CTX_TARGET->ST_MODIFIED} ||= 0;
+        $stat_of{$key}{n}{$ctx->CTX_TARGET->ST_UNCHANGED} ||= 0;
+        $stat_of{$key}{n}{$ctx->CTX_TARGET->ST_FAILED} ||= 0;
+        $stat_of{$key}{t} ||= 0.0;
+        $EVENT->(
+            FCM::Context::Event->MAKE_BUILD_TARGET_TASK_SUMMARY,
+            $key,
+            $stat_of{$key}{n}{$ctx->CTX_TARGET->ST_MODIFIED},
+            $stat_of{$key}{n}{$ctx->CTX_TARGET->ST_UNCHANGED},
+            $stat_of{$key}{n}{$ctx->CTX_TARGET->ST_FAILED},
+            $stat_of{$key}{t},
+        );
+    }
+    $EVENT->(
+        FCM::Context::Event->MAKE_BUILD_TARGET_SUMMARY,
+        scalar(grep {$_->is_modified() } @targets),
+        scalar(grep {$_->is_unchanged()} @targets),
+        scalar(grep {$_->is_failed()   } @targets),
+        $timer->(),
+    );
+    my @failed_targets = grep {$_->is_failed()} @targets;
+    if (@failed_targets) {
+        $EVENT->(
+            FCM::Context::Event->MAKE_BUILD_TARGETS_FAIL,
+            \@failed_targets
+        );
+        die("\n");
+    }
+}
+
+# Updates a target.
+sub _target_update {
+    my ($attrib_ref, $target) = @_;
+    my $file_type_util = $attrib_ref->{file_type_util_of}{$target->get_type()};
+    eval {$file_type_util->task_of()->{$target->get_task()}->main($target)};
+    if ($@) {
+        if ($target->get_path() && -e $target->get_path()) {
+            unlink($target->get_path());
+        }
+        die($@);
+    }
+    if (! -e $target->get_path()) {
+        return $E->throw($E->BUILD_TARGET, $target);
+    }
+    $target->set_status($target->ST_MODIFIED);
+    my $checksum = $UTIL->file_md5($target->get_path());
+    if ($target->get_checksum() && $checksum eq $target->get_checksum()) {
+        $target->set_status($target->ST_UNCHANGED);
+        if ($target->get_path_of_prev()) {
+            $target->set_path($target->get_path_of_prev());
+        }
+    }
+    $target->set_checksum($checksum);
+    $target->set_prop_of_prev_of({}); # unset
+    $target->set_path_of_prev(undef); # unset
+}
+
+# Returns the get/put functions to send/receive targets to update.
+sub _targets_manager_funcs {
+    my ($attrib_ref, $m_ctx, $ctx, $stat_hash_ref) = @_;
+
+    my @targets;
+    _targets_from_sources($attrib_ref, $m_ctx, $ctx, \@targets);
+    _targets_props_assign($attrib_ref, $m_ctx, $ctx, \@targets);
+
+    my ($stack_ref, $state_hash_ref)
+        = _targets_select($attrib_ref, $m_ctx, $ctx, \@targets);
+
+    my $get_action_ref = sub {
+        STATE:
+        while (my $state = pop(@{$stack_ref})) {
+            if (    !$state->is_ready()
+                ||  !_target_deps_are_done($state, $state_hash_ref, $stack_ref)
+            ) {
+                next STATE;
+            }
+            my $target = $state->get_target();
+            if (_target_check_failed_dep($state, $state_hash_ref)) {
+                _target_update_failed(
+                    $stat_hash_ref, $ctx, $target, $state_hash_ref, $stack_ref,
+                );
+            }
+            elsif (_target_check_ood($state, $state_hash_ref)) {
+                _target_prep($state, $ctx);
+                $state->set_value($STATE->PENDING);
+                # Adds tasks that can be triggered by this task
+                for my $key (@{$target->get_triggers()}) {
+                    if (    exists($state_hash_ref->{$key})
+                        &&  !$state_hash_ref->{$key}->is_done()
+                        &&  !grep {$_->get_id() eq $key} @{$stack_ref}
+                    ) {
+                        my $trigger_target
+                            = $state_hash_ref->{$key}->get_target();
+                        $trigger_target->set_status($trigger_target->ST_OOD);
+                        push(@{$stack_ref}, $state_hash_ref->{$key});
+                    }
+                }
+                return FCM::Context::Task->new(
+                    {ctx => $target, id => $state->get_id()},
+                );
+            }
+            else {
+                _target_update_ok(
+                    $stat_hash_ref, $ctx, $target, $state_hash_ref, $stack_ref,
+                );
+            }
+        }
+        return;
+    };
+    my $put_action_ref = sub {
+        my $task = shift();
+        my $target = $task->get_ctx();
+        if ($task->get_state() eq $task->ST_FAILED) {
+            $EVENT->(FCM::Context::Event->E, $task->get_error());
+            _target_update_failed(
+                $stat_hash_ref, $ctx, $target, $state_hash_ref, $stack_ref,
+                $task->get_elapse(),
+            );
+        }
+        else {
+            my $target = $task->get_ctx();
+            _target_update_ok(
+                $stat_hash_ref, $ctx, $target, $state_hash_ref, $stack_ref,
+                $task->get_elapse(),
+            );
+        }
+    };
+    ($get_action_ref, $put_action_ref);
+}
+
+# Determines and returns the targets from the sources.
+sub _targets_from_sources {
+    my ($attrib_ref, $m_ctx, $ctx, $targets_ref) = @_;
+    my %FILE_TYPE_UTIL_OF = %{$attrib_ref->{file_type_util_of}};
+    my %FILE_EXT_OF;
+    my %FILE_NAME_OPTION_OF;
+    for my $FILE_TYPE_UTIL (values(%FILE_TYPE_UTIL_OF)) {
+        while (my $key = each(%{$FILE_TYPE_UTIL->target_file_ext_of()})) {
+            $FILE_EXT_OF{$key} ||= _prop($attrib_ref, 'file-ext.' . $key, $ctx);
+        }
+        while (my $key = each(%{$FILE_TYPE_UTIL->target_file_name_option_of()})) {
+            $FILE_NAME_OPTION_OF{$key}
+                ||= _prop($attrib_ref, 'file-name-option.' . $key, $ctx);
+        }
+    }
+    # Determine the targets for each source
+    #my %target_ns_list_of;
+    SOURCE:
+    while (my ($ns, $source) = each(%{$ctx->get_source_of()})) {
+        my $type = $source->get_type();
+        $type ||= q{};
+        if (!exists($FILE_TYPE_UTIL_OF{$type})) {
+            next SOURCE;
+        }
+        my $FILE_TYPE_UTIL = $FILE_TYPE_UTIL_OF{$type};
+        if (!$FILE_TYPE_UTIL->can('source_to_targets')) {
+            next SOURCE;
+        }
+        for my $target (
+            $FILE_TYPE_UTIL->source_to_targets(
+                $source, \%FILE_EXT_OF, \%FILE_NAME_OPTION_OF)
+        ) {
+            my $key = $target->get_key();
+            if (exists($ctx->get_target_key_of()->{$key})) {
+                $key = $ctx->get_target_key_of()->{$key};
+                $target->set_key($key);
+            }
+            push(@{$targets_ref}, $target);
+            $target->set_ns($ns);
+            $target->set_path(
+                catfile($ctx->get_dest(), $target->get_category(), $key),
+            );
+            $target->set_path_of_source($source->get_path());
+            $target->set_type($type);
+            if (!$source->get_up_to_date()) {
+                $target->set_status($target->ST_OOD);
+            }
+        }
+    }
+    # Determines name-space dependencies
+    my %deps_in_ns_in_cat_of; # $cat => {$ns => [$targets ...]}
+    FILE_TYPE_UTIL:
+    while (my ($type, $FILE_TYPE_UTIL) = each(%FILE_TYPE_UTIL_OF)) {
+        if (!$FILE_TYPE_UTIL->can('ns_targets')) {
+            next FILE_TYPE_UTIL;
+        }
+        for my $cat ($FILE_TYPE_UTIL->ns_targets_deps()) {
+            $deps_in_ns_in_cat_of{$cat} = {};
+        }
+        for my $target (
+            $FILE_TYPE_UTIL->ns_targets(
+                $targets_ref, \%FILE_EXT_OF, \%FILE_NAME_OPTION_OF)
+        ) {
+            my $key = $target->get_key();
+            if (exists($ctx->get_target_key_of()->{$key})) {
+                $key = $ctx->get_target_key_of()->{$key};
+                $target->set_key($key);
+            }
+            push(@{$targets_ref}, $target);
+            $target->set_type($type);
+            $target->set_path(
+                catfile($ctx->get_dest(), $target->get_category(), $key),
+            );
+        }
+    }
+    for my $target (
+        sort {
+            $a->get_ns() cmp $b->get_ns() || $a->get_key() cmp $b->get_key();
+        } @{$targets_ref}
+    ) {
+        $EVENT->(
+            FCM::Context::Event->MAKE_BUILD_TARGET_FROM_NS,
+            ($target->get_ns() ? $target->get_ns() : '/'),
+            $target->get_task(),
+            $target->get_category(),
+            $target->get_key(),
+        );
+    }
+    # Target categories and name-spaces.
+    for my $target (@{$targets_ref}) {
+        my $cat = $target->get_category();
+        if ($cat && exists($deps_in_ns_in_cat_of{$cat})) {
+            my $ns_iter = $UTIL->ns_iter($target->get_ns(), $UTIL->NS_ITER_UP);
+            # $ns_iter->(); # discard
+            while (defined(my $ns = $ns_iter->())) {
+                $deps_in_ns_in_cat_of{$cat}{$ns} ||= [];
+                push(@{$deps_in_ns_in_cat_of{$cat}{$ns}}, $target->get_key());
+            }
+        }
+    }
+    # Adds categorised name-space dependencies.
+    TARGET:
+    for my $target (@{$targets_ref}) {
+        if (!exists($target->get_info_of()->{'deps'})) {
+            next TARGET;
+        }
+        CATEGORY:
+        while (my ($cat, $deps_in_ns_ref) = each(%deps_in_ns_in_cat_of)) {
+            if (!exists($target->get_info_of()->{'deps'}{$cat})) {
+                next CATEGORY;
+            }
+            my $name = 'ns-dep.' . $cat;
+            my @ns_list = map {$_ eq q{/} ? q{} : $_}
+                _props($attrib_ref, $name, $ctx, $target->get_ns());
+            for my $ns (@ns_list) {
+                if (exists($deps_in_ns_ref->{$ns})) {
+                    push(
+                        @{$target->get_deps()},
+                        (   map  {[$_, $cat]}
+                            grep {$_ ne $target->get_key()}
+                            @{$deps_in_ns_ref->{$ns}}
+                        ),
+                    );
+                }
+                else {
+                    # This will be reported later as missing dependency
+                    push(@{$target->get_deps()}, [$ns, $cat, 'ns-dep']);
+                }
+            }
+        }
+    }
+}
+
+# Stores the properties relevant to the target.
+# Assigns previous checksum and properties, where appropriate.
+sub _targets_props_assign {
+    my ($attrib_ref, $m_ctx, $ctx, $targets_ref) = @_;
+    my $P_TARGET_GETTER
+        = _prev_hash_item_getter($m_ctx, $ctx, sub {$_[0]->get_target_of()});
+    my %NO_INHERIT_CATEGORY_IN
+        = map {$_ => 1} _props($attrib_ref, 'no-inherit-target-category', $ctx);
+    my %CTX_PROP_OF = %{$ctx->get_prop_of()};
+    for my $target (@{$targets_ref}) {
+        # Properties
+        my $FILE_TYPE_UTIL
+            = $attrib_ref->{file_type_util_of}->{$target->get_type()};
+        my $task = $FILE_TYPE_UTIL->task_of()->{$target->get_task()};
+        my $key = $target->get_key();
+        if ($task->can('prop_of')) {
+            my %prop_of = %{$task->prop_of($target)};
+            while (my $name = each(%prop_of)) {
+                if (    exists($CTX_PROP_OF{$name})
+                    &&  exists($CTX_PROP_OF{$name}->get_ctx_of()->{$key})
+                ) {
+                    $target->get_prop_of()->{$name}
+                        = $CTX_PROP_OF{$name}->get_ctx_of()->{$key}->get_value();
+                }
+                else {
+                    $target->get_prop_of()->{$name}
+                        = _prop($attrib_ref, $name, $ctx, $target->get_ns());
+                }
+            }
+        }
+        if ($FILE_TYPE_UTIL->can('target_deps_filter')) {
+            $FILE_TYPE_UTIL->target_deps_filter($target);
+        }
+        # Path, checksum and previous properties
+        my $p_target = $P_TARGET_GETTER->($key);
+        if (defined($p_target)) {
+            $target->set_checksum($p_target->get_checksum());
+            if ($p_target->is_ok()) {
+                $target->set_path_of_prev($p_target->get_path());
+                $target->set_prop_of_prev_of($p_target->get_prop_of());
+            }
+            else {
+                $target->set_path_of_prev($p_target->get_path_of_prev());
+                $target->set_prop_of_prev_of($p_target->get_prop_of_prev_of());
+                $target->set_status($target->ST_OOD);
+            }
+            if (exists($NO_INHERIT_CATEGORY_IN{$target->get_category()})) {
+                $target->set_path_of_prev($target->get_path());
+            }
+        }
+    }
+}
+
+# Selects targets to update.
+sub _targets_select {
+    my ($attrib_ref, $m_ctx, $ctx, $targets_ref) = @_;
+    my $time = time();
+    my $timer = $UTIL->timer();
+    my %select_by = %{$ctx->get_target_select_by()};
+    my %target_of;
+    my %targets_of;
+    my %target_set;
+    my %has_ns_in; # available sets of name-spaces
+    for my $target (@{$targets_ref}) {
+        if (    exists($select_by{key}{$target->get_key()})
+            ||      (       !exists($select_by{category})
+                        ||  exists($select_by{category}{$target->get_category()})
+                    )
+                &&  (       !exists($select_by{task})
+                        ||  exists($select_by{task}{$target->get_task()})
+                    )
+                &&  (       !exists($select_by{ns})
+                        ||  $UTIL->ns_in_set($target->get_ns(), $select_by{ns})
+                    )
+        ) {
+            $target_set{$target->get_key()} = 1;
+        }
+        if (exists($target_of{$target->get_key()})) {
+            if (!exists($targets_of{$target->get_key()})) {
+                $targets_of{$target->get_key()}
+                    = [delete($target_of{$target->get_key()})];
+            }
+            push(@{$targets_of{$target->get_key()}}, $target);
+        }
+        else {
+            $target_of{$target->get_key()} = $target;
+        }
+        # Name-spaces
+        my $ns_iter = $UTIL->ns_iter($target->get_ns(), $UTIL->NS_ITER_UP);
+        NS:
+        while (defined(my $ns = $ns_iter->())) {
+            if (exists($has_ns_in{$ns})) {
+                last NS;
+            }
+            $has_ns_in{$ns} = 1;
+        }
+    }
+    my @target_keys = sort keys(%target_set);
+
+    # Wraps each relevant target with a state object.
+    # Walks the target dependency tree to build a state dependency tree.
+    # Checks for missing dependencies.
+    # Checks for duplicated target.
+    my @items = map {[[$_, undef]]} @target_keys;
+    my %state_of;
+    my %dup_in;
+    my %cyc_in;
+    my %missing_deps_in;
+    ITEM:
+    while (my $item = pop(@items)) {
+        my ($unit, @up_units) = @{$item};
+        my ($key, $type) = @{$unit};
+        my @up_keys = map {$_->[0]} @up_units;
+        if (   exists($cyc_in{$key})
+            || exists($dup_in{$key})
+            || exists($missing_deps_in{$key})
+        ) {
+            next ITEM;
+        }
+        if (exists($state_of{$key})) {
+            # Already visited this ITEM
+            # Detect cyclic dependency
+            if (    !$state_of{$key}->get_cyclic_ok()
+                &&  grep {$_->[0] eq $key} @up_units
+            ) {
+                my @_up_units = (@up_units, $unit);
+                my $_up_unit_last = pop(@_up_units);
+                DEP_UP_KEY:
+                while (my $_up_unit = pop(@_up_units)) {
+                    my ($_up_key, $_up_type) = @{$_up_unit};
+                    my @dep_up_deps = @{$state_of{$_up_key}->get_deps()};
+                    # If parent of $_up_unit_last does not depend on
+                    # $_up_unit_last, chain is broken, and we are OK.
+                    my ($_up_key_last, $_up_type_last) = @{$_up_unit_last};
+                    if (!grep {     $_->[0]->get_key() eq $_up_key_last
+                                ||  $_->[1] eq $_up_type_last
+                        } @dep_up_deps
+                    ) {
+                        last DEP_UP_KEY;
+                    }
+                    if ($type && $key eq $_up_key && $type eq $_up_type) {
+                        $cyc_in{$key} = {'keys' => [@up_keys, $key]};
+                        next ITEM;
+                    }
+                    $_up_unit_last = $_up_unit;
+                }
+            }
+            $state_of{$key}->set_cyclic_ok(1);
+            # Float current target up dependency chain
+            my $is_directly_related = 1;
+            UP_KEY:
+            for my $up_key (reverse(@up_keys)) {
+                if ($state_of{$up_key}->add_visitor(
+                        $state_of{$key}->get_target(),
+                        $type,
+                        $is_directly_related,
+                )) {
+                    last UP_KEY;
+                }
+                $is_directly_related = 0;
+            }
+            # Add floatable dependencies up the dependency chain
+            for my $visitor (values(%{$state_of{$key}->get_floatables()})) {
+                UP_KEY:
+                for my $up_key (reverse(@up_keys)) {
+                    if ($state_of{$up_key}->add_visitor(@{$visitor})) {
+                        last UP_KEY;
+                    }
+                }
+            }
+            next ITEM;
+        }
+
+        # First visit to this ITEM
+        # Checks for duplicated target
+        if (exists($targets_of{$key})) {
+            $dup_in{$key} = {
+                'keys' => [@up_keys, $key],
+                'values' => [map {$_->get_ns()} @{$targets_of{$key}}],
+            };
+            next ITEM;
+        }
+        # Wraps all required targets with a STATE object
+        $state_of{$key} = $STATE->new(
+            {id => $key, target => $target_of{$key}},
+        );
+        my $target = $target_of{$key};
+        DEP:
+        for (grep {$_->[0] ne $key} @{$target->get_deps()}) {
+            my ($dep_key, $dep_type, $dep_remark) = @{$_};
+            # Duplicated targets
+            if (exists($targets_of{$dep_key})) {
+                $dup_in{$dep_key} = {
+                    'keys' => [@up_keys, $key, $dep_key],
+                    'values' => [map {$_->get_ns()} @{$targets_of{$dep_key}}],
+                };
+                next DEP;
+            }
+            # Missing dependency
+            if (!exists($target_of{$dep_key})) {
+                if (!exists($missing_deps_in{$key})) {
+                    $missing_deps_in{$key} = {
+                        'keys'   => [@up_keys, $key, $dep_key],
+                        'values' => [],
+                    };
+                }
+                push(
+                    @{$missing_deps_in{$key}{'values'}},
+                    [$dep_key, $dep_type, $dep_remark],
+                );
+                next DEP;
+            }
+            # OK
+            push(@items, [[$dep_key, $dep_type], @up_units, [$key, $type]]);
+            # add_visitor, is_directly_related=1
+            $state_of{$key}->add_visitor($target_of{$dep_key}, $dep_type, 1)
+        }
+        # Float current target up dependency chain
+        my $is_directly_related = 1;
+        UP_KEY:
+        for my $up_key (reverse(@up_keys)) {
+            if ($state_of{$up_key}->add_visitor(
+                    $target, $type, $is_directly_related,
+            )) {
+                last UP_KEY;
+            }
+            $is_directly_related = 0;
+        }
+        # Adds triggers
+        for my $trigger_key (@{$target->get_triggers()}) {
+            if (!exists($state_of{$trigger_key})) {
+                unshift(@items, [[$trigger_key, undef]]);
+            }
+        }
+    }
+    # Visitors no longer used
+    for my $state (values(%state_of)) {
+        $state->free_visitors();
+    }
+    # Assigns targets to build context
+    %{$ctx->get_target_of()}
+        = map {($_->get_id() => $_->get_target())} values(%state_of);
+
+    # Report cyclic dependencies
+    # Report duplicated targets
+    # Report missing dependencies
+    # Report bad keys in target select
+    if (keys(%cyc_in)) {
+        return $E->throw($E->BUILD_TARGET_CYC, \%cyc_in);
+    }
+    if (keys(%dup_in)) {
+        return $E->throw($E->BUILD_TARGET_DUP, \%dup_in);
+    }
+    my @ignore_missing_dep_ns_list
+        = _props($attrib_ref, 'ignore-missing-dep-ns', $ctx);
+    KEY:
+    for my $key (sort(keys(%missing_deps_in))) {
+        my $target = $target_of{$key};
+        for my $ns (@ignore_missing_dep_ns_list) {
+            if ($UTIL->ns_common($ns, $target->get_ns()) eq $ns) { # target in ns
+                my $hash_ref = @{delete($missing_deps_in{$key})};
+                my @deps = @{$hash_ref->{"values"}};
+                for my $dep (@deps) {
+                    $EVENT->(
+                        FCM::Context::Event->MAKE_BUILD_TARGET_MISSING_DEP,
+                        $key, @{$dep},
+                    );
+                }
+                next KEY;
+            }
+        }
+    }
+    if (keys(%missing_deps_in)) {
+        return $E->throw($E->BUILD_TARGET_DEP, \%missing_deps_in);
+    }
+    if (exists($select_by{key})) {
+        my @bad_keys = grep {!exists($state_of{$_})} keys(%{$select_by{key}});
+        if (@bad_keys) {
+            return $E->throw($E->BUILD_TARGET_BAD, \@bad_keys);
+        }
+    }
+    # Walk the tree and report it
+    my @report_items = map {[$_]} @target_keys;
+    my %reported;
+    ITEM:
+    while (my $item = pop(@report_items)) {
+        my ($key, @stack) = @{$item};
+        my @deps = @{$state_of{$key}->get_deps()};
+        my @more_items = reverse(map {[$_->[0]->get_key(), @stack, $key]} @deps);
+        my $n_more_items;
+        if (exists($reported{$key})) {
+            $n_more_items = scalar(@more_items);
+        }
+        else {
+            push(@report_items, @more_items);
+        }
+        $attrib_ref->{util}->event(
+            FCM::Context::Event->MAKE_BUILD_TARGET_STACK,
+            $key, scalar(@stack), $n_more_items,
+        );
+        $reported{$key} = 1;
+    }
+    $EVENT->(
+        FCM::Context::Event->MAKE_BUILD_TARGET_SELECT,
+        {map {$_ => $target_of{$_}} @target_keys},
+    );
+    # TODO: error if nothing to build?
+
+    # Checks whether properties with name-spaces are valid.
+    my @invalid_prop_ns_list;
+    while (my ($name, $prop) = each(%{$ctx->get_prop_of()})) {
+        while (my ($ns, $prop_ctx) = each(%{$prop->get_ctx_of()})) {
+            if (    !$prop_ctx->get_inherited()
+                &&  !exists($target_of{$ns})
+                &&  !exists($has_ns_in{$ns})
+            ) {
+                push(
+                    @invalid_prop_ns_list,
+                    [$ctx->get_id(), $name, $ns, $prop_ctx->get_value()],
+                );
+            }
+        }
+    }
+    if (@invalid_prop_ns_list) {
+        return $E->throw($E->MAKE_PROP_NS, \@invalid_prop_ns_list);
+    }
+
+    $EVENT->(FCM::Context::Event->MAKE_BUILD_TARGET_SELECT_TIMER, $timer->());
+
+    # Returns list of keys of top targets, and the states
+    ([map {$state_of{$_}} reverse(@target_keys)], \%state_of);
+}
+
+# Returns true if $target dependencies are done.
+sub _target_deps_are_done {
+    my ($state, $state_hash_ref, $stack_ref) = @_;
+    my @deps = map {[$_->[0]->get_key(), $_->[1]]} @{$state->get_deps()};
+    for my $k (grep {$state_hash_ref->{$_}->is_ready()} map {$_->[0]} @deps) {
+        if (!grep {$_->get_id() eq $k} @{$stack_ref}) {
+            push(@{$stack_ref}, $state_hash_ref->{$k});
+        }
+    }
+    my %not_done
+        = map  {@{$_}}
+          grep {!$_->[1]->is_done()}
+          map  {[$_->[0], $state_hash_ref->{$_->[0]}]}
+          @deps;
+    if (keys(%not_done)) {
+        $state->set_value($STATE->PENDING);
+        while (my ($k, $s) = each(%not_done)) {
+            $state->get_pending_for()->{$k} = $s;
+            $s->get_needed_by()->{$state->get_id()} = $state;
+        }
+        return 0;
+    }
+    return 1;
+}
+
+# Returns true if $target has failed dependencies.
+sub _target_check_failed_dep {
+    my ($state, $state_hash_ref) = @_;
+    my $target = $state->get_target();
+    for my $dep (@{$state->get_deps()}) {
+        my ($target_of_dep, $type_of_dep) = @{$dep};
+        if ($target_of_dep->is_failed()) {
+            return 1;
+        }
+        if (    exists($target_of_dep->get_status_of()->{$type_of_dep})
+            &&  $target_of_dep->get_status_of()->{$type_of_dep}
+                    eq $target->ST_FAILED
+        ) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+# Returns true if $target is out of date.
+sub _target_check_ood {
+    my ($state, $state_hash_ref) = @_;
+    my $target = $state->get_target();
+    # Dependencies
+    my $rc;
+    for my $dep (@{$state->get_deps()}) {
+        my ($target_of_dep, $type_of_dep) = @{$dep};
+        if (    $target_of_dep->is_modified()
+            ||  exists($target_of_dep->get_status_of()->{$type_of_dep})
+                &&  $target_of_dep->get_status_of()->{$type_of_dep}
+                    eq $target->ST_MODIFIED
+        ) {
+            if (exists($target->get_status_of()->{$type_of_dep})) {
+                $target->get_status_of()->{$type_of_dep} = $target->ST_MODIFIED;
+                if (    $target->get_path_of_prev()
+                    &&  $target->get_path() ne $target->get_path_of_prev()
+                ) {
+                    # Inherited build, cannot just pass on a status
+                    $rc = 1;
+                }
+            }
+            else {
+                $rc = 1;
+            }
+        }
+    }
+    if ($rc || $target->get_status() eq $target->ST_OOD) {
+        return 1;
+    }
+    # Dest and properties
+    my $path_of_prev = $target->get_path_of_prev();
+    my $checksum = $target->get_checksum();
+    my $prop_hash_ref = $target->get_prop_of();
+    my $prop_of_prev_hash_ref = $target->get_prop_of_prev_of();
+    (       !$path_of_prev
+        ||  !-e $path_of_prev
+        ||  $UTIL->file_md5($path_of_prev) ne $checksum
+        ||  $UTIL->hash_cmp($prop_hash_ref, $prop_of_prev_hash_ref)
+    );
+}
+
+# Callback to prepare the target for the task.
+sub _target_prep {
+    my ($state, $ctx) = @_;
+    my $target = $state->get_target();
+    # Creates the container directory, where necessary
+    my %paths_of_dirs_set;
+    for my $t (
+        $target,
+        map {$ctx->get_target_of($_)} @{$target->get_triggers()},
+    ) {
+        $paths_of_dirs_set{dirname($t->get_path())} = 1;
+    }
+    for my $path_of_dir (keys(%paths_of_dirs_set)) {
+        if (!-d $path_of_dir) {
+            eval {mkpath($path_of_dir)};
+            if ($@) {
+                return $E->throw($E->DEST_CREATE, $path_of_dir);
+            }
+        }
+    }
+    # Put in required info
+    if ($target->get_info_of('paths')) {
+        @{$target->get_info_of('paths')} = @{$ctx->get_dests()};
+    }
+    if ($target->get_info_of('deps')) {
+        my $info_deps_ref = $target->get_info_of('deps');
+        my %set_of = map {$_ => {}} keys(%{$info_deps_ref});
+        for my $dep (@{$state->get_deps()}) {
+            my ($target_of_dep, $type) = @{$dep};
+            my $key = $target_of_dep->get_key();
+            if (exists($set_of{$type}) && !$set_of{$type}{$key}) {
+                if ($target_of_dep->get_ns() eq $target->get_ns()) {
+                    # E.g. main *.o of *.exe
+                    unshift(@{$info_deps_ref->{$type}}, $key);
+                }
+                else {
+                    push(@{$info_deps_ref->{$type}}, $key);
+                }
+                $set_of{$type}{$key} = 1;
+            }
+        }
+    }
+}
+
+# Sets state and stack when a $target has failed to update or cannot be updated
+# due to failed dependencies.
+sub _target_update_failed {
+    my ($stat_hash_ref,
+        $ctx,
+        $target,
+        $state_hash_ref,
+        $stack_ref,
+        $elapsed_time, # only defined if target update action is done
+    ) = @_;
+    my $key = $target->get_key();
+    my $state = $state_hash_ref->{$key};
+    $state->set_value($STATE->DONE);
+    # If this target is needed by other targets...
+    while (my ($k, $s) = each(%{$state->get_needed_by()})) {
+        my $pending_for_ref = $s->get_pending_for();
+        delete($pending_for_ref->{$key});
+        if (!keys(%{$pending_for_ref})) {
+            $s->set_value($STATE->DONE);
+            # Remove from stack
+            @{$stack_ref} = grep {$_->get_id() ne $k} @{$stack_ref};
+            $s->get_target()->set_status($target->ST_FAILED);
+            push(@{$s->get_target()->get_failed_by()}, $key);
+        }
+    }
+    if (defined($elapsed_time)) { # Done target update
+        my $target0 = $ctx->get_target_of()->{$target->get_key()};
+        $target0->set_info_of({}); # unset
+        $target0->set_checksum(undef);
+        $target0->set_path(undef);
+        $target0->set_prop_of_prev_of({}); # unset
+        $target0->set_path_of_prev(undef); # unset
+        $target0->set_status($target->ST_FAILED);
+        push(@{$target0->get_failed_by()}, $target->get_key());
+        ++$stat_hash_ref->{$target->get_task()}{n}{$target->ST_FAILED};
+        $stat_hash_ref->{$target->get_task()}{t} += $elapsed_time;
+    }
+    else { # No target update required
+        $target->set_path(undef);
+        $target->set_prop_of_prev_of({}); # unset
+        $target->set_path_of_prev(undef); # unset
+        $target->set_status($target->ST_FAILED);
+        for my $dep (@{$state->get_deps()}) {
+            my ($dep_target, $dep_type) = @{$dep};
+            my $dep_key = $dep_target->get_key();
+            if (    $dep_target->is_failed()
+                &&  !grep {$_ eq $dep_key} @{$target->get_failed_by()}
+            ) {
+                push(@{$target->get_failed_by()}, $dep_key);
+            }
+        }
+        ++$stat_hash_ref->{$target->get_task()}{n}{$target->ST_FAILED};
+    }
+    $EVENT->(
+        FCM::Context::Event->MAKE_BUILD_TARGET_FAIL, $target, $elapsed_time,
+    );
+}
+
+# Sets state and stack when a $target is up to date or updated successfully.
+sub _target_update_ok {
+    my ($stat_hash_ref,
+        $ctx,
+        $target,
+        $state_hash_ref,
+        $stack_ref,
+        $elapsed_time, # only defined if target update action is done
+    ) = @_;
+    my $key = $target->get_key();
+    my $state = $state_hash_ref->{$key};
+    $state->set_value($STATE->DONE);
+    # If this target is needed by other targets...
+    while (my ($k, $s) = each(%{$state->get_needed_by()})) {
+        my $pending_for_ref = $s->get_pending_for();
+        delete($pending_for_ref->{$key});
+        if ($s->is_pending() && !keys(%{$pending_for_ref})) {
+            $s->set_value($STATE->READY);
+            if (!grep {$_->get_id() eq $k} @{$stack_ref}) {
+                push(@{$stack_ref}, $s);
+            }
+        }
+    }
+    if (defined($elapsed_time)) { # Done target update
+        my $target0 = $ctx->get_target_of()->{$target->get_key()};
+        $target0->set_info_of({}); # unset
+        $target0->set_checksum($target->get_checksum());
+        $target0->set_path($target->get_path());
+        $target0->set_prop_of_prev_of({}); # unset
+        $target0->set_path_of_prev(undef); # unset
+        $target0->set_status($target->get_status());
+        ++$stat_hash_ref->{$target->get_task()}{n}{$target->get_status()};
+        $stat_hash_ref->{$target->get_task()}{t} += $elapsed_time;
+    }
+    else { # No target update required
+        if ($target->get_path_of_prev()) {
+            $target->set_path($target->get_path_of_prev());
+        }
+        $target->set_prop_of_prev_of({}); # unset
+        $target->set_path_of_prev(undef); # unset
+        $target->set_status($target->ST_UNCHANGED);
+        ++$stat_hash_ref->{$target->get_task()}{n}{$target->ST_UNCHANGED};
+    }
+    $EVENT->(
+        FCM::Context::Event->MAKE_BUILD_TARGET_DONE, $target, $elapsed_time,
+    );
+}
+
+# Returns a list containing the inherited contexts with the same ID as $ctx.
+sub _i_ctx_list {
+    my ($m_ctx, $ctx) = @_;
+    grep
+        {defined()}
+    map
+        {$_->get_ctx_of($ctx->get_id())}
+    @{$m_ctx->get_inherit_ctx_list()};
+}
+
+# Returns a function that returns the previous source/target of a specified key.
+sub _prev_hash_item_getter {
+    my ($m_ctx, $ctx, $getter_ref) = @_;
+    my $p_m_ctx = $m_ctx->get_prev_ctx();
+    my %p_item_of;
+    my $ctx_id = $ctx->get_id();
+    if (defined($p_m_ctx) && defined($p_m_ctx->get_ctx_of($ctx_id))) {
+        %p_item_of = %{$getter_ref->($p_m_ctx->get_ctx_of($ctx_id))};
+    }
+    else {
+        for my $i_ctx (_i_ctx_list($m_ctx, $ctx)) {
+            %p_item_of = (%p_item_of, %{$getter_ref->($i_ctx)});
+        }
+    }
+    sub {exists($p_item_of{$_[0]}) ? $p_item_of{$_[0]} : undef};
+}
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::State;
+use base qw{FCM::Class::HASH};
+
+use constant {
+    DONE       => 'DONE',       # state value
+    READY      => 'READY',      # state value
+    PENDING    => 'PENDING',    # state value
+};
+
+__PACKAGE__->class({
+    cyclic_ok   => '$',
+    deps        => '@',
+    floatables  => '%',
+    id          => '$',
+    needed_by   => '%',
+    pending_for => '%',
+    target      => 'FCM::Context::Make::Build::Target',
+    value       => {isa => '$', default => READY},
+    visited_by  => '%',
+});
+
+sub add_visitor {
+    my ($self, $dep_target, $dep_type, $is_directly_related) = @_;
+    my $dep_key = $dep_target->get_key();
+    my $dep_str = join(':', $dep_key, $dep_type);
+    # Dependency has already visited me, return cached return value
+    if (exists($self->get_visited_by()->{$dep_str})) {
+        return $self->get_visited_by()->{$dep_str};
+    }
+    # Adopt dep_target as my dependency if there is a policy to do so
+    my $target = $self->get_target();
+    my $policy = $target->get_dep_policy_of($dep_type);
+    if (    $policy
+        &&  ($policy ne $target->POLICY_FILTER_IMMEDIATE || $is_directly_related)
+        &&  (!grep {$_->[0]->get_key() eq $dep_key} @{$self->get_deps()})
+        &&  (!grep {$_ eq $dep_key} @{$target->get_triggers()})
+    ) {
+        push(@{$self->get_deps()}, [$dep_target, $dep_type]);
+    }
+    # If target is captured by me, return true.
+    # Otherwise, return false, and the target is a floatable.
+    $self->get_visited_by()->{$dep_str}
+        = ($policy && $policy eq $target->POLICY_CAPTURE);
+    if (    !$self->get_visited_by()->{$dep_str}
+        &&  !exists($self->get_floatables()->{$dep_str})
+    ) {
+        $self->get_floatables()->{$dep_str} = [$dep_target, $dep_type];
+    }
+    return $self->get_visited_by()->{$dep_str};
+}
+
+sub free_visitors {
+    my ($self) = @_;
+    %{$self->get_floatables()} = ();
+    %{$self->get_visited_by()} = ();
+}
+
+sub is_done {
+    $_[0]->{value} eq DONE;
+}
+
+sub is_pending {
+    $_[0]->{value} eq PENDING;
+}
+
+sub is_ready {
+    $_[0]->{value} eq READY;
+}
+#-------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build;
+
+=head1 DESCRIPTION
+
+Implements the build sub-system. An instance of this class is expected to be
+initialised and called by L<FCM::System::Make|FCM::System::Make>.
+
+=head1 METHODS
+
+See L<FCM::System::Make|FCM::System::Make> for detail.
+
+=head1 ATTRIBUTES
+
+The $class->new(\%attrib) method of this class supports the following
+attributes:
+
+=over 4
+
+=item config_parser_of
+
+A HASH to map the labels in a configuration file to their parsers. (default =
+%FCM::System::Make::Build::CONFIG_PARSER_OF)
+
+=item target_select_by
+
+A HASH to map the default target selector. The keys should be "category", "key",
+"ns", or "task". (default = %FCM::System::Make::Build::TARGET_SELECT_by)
+
+=item file_type_utils
+
+An ARRAY of file type utility classes to be loaded into the file_type_util_of
+HASH. (default = @FCM::System::Make::Build::FILE_TYPE_UTILS)
+
+=item file_type_util_of
+
+A HASH to map the file type names to the utilities to manipulate the given file
+types. An values in this HASH overrides the classes in I<file_type_utils>.
+(default = determined by I<file_type_utils>)
+
+=item prop_of
+
+A HASH to map the names of the properties to their settings. Each setting
+is a 2-element ARRAY reference, where element [0] is the default setting
+and element [1] is a flag to indicate whether the property accepts a name-space
+or not. (default = %FCM::System::Make::Build::PROP_OF + values loaded from the
+file type utilities)
+
+=item util
+
+See L<FCM::System::Make|FCM::System::Make> for detail.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/FileType.pm b/lib/FCM/System/Make/Build/FileType.pm
new file mode 100644
index 0000000..5eee9b3
--- /dev/null
+++ b/lib/FCM/System/Make/Build/FileType.pm
@@ -0,0 +1,226 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::FileType;
+use base qw{FCM::Class::CODE};
+
+use Text::ParseWords qw{shellwords};
+
+# Creates the class.
+__PACKAGE__->class(
+    {   id                         => '$',
+        file_ext                   => '$',
+        file_pat                   => '$',
+        file_she                   => '$',
+        source_analyse_always      => '$',
+        source_analyse_dep_of      => '%',
+        source_analyse_more        => '&',
+        source_analyse_more_init   => '&',
+        source_to_targets          => '&',
+        target_deps_filter         => '&',
+        target_file_ext_of         => '%',
+        target_file_name_option_of => '%',
+        task_class_of              => '%',
+        task_of                    => '%',
+        util                       => '&',
+    },
+    {   init => \&_init,
+        action_of => {
+            (map {my $key = $_; ($key => sub {$_[0]->{$key}})}
+                qw{
+                    id
+                    file_ext
+                    file_pat
+                    file_she
+                    source_analyse_always
+                    target_file_ext_of
+                    target_file_name_option_of
+                    task_of
+                }
+            ),
+            source_analyse      => \&_source_analyse,
+            source_analyse_deps => sub {keys(%{$_[0]->{source_analyse_dep_of}})},
+            source_to_targets   => sub {$_[0]->{source_to_targets}->(@_)},
+            target_deps_filter  => sub {$_[0]->{target_deps_filter}->(@_)},
+        },
+    },
+);
+
+# Initialises some attributes.
+sub _init {
+    my ($attrib_ref) = @_;
+    while (my ($key, $class) = each(%{$attrib_ref->{task_class_of}})) {
+        $attrib_ref->{util}->class_load($class);
+        $attrib_ref->{task_of}{$key}
+            = $class->new({util => $attrib_ref->{util}});
+    }
+}
+
+# Reads information according to the $source.
+sub _source_analyse {
+    my ($attrib_ref, $source) = @_;
+    my %no_dep_of;
+    my %dep_type_of
+        = map {($_ => 1)} keys(%{$attrib_ref->{source_analyse_dep_of}});
+    while (my $type = each(%dep_type_of)) {
+        my $key = 'no-dep.' . $type;
+        if ($source->get_prop_of($key)) {
+            for my $v (shellwords($source->get_prop_of($key))) {
+                if ($v eq '*') {
+                    delete($dep_type_of{$type});
+                }
+                else {
+                    $no_dep_of{$type}{$v} = 1;
+                }
+            }
+        }
+    }
+    if (!keys(%dep_type_of) && !$attrib_ref->{source_analyse_always}) {
+        return;
+    }
+    my $path = $source->get_path();
+    my $handle = $attrib_ref->{util}->file_load_handle($path);
+
+    my @dep_types = keys(%dep_type_of)
+        ? keys(%dep_type_of) : (_source_analyse_deps($attrib_ref));
+    my (%dep_of, %info_of, %state);
+    $attrib_ref->{source_analyse_more_init}->(\%info_of, \%state);
+    LINE:
+    while (my $line = readline($handle)) {
+        chomp($line);
+        TYPE:
+        for my $type (@dep_types) {
+            my ($item, $can_analyse_more)
+                = $attrib_ref->{source_analyse_dep_of}{$type}->($line);
+            if ($item) {
+                $dep_of{$type}{$item} = 1;
+                if ($can_analyse_more) {
+                    last TYPE;
+                }
+                else {
+                    next LINE;
+                }
+            }
+        }
+        $attrib_ref->{source_analyse_more}->($line, \%info_of, \%state);
+    }
+
+    close($handle);
+    $source->set_info_of(\%info_of);
+    while (my ($type, $hash_ref) = each(%dep_of)) {
+        while (my $item = each(%{$hash_ref})) {
+            if (!exists($no_dep_of{$type}{$item})) {
+                push(@{$source->get_deps()}, [$item, $type]);
+            }
+        }
+    }
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::FileType
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::FileType;
+    my $file_type_util = FCM::System::Make::Build::FileType->new(\%attrib);
+    $file_type_util->source_analyse($handle);
+
+=head1 DESCRIPTION
+
+An abstract class to implement the shared methods for gathering information to
+build different types of source files.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Creates and returns a new instance.
+
+=item $instance->id()
+
+Returns the recommended ID of this file type.
+
+=item $instance->file_ext()
+=item $instance->file_pat()
+=item $instance->file_she()
+
+Returns the recommended file name extension, file name pattern and file she-bang
+line pattern of this file type.
+
+=item $instance->source_analyse($source)
+
+Analysis $source for dependencies and other information. Add or modify items in
+@{$source->get_deps()} and %{$source->get_info_of()}.
+
+=item $instance->source_analyse_deps()
+
+Returns a list containing the possible dependency types.
+
+=item $instance->source_analyse_always()
+
+Returns true if $instance->source_analyse($handle,\@dep_types) can read
+information other than dependencies.
+
+=item $instance->source_to_targets($source,\%prop_of)
+
+Using the information in $source, creates and returns the contexts of a list of
+suitable build targets. Where appropriate, the %prop_of should contain a mapping
+of the names of the properties used by this method and their values.
+
+=item $instance->target_deps_filter($target)
+
+This may modify @{$target->get_deps()} in place based on values in
+%{$target->get_prop_of()}. This method is normally implemented by sub-classes.
+
+=item $instance->target_file_ext_of()
+
+Returns a HASH reference containing a map between the named types of file
+extensions used by the $instance->source_to_targets($source,\%prop_of) method
+and their default values.
+
+=item $instance->target_file_name_option_of()
+
+Returns a HASH reference containing a map between the named types of files
+used by the $instance->source_to_targets($source,\%prop_of) method
+and their default settings for other file naming options.
+
+=item $instance->task_of()
+
+Returns a HASH reference containing a map between the named tasks for this file
+type and their implementation objects. Each task should have a
+$task->main($target) method to update a target and optionally a $task->prop_of()
+method to return a HASH reference containing a map between the named properties
+used by the task and their default values.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/FileType/C.pm b/lib/FCM/System/Make/Build/FileType/C.pm
new file mode 100644
index 0000000..bd0a99e
--- /dev/null
+++ b/lib/FCM/System/Make/Build/FileType/C.pm
@@ -0,0 +1,156 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::FileType::C;
+use base qw{FCM::System::Make::Build::FileType};
+
+use FCM::Context::Make::Build; # for FCM::Context::Make::Build::Target
+use FCM::System::Make::Build::Task::Compile::C;
+use FCM::System::Make::Build::Task::Install;
+use FCM::System::Make::Build::Task::Link::C;
+use File::Basename qw{basename};
+
+# RE: file (base) name
+my $RE_FILE = qr{[\w\-+.]+}imsx;
+
+# RE: main program
+my $RE_MAIN = qr{int\s*main\b}msx;
+
+my %SOURCE_ANALYSE_DEP_OF = (
+    include => sub { $_[0] =~ qr{\A\#\s*include\s+"($RE_FILE)"}msx },
+    o => sub { lc($_[0]) =~ qr{\A\s*/\*\s*depends\s*on\s*:\s*($RE_FILE)}imsx },
+);
+my $TARGET = 'FCM::Context::Make::Build::Target';
+my %TASK_CLASS_OF = (
+    'compile' => 'FCM::System::Make::Build::Task::Compile::C',
+    'install' => 'FCM::System::Make::Build::Task::Install',
+    'link'    => 'FCM::System::Make::Build::Task::Link::C',
+);
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        FCM::System::Make::Build::FileType->new({
+            id                       => 'c',
+            file_ext                 => '.c .i .m .mi',
+            source_analyse_always    => 1,
+            source_analyse_dep_of    => {%SOURCE_ANALYSE_DEP_OF},
+            source_analyse_more      => \&_source_analyse_more,
+            source_analyse_more_init => \&_source_analyse_more_init,
+            source_to_targets        => \&_source_to_targets,
+            target_file_ext_of       => {bin => '.exe', o => '.o'},
+            task_class_of            => {%TASK_CLASS_OF},
+            %{$attrib_ref},
+        }),
+        $class,
+    );
+}
+
+sub _source_analyse_more {
+    my ($line, $info_hash_ref) = @_;
+    if (!$info_hash_ref->{main} && $line =~ $RE_MAIN) {
+        return $info_hash_ref->{main} = 1;
+    }
+    return;
+}
+
+sub _source_analyse_more_init {
+    my ($info_hash_ref) = @_;
+    $info_hash_ref->{main} = 0;
+}
+
+# Returns a list of targets for a given build source.
+sub _source_to_targets {
+    my ($attrib_ref, $source, $prop_hash_ref) = @_;
+    my $key = basename($source->get_path());
+    my ($ext, $root) = $attrib_ref->{util}->file_ext($key);
+    my %dot = %{$prop_hash_ref};
+    my @deps = @{$source->get_deps()};
+    my $key_o = lc($root) . $dot{o}; # lc for legacy
+    my @targets = (
+        $TARGET->new(
+            {   category  => $TARGET->CT_INCLUDE,
+                deps      => [@deps],
+                dep_policy_of => {'include' => $TARGET->POLICY_CAPTURE},
+                key       => $key,
+                status_of => {'include' => $TARGET->ST_UNKNOWN},
+                task      => 'install',
+            }
+        ),
+        $TARGET->new(
+            {   category      => $TARGET->CT_O,
+                deps          => [@deps],
+                dep_policy_of => {'include' => $TARGET->POLICY_CAPTURE},
+                info_of       => {paths => []},
+                key           => $key_o,
+                task          => 'compile',
+            }
+        ),
+    );
+
+    if ($source->get_info_of()->{'main'}) {
+        my @link_deps = grep {$_->[1] eq 'o'} @deps;
+        push(
+            @targets,
+            $TARGET->new(
+                {   category   => $TARGET->CT_BIN,
+                    deps       => [[$key_o, 'o'], @link_deps],
+                    dep_policy_of => {
+                        map {($_ => $TARGET->POLICY_CAPTURE)} qw{o o.special},
+                    },
+                    info_of    => {
+                        paths => [], deps => {o => [], 'o.special' => []},
+                    },
+                    key        => $root . $dot{bin},
+                    task       => 'link',
+                }
+            )
+        );
+    }
+    return @targets;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::FileType::C
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::FileType::C;
+    my $helper = FCM::System::Make::Build::FileType::C->new();
+    $helper->source_analyse($handle);
+
+=head1 DESCRIPTION
+
+A wrapper of
+L<FCM::System::Make::Build::FileType|FCM::System::Make::Build::FileType> with
+configurations to work with C source files.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/FileType/CPP.pm b/lib/FCM/System/Make/Build/FileType/CPP.pm
new file mode 100644
index 0000000..54587d4
--- /dev/null
+++ b/lib/FCM/System/Make/Build/FileType/CPP.pm
@@ -0,0 +1,92 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::FileType::CPP;
+use base qw{FCM::System::Make::Build::FileType};
+
+use FCM::Context::Make::Build; # for FCM::Context::Make::Build::Target
+use FCM::System::Make::Build::Task::Preprocess::C;
+
+# Dependency types and CODE to extract them
+my %SOURCE_ANALYSE_DEP_OF
+    = (include => sub { $_[0] =~ qr{\A\#\s*include\s+"([\w\-+.]+)"}msx });
+
+# Handler of tasks
+my %TASK_CLASS_OF
+    = (process => 'FCM::System::Make::Build::Task::Preprocess::C');
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        FCM::System::Make::Build::FileType->new({
+            id                    => 'cpp',
+            file_ext              => '.c .m .cc .cp .cxx .cpp .CPP .c++ .C .mm .M',
+            source_analyse_dep_of => {%SOURCE_ANALYSE_DEP_OF},
+            source_to_targets     => \&_source_to_targets,
+            target_file_ext_of    => {},
+            task_class_of         => {%TASK_CLASS_OF},
+            %{$attrib_ref},
+        }),
+        $class,
+    );
+}
+
+# Returns a list of targets for a given build source.
+sub _source_to_targets {
+    my ($attrib_ref, $source) = @_;
+    my $TARGET = 'FCM::Context::Make::Build::Target';
+    $TARGET->new(
+        {   category      => $TARGET->CT_SRC,
+            deps          => [@{$source->get_deps()}],
+            dep_policy_of => {'include' => $TARGET->POLICY_CAPTURE},
+            info_of       => {paths => []},
+            key           => $source->get_ns(),
+            task          => 'process',
+        }
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::FileType::CPP
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::FileType::CPP;
+    my $helper = FCM::System::Make::Build::FileType::CPP->new();
+    $helper->source_analyse($handle);
+
+=head1 DESCRIPTION
+
+A wrapper of
+L<FCM::System::Make::Build::FileType|FCM::System::Make::Build::FileType> with
+configurations to work with C source files for preprocessing.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/FileType/CXX.pm b/lib/FCM/System/Make/Build/FileType/CXX.pm
new file mode 100644
index 0000000..c7d2a8a
--- /dev/null
+++ b/lib/FCM/System/Make/Build/FileType/CXX.pm
@@ -0,0 +1,72 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::FileType::CXX;
+use base qw{FCM::System::Make::Build::FileType::C};
+
+use FCM::System::Make::Build::Task::Compile::CXX;
+use FCM::System::Make::Build::Task::Install;
+use FCM::System::Make::Build::Task::Link::CXX;
+
+my %TASK_CLASS_OF = (
+    'compile' => 'FCM::System::Make::Build::Task::Compile::CXX',
+    'install' => 'FCM::System::Make::Build::Task::Install',
+    'link'    => 'FCM::System::Make::Build::Task::Link::CXX',
+);
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        FCM::System::Make::Build::FileType::C->new({
+            id            => 'cxx',
+            file_ext      => '.cc .cp .cxx .cpp .CPP .c++ .C .mm .M .mii',
+            task_class_of => {%TASK_CLASS_OF},
+            %{$attrib_ref},
+        }),
+        $class,
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::FileType::CXX
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::FileType::CXX;
+    my $helper = FCM::System::Make::Build::FileType::CXX->new();
+    $helper->source_analyse($handle);
+
+=head1 DESCRIPTION
+
+A wrapper of
+L<FCM::System::Make::Build::FileType::C|FCM::System::Make::Build::FileType::C>
+with configurations to work with C++ source files.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/FileType/Data.pm b/lib/FCM/System/Make/Build/FileType/Data.pm
new file mode 100644
index 0000000..c97e986
--- /dev/null
+++ b/lib/FCM/System/Make/Build/FileType/Data.pm
@@ -0,0 +1,82 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::FileType::Data;
+use base qw{FCM::System::Make::Build::FileType};
+
+use FCM::Context::Make::Build;    # for FCM::Context::Make::Build::Target
+use FCM::System::Make::Build::Task::Install;
+
+# Handler of tasks
+my %TASK_CLASS_OF = (install => 'FCM::System::Make::Build::Task::Install');
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        FCM::System::Make::Build::FileType->new({
+            id                    => q{},
+            source_analyse_dep_of => {},
+            source_to_targets     => \&_source_to_targets,
+            target_file_ext_of    => {},
+            task_class_of         => {%TASK_CLASS_OF},
+            %{$attrib_ref},
+        }),
+        $class,
+    );
+}
+
+# Returns a list of targets for a given build source.
+sub _source_to_targets {
+    my ($attrib_ref, $source) = @_;
+    FCM::Context::Make::Build::Target->new(
+        {   category => FCM::Context::Make::Build::Target->CT_ETC,
+            key      => $source->get_ns(),
+            task     => 'install',
+        }
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::FileType::Data
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::FileType::Data;
+    my $helper = FCM::System::Make::Build::FileType::Data->new();
+    $helper->source_analyse($handle);
+
+=head1 DESCRIPTION
+
+A class based on
+L<FCM::System::Make::Build::FileType|FCM::System::Make::Build::FileType>
+with configurations to install data files to the etc/ sub-directory of a build.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/FileType/FPP.pm b/lib/FCM/System/Make/Build/FileType/FPP.pm
new file mode 100644
index 0000000..61bc67e
--- /dev/null
+++ b/lib/FCM/System/Make/Build/FileType/FPP.pm
@@ -0,0 +1,67 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::FileType::FPP;
+use base qw{FCM::System::Make::Build::FileType::CPP};
+
+use FCM::System::Make::Build::Task::Preprocess::Fortran;
+
+my %TASK_CLASS_OF
+    = (process => 'FCM::System::Make::Build::Task::Preprocess::Fortran');
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        FCM::System::Make::Build::FileType::CPP->new({
+            id            => 'fpp',
+            file_ext      => '.F90 .F95 .F .FTN .FOR',
+            task_class_of => {%TASK_CLASS_OF},
+            %{$attrib_ref},
+        }),
+        $class,
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::FileType::FPP
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::FileType::FPP;
+    my $helper = FCM::System::Make::Build::FileType::FPP->new();
+    $helper->source_analyse($handle);
+
+=head1 DESCRIPTION
+
+A wrapper of
+L<FCM::System::Make::Build::FileType::CPP|FCM::System::Make::Build::FileType::CPP>
+with configurations to work with Fortran source files for preprocessing.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/FileType/Fortran.pm b/lib/FCM/System/Make/Build/FileType/Fortran.pm
new file mode 100644
index 0000000..beb6fed
--- /dev/null
+++ b/lib/FCM/System/Make/Build/FileType/Fortran.pm
@@ -0,0 +1,404 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::FileType::Fortran;
+use base qw{FCM::System::Make::Build::FileType};
+
+use FCM::Context::Make::Build;    # for FCM::Context::Make::Build::Target
+use FCM::System::Make::Build::Task::Compile::Fortran;
+use FCM::System::Make::Build::Task::ExtractInterface;
+use FCM::System::Make::Build::Task::Install;
+use FCM::System::Make::Build::Task::Link::Fortran;
+use File::Basename qw{basename};
+use Text::Balanced qw{extract_bracketed extract_delimited};
+
+# Recommended file extensions of this utility
+our $FILE_EXT = '.F .F90 .F95 .FOR .FTN .f .f90 .f95 .for .ftn .inc';
+
+# List of Fortran intrinsic modules
+our @INTRINSIC_MODULES = qw{
+    ieee_arithmetic
+    ieee_exceptions
+    ieee_features
+    iso_c_binding
+    iso_fortran_env
+    omp_lib
+    omp_lib_kinds
+};
+
+# Prefix for dependency name that is only applicable under OMP
+our $OMP_PREFIX = '!$';
+
+# Regular expressions
+my $RE_FILE = qr{[\w\-+.]+}imsx;
+my $RE_NAME = qr{[A-Za-z]\w*}imsx;
+my $RE_SPEC = qr{
+    character|class|complex|double\s*complex|double\s*precision|integer|
+    logical|procedure|real|type
+}imsx;
+my $RE_UNIT_BASE = qr{block\s*data|module|program|submodule}imsx;
+my $RE_UNIT_CALL = qr{subroutine|function}imsx;
+my %RE           = (
+    DEP_O     => qr{\A\s*!\s*depends\s*on\s*:\s*($RE_FILE)}imsx,
+    DEP_USE   => qr{\A\s*use\s+($RE_NAME)}imsx,
+    DEP_SUBM  => qr{\A\s*submodule\s+\(($RE_NAME)\)}imsx,
+    INCLUDE   => qr{\#?\s*include\s*}imsx,
+    OMP_SENT  => qr{\A(\s*!\$\s+)?(.*)\z}imsx,
+    UNIT_ATTR => qr{\A\s*(?:(?:(?:impure\s+)?elemental|recursive|pure)\s+)+(.*)\z}imsx,
+    UNIT_BASE => qr{\A\s*($RE_UNIT_BASE)\s+($RE_NAME)\s*\z}imsx,
+    UNIT_CALL => qr{\A\s*($RE_UNIT_CALL)\s+($RE_NAME)\b}imsx,
+    UNIT_END  => qr{\A\s*(end)(?:\s+($RE_NAME)(?:\s+($RE_NAME))?)?\s*\z}imsx,
+    UNIT_SPEC => qr{\A\s*$RE_SPEC\b(.*)\z}imsx,
+);
+
+# Dependency types and extractors
+my %SOURCE_ANALYSE_DEP_OF = (
+    'f.module'  => \&_source_analyse_dep_module,
+    'include'   => \&_source_analyse_dep_include,
+    'o'         => sub { lc($_[0]) =~ $RE{DEP_O} }, # lc required for legacy
+    'o.special' => sub {},
+);
+# Alias
+my $TARGET = 'FCM::Context::Make::Build::Target';
+# Classes for tasks used by targets of this file type
+my %TASK_CLASS_OF = (
+    'compile'   => 'FCM::System::Make::Build::Task::Compile::Fortran',
+    'compile+'  => 'FCM::System::Make::Build::Task::Compile::Fortran::Extra',
+    'ext-iface' => 'FCM::System::Make::Build::Task::ExtractInterface',
+    'install'   => 'FCM::System::Make::Build::Task::Install',
+    'link'      => 'FCM::System::Make::Build::Task::Link::Fortran',
+);
+# Property suffices of output file extensions
+my %TARGET_EXT_OF = (
+    'bin'           => '.exe',
+    'f90-interface' => '.interface',
+    'f90-mod'       => '.mod',
+    'o'             => '.o',
+);
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        FCM::System::Make::Build::FileType->new({
+            id                         => 'fortran',
+            file_ext                   => $FILE_EXT,
+            source_analyse_always      => 1,
+            source_analyse_dep_of      => {%SOURCE_ANALYSE_DEP_OF},
+            source_analyse_more        => \&_source_analyse_more,
+            source_analyse_more_init   => \&_source_analyse_more_init,
+            source_to_targets          => \&_source_to_targets,
+            target_deps_filter         => \&_target_deps_filter,
+            target_file_ext_of         => {%TARGET_EXT_OF},
+            target_file_name_option_of => {'f90-mod' => q{}},
+            task_class_of              => {%TASK_CLASS_OF},
+            %{$attrib_ref},
+        }),
+        $class,
+    );
+}
+
+sub _source_analyse_more {
+    my ($line, $info_hash_ref, $state) = @_;
+
+    # End Interface
+    if ($state->{in_interface}) {
+        if ($line =~ qr{\A\s*end\s*interface\b}imsx) {
+            $state->{in_interface} = 0;
+        }
+        return 1;
+    }
+
+    # End Program Unit
+    if (@{$state->{stack}} && $line =~ qr{\A\s*end\b}imsx) {
+        my ($end, $type, $symbol) = lc($line) =~ $RE{UNIT_END};
+        if (!$end) {
+            return 1;
+        }
+        my ($top_type, $top_symbol) = @{$state->{stack}->[-1]};
+        if (!$type
+            || $top_type eq $type && (!$symbol || $top_symbol eq $symbol))
+        {
+            pop(@{$state->{stack}});
+            if ($state->{in_contains} && !@{$state->{stack}}) {
+                $state->{in_contains} = 0;
+            }
+        }
+        return 1;
+    }
+
+    # Interface/Contains
+    if ($line =~ qr{\A\s*contains\b}imsx) {
+        $state->{'in_contains'} = 1;
+        return 1;
+    }
+    if ($line =~ qr{\A\s*(?:abstract\s+)?interface\b}imsx) {
+        $state->{'in_interface'} = 1;
+        return 1;
+    }
+
+    # Program Unit
+    my ($type, $symbol) = _process_prog_unit($line);
+    if ($type) {
+        if (!@{$state->{stack}}) {
+            if ($type eq 'program') {
+                $info_hash_ref->{main} = 1;
+            }
+            $info_hash_ref->{symbols} ||= [];
+            push(@{$info_hash_ref->{symbols}}, [$type, $symbol]);
+        }
+        push(@{$state->{stack}}, [$type, $symbol]);
+        return 1;
+    }
+    return;
+}
+
+sub _source_analyse_more_init {
+    my ($info_ref, $state) = @_;
+    %{$info_ref} = (main => 0, symbols => []);
+    %{$state} = (in_contains => undef, in_interface => undef, stack => []);
+}
+
+# Reads information: extract an include dependency.
+sub _source_analyse_dep_include {
+    my ($line) = @_;
+    my ($omp_sentinel, $extracted);
+    ($omp_sentinel, $line) = $line =~ $RE{OMP_SENT};
+    ($extracted) = extract_delimited($line, q{'"}, $RE{INCLUDE});
+    if (!$extracted) {
+        return;
+    }
+    $extracted = substr($extracted, 1, length($extracted) - 2);
+    if ($omp_sentinel) {
+        $extracted = $OMP_PREFIX . $extracted;
+    }
+    $extracted;
+}
+
+# Reads information: extract a module dependency.
+sub _source_analyse_dep_module {
+    my ($line) = @_;
+    my ($omp_sentinel, $extracted, $can_analyse_more);
+    ($omp_sentinel, $line) = $line =~ $RE{OMP_SENT};
+    ($extracted) = lc($line) =~ $RE{DEP_USE};
+    if (!$extracted) {
+        ($extracted) = lc($line) =~ $RE{DEP_SUBM};
+        $can_analyse_more = 1;
+    }
+    if (!$extracted || grep {$_ eq $extracted} @INTRINSIC_MODULES) {
+        return;
+    }
+    if ($omp_sentinel) {
+        $extracted = $OMP_PREFIX . $extracted;
+    }
+    ($extracted, $can_analyse_more);
+}
+
+# Parse a statement for program unit header. Returns a list containing the type,
+# the symbol and the signature tokens of the program unit.
+sub _process_prog_unit {
+    my ($string) = @_;
+    my ($type, $symbol, @args) = (q{}, q{});
+    ($type, $symbol) = lc($string) =~ $RE{UNIT_BASE};
+    if ($type) {
+        $type = lc($type);
+        $type =~ s{\s*}{}gmsx;
+        return ($type, $symbol);
+    }
+    $string =~ s/$RE{UNIT_ATTR}/$1/;
+    my ($match) = $string =~ $RE{UNIT_SPEC};
+    if ($match) {
+        $string = $match;
+        if ($string =~ qr{\A \s* \(}msx) {
+            extract_bracketed($string);
+        }
+        elsif ($string =~ qr{\A \s* \*}msx) {
+            $string =~ s{\A \s* \* \d+ \s*}{}msx;
+        }
+    }
+    ($type, $symbol) = lc($string) =~ $RE{UNIT_CALL};
+    if (!$type) {
+        return;
+    }
+    return (lc($type), lc($symbol));
+}
+
+# Returns a list of targets for a given build source.
+sub _source_to_targets {
+    my ($attrib_ref, $source, $ext_hash_ref, $option_hash_ref) = @_;
+    my $key = basename($source->get_path());
+    my $TARGET_OF = sub {
+        my ($symbol, $type) = @_;
+        if (exists($option_hash_ref->{$type})) {
+            my $is_upper = index($option_hash_ref->{$type}, 'case=upper') >= 0;
+            $symbol = $is_upper ? uc($symbol) : lc($symbol);
+        }
+        $symbol . $ext_hash_ref->{$type};
+    };
+    my @deps = map {
+        my ($k, $type) = @{$_};
+        my $ext = $attrib_ref->{util}->file_ext($k);
+          $type eq 'f.module'   ? [$TARGET_OF->($k, 'f90-mod'), 'include', 1]
+        : $type eq 'o' && !$ext ? [$TARGET_OF->($k, 'o'), $type]
+        :                         [$k, $type]
+    } @{$source->get_deps()};
+    # All source files can be used as include files
+    my @targets = (
+        $TARGET->new(
+            {   category  => $TARGET->CT_INCLUDE,
+                deps      => [@deps],
+                dep_policy_of => {'include' => $TARGET->POLICY_CAPTURE},
+                key       => $key,
+                status_of => {'include' => $TARGET->ST_UNKNOWN},
+                task      => 'install',
+            }
+        ),
+    );
+    my ($ext, $root) = $attrib_ref->{util}->file_ext($key);
+    my $symbols_ref = $source->get_info_of()->{symbols};
+    # FIXME: hard code the handling of "*.inc" files as include files
+    if (!defined($symbols_ref) || !@{$symbols_ref} || $ext eq 'inc') {
+        return @targets;
+    }
+    my $key_of_o = $TARGET_OF->($symbols_ref->[0][1], 'o');
+    my @keys_of_mod;
+    for (grep {$_->[0] eq 'module'} @{$symbols_ref}) {
+        my ($type, $symbol) = @{$_};
+        my $key_of_mod = $TARGET_OF->($symbol, 'f90-mod');
+        my @include_deps = grep {$_->[1] eq 'include'} @deps;
+        push(
+            @targets,
+            $TARGET->new(
+                {   category      => $TARGET->CT_INCLUDE,
+                    deps          => [[$key_of_o, 'o']],
+                    dep_policy_of => {
+                        'include' => $TARGET->POLICY_CAPTURE,
+                        'o'       => $TARGET->POLICY_FILTER_IMMEDIATE,
+                    },
+                    key         => $key_of_mod,
+                    task        => 'compile+',
+                }
+            )
+        );
+        push(@keys_of_mod, $key_of_mod);
+    }
+    push(
+        @targets,
+        $TARGET->new(
+            {   category      => $TARGET->CT_O,
+                deps          => [@deps],
+                dep_policy_of => {'include' => $TARGET->POLICY_CAPTURE},
+                info_of       => {paths => []},
+                key           => $key_of_o,
+                task          => 'compile',
+                triggers      => \@keys_of_mod,
+            }
+        ),
+    );
+    if (grep {$_->[0] eq 'subroutine' || $_->[0] eq 'function'} @{$symbols_ref}) {
+        my $target_key = $root . $ext_hash_ref->{'f90-interface'};
+        push(
+            @targets,
+            $TARGET->new(
+                {   category      => $TARGET->CT_INCLUDE,
+                    deps          => [[$key_of_o, 'o'], grep {exists($_->[2])} @deps],
+                    dep_policy_of => {
+                        'include' => $TARGET->POLICY_FILTER_IMMEDIATE,
+                    },
+                    key           => $target_key,
+                    task          => 'ext-iface',
+                }
+            )
+        );
+    }
+    if ($source->get_info_of()->{main}) {
+        my @link_deps = grep {$_->[1] eq 'o' || $_->[1] eq 'o.special'} @deps;
+        push(
+            @targets,
+            $TARGET->new(
+                {   category      => $TARGET->CT_BIN,
+                    deps          => [[$key_of_o, 'o'], @link_deps],
+                    dep_policy_of => {
+                        'o'         => $TARGET->POLICY_CAPTURE,
+                        'o.special' => $TARGET->POLICY_CAPTURE,
+                    },
+                    info_of       => {
+                        paths => [], deps => {o => [], 'o.special' => []},
+                    },
+                    key           => $root . $ext_hash_ref->{bin},
+                    task          => 'link',
+                }
+            )
+        );
+    }
+    return @targets;
+}
+
+# If target's fc.flag-omp property is empty, remove !$OMP dependencies.
+# Otherwise, remove !$OMP sentinels from the dependencies.
+sub _target_deps_filter {
+    my ($attrib_ref, $target) = @_;
+    if ($target->get_prop_of()->{'fc.flag-omp'}) {
+        for my $dep_ref (@{$target->get_deps()}) {
+            if (index($dep_ref->[0], $OMP_PREFIX) == 0) {
+                substr($dep_ref->[0], 0, length($OMP_PREFIX), q{});
+            }
+        }
+    }
+    else {
+        $target->set_deps(
+            [grep {index($_->[0], $OMP_PREFIX) == -1} @{$target->get_deps()}],
+        );
+    }
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::FileType::Fortran
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::FileType::Fortran;
+    my $file_type_util = FCM::System::Make::Build::FileType::Fortran->new();
+
+    $file_type_util->source_analyse($source);
+
+    my @targets = $file_type_util->source_to_targets($m_ctx, $ctx, $source);
+
+=head1 DESCRIPTION
+
+A wrapper of
+L<FCM::System::Make::Build::FileType|FCM::System::Make::Build::FileType> with
+configurations to work with Fortran source files.
+
+=head1 TODO
+
+Combine the code with FCM::System::Make::Build::Task::ExtractInterface.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/FileType/H.pm b/lib/FCM/System/Make/Build/FileType/H.pm
new file mode 100644
index 0000000..eebef0c
--- /dev/null
+++ b/lib/FCM/System/Make/Build/FileType/H.pm
@@ -0,0 +1,131 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::FileType::H;
+use base qw{FCM::System::Make::Build::FileType};
+
+use FCM::Context::Make::Build;    # for FCM::Context::Make::Build::Target
+use FCM::System::Make::Build::Task::Install;
+use File::Basename qw{basename};
+
+# RE: file (base) name
+my $RE_FILE = qr{[\w\-+.]+}imsx;
+
+# Dependency types and CODE to extract them
+my %SOURCE_ANALYSE_DEP_OF = (
+    include => sub { $_[0] =~ qr{\A\#\s*include\s+"($RE_FILE)"}msx },
+
+    # Note: handle ! as a comment, for *.h files containing Fortran source
+    o => sub {
+        $_[0] =~ qr{\A\s*(?:!|/\*)\s*depends\s*on\s*:\s*($RE_FILE)}imsx;
+    },
+);
+
+# Alias
+my $TARGET = 'FCM::Context::Make::Build::Target';
+
+# Handler of tasks
+my %TASK_CLASS_OF = (install => 'FCM::System::Make::Build::Task::Install');
+
+# Property suffices of output file extensions
+my %TARGET_EXT_OF = ('o' => '.o');
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        FCM::System::Make::Build::FileType->new({
+            id                    => 'h',
+            file_ext              => '.h',
+            source_analyse_dep_of => {%SOURCE_ANALYSE_DEP_OF},
+            source_to_targets     => \&_source_to_targets,
+            target_file_ext_of    => {%TARGET_EXT_OF},
+            task_class_of         => {%TASK_CLASS_OF},
+            %{$attrib_ref},
+        }),
+        $class,
+    );
+}
+
+# Returns a list of targets for a given build source.
+sub _source_to_targets {
+    my ($attrib_ref, $source, $prop_hash_ref) = @_;
+    my %dot = %{$prop_hash_ref};
+    my $key = basename($source->get_path());
+    my @deps = map {
+        my $ext = $attrib_ref->{util}->file_ext($_->[0]);
+        $_->[1] eq 'o' && !$ext ? [lc($_->[0]) . $dot{o}, $_->[1]] : $_;
+    } @{$source->get_deps()};
+    $TARGET->new(
+        {   category => $TARGET->CT_INCLUDE,
+            deps     => [@deps],
+            dep_policy_of => {'include' => $TARGET->POLICY_CAPTURE},
+            key      => $key,
+            status_of=> {'include' => $TARGET->ST_UNKNOWN},
+            task     => 'install',
+        }
+    );
+}
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::FileType::HPP;
+use base qw{FCM::System::Make::Build::FileType::H};
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        FCM::System::Make::Build::FileType::H->new({
+            source_analyse_dep_of => {include => $SOURCE_ANALYSE_DEP_OF{include}},
+            target_file_ext_of    => {},
+            %{$attrib_ref},
+        }),
+        $class,
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::FileType::H
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::FileType::H;
+    my $helper = FCM::System::Make::Build::FileType::H->new();
+    $helper->source_analyse($handle);
+
+    my $helper = FCM::System::Make::Build::FileType::HPP->new();
+    $helper->source_analyse($handle);
+
+=head1 DESCRIPTION
+
+A wrapper of
+L<FCM::System::Make::Build::FileType|FCM::System::Make::Build::FileType> with
+configurations to work with C or Fortran preprocessor header files.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/FileType/NS.pm b/lib/FCM/System/Make/Build/FileType/NS.pm
new file mode 100644
index 0000000..e6c1eea
--- /dev/null
+++ b/lib/FCM/System/Make/Build/FileType/NS.pm
@@ -0,0 +1,210 @@
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+use strict;
+use warnings;
+#-------------------------------------------------------------------------------
+
+package FCM::System::Make::Build::FileType::NS;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::Make::Build;    # for FCM::Context::Make::Build::Target
+use FCM::System::Make::Build::Task::Archive;
+use FCM::System::Make::Build::Task::Install;
+use File::Spec::Functions qw{catfile};
+
+my $ID = '/';
+
+my %TARGET_FILE_EXT_OF = (a => '.a', etc => '.etc');
+
+my %TASK_CLASS_OF = (
+    'archive' => 'FCM::System::Make::Build::Task::Archive',
+    'install' => 'FCM::System::Make::Build::Task::Install',
+);
+
+# Creates the class.
+__PACKAGE__->class(
+    {   id                 => {isa => '$', default => $ID},
+        target_file_ext_of => {isa => '%', default => {%TARGET_FILE_EXT_OF}},
+        target_file_name_option_of => '%',
+        task_class_of      => {isa => '%', default => {%TASK_CLASS_OF}},
+        shared_util_of     => '%',
+        task_of            => '%',
+        util               => '&',
+    },
+    {   init => \&_init,
+        action_of => {
+            (map {my $key = $_; ($key => sub {$_[0]->{$key}})}
+                qw{id target_file_ext_of target_file_name_option_of task_of}
+            ),
+            ns_targets_deps => sub {('o')},
+            ns_targets      => \&_ns_targets,
+        },
+    },
+);
+
+# Initialises some attributes.
+sub _init {
+    my ($attrib_ref) = @_;
+    while (my ($key, $class) = each(%{$attrib_ref->{task_class_of}})) {
+        $attrib_ref->{util}->class_load($class);
+        $attrib_ref->{task_of}{$key}
+            = $class->new({util => $attrib_ref->{util}});
+    }
+}
+
+# Returns a list of targets for a given build source.
+sub _ns_targets {
+    my ($attrib_ref, $targets_ref, $prop_hash_ref) = @_;
+    my %target_of;
+    TARGET:
+    for my $target (@{$targets_ref}) {
+        my @ns_targets;
+        for (
+            [sub {!$_[0]->get_type()}          , \&_ns_target_new_etc],
+            [sub {$_[0]->get_category() eq 'o'}, \&_ns_target_new_lib],
+        ) {
+            my ($test, $new) = @{$_};
+            if ($test->($target)) {
+                my $ns_iter = $attrib_ref->{util}->ns_iter(
+                    $target->get_ns(), $attrib_ref->{util}->NS_ITER_UP,
+                );
+                $ns_iter->(); # discard
+                while (defined(my $ns = $ns_iter->())) {
+                    my $ns_target = $new->($ns, $prop_hash_ref);
+                    my $key = $ns_target->get_key();
+                    if (!exists($target_of{$key})) {
+                        $target_of{$key} = $ns_target;
+                    }
+                    push(
+                        @{$target_of{$key}->get_deps()},
+                        [$target->get_key(), $target->get_category()],
+                    );
+                }
+                next TARGET;
+            }
+        }
+    }
+    values(%target_of);
+}
+
+# Returns a new etc target for building data files in a namespace.
+sub _ns_target_new_etc {
+    my ($ns, $prop_hash_ref) = @_;
+    my $DOT_ETC = $prop_hash_ref->{etc};
+    my $TARGET = 'FCM::Context::Make::Build::Target';
+    $TARGET->new(
+        {   category      => $TARGET->CT_ETC,
+            dep_policy_of => {'etc' => $TARGET->POLICY_CAPTURE},
+            key           => ($ns ? catfile($ns, $DOT_ETC) : $DOT_ETC),
+            ns            => $ns,
+            task          => 'install',
+        }
+    );
+}
+
+# Returns a new archive target for building an object library for a namespace.
+sub _ns_target_new_lib {
+    my ($ns, $prop_hash_ref) = @_;
+    my $NAME = 'libo' . $prop_hash_ref->{a}; # FIXME: libo hard-coded
+    my $TARGET = 'FCM::Context::Make::Build::Target';
+    $TARGET->new(
+        {   category      => $TARGET->CT_LIB,
+            dep_policy_of => {'o' => $TARGET->POLICY_CAPTURE},
+            info_of       => {paths => [], deps => {o => []}},
+            key           => ($ns ? catfile($ns, $NAME) : $NAME),
+            ns            => $ns,
+            task          => 'archive',
+        }
+    );
+}
+
+#-------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::FileType::NS
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::FileType::NS;
+    my $file_type_util = FCM::System::Make::Build::FileType->new(\%attrib);
+    $file_type_util->ns_targets($m_ctx, $ctx, @targets);
+
+=head1 DESCRIPTION
+
+Generates name space level targets.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Creates and returns a new instance.
+
+=item $instance->id()
+
+Returns the recommended ID of this file type.
+
+=item $instance->ns_targets(\@targets,\%prop_of)
+
+Using the information in the original list of targets, creates and returns the
+contexts of a list of extra targets based on the name spaces of the original
+list. In the current settings, a target with no type (i.e. a data file target)
+will generate a C<.etc> target for the container name spaces; a target in the
+C<o> category will generate a C<libo.a> target for the container name spaces.
+
+=item $instance->ns_targets_deps()
+
+Returns a list of dependency types used by
+$instance->ns_targets(\@targets,\%prop_of).
+
+=item $instance->target_file_ext_of()
+
+Returns a HASH reference containing a map between the named types of file
+extensions used by the $instance->ns_targets(\@targets,\%prop_of) method
+and their default values.
+
+=item $instance->target_file_name_option_of()
+
+Returns a HASH reference containing a map between the named types of files
+used by the $instance->source_to_targets($source,\%prop_of) method
+and their default settings for other file naming options.
+
+=item $instance->task_of()
+
+Returns a HASH reference containing a map between the named tasks for this file
+type and their implementation objects. Each task should have a
+$task->main($target) method to update a target and optionally a $task->prop_of()
+method to return a HASH reference containing a map between the named properties
+used by the task and their default values.
+
+=back
+
+=head1 TODO
+
+The configuration in this module is a bit hard coded. It can do with a refactor.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/FileType/Script.pm b/lib/FCM/System/Make/Build/FileType/Script.pm
new file mode 100644
index 0000000..aa466b3
--- /dev/null
+++ b/lib/FCM/System/Make/Build/FileType/Script.pm
@@ -0,0 +1,100 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::FileType::Script;
+use base qw{FCM::System::Make::Build::FileType};
+
+use FCM::Context::Make::Build;    # for FCM::Context::Make::Build::Target
+use FCM::System::Make::Build::Task::Install;
+use File::Basename qw{basename};
+
+# RE: file (base) name
+my $RE_FILE = qr{[\w\-+.]+}imsx;
+
+# Dependency types and CODE to extract them
+my %SOURCE_ANALYSE_DEP_OF
+    = (bin => sub { $_[0] =~ qr{\A\s*(?:\#|;)\s*calls\s*:\s*($RE_FILE)}imsx });
+
+# Alias
+my $TARGET = 'FCM::Context::Make::Build::Target';
+
+# Handler of tasks
+my %TASK_CLASS_OF = (install => 'FCM::System::Make::Build::Task::Install');
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    $attrib_ref->{dest_keys} ||= [$TARGET->CT_INCLUDE, 'o', 'o.special'];
+    my $SOURCE_TO_TARGETS
+        = sub {_source_to_targets($attrib_ref->{dest_keys}, @_)};
+    bless(
+        FCM::System::Make::Build::FileType->new({
+            id                    => 'script',
+            file_she              => q{}, # Value not used, for file type match
+            file_ext              => q{},
+            source_analyse_dep_of => {%SOURCE_ANALYSE_DEP_OF},
+            source_to_targets     => \&_source_to_targets,
+            task_class_of         => {%TASK_CLASS_OF},
+            %{$attrib_ref},
+        }),
+        $class,
+    );
+}
+
+# Returns a list of targets for a given build source.
+sub _source_to_targets {
+    my ($attrib_ref, $source, $prop_hash_ref) = @_;
+    my $key = basename($source->get_path());
+    $TARGET->new(
+        {   category      => $TARGET->CT_BIN,
+            deps          => [@{$source->get_deps()}],
+            dep_policy_of => {'bin', $TARGET->POLICY_CAPTURE},
+            key           => $key,
+            task          => 'install',
+        }
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::FileType::Script
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::FileType::Script;
+    my $helper = FCM::System::Make::Build::FileType::Script->new();
+    $helper->source_analyse($handle);
+
+=head1 DESCRIPTION
+
+A wrapper of
+L<FCM::System::Make::Build::FileType|FCM::System::Make::Build::FileType>
+with configurations to work with some UKMO script files.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Archive.pm b/lib/FCM/System/Make/Build/Task/Archive.pm
new file mode 100644
index 0000000..e486483
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Archive.pm
@@ -0,0 +1,133 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Archive;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::Event;
+use FCM::System::Exception;
+use File::Spec::Functions qw{abs2rel catfile};
+use List::Util qw{first};
+use Text::ParseWords qw{shellwords};
+
+our %PROP_OF = (ar => 'ar', 'ar.flags' => 'rs');
+my $E = 'FCM::System::Exception';
+
+__PACKAGE__->class(
+    {prop_of => {isa => '%', default => {%PROP_OF}}, util => '&'},
+    {action_of => {main => \&_main, prop_of => sub {\%PROP_OF}}},
+);
+
+sub _main {
+    my ($attrib_ref, $target) = @_;
+    # Selects the correct dependent objects
+    my @paths = @{$target->get_info_of('paths')};
+    my %dep_keys_of = %{$target->get_info_of('deps')};
+    my @paths_of_o = ();
+    my $abs2rel_func
+        = sub {index($_[0], $paths[0]) == 0 ? abs2rel($_[0], $paths[0]) : $_[0]};
+    while (my ($type, $key_list_ref) = each(%dep_keys_of)) {
+        for my $key (@{$key_list_ref}) {
+            my $path = first {-e} map {catfile($_, 'o', $key)} @paths;
+            if ($path) {
+                push(@paths_of_o, $abs2rel_func->($path));
+            }
+        }
+    }
+    my @command_list = (
+        (map {shellwords($target->get_prop_of($_))} qw{ar ar.flags}),
+        $target->get_path(),
+        @paths_of_o,
+    );
+    my %value_of = %{$attrib_ref->{util}->shell_simple(\@command_list)};
+    if ($value_of{rc}) {
+        return $E->throw(
+            $E->SHELL, {command_list => \@command_list, %value_of}, $value_of{e},
+        );
+    }
+    $attrib_ref->{util}->event(
+        FCM::Context::Event->MAKE_BUILD_SHELL_OUT, @value_of{qw{o e}},
+    );
+    $target;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Link
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Link;
+    my $build_task = FCM::System::Make::Build::Task::Link->new(\%attrib);
+    $build_task->main($target);
+
+=head1 DESCRIPTION
+
+Invokes the linker to create the target executable.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Creates and returns a new instance. %attrib should contain:
+
+=over 4
+
+=item {prop_of}
+
+A HASH that maps the property names (used by this task) to their default values.
+
+=item {util}
+
+An instance of L<FCM::Util|FCM::Util>.
+
+=back
+
+=item $instance->main($target)
+
+Invokes the "ar" command to create the $target object archive. It uses the
+$target->get_info_of('deps')->{o} ARRAY. All "o" dependency items are placed in
+the archive.
+
+=item $instance->prop_of()
+
+Returns the HASH that maps the property names (used by this task) to their
+default values.
+
+=back
+
+=head1 CONSTANTS
+
+=item %FCM::System::Make::Build::Task::Link::PROP_OF
+
+A map containing the property names and their default values.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Compile.pm b/lib/FCM/System/Make/Build/Task/Compile.pm
new file mode 100644
index 0000000..cf33d26
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Compile.pm
@@ -0,0 +1,143 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+use FCM::System::Exception;
+my $E = 'FCM::System::Exception';
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Compile;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::Event;
+use FCM::System::Make::Build::Task::Share qw{_props_to_opts};
+use File::Spec::Functions qw{abs2rel catfile};
+use Text::ParseWords qw{shellwords};
+
+__PACKAGE__->class(
+    {name => '$', prop_of => '&', util => '&'},
+    {action_of => {main => \&_main, prop_of => \&_prop_of}},
+);
+
+sub _main {
+    my ($attrib_ref, $target) = @_;
+    my $NAME  = $attrib_ref->{name};
+    my $P     = sub {scalar($target->get_prop_of($_[0]))};
+    my @paths = @{$target->get_info_of('paths')};
+    my $abs2rel_func
+        = sub {index($_[0], $paths[0]) == 0 ? abs2rel($_[0], $paths[0]) : $_[0]};
+    my @include_paths
+        = map {catfile(($_ eq $paths[0] ? q{.} : $_), 'include')} @paths;
+    my %opt_of = (
+        c   => $P->($NAME . '.flag-compile'),
+        D   => $P->($NAME . '.flag-define'),
+        I   => $P->($NAME . '.flag-include'),
+        M   => $P->($NAME . '.flag-module'),    # FIXME
+        o   => $P->($NAME . '.flag-output'),
+    );
+    my @command_list = (
+        shellwords($P->($NAME)),
+        _props_to_opts($opt_of{o}, $abs2rel_func->($target->get_path())),
+        $opt_of{c},
+        _props_to_opts($opt_of{D}, shellwords($P->($NAME .  '.defs'))),
+        _props_to_opts($opt_of{I}, @include_paths),
+        _props_to_opts($opt_of{I}, shellwords($P->($NAME .  '.include-paths'))),
+        _props_to_opts($opt_of{M}, @include_paths),
+        shellwords($P->($NAME . '.flag-omp')),
+        shellwords($P->($NAME . '.flags')),
+        $target->get_path_of_source(),
+    );
+    my %value_of = %{$attrib_ref->{util}->shell_simple(\@command_list)};
+    if ($value_of{rc}) {
+        return $E->throw(
+            $E->SHELL, {command_list => \@command_list, %value_of}, $value_of{e},
+        );
+    }
+    $attrib_ref->{util}->event(
+        FCM::Context::Event->MAKE_BUILD_SHELL_OUT, @value_of{qw{o e}},
+    );
+    $target;
+}
+
+sub _prop_of {
+    my ($attrib_ref) = @_;
+    $attrib_ref->{prop_of}->(@_);
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Compile
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Compile;
+    my $build_task = FCM::System::Make::Build::Task::Compile->new(\%attrib);
+    $build_task->main($target);
+
+=head1 DESCRIPTION
+
+Invokes the compiler command on the source of a target to generate the path of
+the target.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Creates and returns a new instance. %attrib should contain:
+
+=over 4
+
+=item {name}
+
+The property name of the compiler command.
+
+=item {prop_of}
+
+A CODE to implement the $instance->prop_of($target) method.
+
+=item {util}
+
+An instance of L<FCM::Util|FCM::Util>.
+
+=back
+
+=item $instance->main($target)
+
+Invokes the compiler command in a shell to compile the source path of the
+$target into an object file in the path of the $target.
+
+=item $instance->prop_of($target)
+
+Returns the HASH that maps the property names (used by this task) to their
+default values.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Compile/C.pm b/lib/FCM/System/Make/Build/Task/Compile/C.pm
new file mode 100644
index 0000000..7fc470c
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Compile/C.pm
@@ -0,0 +1,70 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Compile::C;
+use base qw{FCM::System::Make::Build::Task::Compile};
+
+our %PROP_OF = (
+    'cc'               => 'gcc',
+    'cc.defs'          => '',
+    'cc.flags'         => '',
+    'cc.flag-compile'  => '-c',
+    'cc.flag-define'   => '-D%s',
+    'cc.flag-include'  => '-I%s',
+    'cc.flag-omp'      => '',
+    'cc.flag-output'   => '-o%s',
+    'cc.include-paths' => '',
+);
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        $class->SUPER::new(
+            {name => 'cc', prop_of => sub {return {%PROP_OF}}, %{$attrib_ref}},
+        ),
+        $class,
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Compile::C
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Compile::C;
+    my $task = FCM::System::Make::Build::Task::Compile::C->new(\%attrib);
+    $task->main($target);
+
+=head1 DESCRIPTION
+
+Wraps L<FCM::System::Make::Build::Task::Compile> to compile a C source into an
+object.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Compile/CXX.pm b/lib/FCM/System/Make/Build/Task/Compile/CXX.pm
new file mode 100644
index 0000000..7f7e509
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Compile/CXX.pm
@@ -0,0 +1,70 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Compile::CXX;
+use base qw{FCM::System::Make::Build::Task::Compile};
+
+our %PROP_OF = (
+    'cxx'               => 'g++',
+    'cxx.defs'          => '',
+    'cxx.flags'         => '',
+    'cxx.flag-compile'  => '-c',
+    'cxx.flag-define'   => '-D%s',
+    'cxx.flag-include'  => '-I%s',
+    'cxx.flag-omp'      => '',
+    'cxx.flag-output'   => '-o%s',
+    'cxx.include-paths' => '',
+);
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        $class->SUPER::new(
+            {name => 'cxx', prop_of => sub {return {%PROP_OF}}, %{$attrib_ref}},
+        ),
+        $class,
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Compile::CXX
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Compile::CXX;
+    my $task = FCM::System::Make::Build::Task::Compile::CXX->new(\%attrib);
+    $task->main($target);
+
+=head1 DESCRIPTION
+
+Wraps L<FCM::System::Make::Build::Task::Compile> to compile a C++ source into an
+object.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Compile/Fortran.pm b/lib/FCM/System/Make/Build/Task/Compile/Fortran.pm
new file mode 100644
index 0000000..abee5d4
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Compile/Fortran.pm
@@ -0,0 +1,114 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Compile::Fortran;
+use base qw{FCM::System::Make::Build::Task::Compile};
+
+our %PROP_NO_PREPROCESS_OF = (
+    'fc'               => 'gfortran',
+    'fc.flags'         => '',
+    'fc.flag-compile'  => '-c',
+    'fc.flag-include'  => '-I%s',
+    'fc.flag-module'   => '',
+    'fc.flag-omp'      => '',
+    'fc.flag-output'   => '-o%s',
+    'fc.include-paths' => '',
+);
+
+our %PROP_OF = (
+    %PROP_NO_PREPROCESS_OF,
+    'fc.defs'          => '',
+    'fc.flag-define'   => '-D%s',
+);
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        $class->SUPER::new(
+            {name => 'fc', prop_of => \&_prop_of, %{$attrib_ref}},
+        ),
+        $class,
+    );
+}
+
+sub _prop_of {
+    my ($attrib_ref, $target) = @_;
+    if (!defined($target)) {
+        return {%PROP_OF};
+    }
+    my $file_ext = $attrib_ref->{util}->file_ext($target->get_path_of_source());
+    (lc($file_ext) eq $file_ext) ? {%PROP_NO_PREPROCESS_OF} : {%PROP_OF};
+}
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Compile::Fortran::Extra;
+use base qw{FCM::Class::CODE};
+
+use FCM::System::Exception;
+use File::Basename qw{basename};
+
+my $E = 'FCM::System::Exception';
+
+our %PROP_OF = %FCM::System::Make::Build::Task::Compile::Fortran::PROP_OF;
+
+__PACKAGE__->class(
+    {prop_of => {isa => '%', default => {%PROP_OF}}},
+    {action_of => {main => \&_main, prop_of => sub {\%PROP_OF}}},
+);
+
+sub _main {
+    my ($attrib_ref, $target) = @_;
+    my ($source, $dest) = (basename($target->get_key()), $target->get_path());
+    if (!-e $source) {
+        return;
+    }
+    rename($source, $dest) || return $E->throw($E->COPY, [$source, $dest], $!);
+    $target;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Compile::Fortran
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Compile::Fortran;
+    my $task = FCM::System::Make::Build::Task::Compile::Fortran->new(\%attrib);
+    $task->main($target);
+
+=head1 DESCRIPTION
+
+Wraps L<FCM::System::Make::Build::Task::Compile> to compile a Fortran source into
+an object.
+
+The module also provides the
+FCM::System::Make::Build::Task::Compile::Fortran::Extra class to deal with the
+module definition file generated by the compiler in the working directory.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/ExtractInterface.pm b/lib/FCM/System/Make/Build/Task/ExtractInterface.pm
new file mode 100644
index 0000000..82e602d
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/ExtractInterface.pm
@@ -0,0 +1,536 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::ExtractInterface;
+use base qw{FCM::Class::CODE};
+
+use FCM::System::Exception;
+use Text::Balanced qw{extract_bracketed extract_delimited};
+use Text::ParseWords qw{shellwords};
+
+# Alias
+my $E = 'FCM::System::Exception';
+
+# Regular expressions
+my $RE_ATTR = qr{
+    allocatable|dimension|external|intent|optional|parameter|pointer|save|target
+}imsx;
+my $RE_FILE = qr{[\w\-+.]+}imsx;
+my $RE_NAME = qr{[A-Za-z]\w*}imsx;
+my $RE_SPEC = qr{
+    character|class|complex|double\s*complex|double\s*precision|integer|
+    logical|procedure|real|type
+}imsx;
+my $RE_UNIT_BASE = qr{block\s*data|module|program|submodule}imsx;
+my $RE_UNIT_CALL = qr{function|subroutine}imsx;
+my $RE_UNIT      = qr{$RE_UNIT_BASE|$RE_UNIT_CALL}msx;
+my %RE           = (
+    COMMENT     => qr{\A\s*(?:!|\z)}msx,
+    COMMENT_END => qr{\A([^'"]*?)\s*!.*\z}msx,
+    CONT        => qr{\A(.*)&\s*\z}msx,
+    CONT_LEAD   => qr{\A(\s*&)(.*)\z}msx,
+    INCLUDE     => qr{(?:\#|\s*)include\s*}imsx,
+    NAME_COMP   => qr{\b($RE_NAME)(?:\s*\%\s*$RE_NAME)*\b}msx,
+    NAME_LEAD   => qr{\A\s*$RE_NAME\s*}msx,
+    NAME_LIST   => qr{\A(?:.*?)\s*,\s*($RE_NAME)\b(.*)\z}msx,
+    QUOTE       => qr{\A[^'"]*(['"])}msx,
+    TYPE_ATTR   => qr{\A\s*($RE_ATTR)\b}msx,
+    TYPE_SPEC   => qr{\A\s*($RE_SPEC)\b}msx,
+    UNIT_ATTR   => qr{\A\s*(?:(?:(?:impure\s+)?elemental|recursive|pure)\s+)+(.*)\z}imsx,
+    UNIT_BASE   => qr{\A\s*($RE_UNIT_BASE)\s+($RE_NAME)\s*\z}imsx,
+    UNIT_CALL   => qr{\A\s*($RE_UNIT_CALL)\s+($RE_NAME)\b}imsx,
+    UNIT_END  => qr{\A\s*(end)(?:\s+($RE_NAME)(?:\s+($RE_NAME))?)?\s*\z}imsx,
+    UNIT_SPEC => qr{\A\s*$RE_SPEC\b(.*)\z}imsx,
+);
+
+# Keywords in type declaration statements
+my %TYPE_DECL_KEYWORD_SET = map { ($_, 1) } qw{
+    allocatable
+    asynchronous
+    contiguous
+    dimension
+    external
+    in
+    inout
+    intent
+    kind
+    len
+    optional
+    out
+    parameter
+    pointer
+    save
+    target
+    value
+    volatile
+};
+
+__PACKAGE__->class({util => '&'}, {action_of => {main => \&_main}});
+
+sub _main {
+    my ($attrib_ref, $target) = @_;
+    my $handle
+        = $attrib_ref->{util}->file_load_handle($target->get_path_of_source());
+    eval {
+        $attrib_ref->{util}->file_save(
+            $target->get_path(),
+            [   map { s{\s+}{ }gmsx; s{\s+\z}{\n}msx; $_ }
+                    map { @{$_->{lines}} }
+                    @{_reduce_to_interface(_extract_statements($handle))}
+            ],
+        );
+    };
+    if ($@) {
+        my $e = $@;
+        if ($E->caught($e) && $e->get_code() eq $E->BUILD_SOURCE_SYN) {
+            unshift(@{$e->get_ctx()}, $target->get_path_of_source());
+        }
+        die($e);
+    }
+    close($handle);
+    $target;
+}
+
+# Reads $handle for the next Fortran statement, handling continuations.
+sub _extract_statements {
+    my ($handle) = @_;
+    my $context = {signature_token_set_of => {}, statements => []};
+    my $state = {
+        in_contains  => undef,
+        in_interface => undef,
+        in_quote     => undef,
+        in_type      => undef,
+        stack        => [],
+    };
+    my $NEW_STATEMENT = sub {
+        {   name        => q{},
+            lines       => [],
+            line_number => 0,
+            symbol      => q{},
+            type        => q{},
+            value       => q{},
+        };
+    };
+    my $statement;
+LINE:
+    while (my $line = readline($handle)) {
+        if (!defined($statement)) {
+            $statement = $NEW_STATEMENT->();
+        }
+        my $value = $line;
+        chomp($value);
+        if (!$statement->{line_number} && index($value, '#') == 0) {
+            $statement->{line_number} = $.;
+            $statement->{name}        = 'cpp';
+        }
+        if ($statement->{name} eq 'cpp') {
+            push(@{$statement->{lines}}, $line);
+            $statement->{value} .= $value;
+            if (rindex($value, '\\') != length($value) - 1) {
+                #push(@{$context->{statements}}, $statement);
+                $statement = undef;
+            }
+            next LINE;
+        }
+        if ($value =~ $RE{COMMENT}) {
+            next LINE;
+        }
+        if (!$statement->{line_number}) {
+            $statement->{line_number} = $.;
+        }
+        my ($cont_head, $cont_tail);
+        if ($statement->{line_number} != $.) {    # is a continuation
+            ($cont_head, $cont_tail) = $value =~ $RE{CONT_LEAD};
+            if ($cont_head) {
+                $value = $cont_tail;
+            }
+        }
+        my ($head, $tail) = (q{}, $value);
+        if ($state->{in_quote} && index($value, $state->{in_quote}) >= 0) {
+            my $index = index($value, $state->{in_quote});
+            $head = substr($value, 0, $index + 1);
+            $tail
+                = length($value) > $index + 1
+                ? substr($value, $index + 2)
+                : q{};
+            $state->{in_quote} = undef;
+        }
+        if (!$state->{in_quote}) {
+            while ($tail) {
+                if (index($tail, q{!}) >= 0) {
+                    if (!($tail =~ s/$RE{COMMENT_END}/$1/)) {
+                        ($head, $tail, $state->{in_quote})
+                            = _extract_statement_quote($head, $tail);
+                    }
+                }
+                else {
+                    while (index($tail, q{'}) > 0
+                        || index($tail, q{"}) > 0)
+                    {
+                        ($head, $tail, $state->{in_quote})
+                            = _extract_statement_quote($head, $tail);
+                    }
+                    $head .= $tail;
+                    $tail = q{};
+                }
+            }
+        }
+        $cont_head ||= q{};
+        push(@{$statement->{lines}}, $cont_head . $head . $tail . "\n");
+        $statement->{value} .= $head . $tail;
+        if (!($statement->{value} =~ s/$RE{CONT}/$1/)) {
+            $statement->{value} =~ s{\s+\z}{}msx;
+            if (_process($statement, $context, $state)) {
+                push(@{$context->{statements}}, $statement);
+            }
+            $statement = undef;
+        }
+    }
+    return $context;
+}
+
+# Helper, removes a quoted string from $tail.
+sub _extract_statement_quote {
+    my ($head, $tail) = @_;
+    my ($extracted, $remainder, $prefix)
+        = extract_delimited($tail, q{'"}, qr{[^'"]*}msx, q{});
+    if ($extracted) {
+        return ($head . $prefix . $extracted, $remainder);
+    }
+    else {
+        my ($quote) = $tail =~ $RE{QUOTE};
+        return ($head . $tail, q{}, $quote);
+    }
+}
+
+# Read a statement and put attributes into $statement
+sub _process {
+    my ($statement, $context, $state) = @_;
+    my $name;
+
+    # End Interface
+    if ($state->{in_interface}) {
+        if ($statement->{value} =~ qr{\A\s*end\s*interface\b}imsx) {
+            $state->{in_interface} = 0;
+        }
+        return;
+    }
+
+    # End Program Unit
+    if (@{$state->{stack}} && $statement->{value} =~ qr{\A\s*end\b}imsx) {
+        my ($end, $type, $symbol) = lc($statement->{value}) =~ $RE{UNIT_END};
+        if (!$end) {
+            return;
+        }
+        my ($top_type, $top_symbol) = @{$state->{stack}->[-1]};
+        if (!$type
+            || $top_type eq $type && (!$symbol || $top_symbol eq $symbol))
+        {
+            pop(@{$state->{stack}});
+            if ($state->{in_contains} && !@{$state->{stack}}) {
+                $state->{in_contains} = 0;
+            }
+            if (!$state->{in_contains}) {
+                $statement->{name}   = $top_type;
+                $statement->{symbol} = $top_symbol;
+                $statement->{type}   = 'end';
+                return $statement;
+            }
+        }
+        return;
+    }
+
+    # Interface/Contains
+    if ($statement->{value} =~ qr{\A\s*contains\b}imsx) {
+        $state->{'in_contains'} = 1;
+        return;
+    }
+    if ($statement->{value} =~ qr{\A\s*(?:abstract\s+)?interface\b}imsx) {
+        $state->{'in_interface'} = 1;
+        return;
+    }
+
+    # Program Unit
+    my ($type, $symbol, @tokens) = _process_prog_unit($statement->{value});
+    if ($type) {
+        push(@{$state->{stack}}, [$type, $symbol]);
+        if ($state->{in_contains}) {
+            return;
+        }
+        $statement->{name}   = lc($type);
+        $statement->{type}   = 'signature';
+        $statement->{symbol} = lc($symbol);
+        $context->{signature_token_set_of}{$symbol}
+            = {map { (lc($_) => 1) } @tokens};
+        return $statement;
+    }
+    if ($state->{in_contains}) {
+        return;
+    }
+
+    # Use
+    if ($statement->{value} =~ qr{\A\s*(use)\b}imsx) {
+        $statement->{name} = 'use';
+        $statement->{type} = 'use';
+        return $statement;
+    }
+
+    # Type Declarations
+    ($name) = $statement->{value} =~ $RE{TYPE_SPEC};
+    if ($name) {
+        $name =~ s{\s}{}gmsx;
+        $statement->{name} = lc($name);
+        $statement->{type} = 'type';
+        return $statement;
+    }
+
+    # Attribute Statements
+    ($name) = $statement->{value} =~ $RE{TYPE_ATTR};
+    if ($name) {
+        $statement->{name} = $name;
+        $statement->{type} = 'attr';
+    }
+}
+
+# Parse a statement for program unit header. Returns a list containing the type,
+# the symbol and the signature tokens of the program unit.
+sub _process_prog_unit {
+    my ($string) = @_;
+    my ($type, $symbol, @args) = (q{}, q{});
+    ($type, $symbol) = $string =~ $RE{UNIT_BASE};
+    if ($type) {
+        $type = lc($type);
+        $type =~ s{\s*}{}gmsx;
+        return ($type, $symbol);
+    }
+    $string =~ s/$RE{UNIT_ATTR}/$1/;
+    my ($match) = $string =~ $RE{UNIT_SPEC};
+    if ($match) {
+        $string = $match;
+        extract_bracketed($string);
+    }
+    ($type, $symbol) = lc($string) =~ $RE{UNIT_CALL};
+    if (!$type) {
+        return;
+    }
+    my $extracted = extract_bracketed($string, q{()}, qr{[^(]*}msx);
+
+    # Get arguments/keywords from SUBROUTINE/FUNCTION
+    if ($extracted) {
+        $extracted =~ s{\s}{}gmsx;
+        @args = split(q{,}, substr($extracted, 1, length($extracted) - 2));
+        if ($type eq 'function') {
+            my $result = extract_bracketed($string, q{()}, qr{[^(]*}msx);
+            if ($result) {
+                $result =~ s{\A\(\s*(.*?)\s*\)\z}{$1}msx;    # remove braces
+                push(@args, $result);
+            }
+            else {
+                push(@args, $symbol);
+            }
+        }
+    }
+    return (lc($type), lc($symbol), map { lc($_) } @args);
+}
+
+# Reduces the list of statements to contain only the interface block.
+sub _reduce_to_interface {
+    my ($context) = @_;
+    my (%token_set, @interface_statements);
+STATEMENT:
+    for my $statement (reverse(@{$context->{statements}})) {
+        if ($statement->{type} eq 'end'
+            && grep { $_ eq $statement->{name} } qw{subroutine function})
+        {
+            push(@interface_statements, $statement);
+            %token_set
+                = %{$context->{signature_token_set_of}{$statement->{symbol}}};
+            next STATEMENT;
+        }
+        if ($statement->{type} eq 'signature'
+            && grep { $_ eq $statement->{name} } qw{subroutine function})
+        {
+            push(@interface_statements, $statement);
+            %token_set = ();
+            next STATEMENT;
+        }
+        if ($statement->{type} eq 'use') {
+            my ($head, $tail)
+                = split(qr{,\s*only\s*:\s*}msx, lc($statement->{value}), 2);
+            if ($tail) {
+                my @imports = map { [split(qr{\s*=>\s*}msx, $_, 2)] }
+                    split(qr{\s*,\s*}msx, $tail);
+                my @useful_imports
+                    = grep { exists($token_set{$_->[0]}) } @imports;
+                if (!@useful_imports) {
+                    next STATEMENT;
+                }
+                if (@imports != @useful_imports) {
+                    my @token_strings
+                        = map { $_->[0] . ($_->[1] ? ' => ' . $_->[1] : q{}) }
+                        @useful_imports;
+                    my ($last, @rest) = reverse(@token_strings);
+                    my @token_lines
+                        = (reverse(map { $_ . q{,&} } @rest), $last);
+                    push(
+                        @interface_statements,
+                        {   lines => [
+                                sprintf("%s, only:&\n", $head),
+                                (map { sprintf(" & %s\n", $_) } @token_lines),
+                            ]
+                        },
+                    );
+                    next STATEMENT;
+                }
+            }
+            push(@interface_statements, $statement);
+            next STATEMENT;
+        }
+        if ($statement->{type} eq 'attr') {
+            my ($spec, @tokens) = ($statement->{value} =~ /$RE{NAME_COMP}/g);
+            if (grep { exists($token_set{$_}) } @tokens) {
+                for my $token (@tokens) {
+                    $token_set{$token} = 1;
+                }
+                push(@interface_statements, $statement);
+                next STATEMENT;
+            }
+        }
+        if ($statement->{type} eq 'type') {
+            my ($variable_string, $spec_string)
+                = reverse(split('::', lc($statement->{value}), 2));
+            if ($spec_string) {
+                $spec_string =~ s{$RE{NAME_LEAD}}{}msx;
+            }
+            else {
+                $variable_string =~ s{$RE{NAME_LEAD}}{}msx;
+                $spec_string = extract_bracketed($variable_string, '()',
+                    qr{[\s\*]*}msx);
+            }
+            my $tail = q{,} . lc($variable_string);
+            my @tokens;
+            while ($tail) {
+                if ($tail =~ qr{\A\s*['"]}msx) {
+                    my $old_tail = $tail;
+                    extract_delimited($tail, q{'"}, qr{\A[^'"]*}msx, q{});
+                    if ($old_tail eq $tail) {
+                        return $E->throw(
+                            $E->BUILD_SOURCE_SYN, [$statement->{line_number}]);
+                    }
+                }
+                elsif ($tail =~ qr{\A\s*\(}msx) {
+                    my $old_tail = $tail;
+                    extract_bracketed($tail, '()', qr{\A[^(]*}msx);
+                    if ($old_tail eq $tail) {
+                        return $E->throw(
+                            $E->BUILD_SOURCE_SYN, [$statement->{line_number}]);
+                    }
+                }
+                else {
+                    my $token;
+                    ($token, $tail) = $tail =~ $RE{NAME_LIST};
+                    if ($token && $token_set{$token}) {
+                        @tokens = ($variable_string =~ /$RE{NAME_COMP}/g);
+                        $tail = q{};
+                    }
+                }
+            }
+            if (@tokens && $spec_string) {
+                my @spec_tokens = (lc($spec_string) =~ /$RE{NAME_COMP}/g);
+                push(
+                    @tokens,
+                    (   grep { !exists($TYPE_DECL_KEYWORD_SET{$_}) }
+                            @spec_tokens
+                    ),
+                );
+            }
+            if (grep { exists($token_set{$_}) } @tokens) {
+                for my $token (@tokens) {
+                    $token_set{$token} = 1;
+                }
+                push(@interface_statements, $statement);
+                next STATEMENT;
+            }
+        }
+    }
+    if (!@interface_statements) {
+        return [];
+    }
+    [   {lines => ["interface\n"]},
+        reverse(@interface_statements),
+        {lines => ["end interface\n"]},
+    ];
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::ExtractInterface
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::ExtractInterface;
+    my $task = FCM::System::Make::Build::Task::ExtractInterface->new(\%attrib);
+    $task->main($target);
+
+=head1 DESCRIPTION
+
+Extracts the calling interfaces of top level functions and subroutines in the
+Fortran source file of the target.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Creates and returns a new instance. %attrib should contain:
+
+=over 4
+
+=item {util}
+
+An instance of L<FCM::Util|FCM::Util>.
+
+=back
+
+=item $instance->main($target)
+
+Extracts the calling interfaces of top level functions and subroutines in the
+Fortran source file of the target, and writes the results to the path of the
+target.
+
+=back
+
+=head1 ACKNOWLEDGEMENT
+
+This module is inspired by the logic developed by the European Centre
+for Medium-Range Weather Forecasts (ECMWF).
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Install.pm b/lib/FCM/System/Make/Build/Task/Install.pm
new file mode 100644
index 0000000..45bd1b0
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Install.pm
@@ -0,0 +1,90 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Install;
+use base qw{FCM::Class::CODE};
+
+use FCM::System::Exception;
+use File::Copy qw{copy};
+
+my $E = 'FCM::System::Exception';
+
+__PACKAGE__->class({util => '&'}, {action_of => {main => \&_main}});
+
+sub _main {
+    my ($attrib_ref, $target) = @_;
+    my ($source, $dest) = ($target->get_path_of_source(), $target->get_path());
+    if ($source) {
+        copy($source, $dest) || return $E->throw($E->COPY, [$source, $dest], $!);
+        chmod((stat($source))[2] & oct(7777), $dest)
+            || return $E->throw($E->DEST_CREATE, $dest, $!);
+    }
+    else {
+        $attrib_ref->{util}->file_save($dest, q{});
+    }
+    $target;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Install
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Install;
+    my $build_task = FCM::System::Make::Build::Task::Install->new(\%attrib);
+    $build_task->main( $target);
+
+=head1 DESCRIPTION
+
+Copies the source of the target to its path.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Creates and returns a new instance. %attrib should contain:
+
+=over 4
+
+=item {util}
+
+An instance of L<FCM::Util|FCM::Util>.
+
+=back
+
+=item $instance->main($target)
+
+Copies the source of the target to its path.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Link.pm b/lib/FCM/System/Make/Build/Task/Link.pm
new file mode 100644
index 0000000..5ab63da
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Link.pm
@@ -0,0 +1,195 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Link;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::Event;
+use FCM::System::Exception;
+use FCM::System::Make::Build::Task::Archive;
+use FCM::System::Make::Build::Task::Share qw{_props_to_opts};
+use File::Basename qw{basename};
+use File::Path qw{mkpath rmtree};
+use File::Spec::Functions qw{abs2rel catfile};
+use File::Temp qw{tempdir};
+use List::Util qw{first};
+use Text::ParseWords qw{shellwords};
+
+my $E = 'FCM::System::Exception';
+
+our %PROP_OF = (
+    %FCM::System::Make::Build::Task::Archive::PROP_OF,
+    'ld' => '',
+    'keep-lib-o' => '',
+);
+
+__PACKAGE__->class(
+    {name => '$', prop_of => '%', util => '&'},
+    {action_of => {main => \&_main, prop_of => sub {$_[0]->{prop_of}}}},
+);
+
+sub _main {
+    my ($attrib_ref, $target) = @_;
+    my $NAME  = $attrib_ref->{name};
+    my $P     = sub {$target->get_prop_of($_[0])};
+    # Selects the correct dependent objects
+    my @paths = @{$target->get_info_of('paths')};
+    my %dep_keys_of = %{$target->get_info_of('deps')};
+    my %paths_of = (o => [], 'o.special' => []);
+    my $abs2rel_func
+        = sub {index($_[0], $paths[0]) == 0 ? abs2rel($_[0], $paths[0]) : $_[0]};
+    while (my ($type, $key_list_ref) = each(%dep_keys_of)) {
+        for my $key (@{$key_list_ref}) {
+            my $path = first {-e} map {catfile($_, 'o', $key)} @paths;
+            if ($path) {
+                push(@{$paths_of{$type}}, $abs2rel_func->($path));
+            }
+        }
+    }
+    my $path_of_main_o = shift(@{$paths_of{o}});
+    my $keep_lib_o = $P->('keep-lib-o');
+    my $lib_o_dir;
+    if ($keep_lib_o) {
+        $lib_o_dir = $target->CT_LIB;
+        mkpath($lib_o_dir);
+    }
+    else {
+        $lib_o_dir = tempdir(CLEANUP => 1);
+    }
+    my ($extension, $root)
+        = $attrib_ref->{util}->file_ext(basename($target->get_key()));
+    my $lib_o = catfile($lib_o_dir, "lib$root.a");
+    my %opt_of = (
+        o => $P->($NAME . '.flag-output'),
+        L => $P->($NAME . '.flag-lib-path'),
+        l => $P->($NAME . '.flag-lib'),
+    );
+    for my $command_list_ref (
+        # Archive (when linking multiple objects)
+        (   @{$paths_of{o}}
+            ?   [   shellwords($P->('ar')),
+                    shellwords($P->('ar.flags')),
+                    $lib_o,
+                    @{$paths_of{o}},
+                ]
+            :   ()
+        ),
+        # Link
+        [   ($P->('ld') ? shellwords($P->('ld')) : shellwords($P->($NAME))),
+            _props_to_opts($opt_of{o}, $abs2rel_func->($target->get_path())),
+            $path_of_main_o,
+            @{$paths_of{'o.special'}},
+            (   @{$paths_of{o}}
+                ?   (   _props_to_opts($opt_of{L}, $lib_o_dir),
+                        _props_to_opts($opt_of{l}, $root),
+                    )
+                :   ()
+            ),
+            _props_to_opts($opt_of{L}, shellwords($P->($NAME .  '.lib-paths'))),
+            _props_to_opts($opt_of{l}, shellwords($P->($NAME .  '.libs'))),
+            shellwords($P->($NAME . '.flag-omp')),
+            shellwords($P->($NAME . '.flags-ld')),
+        ],
+    ) {
+        my %value_of = %{$attrib_ref->{util}->shell_simple($command_list_ref)};
+        if ($value_of{rc}) {
+            return $E->throw(
+                $E->SHELL,
+                {command_list => $command_list_ref, %value_of},
+                $value_of{e},
+            );
+        }
+        $attrib_ref->{util}->event(
+            FCM::Context::Event->MAKE_BUILD_SHELL_OUT, @value_of{qw{o e}},
+        );
+    }
+    if (!$keep_lib_o) {
+        unlink($lib_o);
+        rmtree($lib_o_dir);
+    }
+    $target;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Link
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Link;
+    my $build_task = FCM::System::Make::Build::Task::Link->new(\%attrib);
+    $build_task->main($target);
+
+=head1 DESCRIPTION
+
+Invokes the linker to create the target executable.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Creates and returns a new instance. %attrib should contain:
+
+=over 4
+
+=item {name}
+
+The property name of the linker.
+
+=item {prop_of}
+
+A HASH to map the property names (used by this task) to their default values.
+
+=item {util}
+
+An instance of L<FCM::Util|FCM::Util>.
+
+=back
+
+=item $instance->main($target)
+
+Invokes the linker to create the $target executable. It uses the
+$target->get_info_of('deps')->{o} ARRAY and
+$target->get_info_of('deps')->{"o.special"} ARRAY as dependencies. The first
+type "o" dependency item is expected to be the object file containing the main
+program. All other "o" dependency items are placed in a temporary archive
+before invoking the linker command. The main object and "o.special" dependency
+items are entered into the command line of the linker to produce the
+executable.
+
+=item $instance->prop_of()
+
+Returns the HASH that maps the property names (used by this task) to their
+default values.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Link/C.pm b/lib/FCM/System/Make/Build/Task/Link/C.pm
new file mode 100644
index 0000000..99ebef3
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Link/C.pm
@@ -0,0 +1,72 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Link::C;
+use base qw{FCM::System::Make::Build::Task::Link};
+
+use FCM::System::Make::Build::Task::Compile::C;
+
+our %PROP_OF = (
+    %FCM::System::Make::Build::Task::Link::PROP_OF,
+    (   map {$_ => $FCM::System::Make::Build::Task::Compile::C::PROP_OF{$_}}
+        qw{cc cc.flag-omp cc.flag-output}
+    ),
+    'cc.flags-ld'      => '',
+    'cc.flag-lib'      => '-l%s',
+    'cc.flag-lib-path' => '-L%s',
+    'cc.libs'          => '',
+    'cc.lib-paths'     => '',
+);
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        $class->SUPER::new(
+            {name => 'cc', prop_of => {%PROP_OF}, %{$attrib_ref}},
+        ),
+        $class,
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Link::C
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Link::C;
+    my $task = FCM::System::Make::Build::Task::Link::C->new(\%attrib);
+    $task->main($target);
+
+=head1 DESCRIPTION
+
+Wraps L<FCM::System::Make::Build::Task::Link> to link a C object into an
+executable.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Link/CXX.pm b/lib/FCM/System/Make/Build/Task/Link/CXX.pm
new file mode 100644
index 0000000..1c5848c
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Link/CXX.pm
@@ -0,0 +1,72 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Link::CXX;
+use base qw{FCM::System::Make::Build::Task::Link};
+
+use FCM::System::Make::Build::Task::Compile::CXX;
+
+our %PROP_OF = (
+    %FCM::System::Make::Build::Task::Link::PROP_OF,
+    (   map {$_ => $FCM::System::Make::Build::Task::Compile::CXX::PROP_OF{$_}}
+        qw{cxx cxx.flag-omp cxx.flag-output}
+    ),
+    'cxx.flags-ld'      => '',
+    'cxx.flag-lib'      => '-l%s',
+    'cxx.flag-lib-path' => '-L%s',
+    'cxx.libs'          => '',
+    'cxx.lib-paths'     => '',
+);
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        $class->SUPER::new(
+            {name => 'cxx', prop_of => {%PROP_OF}, %{$attrib_ref}},
+        ),
+        $class,
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Link::CXX
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Link::CXX;
+    my $task = FCM::System::Make::Build::Task::Link::CXX->new(\%attrib);
+    $task->main($target);
+
+=head1 DESCRIPTION
+
+Wraps L<FCM::System::Make::Build::Task::Link> to link a C++ object into an
+executable.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Link/Fortran.pm b/lib/FCM/System/Make/Build/Task/Link/Fortran.pm
new file mode 100644
index 0000000..94e8cb4
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Link/Fortran.pm
@@ -0,0 +1,72 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Link::Fortran;
+use base qw{FCM::System::Make::Build::Task::Link};
+
+use FCM::System::Make::Build::Task::Compile::Fortran;
+
+our %PROP_OF = (
+    %FCM::System::Make::Build::Task::Link::PROP_OF,
+    (   map {$_ => $FCM::System::Make::Build::Task::Compile::Fortran::PROP_OF{$_}}
+        qw{fc fc.flag-omp fc.flag-output}
+    ),
+    'fc.flags-ld'      => '',
+    'fc.flag-lib'      => '-l%s',
+    'fc.flag-lib-path' => '-L%s',
+    'fc.libs'          => '',
+    'fc.lib-paths'     => '',
+);
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        $class->SUPER::new(
+            {name => 'fc', prop_of => {%PROP_OF}, %{$attrib_ref}},
+        ),
+        $class,
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Link::Fortran
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Link::Fortran;
+    my $task = FCM::System::Make::Build::Task::Link::Fortran->new(\%attrib);
+    $task->main($target);
+
+=head1 DESCRIPTION
+
+Wraps L<FCM::System::Make::Build::Task::Link> to link a Fortran object into an
+executable.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Preprocess.pm b/lib/FCM/System/Make/Build/Task/Preprocess.pm
new file mode 100644
index 0000000..2147d7b
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Preprocess.pm
@@ -0,0 +1,131 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Preprocess;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::Event;
+use FCM::System::Exception;
+use FCM::System::Make::Build::Task::Share qw{_props_to_opts};
+use File::Spec::Functions qw{catfile};
+use Text::ParseWords qw{shellwords};
+
+use FCM::System::Exception;
+
+my $E = 'FCM::System::Exception';
+
+__PACKAGE__->class(
+    {name => '$', prop_of => '%', util => '&'},
+    {action_of => {main => \&_main, prop_of => sub {$_[0]->{prop_of}}}},
+);
+
+sub _main {
+    my ($attrib_ref, $target) = @_;
+    my $NAME = $attrib_ref->{name};
+    my $P     = sub {$target->get_prop_of($_[0])};
+    my @paths = @{$target->get_info_of('paths')};
+    my @include_paths
+        = map {catfile(($_ eq $paths[0] ? q{.} : $_), 'include')} @paths;
+    my %opt_of = (
+        D => $P->($NAME . '.flag-define'),
+        I => $P->($NAME . '.flag-include'),
+    );
+    my @command = (
+        shellwords($P->($NAME)),
+        shellwords($P->($NAME . '.flags')),
+        _props_to_opts($opt_of{D}, shellwords($P->($NAME .  '.defs'))),
+        _props_to_opts($opt_of{I}, @include_paths),
+        _props_to_opts($opt_of{I}, shellwords($P->($NAME .  '.include-paths'))),
+        $target->get_path_of_source(),
+    );
+    my %value_of = %{$attrib_ref->{util}->shell_simple(\@command)};
+    if ($value_of{rc}) {
+        return $E->throw(
+            $E->SHELL, {command_list => \@command, %value_of}, $value_of{e},
+        );
+    }
+    $attrib_ref->{util}->event(
+        FCM::Context::Event->MAKE_BUILD_SHELL_OUT, undef, $value_of{e},
+    );
+    $value_of{o} ||= q{};
+    $attrib_ref->{util}->file_save($target->get_path(), $value_of{o});
+    $target;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Preprocess
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Preprocess;
+    my $build_task = FCM::System::Make::Build::Task::Preprocess->new(\%attrib);
+    $build_task->main($target);
+
+=head1 DESCRIPTION
+
+Invokes the preprocessor on the target source to generate the target.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Creates and returns a new instance. %attrib should contain:
+
+=over 4
+
+=item {name}
+
+The property name of the preprocessor.
+
+=item {prop_of}
+
+A HASH to map the property names and their default values.
+
+=item {util}
+
+An instance of L<FCM::Util|FCM::Util>.
+
+=back
+
+=item $instance->main($target)
+
+Invokes the preprocessor shell command on the target source to generate the
+target.
+
+=item $instance->prop_of()
+
+Returns the HASH that maps the property names (used by this task) to their
+default values.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Preprocess/C.pm b/lib/FCM/System/Make/Build/Task/Preprocess/C.pm
new file mode 100644
index 0000000..75961fb
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Preprocess/C.pm
@@ -0,0 +1,66 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Preprocess::C;
+use base qw{FCM::System::Make::Build::Task::Preprocess};
+
+our %PROP_OF = (
+    'cpp'               => 'cpp',
+    'cpp.defs'          => '',
+    'cpp.flags'         => '',
+    'cpp.flag-define'   => '-D%s',
+    'cpp.flag-include'  => '-I%s',
+    'cpp.include-paths' => '',
+);
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        $class->SUPER::new(
+            {name => 'cpp', prop_of => {%PROP_OF}, %{$attrib_ref}},
+        ),
+        $class,
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Preprocess::C
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Preprocess::C;
+    my $task = FCM::System::Make::Build::Task::Preprocess::C->new(\%attrib);
+    $task->main($target);
+
+=head1 DESCRIPTION
+
+Wraps L<FCM::System::Make::Build::Task::Preprocess> to preprocess a C source.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Preprocess/Fortran.pm b/lib/FCM/System/Make/Build/Task/Preprocess/Fortran.pm
new file mode 100644
index 0000000..f093f43
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Preprocess/Fortran.pm
@@ -0,0 +1,67 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Preprocess::Fortran;
+use base qw{FCM::System::Make::Build::Task::Preprocess};
+
+our %PROP_OF = (
+    'fpp'               => 'cpp',
+    'fpp.defs'          => '',
+    'fpp.flags'         => '-P -traditional',
+    'fpp.flag-define'   => '-D%s',
+    'fpp.flag-include'  => '-I%s',
+    'fpp.include-paths' => '',
+);
+
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        $class->SUPER::new(
+            {name => 'fpp', prop_of => {%PROP_OF}, %{$attrib_ref}},
+        ),
+        $class,
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Preprocess::Fortran
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Preprocess::Fortran;
+    my $task = FCM::System::Make::Build::Task::Preprocess::Fortran->new(\%attrib);
+    $task->main($target);
+
+=head1 DESCRIPTION
+
+Wraps L<FCM::System::Make::Build::Task::Preprocess> to preprocess a Fortran
+source.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Build/Task/Share.pm b/lib/FCM/System/Make/Build/Task/Share.pm
new file mode 100644
index 0000000..4625b37
--- /dev/null
+++ b/lib/FCM/System/Make/Build/Task/Share.pm
@@ -0,0 +1,94 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Build::Task::Share;
+use base qw{Exporter};
+
+use Text::ParseWords qw{shellwords};
+
+our @EXPORT = qw{_props_to_opts};
+
+sub _props_to_opts {
+    # $opt_value should be an sprintf format with one %s.
+    my ($opt_value, @props) = @_;
+    if (!$opt_value) {
+        return;
+    }
+    my @opt_values = shellwords($opt_value);
+    my $index = -1;
+    I:
+    for my $i (0 .. $#opt_values) {
+        if (index($opt_values[$i], '%s') >= 0) {
+            $index = $i;
+            last I;
+        }
+    }
+    if ($index == -1) {
+        return (@opt_values, @props);
+    }
+    my @return;
+    for my $prop (@props) {
+        push(@return, @opt_values[0 .. $index - 1]);
+        push(@return, sprintf($opt_values[$index], $prop));
+        push(@return, @opt_values[$index + 1 .. $#opt_values]);
+    }
+    return @return;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Build::Task::Share
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Build::Task::Share
+
+=head1 DESCRIPTION
+
+Provides common "local" functions for a make build task.
+
+=head1 FUNCTIONS
+
+The following functions are automatically exported by this module.
+
+=over 4
+
+=item _props_to_opts($opt_value, @props)
+
+Expect $opt_value to be an sprintf format containing one %s, and @props is a
+list of values. Return a list that can be used in a shell command. E.g.:
+
+    _props_to_opts('-D%s', 'HELLO="greetings"', 'WORLD="mars and venus"')
+    # => ('-DHELLO="greetings"', '-DWORLD="mars and venus"')
+    
+    _props_to_opts('-I %s', '/path/1', '/path/2')
+    # => ('-I', '/path/1', '-I', '/path/2')
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Extract.pm b/lib/FCM/System/Make/Extract.pm
new file mode 100644
index 0000000..bcff360
--- /dev/null
+++ b/lib/FCM/System/Make/Extract.pm
@@ -0,0 +1,1212 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Extract;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::ConfigEntry;
+use FCM::Context::Event;
+use FCM::Context::Make::Extract;
+use FCM::Context::Locator;
+use FCM::Context::Task;
+use FCM::System::Exception;
+use FCM::System::Make::Share::Subsystem;
+use File::Basename qw{dirname};
+use File::Compare qw{compare};
+use File::Copy qw{copy};
+use File::Path qw{mkpath rmtree};
+use File::Spec::Functions qw{catfile tmpdir};
+use File::Temp;
+use List::Util qw{first};
+use Storable qw{dclone};
+
+# Aliases
+our $UTIL;
+my $E = 'FCM::System::Exception';
+
+# Configuration parser map: label to action
+our %CONFIG_PARSER_OF = (
+    'location'  => \&_config_parse_location,
+    'ns'        => \&_config_parse_ns_list,
+    'path-excl' => _config_parse_path_func(
+        sub {$_->get_path_excl()}, sub {$_->set_path_excl(@_)}, '@',
+    ),
+    'path-incl' => _config_parse_path_func(
+        sub {$_->get_path_incl()}, sub {$_->set_path_incl(@_)}, '@',
+    ),
+    'path-root' => _config_parse_path_func(
+        sub {$_->get_path_root()}, sub {$_->set_path_root(@_)},
+    ),
+);
+
+# Properties from FCM::Util
+our @UTIL_PROP_KEYS = qw{diff3 diff3.flags};
+
+# Creates the class.
+__PACKAGE__->class(
+    {   config_parser_of => {isa => '%', default => {%CONFIG_PARSER_OF}},
+        prop_of          => '%',
+        shared_util_of   => '%',
+        util             => '&',
+    },
+    {   init => \&_init,
+        action_of => {
+            config_parse              => \&_config_parse,
+            config_parse_class_prop   => \&_config_parse_class_prop,
+            config_parse_inherit_hook => \&_config_parse_inherit_hook,
+            config_unparse            => \&_config_unparse,
+            config_unparse_class_prop => \&_config_unparse_class_prop,
+            ctx                       => \&_ctx,
+            main                      => \&_main,
+        },
+    },
+);
+
+# Initialises the helpers of the class.
+sub _init {
+    my ($attrib_ref) = @_;
+    for my $util_prop_key (@UTIL_PROP_KEYS) {
+        my $prop = $attrib_ref->{util}->external_cfg_get($util_prop_key);
+        $attrib_ref->{prop_of}{$util_prop_key} = [$prop];
+    }
+}
+
+# Reads the extract.location declaration from a config entry.
+sub _config_parse_location {
+    my ($attrib_ref, $ctx, $entry) = @_;
+    if (!@{$entry->get_ns_list()}) {
+        return $E->throw($E->CONFIG_NS, $entry);
+    }
+    my %PARSER_OF = (
+        'base'    => \&_config_parse_location_base,
+        'diff'    => \&_config_parse_location_diff,
+        'primary' => \&_config_parse_location_primary,
+    );
+    my %modifier_of = %{$entry->get_modifier_of()};
+    if (!grep {exists($modifier_of{$_})} keys(%PARSER_OF)) {
+        $modifier_of{'base'} = 1;
+    }
+    for my $key (grep {exists($modifier_of{$_})} keys(%PARSER_OF)) {
+        $PARSER_OF{$key}->($attrib_ref, $ctx, $entry);
+    }
+}
+
+# Reads the extract.location{base} declaration from a config entry.
+sub _config_parse_location_base {
+    my ($attrib_ref, $ctx, $entry) = @_;
+    my %option;
+    if (exists($entry->get_modifier_of()->{'type'})) {
+        %option = ('type' => $entry->get_modifier_of()->{'type'});
+    }
+    for my $ns (@{$entry->get_ns_list()}) {
+        if (!exists($ctx->get_project_of()->{$ns})) {
+            $ctx->get_project_of()->{$ns} = $ctx->CTX_PROJECT->new({ns => $ns});
+        }
+        my $project = $ctx->get_project_of()->{$ns};
+        if ($project->get_inherited()) {
+            if (!$entry->get_value()) {
+                return $E->throw($E->CONFIG_VALUE, $entry);
+            }
+            my $locator = FCM::Context::Locator->new(
+                $entry->get_value(), \%option,
+            );
+            if ($project->get_locator()) {
+                $attrib_ref->{util}->loc_rel2abs(
+                    $locator,
+                    $project->get_locator(),
+                );
+            }
+            $attrib_ref->{util}->loc_as_invariant($locator);
+            my $i_locator = $project->get_trees()->[0]->get_locator();
+            if ($locator->get_value() ne $i_locator->get_value()) {
+                return $E->throw($E->CONFIG_CONFLICT, $entry);
+            }
+        }
+        else {
+            if (    !exists($project->get_trees()->[0])
+                ||  !defined($project->get_trees()->[0])
+            ) {
+                $project->get_trees()->[0]
+                    = $ctx->CTX_TREE->new({key => 0, ns => $ns});
+            }
+            if ($entry->get_value()) {
+                my $locator = FCM::Context::Locator->new(
+                    $entry->get_value(), \%option,
+                );
+                $project->get_trees()->[0]->set_locator($locator);
+            }
+            else {
+                $project->get_trees()->[0] = undef;
+            }
+        }
+    }
+}
+
+# Reads the extract.location{diff} declaration from a config entry.
+sub _config_parse_location_diff {
+    my ($attrib_ref, $ctx, $entry) = @_;
+    my %option;
+    if (exists($entry->get_modifier_of()->{'type'})) {
+        %option = ('type' => $entry->get_modifier_of()->{'type'});
+    }
+    for my $ns (@{$entry->get_ns_list()}) {
+        if (!exists($ctx->get_project_of()->{$ns})) {
+            $ctx->get_project_of()->{$ns} = $ctx->CTX_PROJECT->new({ns => $ns});
+        }
+        my $project = $ctx->get_project_of()->{$ns};
+        my ($base, @diffs) = @{$project->get_trees()};
+        @diffs = grep {
+                $_->get_inherited()
+            ||      $option{type}
+                &&  $_->get_locator()->get_type()
+                &&  $option{type} ne $_->get_locator()->get_type()
+        } @diffs;
+        for my $value ($entry->get_values()) {
+            if (!$value) {
+                return $E->throw($E->CONFIG_VALUE, $entry);
+            }
+            push(
+                @diffs,
+                $ctx->CTX_TREE->new({
+                    key     => scalar(@diffs) + 1,
+                    locator => FCM::Context::Locator->new($value, \%option),
+                    ns      => $ns,
+                }),
+            );
+        }
+        @{$project->get_trees()} = ($base, @diffs);
+    }
+}
+
+# Reads the extract.location{primary} declaration from a config entry.
+sub _config_parse_location_primary {
+    my ($attrib_ref, $ctx, $entry) = @_;
+    my %option;
+    if (exists($entry->get_modifier_of()->{'type'})) {
+        %option = ('type' => $entry->get_modifier_of()->{'type'});
+    }
+    for my $ns (@{$entry->get_ns_list()}) {
+        if (!exists($ctx->get_project_of()->{$ns})) {
+            $ctx->get_project_of()->{$ns} = $ctx->CTX_PROJECT->new({ns => $ns});
+        }
+        my $project = $ctx->get_project_of()->{$ns};
+        if ($project->get_inherited()) {
+            if ($project->get_locator()->get_value() ne $entry->get_value()) {
+                return $E->throw($E->CONFIG_CONFLICT, $entry);
+            }
+        }
+        elsif ($entry->get_value()) {
+            $project->set_locator(
+                FCM::Context::Locator->new($entry->get_value(), \%option),
+            );
+        }
+        else {
+            $project->set_locator(undef);
+        }
+    }
+}
+
+# Reads the extract.ns declaration from a config entry.
+sub _config_parse_ns_list {
+    my ($attrib_ref, $ctx, $entry) = @_;
+    @{$ctx->get_ns_list()} = $entry->get_values();
+}
+
+# Returns a function to parse extract.path-*.
+sub _config_parse_path_func {
+    my ($getter, $setter, $isa) = @_;
+    $isa ||= '$';
+    sub {
+        my ($attrib_ref, $ctx, $entry) = @_;
+        my @ns_list
+            = @{$entry->get_ns_list()} ? @{$entry->get_ns_list()}
+            :                            @{$ctx->get_ns_list()}
+            ;
+        for my $ns (@ns_list) {
+            if (!exists($ctx->get_project_of()->{$ns})) {
+                $ctx->get_project_of()->{$ns}
+                    = $ctx->CTX_PROJECT->new({ns => $ns});
+            }
+            my $project = $ctx->get_project_of()->{$ns};
+            my $value = $entry->get_value();
+            if ($isa eq '@') {
+                $value = [map {$_ eq q{/} ? q{} : $_} $entry->get_values()];
+            }
+            local($_) = $project;
+            if ($_->get_inherited()) {
+                my $old = $getter->();
+                my $new = $value;
+                if ($isa eq '@') {
+                    $old = _config_unparse_join(@{$old});
+                    $new = _config_unparse_join(@{$new});
+                }
+                if ($old ne $new) {
+                    return $E->throw($E->CONFIG_CONFLICT, $entry);
+                }
+            }
+            else {
+                $setter->($value);
+            }
+        }
+    };
+}
+
+# A hook command for the "inherit/use" declaration.
+sub _config_parse_inherit_hook {
+    my ($attrib_ref, $ctx, $i_ctx) = @_;
+    @{$ctx->get_ns_list()} = @{$i_ctx->get_ns_list()};
+    while (my ($ns, $i_project) = each(%{$i_ctx->get_project_of()})) {
+        my $project = dclone($i_project);
+        $project->set_inherited(1);
+        for my $tree (@{$project->get_trees()}) {
+            $tree->set_inherited(1);
+        }
+        $ctx->get_project_of()->{$ns} = $project;
+    }
+    _config_parse_inherit_hook_prop($attrib_ref, $ctx, $i_ctx);
+}
+
+# Turns a context into a list of configuration entries.
+sub _config_unparse {
+    my ($attrib_ref, $ctx) = @_;
+    my %LABEL_OF
+        = map {($_ => $ctx->get_id() . q{.} . $_)} keys(%CONFIG_PARSER_OF);
+    my @entries = (
+        FCM::Context::ConfigEntry->new({
+            label => $LABEL_OF{ns},
+            value => _config_unparse_join(@{$ctx->get_ns_list()}),
+        }),
+    );
+    for my $p_ns (sort keys(%{$ctx->get_project_of()})) {
+        my $project = $ctx->get_project_of($p_ns);
+        my ($base, @diffs) = @{$project->get_trees()};
+        if (!$project->get_inherited()) {
+            if (defined($project->get_locator())) {
+                my $locator = $project->get_locator();
+                my %modifier_of = (primary => 1, type => $locator->get_type());
+                push(
+                    @entries,
+                    FCM::Context::ConfigEntry->new({
+                        label       => $LABEL_OF{location},
+                        modifier_of => \%modifier_of,
+                        ns_list     => [$p_ns],
+                        value       => $locator->get_value(),
+                    }),
+                );
+            }
+            if (@{$project->get_path_excl()}) {
+                my @values = map {$_ ? $_ : q{/}} @{$project->get_path_excl()};
+                push(
+                    @entries,
+                    FCM::Context::ConfigEntry->new({
+                        label       => $LABEL_OF{'path-excl'},
+                        ns_list     => [$p_ns],
+                        value       => _config_unparse_join(@values),
+                    }),
+                );
+            }
+            if (@{$project->get_path_incl()}) {
+                my @values = map {$_ ? $_ : q{/}} @{$project->get_path_incl()};
+                push(
+                    @entries,
+                    FCM::Context::ConfigEntry->new({
+                        label       => $LABEL_OF{'path-incl'},
+                        ns_list     => [$p_ns],
+                        value       => _config_unparse_join(@values),
+                    }),
+                );
+            }
+            if ($project->get_path_root()) {
+                push(
+                    @entries,
+                    FCM::Context::ConfigEntry->new({
+                        label      => $LABEL_OF{'path-root'},
+                        ns_list    => [$p_ns],
+                        value      => $project->get_path_root(),
+                    }),
+                );
+            }
+            my $value = $base->get_locator()->get_value();
+            push(
+                @entries,
+                FCM::Context::ConfigEntry->new({
+                    label       => $LABEL_OF{'location'},
+                    modifier_of => {type => $base->get_locator()->get_type()},
+                    ns_list     => [$p_ns],
+                    value       => $value,
+                }),
+            );
+        }
+        @diffs = grep {!$_->get_inherited()} @diffs;
+        if (@diffs) {
+            my %type_set = map {($_->get_locator()->get_type() => 1)} @diffs;
+            for my $type (sort(keys(%type_set))) {
+                my $value = _config_unparse_join(
+                    map  {$_->get_locator()->get_value()}
+                    grep {$_->get_locator()->get_type() eq $type}
+                    @diffs
+                );
+                push(
+                    @entries,
+                    FCM::Context::ConfigEntry->new({
+                        label       => $LABEL_OF{'location'},
+                        modifier_of => {diff => 1, type => $type},
+                        ns_list     => [$p_ns],
+                        value       => $value,
+                    }),
+                );
+            }
+        }
+    }
+    push(@entries, _config_unparse_prop($attrib_ref, $ctx));
+    return @entries;
+}
+
+# Returns a new context.
+sub _ctx {
+    my ($attrib_ref, $id_of_class, $id) = @_;
+    FCM::Context::Make::Extract->new({id => $id, id_of_class => $id_of_class});
+}
+
+# The main function of this class.
+sub _main {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    local($UTIL) = $attrib_ref->{util};
+    for my $function (
+        \&_elaborate_ctx_of_project,
+        \&_elaborate_ctx_of_target,
+        \&_extract_incremental,
+        \&_project_tree_caches_update,
+        \&_symlink_handle,
+        \&_targets_update,
+    ) {
+        $function->($attrib_ref, $m_ctx, $ctx);
+    }
+}
+
+# Elaborates the context: project and tree.
+sub _elaborate_ctx_of_project {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+
+    # Reports projects that are not used
+    my @bad_ns_list;
+    while (my ($p_ns, $project) = each(%{$ctx->get_project_of()})) {
+        if (    !$project->get_inherited()
+            &&  !grep {$_ eq $p_ns} @{$ctx->get_ns_list()}
+        ) {
+            push(@bad_ns_list, $p_ns);
+        }
+    }
+    if (@bad_ns_list) {
+        return $E->throw($E->EXTRACT_NS, \@bad_ns_list);
+    }
+
+    # Determines a list of new trees
+    my $prev_m_ctx = $m_ctx->get_prev_ctx();
+    my $prev_ctx
+        = defined($prev_m_ctx) ? $prev_m_ctx->get_ctx_of($ctx->get_id())
+        :                          undef
+        ;
+    my @trees; # list of new trees
+    for my $p_ns (@{$ctx->get_ns_list()}) {
+        # Ensures the project settings are defined
+        if (!exists($ctx->get_project_of()->{$p_ns})) {
+            $ctx->get_project_of()->{$p_ns}
+                = $ctx->CTX_PROJECT->new({ns => $p_ns});
+        }
+        my $project = $ctx->get_project_of()->{$p_ns};
+        
+        # Determine the root location of the project, if possible
+        if (defined($project->get_locator())) {
+            $UTIL->loc_as_normalised($project->get_locator());
+        }
+        else {
+            my $uri = $UTIL->loc_kw_prefix() . ':' . $p_ns;
+            my $locator = FCM::Context::Locator->new($uri);
+            local($@);
+            eval {$UTIL->loc_as_normalised($locator)};
+            if (!$@) {
+                $project->set_locator($locator);
+            }
+        }
+        # Ensures base tree is defined
+        if (!@{$project->get_trees()} || !defined($project->get_trees()->[0])) {
+            if (!defined($project->get_locator())) {
+                return $E->throw($E->EXTRACT_LOC_BASE, $p_ns);
+            }
+            my $head_locator = $UTIL->loc_trunk_at_head($project->get_locator());
+            my $locator
+                = $head_locator ? $head_locator
+                :                 dclone($project->get_locator())
+                ;
+            $project->get_trees()->[0] = $ctx->CTX_TREE->new(
+                {key => 0, locator => $locator, ns => $p_ns},
+            );
+        }
+        # Determine whether there is a usable previous extract
+        my %path_excl = map {($_, 1)} @{$project->get_path_excl()};
+        my %path_incl = map {($_, 1)} @{$project->get_path_incl()};
+        my $path_root = $project->get_path_root();
+        my ($can_use_prev, $prev_project);
+        if (defined($prev_ctx) && defined($prev_ctx->get_project_of($p_ns))) {
+            $prev_project = $prev_ctx->get_project_of($p_ns);
+            my %prev_path_excl = map {($_, 1)} @{$prev_project->get_path_excl()};
+            my %prev_path_incl = map {($_, 1)} @{$prev_project->get_path_incl()};
+            my $prev_path_root = $prev_project->get_path_root();
+            $can_use_prev
+                =  $prev_ctx->get_status() eq $m_ctx->ST_OK
+                && !$UTIL->hash_cmp(\%path_excl, \%prev_path_excl, 1)
+                && !$UTIL->hash_cmp(\%path_incl, \%prev_path_incl, 1)
+                && $path_root eq $prev_path_root
+                ;
+        }
+        # Tree locators as invariant
+        TREE:
+        for my $tree (grep {!$_->get_inherited()} @{$project->get_trees()}) {
+            my $tree_locator = $tree->get_locator();
+            # Ensures that the tree locator is an absolute path
+            if (defined($project->get_locator())) {
+                $UTIL->loc_rel2abs($tree_locator, $project->get_locator());
+            }
+            # Determines invariant form of the locator of the project tree.
+            $UTIL->loc_as_invariant($tree_locator);
+        }
+        # Remove diff trees that are the same as the base tree
+        my ($base_tree, @old_diff_trees) = @{$project->get_trees()};
+        my $base_value = $base_tree->get_locator()->get_value();
+        my @new_diff_trees;
+        TREE:
+        for my $tree (@old_diff_trees) {
+            if ($base_value ne $tree->get_locator()->get_value()) {
+                push(@new_diff_trees, $tree);
+                $tree->set_key(scalar(@new_diff_trees)); # reset key (index)
+            }
+        }
+        $project->set_trees([$base_tree, @new_diff_trees]);
+        # Determine the new trees
+        TREE:
+        for my $tree (grep {!$_->get_inherited()} @{$project->get_trees()}) {
+            my $tree_locator = $tree->get_locator();
+            if (    $can_use_prev
+                &&  $tree_locator->get_value_level() >= $tree_locator->L_INVARIANT
+            ) {
+                my $prev_tree = first {
+                    $tree_locator->get_value() eq $_->get_locator()->get_value()
+                } @{$prev_project->get_trees()};
+                if ($prev_tree) {
+                    my $prev_tree_locator = $prev_tree->get_locator();
+                    $tree->set_sources($prev_tree->get_sources());
+                    if ($tree->get_key() || !$prev_tree->get_key()) {
+                        # Only safe to re-use cache if both are base trees
+                        # or for diff tree with an unchanged base tree
+                        $tree->set_cache($prev_tree->get_cache());
+                    }
+                    next TREE;
+                }
+                if (!$tree->get_key()) { # base tree changed
+                    $can_use_prev = 0;
+                }
+            }
+            push(@trees, $tree); # new tree
+        }
+    }
+
+    # Obtain source info for each new tree, using the task runner
+    if (@trees) {
+        my $timer = $UTIL->timer();
+        my $n_jobs = $m_ctx->get_option_of('jobs');
+        if ($n_jobs && $n_jobs > scalar(@trees)) {
+            $n_jobs = scalar(@trees);
+        }
+        my $elapse_tasks = 0;
+        my $runner = $UTIL->task_runner(
+            sub {_elaborate_ctx_of_project_tree($attrib_ref, $m_ctx, $ctx, @_)},
+            $n_jobs,
+        );
+        my $n = eval {
+            $runner->main(
+                # get
+                sub {
+                    if (!@trees) {
+                        return;
+                    }
+                    my $tree = shift(@trees);
+                    my $id = join(':', $tree->get_ns(), $tree->get_key());
+                    FCM::Context::Task->new({ctx => $tree, id => $id});
+                },
+                # put
+                sub {
+                    my ($task) = @_;
+                    if ($task->get_state() eq $task->ST_FAILED) {
+                        die($task->get_error());
+                    }
+                    my $ns = $task->get_ctx()->get_ns();
+                    my $key = $task->get_ctx()->get_key();
+                    my $project = $ctx->get_project_of()->{$ns};
+                    my $tree = $project->get_trees()->[$key];
+                    $tree->set_locator($task->get_ctx()->get_locator());
+                    $tree->set_sources($task->get_ctx()->get_sources());
+                    $elapse_tasks += $task->get_elapse();
+                },
+            );
+        };
+        my $e = $@;
+        $runner->destroy();
+        if ($e) {
+            die($e);
+        }
+        $UTIL->event(
+            FCM::Context::Event->MAKE_EXTRACT_RUNNER_SUMMARY,
+            'tree-sources-info-get', $n, $timer->(), $elapse_tasks,
+        );
+    }
+    $UTIL->event(
+        FCM::Context::Event->MAKE_EXTRACT_PROJECT_TREE,
+        {   map {($_ => [
+                map {$_->get_locator()}
+                    @{$ctx->get_project_of()->{$_}->get_trees()}
+            ])}
+            sort keys(%{$ctx->get_project_of()})
+        },
+    );
+}
+
+# Elaborates the context: new tree in a project.
+sub _elaborate_ctx_of_project_tree {
+    my ($attrib_ref, $m_ctx, $ctx, $tree) = @_;
+    my $project = $ctx->get_project_of()->{$tree->get_ns()};
+    my $path_root = $project->get_path_root();
+    # TODO: support regular expression or wildcards?
+    my %path_incl = map {($_ => 1)} @{$project->get_path_incl()};
+    my %path_excl = map {($_ => 1)} @{$project->get_path_excl()};
+    $UTIL->loc_find(
+        $tree->get_locator(),
+        sub {
+            my ($locator, $locator_attrib_ref) = @_;
+            if ($locator_attrib_ref->{is_dir}) {
+                return;
+            }
+            my $ns_in_tree = $locator_attrib_ref->{ns};
+            my $ns = $ns_in_tree;
+            if ($path_root) {
+                if ($path_root ne $UTIL->ns_common($path_root, $ns)) {
+                    return;
+                }
+                $ns = $ns eq $path_root ? q{}
+                    :                     substr($ns, length($path_root) + 1)
+                    ;
+            }
+            my $ns_iter_ref = $UTIL->ns_iter($ns, $UTIL->NS_ITER_UP);
+            NS:
+            while (defined(my $head = $ns_iter_ref->())) {
+                if (exists($path_incl{$head})) {
+                    last NS;
+                }
+                if (exists($path_excl{$head})) {
+                    return;
+                }
+            }
+            push(
+                @{$tree->get_sources()},
+                $ctx->CTX_SOURCE->new({
+                    key_of_tree => $tree->get_key(),
+                    locator     => $locator,
+                    ns          => $UTIL->ns_cat($tree->get_ns(), $ns),
+                    ns_in_tree  => $ns_in_tree,
+                }),
+            );
+        },
+    );
+    $tree;
+}
+
+# Elaborates the context: target.
+sub _elaborate_ctx_of_target {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    # Works out the extract sources and targets
+    my $DEST = $attrib_ref->{shared_util_of}{dest};
+    my $ns_sep = $UTIL->ns_sep();
+    while (my ($p_ns, $project) = each(%{$ctx->get_project_of()})) {
+        my ($tree_base, @trees) = @{$project->get_trees()};
+        # Sources from the base tree
+        for my $source (@{$tree_base->get_sources()}) {
+            my $ns = $source->get_ns();
+            my @paths = split($ns_sep, $ns);
+            my $dest_list_ref = $DEST->paths(
+                $m_ctx, 'target', $ctx->get_id(), @paths
+            );
+            $ctx->get_target_of()->{$ns} = $ctx->CTX_TARGET->new({
+                dests     => $dest_list_ref,
+                ns        => $ns,
+                source_of => {$tree_base->get_key() => $source},
+            });
+        }
+        my %sources_in_base
+            = map {($_->get_ns() => $_)} @{$tree_base->get_sources()};
+        # Sources from the diff trees
+        for my $tree (@trees) {
+            my $key = $tree->get_key();
+            my %sources_deleted = %sources_in_base;
+            # Handles new/modified sources
+            for my $source (@{$tree->get_sources()}) {
+                my $ns = $source->get_ns();
+                delete($sources_deleted{$ns});
+                if (exists($ctx->get_target_of()->{$ns})) {
+                    my $target = $ctx->get_target_of()->{$ns};
+                    my $base_source = $target->get_source_of()->{0};
+                    if (    $base_source->get_locator()
+                        &&  _source_eq($base_source, $source)
+                    ) {
+                        $source->set_status($source->ST_UNCHANGED);
+                    }
+                    else {
+                        # Source modified by diff tree
+                        $target->get_source_of()->{$key} = $source;
+                    }
+                }
+                else {
+                    # Source added by diff tree
+                    my @paths = split($ns_sep, $ns);
+                    my $dest_list_ref = $DEST->paths(
+                        $m_ctx, 'target', $ctx->get_id(), @paths,
+                    );
+                    $ctx->get_target_of()->{$ns} = $ctx->CTX_TARGET->new({
+                        dests     => $dest_list_ref,
+                        ns        => $ns,
+                        source_of => {
+                            0 => $ctx->CTX_SOURCE->new({
+                                key_of_tree => 0,
+                                status      => $ctx->CTX_SOURCE->ST_MISSING,
+                            }),
+                            $key => $source,
+                        },
+                    });
+                }
+            }
+            # Handle deleted sources
+            while (my ($ns) = each(%sources_deleted)) {
+                my $target = $ctx->get_target_of()->{$ns};
+                $target->get_source_of()->{$key} = $ctx->CTX_SOURCE->new({
+                    key_of_tree => $key,
+                    ns          => $ns,
+                    status      => $ctx->CTX_SOURCE->ST_MISSING,
+                });
+            }
+        }
+    }
+}
+
+# Extract: compare with previous extract.
+sub _extract_incremental {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    my $prev_m_ctx = $m_ctx->get_prev_ctx();
+    my $prev_ctx
+        = defined($prev_m_ctx) ? $prev_m_ctx->get_ctx_of($ctx->get_id())
+        :                          undef
+        ;
+    if (!defined($prev_ctx)) {
+        return;
+    }
+    my %deleted = map {($_ => 1)} keys(%{$prev_ctx->get_target_of()});
+    # Compares the sources in each target
+    TARGET:
+    while (my ($ns, $target) = each(%{$ctx->get_target_of()})) {
+        delete($deleted{$ns});
+        if (!exists($prev_ctx->get_target_of()->{$ns})) {
+            next TARGET;
+        }
+        my $prev_target = $prev_ctx->get_target_of()->{$ns};
+        my %prev_source_of = %{$prev_target->get_source_of()};
+        my %source_of = %{$target->get_source_of()};
+        if (keys(%prev_source_of) != keys(%source_of)) {
+            next TARGET;
+        }
+        while (my ($key_of_tree, $source) = each(%source_of)) {
+            if (!exists($prev_source_of{$key_of_tree})) {
+                next TARGET;
+            }
+            my $prev_source = $prev_source_of{$key_of_tree};
+            if (   $prev_source->get_status() ne $source->get_status()
+                || !$source->is_missing() && !_source_eq($prev_source, $source)
+            ) {
+                next TARGET;
+            }
+        }
+        $target->set_status_of_source($prev_target->get_status_of_source());
+        if ($prev_target->is_ok()) {
+            $target->set_path($prev_target->get_path());
+            $target->set_status($target->ST_UNCHANGED);
+        }
+    }
+    # Creates a dummy target for each deleted target
+    my $ns_sep = $UTIL->ns_sep();
+    while (my $ns = each(%deleted)) {
+        my $target = $prev_ctx->get_target_of($ns);
+        if ($target->get_status() ne $target->ST_DELETED) {
+            my @paths = split($ns_sep, $ns);
+            my $dest_list_ref = $attrib_ref->{shared_util_of}{dest}->paths(
+                $m_ctx, 'target', $ctx->get_id(), @paths,
+            );
+            $ctx->get_target_of()->{$ns}
+                = $ctx->CTX_TARGET->new({dests => $dest_list_ref, ns => $ns});
+        }
+    }
+}
+
+# Updates the project tree caches.
+sub _project_tree_caches_update {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    my $timer = $UTIL->timer();
+    my $n_jobs = $m_ctx->get_option_of('jobs');
+    my $n_trees = scalar(
+        grep {!$_->get_cache()}
+        map {@{$_->get_trees()}}
+        values(%{$ctx->get_project_of()})
+    );
+    if ($n_trees == 0) {
+        return;
+    }
+    if ($n_jobs && $n_jobs > $n_trees) {
+        $n_jobs = $n_trees;
+    }
+    my $elapse_tasks = 0;
+    my @args = ($attrib_ref, $m_ctx, $ctx);
+    my $runner = $UTIL->task_runner(
+        sub {_project_tree_cache_update_by_export(@args, @_)},
+        $n_jobs,
+    );
+    my $n = eval {
+        $runner->main(
+            _project_tree_cache_update_get_func(@args),
+            _project_tree_cache_update_put_func(@args, \$elapse_tasks),
+        );
+    };
+    my $e = $@;
+    $runner->destroy();
+    if ($e) {
+        die($e);
+    }
+    $UTIL->event(
+        FCM::Context::Event->MAKE_EXTRACT_RUNNER_SUMMARY,
+        'tree-cache-export', $n, $timer->(), $elapse_tasks,
+    );
+}
+
+# Updates the source cache for a project tree by exporting it.
+sub _project_tree_cache_update_by_export {
+    my ($attrib_ref, $m_ctx, $ctx, $tree) = @_;
+    my $cache = $tree->get_cache();
+    # Exports the smallest common tree
+    my $root_ns;
+    SOURCE:
+    for my $source (@{$tree->get_sources()}) {
+        if ($source->is_unchanged()) {
+            next SOURCE;
+        }
+        if (!defined($root_ns)) {
+            $root_ns = $source->get_ns_in_tree();
+            next SOURCE;
+        }
+        $root_ns = $UTIL->ns_common(
+            $root_ns, $source->get_ns_in_tree(),
+        );
+        if (!$root_ns) {
+            last SOURCE;
+        }
+    }
+    if (!defined($root_ns)) {
+        return;
+    }
+    my $cache_ns = $root_ns ? catfile($cache, $root_ns) : $cache;
+    my $locator_ns = $UTIL->loc_cat(
+        $tree->get_locator(), split($UTIL->ns_sep(), $root_ns),
+    );
+    eval{
+        mkpath(dirname($cache_ns));
+        $UTIL->loc_export($locator_ns, $cache_ns);
+    };
+    if (my $e = $@ || !-e $cache_ns && !-l $cache_ns) {
+        return $E->throw($E->DEST_CREATE, $cache_ns, $e);
+    }
+}
+
+# Generates an iterator for each tree requiring cache update.
+sub _project_tree_cache_update_get_func {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    my @trees = map {@{$_->get_trees()}} values(%{$ctx->get_project_of()});
+    sub {
+        while (my $tree = shift(@trees)) {
+            if (!$tree->get_cache()) {
+                if ($UTIL->loc_export_ok($tree->get_locator())) {
+                    my $cache = $attrib_ref->{shared_util_of}{dest}->path(
+                        $m_ctx,
+                        'sys-cache',
+                        $ctx->get_id(),
+                        $tree->get_ns(),
+                        $tree->get_key(),
+                    );
+                    $tree->set_cache($cache);
+                    rmtree($cache);
+                    mkpath(dirname($cache));
+                    my $id = $tree->get_ns() . '/' . $tree->get_key();
+                    return FCM::Context::Task->new({ctx => $tree, id  => $id});
+                }
+                else {
+                    $tree->set_cache($tree->get_locator()->get_value());
+                    _project_tree_cache_update_sources(
+                        $attrib_ref, $m_ctx, $ctx, $tree,
+                    );
+                }
+            }
+        }
+        return;
+    };
+}
+
+# Generates a callback when a tree has a cache.
+sub _project_tree_cache_update_put_func {
+    my ($attrib_ref, $m_ctx, $ctx, $elapse_tasks_ref) = @_;
+    sub {
+        my ($task) = @_;
+        if ($task->get_state() eq $task->ST_FAILED) {
+            die($task->get_error());
+        }
+        my $ns = $task->get_ctx()->get_ns();
+        my $key = $task->get_ctx()->get_key();
+        my $tree = $ctx->get_project_of()->{$ns}->get_trees()->[$key];
+        _project_tree_cache_update_sources($attrib_ref, $m_ctx, $ctx, $tree);
+        ${$elapse_tasks_ref} += $task->get_elapse();
+    };
+}
+
+# Sets the caches of individual project tree sources.
+sub _project_tree_cache_update_sources {
+    my ($attrib_ref, $m_ctx, $ctx, $tree) = @_;
+    for my $source (@{$tree->get_sources()}) {
+        my $cache = catfile(
+            $tree->get_cache(),
+            split($UTIL->ns_sep(), $source->get_ns_in_tree()),
+        );
+        $source->set_cache($cache);
+    }
+}
+
+# Handles symbolic links.
+sub _symlink_handle {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    TARGET:
+    while (my ($ns, $target) = each(%{$ctx->get_target_of()})) {
+        if ($target->is_unchanged()) {
+            next TARGET;
+        }
+        my $source_hash_ref = $target->get_source_of();
+        # Remove sources that are symbolic links
+        while (my ($key, $source) = each(%{$source_hash_ref})) {
+            if ($source->get_cache() && -l $source->get_cache()) {
+                delete($source_hash_ref->{$key});
+                $UTIL->event(
+                    FCM::Context::Event->MAKE_EXTRACT_SYMLINK, $source,
+                );
+            }
+        }
+        # It is OK to have a target with no sources, but a target must have a
+        # base source if it has at least one diff source.
+        if (    keys(%{$source_hash_ref})
+            &&  !exists($source_hash_ref->{0})
+        ) {
+            $source_hash_ref->{0} = $ctx->CTX_SOURCE->new(
+                {key_of_tree => 0, status => $ctx->CTX_SOURCE->ST_MISSING},
+            );
+        }
+    }
+}
+
+# Updates the targets.
+sub _targets_update {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    my %basket_of = (status => {}, status_of_source => {});
+    while (my ($ns, $target) = each(%{$ctx->get_target_of()})) {
+        if ($target->get_status() eq $target->ST_UNKNOWN) {
+            my %source_of = %{$target->get_source_of()};
+            my $handler
+                = keys(%source_of) ? \&_target_update
+                :                    \&_target_delete
+                ;
+            $handler->($attrib_ref, $m_ctx, $ctx, $target);
+            my $base = delete($source_of{0});
+            my @diffs = grep {!$_->is_unchanged()} values(%source_of);
+            $target->set_status_of_source(
+                  !keys(%{$target->get_source_of()}) ? $target->ST_UNKNOWN
+                : $base->is_missing()                ? $target->ST_ADDED
+                : (grep {$_->is_missing()} @diffs)   ? $target->ST_DELETED
+                : scalar(@diffs) > 1                 ? $target->ST_MERGED
+                : scalar(@diffs)                     ? $target->ST_MODIFIED
+                :                                      $target->ST_UNCHANGED
+            );
+            $UTIL->event(
+                FCM::Context::Event->MAKE_EXTRACT_TARGET, $target,
+            );
+        }
+        $basket_of{status}{$target->get_status()}++;
+        $basket_of{status_of_source}{$target->get_status_of_source()}++;
+    }
+    $UTIL->event(
+        FCM::Context::Event->MAKE_EXTRACT_TARGET_SUMMARY, \%basket_of,
+    );
+}
+
+# Updates a deleted target.
+sub _target_delete {
+    my ($attrib_ref, $m_ctx, $ctx, $target) = @_;
+    my ($dest, @inherited_dests) = @{$target->get_dests()};
+    if (-f $dest) {
+        unlink($dest) || return $E->throw($E->DEST_CLEAN, $dest, $!);
+        $target->set_status($target->ST_DELETED);
+    }
+    for my $inherited_dest (@inherited_dests) {
+        if (-f $inherited_dest) {
+            $target->set_status($target->ST_O_DELETED);
+            return;
+        }
+    }
+}
+
+# Updates a normal target.
+sub _target_update {
+    my ($attrib_ref, $m_ctx, $ctx, $target) = @_;
+    my %source_of = %{$target->get_source_of()};
+    my $source_of_base = delete($source_of{0});
+    # Either missing source in a diff-tree
+    # Or     missing source in base-tree and no diff-trees
+    if (    (grep {$_->is_missing()} values(%source_of))
+        ||  $source_of_base->is_missing() && !keys(%source_of)
+    ) {
+        return _target_delete($attrib_ref, $m_ctx, $ctx, $target);
+    }
+    $target->set_status($target->ST_UNCHANGED);
+    my $path = _target_update_source($attrib_ref, $m_ctx, $ctx, $target);
+    # Note: $path may be a File::Temp object.
+    my ($is_diff, $is_diff_in_perms, $is_in_prev, $rc) = (1, 1, undef, 1);
+    DEST:
+    for my $i (0 .. @{$target->get_dests()} - 1) {
+        my $dest = $target->get_dests()->[$i];
+        if (-f $dest) {
+            $is_in_prev = $i;
+            ($is_diff_in_perms, $is_diff) = _compare("$path", $dest);
+            last DEST;
+        }
+    }
+    if (!$is_diff && !$is_diff_in_perms) {
+        $target->set_path($target->get_dests()->[$is_in_prev]);
+        return; # up to date
+    }
+    my $dest = $target->get_dests()->[0];
+    if ($is_diff) {
+        my $dest_dir = dirname($dest);
+        if (!-d $dest_dir) {
+            eval {mkpath($dest_dir)};
+            if (my $e = $@) {
+                return $E->throw($E->DEST_CREATE, $dest_dir, $e);
+            }
+        }
+        copy("$path", $dest)
+            || return $E->throw($E->COPY, ["$path", $dest], $!);
+    }
+    chmod((stat("$path"))[2] & oct(7777), $dest)
+        || return $E->throw($E->DEST_CREATE, $dest, $!);
+    $target->set_path($target->get_dests()->[0]);
+    $target->set_status(
+          $is_in_prev          ? $target->ST_O_ADDED
+        : defined($is_in_prev) ? $target->ST_MODIFIED
+        :                        $target->ST_ADDED
+    );
+}
+
+# Returns the source path that is to be used to update a target.
+sub _target_update_source {
+    my ($attrib_ref, $m_ctx, $ctx, $target) = @_;
+    my %source_of = %{$target->get_source_of()};
+    my $path_of_base = delete($source_of{0})->get_cache();
+    my @keys_and_paths;
+    while (my ($key, $source) = each(%source_of)) {
+        my $path = $source->get_cache();
+        if (!$path_of_base || _compare($path_of_base, $path)) {
+            if (!grep {!_compare($_->[1], $path)} @keys_and_paths) {
+                push(@keys_and_paths, [$key, $path]);
+            }
+        }
+        else {
+            $source->set_status($source->ST_UNCHANGED);
+        }
+    }
+    my @args = (
+        $m_ctx, $ctx, $target, $path_of_base,
+        (sort {$a->[0] <=> $b->[0]} @keys_and_paths),
+    );
+    return (
+          @keys_and_paths == 0 ? $path_of_base
+        : @keys_and_paths == 1 ? $keys_and_paths[0][1]
+        :                        _target_update_source_merge($attrib_ref, @args)
+    );
+}
+
+# Merges changes in contents of paths in @keys_and_paths against content in
+# $path_of_base.
+sub _target_update_source_merge {
+    my ($attrib_ref, $m_ctx, $ctx, $target, $path_of_base, @keys_and_paths) = @_;
+    if (!$path_of_base) {
+        $path_of_base = File::Temp->new();
+        if (!defined($path_of_base) || !close($path_of_base)) {
+            return $E->throw($E->DEST_CREATE, tmpdir(), $!);
+        }
+    }
+    my ($key_of_mine, $path_of_mine) = @{shift(@keys_and_paths)};
+    my @keys_done = ($key_of_mine);
+    while (my $key_and_path = shift(@keys_and_paths)) {
+        my ($key, $path) = @{$key_and_path};
+        my @command = (
+            (map {_props($attrib_ref, $_, $ctx)} qw{diff3 diff3.flags}),
+            "$path_of_mine", "$path_of_base", $path,
+        );
+        my %value_of = %{$UTIL->shell_simple(\@command)};
+        if ($value_of{rc} && $value_of{rc} == 1) {
+            # Write conflict output to .fcm-make/extract/conflict/$NS
+            my $file = $attrib_ref->{shared_util_of}{dest}->path(
+                $m_ctx, 'sys', $ctx->get_id(), 'merge',
+                $target->get_ns() . '.diff',
+            );
+            $UTIL->file_save($file, $value_of{o});
+            return $E->throw($E->EXTRACT_MERGE, {
+                'target'    => $target,
+                'output'    => $file,
+                'keys_done' => \@keys_done,
+                'key'       => $key,
+                'keys_left' => [map {$_->[0]} @keys_and_paths],
+            });
+        }
+        elsif ($value_of{rc}) {
+            return $E->throw(
+                $E->SHELL, {command_list => \@command, %value_of}, $value_of{e},
+            );
+        }
+        my $perm = (stat("$path_of_mine"))[2] & 07777 | (stat($path))[2] & 07777;
+        for my $action (
+            sub {$path_of_mine = File::Temp->new()},
+            sub {print({$path_of_mine} $value_of{o})},
+            sub {close($path_of_mine)},
+            sub {chmod($perm, "$path_of_mine")},
+        ) {
+            $action->() || return $E->throw($E->DEST_CREATE, "$path_of_mine", $!);
+        }
+        push(@keys_done, $key);
+    }
+    return $path_of_mine;
+}
+
+# In scalar context, returns true if the contents or permissions of 2 paths
+# differ. In array context, returns ($is_diff_in_perms, $is_diff_in_content).
+sub _compare {
+    my ($path1, $path2) = @_;
+    my $is_diff_in_perms = (stat($path1))[2] != (stat($path2))[2];
+    wantarray()
+        ? ($is_diff_in_perms, compare($path1, $path2))
+        : ($is_diff_in_perms || compare($path1, $path2))
+    ;
+}
+
+# Returns true if two sources are the same or if their latest modified revisions
+# are the same.
+sub _source_eq {
+    my ($source1, $source2) = @_;
+    my ($locator1, $locator2) = map {$_->get_locator()} ($source1, $source2);
+    # Compares their value + mtime or their last modified revision
+            $locator1->get_value() eq $locator2->get_value()
+        &&  defined($locator1->get_last_mod_time())
+        &&  defined($locator2->get_last_mod_time())
+        &&  $locator1->get_last_mod_time() eq $locator2->get_last_mod_time()
+    ||      defined($locator1->get_last_mod_rev())
+        &&  defined($locator2->get_last_mod_rev())
+        &&  $locator1->get_last_mod_rev() eq $locator2->get_last_mod_rev()
+    ;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Extract
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Extract;
+    my $extract = FCM::System::Make::Extract->new(\%attrib);
+    $extract->($m_ctx, $ctx);
+
+=head1 DESCRIPTION
+
+Implements the extract sub-system. An instance of this class is expected to be
+initialised and called by L<FCM::System::Make|FCM::System::Make>.
+
+=head1 METHODS
+
+See L<FCM::System::Make|FCM::System::Make> for detail.
+
+=head1 ATTRIBUTES
+
+The $class->new(\%attrib) method of this class supports the following
+attributes:
+
+=over 4
+
+=item config_parser_of
+
+A HASH to map the labels in a configuration file to their parsers. (default =
+%FCM::System::Make::Extract::CONFIG_PARSER_OF)
+
+=item prop_of
+
+A HASH to map the names of the properties to their settings. Each setting
+is a 2-element ARRAY reference, where element [0] is the default setting
+and element [1] is a flag to indicate whether the property accepts a name-space
+or not. (default = %FCM::System::Make::Extract::PROP_OF)
+
+=item shared_util_of
+
+See L<FCM::System::Make|FCM::System::Make> for detail.
+
+=item util
+
+See L<FCM::System::Make|FCM::System::Make> for detail.
+
+=back
+
+=head1 TODO
+
+Handle alternate method of merge (e.g. Algorithm::Merge).
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Mirror.pm b/lib/FCM/System/Make/Mirror.pm
new file mode 100644
index 0000000..b1e117b
--- /dev/null
+++ b/lib/FCM/System/Make/Mirror.pm
@@ -0,0 +1,415 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Mirror;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::ConfigEntry;
+use FCM::Context::Event;
+use FCM::Context::Make;
+use FCM::Context::Make::Mirror;
+use FCM::Context::Make::Share::Property;
+use FCM::System::Make::Share::Subsystem;
+use File::Basename qw{dirname};
+use File::Path qw{mkpath};
+use File::Spec::Functions qw{abs2rel file_name_is_absolute rel2abs};
+use POSIX qw{strftime};
+use Storable qw{dclone};
+use Sys::Hostname qw{hostname};
+use Text::ParseWords qw{shellwords};
+
+# Alias
+my $E = 'FCM::System::Exception';
+
+# Configuration parser label to action map
+our %CONFIG_PARSER_OF = (
+    'target' => \&_config_parse_target,
+);
+
+# Default properties
+our %PROP_OF = (
+    'config-file.steps' => [q{}],
+    'no-config-file'    => [q{}],
+);
+
+# Properties from FCM::Util
+our @UTIL_PROP_KEYS = qw{ssh ssh.flags rsync rsync.flags};
+
+# Creates the class.
+__PACKAGE__->class(
+    {   config_parser_of => {isa => '%', default => {%CONFIG_PARSER_OF}},
+        prop_of          => {isa => '%', default => {%PROP_OF}},
+        shared_util_of   => '%',
+        util             => '&',
+    },
+    {   init => \&_init,
+        action_of => {
+            config_parse              => \&_config_parse,
+            config_parse_class_prop   => \&_config_parse_class_prop,
+            config_parse_inherit_hook => \&_config_parse_inherit_hook,
+            config_unparse            => \&_config_unparse,
+            config_unparse_class_prop => \&_config_unparse_class_prop,
+            ctx                       => \&_ctx,
+            main                      => \&_main,
+        },
+    },
+);
+
+# Initialises the helpers of the class.
+sub _init {
+    my ($attrib_ref) = @_;
+    for my $util_prop_key (@UTIL_PROP_KEYS) {
+        my $prop = $attrib_ref->{util}->external_cfg_get($util_prop_key);
+        $attrib_ref->{prop_of}{$util_prop_key} = [$prop];
+    }
+}
+
+# Reads the mirror.target declaration from a config entry.
+sub _config_parse_target {
+    my ($attrib_ref, $ctx, $entry) = @_;
+    my $value = $entry->get_value();
+    # Note: it is easier to parse the value in reverse because a target may look
+    # like "path", "machine:path" or "logname at machine:path"
+    my ($path, $auth) = reverse(split(':', $value, 2));
+    my ($machine, $logname) = $auth ? reverse(split('@', $auth, 2)) : ();
+    if (!$path || ($logname && !$machine)) {
+        return $E->throw($E->CONFIG_VALUE, $entry);
+    }
+    $ctx->set_target_logname($logname);
+    $ctx->set_target_machine($machine);
+    $ctx->set_target_path($path);
+}
+
+# A hook command for the "inherit/use" declaration (extract).
+sub _config_parse_inherit_hook {
+    my ($attrib_ref, $ctx, $i_ctx) = @_;
+    $ctx->set_target_machine($i_ctx->get_target_machine());
+    _config_parse_inherit_hook_prop($attrib_ref, $ctx, $i_ctx);
+}
+
+# Turns a context into a list of configuration entries.
+sub _config_unparse {
+    my ($attrib_ref, $ctx) = @_;
+    (   (   $ctx->get_target_path()
+            ? FCM::Context::ConfigEntry->new({
+                label => $ctx->get_id() . q{.} . 'target',
+                value => (_target_and_authority($ctx))[0],
+            })
+            : ()
+        ),
+        _config_unparse_prop($attrib_ref, $ctx),
+    );
+}
+
+# Returns a new context.
+sub _ctx {
+    my ($attrib_ref, $id_of_class, $id) = @_;
+    FCM::Context::Make::Mirror->new({id => $id, id_of_class => $id_of_class});
+}
+
+# The main function.
+sub _main {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    if (!$ctx->get_target_path()) {
+        return $E->throw($E->MIRROR_NULL);
+    }
+    my $do_config_file = !_prop($attrib_ref, 'no-config-file', $ctx);
+    my @sources;
+    my @bad_step_list;
+    for my $step (@{$m_ctx->get_steps()}) {
+        my $ctx = $m_ctx->get_ctx_of($step);
+        if (    $ctx->get_status() eq $m_ctx->ST_OK
+            &&  $ctx->can('get_dest')
+            &&  -e $ctx->get_dest()
+        ) {
+            if ($do_config_file && !$ctx->can('MIRROR')) {
+                push(@bad_step_list, $step);
+            }
+            else {
+                push(@sources, $ctx->get_dest());
+            }
+        }
+    }
+    if (@bad_step_list) {
+        return $E->throw($E->MIRROR_SOURCE, \@bad_step_list);
+    }
+    for my $action (
+        \&_mirror_mkdir,
+        (@sources        ? \&_mirror             : ()),
+        ($do_config_file ? \&_mirror_config_file : ()),
+        \&_mirror_orig_config_file,
+    ) {
+        $action->($attrib_ref, $m_ctx, $ctx, \@sources);
+    }
+}
+
+# Creates a configuration file at the destination.
+sub _mirror_config_file {
+    my ($attrib_ref, $m_ctx, $ctx, $sources_ref) = @_;
+    my ($target) = _target_and_authority($ctx);
+    my $mirror_m_ctx = FCM::Context::Make->new({dest => '$HERE'});
+    my %no_inherit_from;
+    if (@{$m_ctx->get_inherit_ctx_list()}) {
+        # Inherited destinations
+        for my $i_m_ctx (@{$m_ctx->get_inherit_ctx_list()}) {
+            my $i_ctx = $i_m_ctx->get_ctx_of($ctx->get_id());
+            if (defined($i_ctx)) {
+                push(
+                    @{$mirror_m_ctx->get_inherit_ctx_list()},
+                    FCM::Context::Make->new({dest => $i_ctx->get_target_path()}),
+                );
+            }
+        }
+        # Completed steps, from which the targets can be sourced
+        DONE_STEP:
+        for my $step (@{$m_ctx->get_steps()}) {
+            my $step_ctx = $m_ctx->get_ctx_of($step);
+            if (    !defined($step_ctx)
+                ||  $step_ctx->get_status() ne $m_ctx->ST_OK
+                ||  !$step_ctx->can('get_target_of')
+            ) {
+                next DONE_STEP;
+            }
+            while (my ($key, $target) = each(%{$step_ctx->get_target_of()})) {
+                if (!$target->is_ok()) {
+                    $no_inherit_from{$target->get_ns()} = 1;
+                }
+            }
+        }
+    }
+    # Steps to include in the configuration file
+    for my $step (_props($attrib_ref, 'config-file.steps', $ctx)) {
+        my $step_ctx = $m_ctx->get_ctx_of($step);
+        if (    !defined($step_ctx)
+            ||  $step_ctx->get_status() ne $m_ctx->ST_UNKNOWN
+        ) {
+            return $E->throw(
+                $E->MAKE_PROP_VALUE,
+                [[$ctx->get_id(), 'config-file.steps', q{}, $step]],
+            );
+        }
+        push(@{$mirror_m_ctx->get_steps()}, $step);
+        $mirror_m_ctx->get_ctx_of()->{$step} = dclone($step_ctx);
+        my $mirror_ctx = $mirror_m_ctx->get_ctx_of()->{$step};
+        if ($mirror_ctx->can('get_input_source_of')) {
+            %{$mirror_ctx->get_input_source_of()} = (
+                q{} => [map {abs2rel($_, $m_ctx->get_dest())} @{$sources_ref}],
+            );
+        }
+        if (keys(%no_inherit_from)) {
+            my @no_inherit_from_ns_list = sort keys(%no_inherit_from);
+            push(
+                @no_inherit_from_ns_list,
+                _props($attrib_ref, 'no-inherit-source', $mirror_ctx),
+            );
+            my $prop_value = FCM::Context::Make::Share::Property::Value->new({
+                value => join(
+                    q{ },
+                    map {s{['"\s]}{\\$1}gmsx; $_} @no_inherit_from_ns_list,
+                ),
+            });
+            my $prop = FCM::Context::Make::Share::Property->new({
+                id => 'no-inherit-source',
+                ctx_of => {q{} => $prop_value},
+            });
+            $mirror_ctx->get_prop_of()->{'no-inherit-source'} = $prop;
+        }
+    }
+    # Saves the configuration file
+    my @lines = map {$_ . "\n"}
+        $attrib_ref->{shared_util_of}{config}->unparse($mirror_m_ctx);
+    my $path = $attrib_ref->{shared_util_of}{dest}->path($ctx, 'config');
+    $attrib_ref->{util}->file_save($path, \@lines);
+    _mirror(
+        $attrib_ref, $m_ctx, $ctx,
+        [$path], $attrib_ref->{shared_util_of}{dest}->path($target, 'config'),
+    );
+}
+
+# Creates mirror destination.
+sub _mirror_mkdir {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    my ($target, $authority, $path) = _target_and_authority($ctx);
+    eval {
+        if ($authority) {
+            my @ssh
+                = (_shell_cmd_list($attrib_ref, 'ssh', $ctx), $authority);
+            if (!file_name_is_absolute($path)) {
+                my $value_hash_ref = _shell($attrib_ref, [@ssh, 'pwd']);
+                my $path_root = $value_hash_ref->{'o'};
+                chomp($path_root);
+                $ctx->set_target_path(rel2abs($path, $path_root));
+            }
+            _shell($attrib_ref, [@ssh, 'mkdir', '-p', $path]);
+        }
+        else {
+            if (!file_name_is_absolute($path)) {
+                $ctx->set_target_path(rel2abs($path));
+            }
+            if (!-d $path) {
+                mkpath($path);
+            }
+        }
+    };
+    if (my $e = $@) {
+        return $E->throw($E->MIRROR_TARGET, $target, $e);
+    }
+    1;
+}
+
+# Mirror original configuration (by unparsing $m_ctx).
+sub _mirror_orig_config_file {
+    my ($attrib_ref, $m_ctx, $ctx) = @_;
+    my @lines = (
+        "# Original fcm make configuration.\n",
+        sprintf("# Generated by %s@%s at %s.\n",
+            scalar(getpwuid($<)),
+            hostname(),
+            strftime("%Y-%m-%dT%H:%M:%S%z", localtime()),
+        ),
+        map {$_ . "\n"} $attrib_ref->{shared_util_of}{config}->unparse($m_ctx),
+    );
+    my $path = $attrib_ref->{shared_util_of}{dest}->path($ctx, 'config-orig');
+    $attrib_ref->{util}->file_save($path, \@lines);
+    my ($target) = _target_and_authority($ctx);
+    _mirror(
+        $attrib_ref, $m_ctx, $ctx,
+        [$path],
+        $attrib_ref->{shared_util_of}{dest}->path($target, 'config-orig'),
+    );
+}
+
+# Mirrors.
+sub _mirror {
+    my ($attrib_ref, $m_ctx, $ctx, $sources_ref, $target) = @_;
+    $target ||= (_target_and_authority($ctx))[0];
+    $attrib_ref->{util}->event(
+        FCM::Context::Event->MAKE_MIRROR, $target, @{$sources_ref},
+    );
+    eval {
+        _shell(
+            $attrib_ref,
+            [   _shell_cmd_list($attrib_ref, 'rsync', $ctx),
+                @{$sources_ref},
+                $target,
+            ],
+        );
+    };
+    if (my $e = $@) {
+        return $E->throw($E->MIRROR, [$target, @{$sources_ref}], $e);
+    }
+    1;
+}
+
+# Invokes a known shell command.
+sub _shell {
+    my ($attrib_ref, $command_list_ref) = @_;
+    my $value_hash_ref = $attrib_ref->{util}->shell_simple($command_list_ref);
+    if ($value_hash_ref->{rc}) {
+        return $E->throw(
+            $E->SHELL,
+            {command_list => $command_list_ref, %{$value_hash_ref}},
+            $value_hash_ref->{e},
+        );
+    }
+    $value_hash_ref;
+}
+
+# Returns a shell command and its flags from a named property.
+sub _shell_cmd_list {
+    my ($attrib_ref, $id, $ctx) = @_;
+    map {_props($attrib_ref, $_, $ctx)} ($id, $id . '.flags');
+}
+
+# Returns the authority and the target.
+sub _target_and_authority {
+    my ($ctx) = @_;
+    my $logname = $ctx->get_target_logname();
+    my $machine = $ctx->get_target_machine();
+    my $path    = $ctx->get_target_path();
+    my $authority
+        = $logname && $machine ? $logname . '@' . $machine
+        : $logname             ? $logname . '@' . 'localhost'
+        : $machine             ?                  $machine
+        :                        undef
+        ;
+    my $target = $authority ? $authority . ':' . $path : $path;
+    ($target, $authority, $path);
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Mirror
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Mirror;
+    my $subsystem = FCM::System::Make::Mirror->new(\%attrib);
+    $subsystem->main($m_ctx, $ctx);
+
+=head1 DESCRIPTION
+
+Implements the mirror sub-system. An instance of this class is expected to be
+initialised and called by L<FCM::System::Make|FCM::System::Make>.
+
+=head1 METHODS
+
+See L<FCM::System::Make|FCM::System::Make> for detail.
+
+=head1 ATTRIBUTES
+
+The $class->new(\%attrib) method of this class supports the following
+attributes:
+
+=over 4
+
+=item config_parser_of
+
+A HASH to map the labels in a configuration file to their parsers. (default =
+%FCM::System::Make::Mirror::CONFIG_PARSER_OF)
+
+=item prop_of
+
+A HASH to map the names of the properties to their settings. Each setting
+is a 2-element ARRAY reference, where element [0] is the default setting
+and element [1] is a flag to indicate whether the property accepts a name-space
+or not. (default = %FCM::System::Make::Mirror::PROP_OF)
+
+=item shared_util_of
+
+See L<FCM::System::Make|FCM::System::Make> for detail.
+
+=item util
+
+See L<FCM::System::Make|FCM::System::Make> for detail.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Preprocess.pm b/lib/FCM/System/Make/Preprocess.pm
new file mode 100644
index 0000000..890c6b3
--- /dev/null
+++ b/lib/FCM/System/Make/Preprocess.pm
@@ -0,0 +1,87 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Preprocess;
+use base qw{FCM::System::Make::Build};
+
+use FCM::System::Make::Build::FileType::CPP;
+use FCM::System::Make::Build::FileType::FPP;
+use FCM::System::Make::Build::FileType::H  ;
+
+# Default target selection
+our %TARGET_SELECT_BY = (task => {'process' => 1});
+
+# Classes for working with typed source files
+our @FILE_TYPE_UTILS = (
+    'FCM::System::Make::Build::FileType::FPP',
+    'FCM::System::Make::Build::FileType::CPP',
+    'FCM::System::Make::Build::FileType::HPP',
+);
+
+# Default properties
+my %PROP_OF = (
+    'ignore-missing-dep-ns'      => [q{}, undef],
+    'no-step-source'             => [q{}, undef],
+    'no-inherit-source'          => [q{}, undef],
+    'no-inherit-target-category' => [q{}, undef],
+);
+
+# Returns an instance of FCM::System::Make::Build;
+sub new {
+    my ($class, $attrib_ref) = @_;
+    bless(
+        FCM::System::Make::Build->new({
+            target_select_by => {%TARGET_SELECT_BY},
+            file_type_utils  => [@FILE_TYPE_UTILS],
+            prop_of          => {%PROP_OF},
+            %{$attrib_ref},
+        }),
+        $class,
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Preprocess
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Preprocess;
+    my $system = FCM::System::Make::Preprocess->new(\%attrib);
+    $system->main(\%option_of, @args);
+
+=head1 DESCRIPTION
+
+A wrapper of L<FCM::System::Make::Build|FCM::System::Make::Build> with
+configuration to trigger the preprocessing of Fortran and C source files.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
+
diff --git a/lib/FCM/System/Make/Share/Config.pm b/lib/FCM/System/Make/Share/Config.pm
new file mode 100644
index 0000000..ec2639a
--- /dev/null
+++ b/lib/FCM/System/Make/Share/Config.pm
@@ -0,0 +1,404 @@
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+use strict;
+use warnings;
+#-------------------------------------------------------------------------------
+
+package FCM::System::Make::Share::Config;
+use base qw{FCM::Class::CODE};
+
+use Cwd qw{cwd};
+use FCM::Context::ConfigEntry;
+use FCM::Context::Locator;
+use FCM::System::Exception;
+use File::Spec::Functions qw{file_name_is_absolute};
+use File::Temp;
+use Scalar::Util qw{blessed};
+
+# Alias to class name
+my $E = 'FCM::System::Exception';
+
+# Configuration parser label to action map
+my %CONFIG_PARSER_OF = (
+    'dest'       => \&_parse_dest,
+    'use'        => \&_parse_use,
+    'step.class' => \&_parse_step_class,
+    'steps'      => \&_parse_steps,
+);
+
+__PACKAGE__->class(
+    {shared_util_of => '%', subsystem_of => '%', util => '&'},
+    {action_of => {parse => \&_parse, unparse => \&_unparse}},
+);
+
+# Get configuration file entries from an iterator, and use the entries to
+# populate the context of the current make.
+sub _parse {
+    my ($attrib_ref, $entry_callback_ref, $m_ctx, @args) = @_;
+    my $dir = $m_ctx->get_option_of('directory')
+        ? $m_ctx->get_option_of('directory') : cwd();
+    my $dir_locator = FCM::Context::Locator->new($dir);
+    my @config_file_paths = $m_ctx->get_option_of('config-file-path')
+        ? @{$m_ctx->get_option_of('config-file-path')} : ();
+    my @config_file_path_locators
+        = map {FCM::Context::Locator->new($_)} @config_file_paths;
+    my @config_file_names = $m_ctx->get_option_of('config-file')
+        ? @{$m_ctx->get_option_of('config-file')} : (undef);
+    my @config_reader_refs;
+    for my $config_file_name (@config_file_names) {
+        my $is_specified_name = 1;
+        if (!defined($config_file_name)) {
+            $config_file_name
+                = $attrib_ref->{shared_util_of}{dest}->path_of('config');
+            $is_specified_name = 0;
+        }
+        if (    $attrib_ref->{util}->uri_match($config_file_name)
+            ||  file_name_is_absolute($config_file_name)
+        ) {
+            push(@config_reader_refs, _get_config_reader(
+                $attrib_ref, $config_file_name, [@config_file_path_locators],
+            ));
+        }
+        else {  # $config_file_name is relative
+            my $config_reader_ref;
+            HEAD:
+            for my $head_locator ($dir_locator, @config_file_path_locators) {
+                my $locator = $attrib_ref->{util}->loc_cat(
+                    $head_locator, $config_file_name,
+                );
+                if ($attrib_ref->{util}->loc_exists($locator)) {
+                    $config_reader_ref = _get_config_reader(
+                        $attrib_ref, $locator, [@config_file_path_locators],
+                    );
+                    last HEAD;
+                }
+            }
+            if (defined($config_reader_ref)) {
+                push(@config_reader_refs, $config_reader_ref);
+            }
+            elsif ($is_specified_name) {
+                return $E->throw($E->MAKE_CFG_FILE, $config_file_name);
+            }
+        }
+    }
+    if (!@config_reader_refs) {
+        my $config_file_name = $attrib_ref->{shared_util_of}{dest}->path(
+            $dir_locator->get_value(), 'config',
+        );
+        if (-f $config_file_name) {
+            push(@config_reader_refs, _get_config_reader(
+                $attrib_ref, $config_file_name, [@config_file_path_locators],
+            ));
+        }
+    }
+    my $args_config_handle;
+    if (@args) {
+        $args_config_handle = File::Temp->new(
+            SUFFIX   => '-fcm-make-args.cfg',
+            TEMPLATE => 'XXXXXX',
+            TMPDIR   => 1,
+        );
+        for my $arg (@args) {
+            print($args_config_handle "$arg\n");
+        }
+        $args_config_handle->seek(0, 0);
+        push(@config_reader_refs, _get_config_reader(
+            $attrib_ref,
+            $args_config_handle->filename(),
+            [@config_file_path_locators],
+        ));
+    }
+    if (!@config_reader_refs) {
+        return $E->throw($E->MAKE_CFG);
+    }
+    my $entry_iter_ref = sub {
+        while (@config_reader_refs) {
+            my $entry = $config_reader_refs[0]->();
+            if (defined($entry)) {
+                return $entry;
+            }
+            shift(@config_reader_refs);
+        }
+        return undef;
+    };
+    my @unknown_entries;
+    while (defined(my $entry = $entry_iter_ref->())) {
+        if (defined($entry_callback_ref)) {
+            $entry_callback_ref->($entry);
+        }
+        if (exists($CONFIG_PARSER_OF{$entry->get_label()})) {
+            $CONFIG_PARSER_OF{$entry->get_label()}->(
+                $attrib_ref, $m_ctx, $entry,
+            );
+        }
+        else {
+            my ($id, $label) = split(qr{\.}msx, $entry->get_label(), 2);
+            if (    $label
+                &&  $label eq 'prop'
+                &&  exists($entry->get_modifier_of()->{'class'})
+                &&  exists($attrib_ref->{subsystem_of}{$id})
+            ) {
+                my $subsystem = $attrib_ref->{subsystem_of}{$id};
+                if (!$subsystem->config_parse_class_prop($entry, $label)) {
+                    push(@unknown_entries, $entry);
+                }
+            }
+            else {
+                my $ctx = $m_ctx->get_ctx_of($id);
+                if (    !defined($ctx)
+                    &&  exists($attrib_ref->{subsystem_of}{$id})
+                ) {
+                    $ctx = $attrib_ref->{subsystem_of}{$id}->ctx($id, $id);
+                    $m_ctx->get_ctx_of()->{$id} = $ctx;
+                }
+                my $rc;
+                if (defined($ctx)) {
+                    my $id_of_class = $ctx->get_id_of_class();
+                    my $subsystem = $attrib_ref->{subsystem_of}{$id_of_class};
+                    $rc = $subsystem->config_parse($ctx, $entry, $label);
+                }
+                if (!$rc) {
+                    push(@unknown_entries, $entry);
+                }
+            }
+        }
+    }
+    if (defined($args_config_handle)) {
+        $args_config_handle->close();
+    }
+    if (@unknown_entries) {
+        return $E->throw($E->CONFIG_UNKNOWN, \@unknown_entries);
+    }
+    $m_ctx;
+}
+
+# Returns a config reader.
+sub _get_config_reader {
+    my ($attrib_ref, $locator, $config_file_path_locators_ref) = @_;
+    if (!blessed($locator)) {
+        $locator = FCM::Context::Locator->new($locator);
+    }
+    $attrib_ref->{util}->config_reader(
+        $locator,
+        {   event_level   => $attrib_ref->{util}->util_of_report()->LOW,
+            include_paths => $config_file_path_locators_ref,
+        },
+    );
+}
+
+# Reads the dest declaration.
+sub _parse_dest {
+    my ($attrib_ref, $m_ctx, $entry) = @_;
+    $m_ctx->set_dest($entry->get_value());
+}
+
+# Reads the step.class declaration from a config entry.
+sub _parse_step_class {
+    my ($attrib_ref, $m_ctx, $entry) = @_;
+    my $id_of_class = $entry->get_value();
+    if (!exists($attrib_ref->{subsystem_of}{$id_of_class})) {
+        return $E->throw($E->CONFIG_VALUE, $entry);
+    }
+    my $subsystem = $attrib_ref->{subsystem_of}{$id_of_class};
+    for my $id (@{$entry->get_ns_list()}) {
+        if (!defined($m_ctx->get_ctx_of($id))) {
+            $m_ctx->get_ctx_of()->{$id} = $subsystem->ctx($id_of_class, $id);
+        }
+    }
+}
+
+# Reads the steps declaration from a config entry.
+sub _parse_steps {
+    my ($attrib_ref, $m_ctx, $entry) = @_;
+    my @steps = $entry->get_values();
+    $m_ctx->set_steps(\@steps);
+    for my $id (@steps) {
+        if (!defined($m_ctx->get_ctx_of($id))) {
+            if (!exists($attrib_ref->{subsystem_of}{$id})) {
+                return $E->throw($E->CONFIG_VALUE, $entry);
+            }
+            my $subsystem = $attrib_ref->{subsystem_of}{$id};
+            $m_ctx->get_ctx_of()->{$id} = $subsystem->ctx($id, $id);
+        }
+    }
+}
+
+# Reads the use declaration.
+sub _parse_use {
+    my ($attrib_ref, $m_ctx, $entry) = @_;
+    my $DEST = $attrib_ref->{shared_util_of}{dest};
+    my $inherit_ctx_list_ref = $m_ctx->get_inherit_ctx_list();
+    for my $value ($entry->get_values()) {
+        $value = $attrib_ref->{util}->file_tilde_expand($value);
+        my $i_m_ctx = eval {
+            $DEST->ctx_load(
+                $DEST->path($value, 'sys-ctx'),
+                blessed($m_ctx),
+            );
+        };
+        if (!defined($i_m_ctx) && (my $e = $@)) {
+            return $E->throw($E->CONFIG_VALUE, $entry, $e);
+        }
+        if ($i_m_ctx->get_status() != $i_m_ctx->ST_OK) {
+            return $E->throw($E->CONFIG_INHERIT, $entry);
+        }
+        push(@{$m_ctx->get_inherit_ctx_list()}, $i_m_ctx);
+        while (my ($id, $i_ctx) = each(%{$i_m_ctx->get_ctx_of()})) {
+            my $id_of_class = $i_ctx->get_id_of_class();
+            if (exists($attrib_ref->{subsystem_of}{$id_of_class})) {
+                my $subsystem = $attrib_ref->{subsystem_of}{$id_of_class};
+                if (!defined($m_ctx->get_ctx_of($id))) {
+                    $m_ctx->get_ctx_of()->{$id}
+                        = $subsystem->ctx($id_of_class, $id);
+                }
+                if ($subsystem->can('config_parse_inherit_hook')) {
+                    $subsystem->config_parse_inherit_hook(
+                        $m_ctx->get_ctx_of($id), $i_ctx,
+                    );
+                }
+            }
+        }
+        if (!@{$m_ctx->get_steps()}) {
+            $m_ctx->set_steps([@{$i_m_ctx->get_steps()}]);
+        }
+    }
+}
+
+# Turns the context back into a config.
+sub _unparse {
+    my ($attrib_ref, $m_ctx) = @_;
+    my %subsystem_of = map {
+        my $id = $m_ctx->get_ctx_of()->{$_}->get_id_of_class();
+        ($id, $attrib_ref->{subsystem_of}->{$id});
+    } @{$m_ctx->get_steps()};
+    map {$_->as_string()} (
+        (   map {   FCM::Context::ConfigEntry->new({
+                        label   => 'step.class',
+                        ns_list => [$_->get_id()],
+                        value   => $_->get_id_of_class(),
+                    });
+                }
+            grep {$_->get_id() ne $_->get_id_of_class()}
+            values(%{$m_ctx->get_ctx_of()})
+        ),
+        (   map {   my ($action_ref, $label) = @{$_};
+                    my $value = $action_ref->($attrib_ref, $m_ctx);
+                    defined($value)
+                        ? FCM::Context::ConfigEntry->new(
+                            {label => $label, value => $value},
+                        )
+                        : ()
+                    ;
+                }
+            (   [\&_unparse_use     , 'use'  ],
+                [\&_unparse_steps   , 'steps'],
+                [sub {$m_ctx->get_dest()}, 'dest' ],
+            ),
+        ),
+        (   map {   my $id = $_;
+                    $subsystem_of{$id}->config_unparse_class_prop($id);
+            }
+            sort keys(%subsystem_of)
+        ),
+        (   map {   my $ctx = $m_ctx->get_ctx_of()->{$_};
+                    my $id_of_class = $ctx->get_id_of_class();
+                    $subsystem_of{$id_of_class}->config_unparse($ctx);
+                }
+            @{$m_ctx->get_steps()}
+        ),
+    );
+}
+
+# Serializes a list of words.
+sub _unparse_join {
+    join(q{ }, map {s{(["'\s])}{\\$1}xms; $_} grep {defined()} @_);
+}
+
+# The value of "steps" declaration from the context.
+sub _unparse_steps {
+    my ($attrib_ref, $m_ctx) = @_;
+    if (!@{$m_ctx->get_steps()}) {
+        return;
+    }
+    _unparse_join(@{$m_ctx->get_steps()});
+}
+
+# The value of "use" declaration from the context.
+sub _unparse_use {
+    my ($attrib_ref, $m_ctx) = @_;
+    if (!@{$m_ctx->get_inherit_ctx_list()}) {
+        return;
+    }
+    my @i_ctx_list = @{$m_ctx->get_inherit_ctx_list()};
+    _unparse_join(map {$_->get_dest()} @i_ctx_list);
+}
+
+#-------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Share::Config
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Share::Config;
+    my $instance = FCM::System::Make::Share::Config->new(\%attrib);
+    my $ok = $instance->parse($m_ctx, $entry_iter_ref);
+    my @entries = $instance->unparse($m_ctx);
+
+=head1 DESCRIPTION
+
+A helper class for (un)parsing make config entries into the make context.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Returns a new instance. The allowed elements for %attrib are:
+
+=over 4
+
+=item {shared_util_of}{dest}
+
+A helper object for manipulating the destination in a make context. Expects an
+instance of L<FCM::System::Make::Share::Dest|FCM::System::Make::Share::Dest>.
+
+=back
+
+=item $instance->parse($m_ctx, $entry_iter_ref)
+
+Parses entries returned by the $entry_iter_ref iterator into the $m_ctx.
+Throws a variety of L<FCM::System::Exception|FCM::System::Exception> if some
+data in the configuration file is incorrectly set.
+
+=item $instance->unparse($m_ctx)
+
+Turns $m_ctx back into a list of configuration entries.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Share/Dest.pm b/lib/FCM/System/Make/Share/Dest.pm
new file mode 100644
index 0000000..c3f4ad6
--- /dev/null
+++ b/lib/FCM/System/Make/Share/Dest.pm
@@ -0,0 +1,438 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Share::Dest;
+use base qw{FCM::Class::CODE};
+
+use Cwd qw{cwd};
+use FCM::Context::Event;
+use FCM::System::Exception;
+use File::Basename qw{dirname};
+use File::Path qw{mkpath rmtree};
+use File::Spec::Functions qw{catfile rel2abs};
+use IO::File;
+use IO::Uncompress::Gunzip qw{gunzip};
+use IO::Compress::Gzip qw{gzip};
+use Scalar::Util qw{blessed};
+use Storable qw{fd_retrieve nstore_fd};
+use Sys::Hostname qw{hostname};
+
+# The relative paths for locating files in a destination
+our %PATH_OF = (
+    'config'                        => 'fcm-make.cfg',
+    'config-orig'                   => 'fcm-make.cfg.orig',
+    'sys'                           => '.fcm-make',
+    'sys-cache'                     => '.fcm-make/cache',
+    'sys-config-as-parsed'          => '.fcm-make/config-as-parsed.cfg',
+    'sys-config-as-parsed-symlink'  => 'fcm-make-as-parsed.cfg',
+    'sys-config-on-success'         => '.fcm-make/config-on-success.cfg',
+    'sys-config-on-success-symlink' => 'fcm-make-on-success.cfg',
+    'sys-ctx-uncompressed'          => '.fcm-make/ctx',
+    'sys-ctx'                       => '.fcm-make/ctx.gz',
+    'sys-log'                       => '.fcm-make/log',
+    'sys-log-symlink'               => 'fcm-make.log',
+    'sys-lock'                      => 'fcm-make.lock',
+    'sys-lock-info'                 => 'fcm-make.lock/info.txt',
+    'target'                        => '',
+);
+
+# Aliases to exception classes
+my $E = 'FCM::System::Exception';
+# List of actions
+my %ACTION_OF = (
+    ctx_load  => \&_ctx_load,
+    dest_done => \&_dest_done,
+    dest_init => \&_dest_init,
+    path      => \&_path,
+    paths     => \&_paths,
+    path_of   => sub {$_[0]->{'path_of'}{$_[1]}},
+    save      => \&_save,
+    tidy      => \&_tidy,
+);
+
+# Creates the class.
+__PACKAGE__->class(
+    {path_of => {isa => '%', default => {%PATH_OF}}, util => '&'},
+    {action_of => \%ACTION_OF},
+);
+
+# Loads a storable context from a path.
+sub _ctx_load {
+    my ($attrib_ref, $path, $expected_class) = @_;
+    my $ctx = eval {
+        my $handle = IO::File->new_tmpfile();
+        gunzip($path, $handle) || die($!);
+        $handle->seek(0, 0);
+        fd_retrieve($handle);
+    };
+    if (my $e = $@) {
+        return $E->throw($E->CACHE_LOAD, $path, $e);
+    }
+    if (!$ctx || !$ctx->isa($expected_class)) {
+        return $E->throw($E->CACHE_TYPE, $path);
+    }
+    return $ctx;
+}
+
+# Finalises the destination of a make context.
+sub _dest_done {
+    my ($attrib_ref, $m_ctx) = @_;
+    if (!$m_ctx->get_dest()) {
+        return;
+    }
+    my $dest = _path($attrib_ref, $m_ctx, 'sys-ctx-uncompressed');
+    my $dest_parent = dirname($dest);
+    if (-d $dest_parent) {
+        eval {
+            my $handle = IO::File->new_tmpfile();
+            nstore_fd($m_ctx, $handle) || die($!);
+            $handle->seek(0, 0) || die($!);
+            gzip($handle, _path($attrib_ref, $m_ctx, 'sys-ctx')) || die($!);
+        };
+        if (my $e = $@) {
+            return $E->throw($E->DEST_CREATE, $dest, $e);
+        }
+    }
+    my %ctx_of = %{$m_ctx->get_ctx_of()};
+    for my $path (
+        _path($attrib_ref, $m_ctx, 'sys'),
+        (map {_path($attrib_ref, $m_ctx, 'target', $_)} keys(%ctx_of)),
+    ) {
+        _tidy($attrib_ref, $path);
+    }
+    if ($m_ctx->get_dest_lock()) {
+        rmtree($m_ctx->get_dest_lock());
+    }
+}
+
+# Initialises the destination of a make context.
+sub _dest_init {
+    my ($attrib_ref, $m_ctx) = @_;
+    my %OPTION_OF = %{$m_ctx->get_option_of()};
+    # Select destination
+    my $dest
+        = $OPTION_OF{directory} ? $OPTION_OF{directory}
+        : $m_ctx->get_dest()    ? $m_ctx->get_dest()
+        :                         cwd()
+        ;
+    $m_ctx->set_dest(rel2abs($dest));
+    # Check lock
+    my $lock = _path($attrib_ref, $m_ctx, 'sys-lock');
+    if (!$OPTION_OF{'ignore-lock'} && -e $lock) {
+        return $E->throw($E->DEST_LOCKED, $lock);
+    }
+    # Creates the lock (and the destination), if necessary
+    if (!-e $lock) {
+        eval {mkpath($lock)};
+        if (my $e = $@) {
+            return $E->throw($E->DEST_CREATE, $lock, $e);
+        }
+        my $lock_info = scalar(getpwuid($<)) . '@' . hostname() .  ':' . $$;
+        _save($attrib_ref, $lock_info, $m_ctx, 'sys-lock-info');
+    }
+    $m_ctx->set_dest_lock($lock);
+    # Cleans items created by previous make, if necessary
+    for my $path (
+        _path($attrib_ref, $m_ctx, 'sys-config-as-parsed-symlink'),
+        _path($attrib_ref, $m_ctx, 'sys-config-on-success-symlink'),
+        _path($attrib_ref, $m_ctx, 'sys-config-on-success'),
+        _path($attrib_ref, $m_ctx, 'sys-log-symlink'),
+    ) {
+        eval {rmtree($path)};
+        if (my $e = $@) {
+            return $E->throw($E->DEST_CLEAN, $path, $e);
+        }
+    }
+    if ($OPTION_OF{new}) {
+        my @steps = @{$m_ctx->get_steps()};
+        for my $path (
+            _path($attrib_ref, $m_ctx, 'sys'),
+            (map {_path($attrib_ref, $m_ctx, 'target', $_)} @steps),
+        ) {
+            eval {rmtree($path)};
+            if (my $e = $@) {
+                return $E->throw($E->DEST_CLEAN, $path, $e);
+            }
+        }
+    }
+    # Loads context of previous make, if possible
+    my $prev_m_ctx = eval {
+        my $path = _path($attrib_ref, $m_ctx, 'sys-ctx');
+        -f $path ? _ctx_load($attrib_ref, $path, blessed($m_ctx)) : undef;
+    };
+    if (my $e = $@) {
+        if (!$E->caught($e) || $e->get_code() ne $E->CACHE_LOAD) {
+            die($e);
+        }
+        $@ = undef;
+    }
+    if (defined($prev_m_ctx)) {
+        $m_ctx->set_prev_ctx($prev_m_ctx);
+    }
+    else {
+        # Creates the system directory
+        my $sys_dir_path = _path($attrib_ref, $m_ctx, 'sys');
+        eval {mkpath($sys_dir_path)};
+        if (my $e = $@) {
+            return $E->throw($E->DEST_CREATE, $sys_dir_path, $e);
+        }
+    }
+    # Diagnostic
+    $attrib_ref->{util}->event(
+        FCM::Context::Event->MAKE_DEST,
+        $m_ctx, join('@', scalar(getpwuid($<)), hostname()),
+    );
+    1;
+}
+
+# Returns the path of a named item relative to the context destination.
+sub _path {
+    my ($attrib_ref, $m_ctx, $key, @paths) = @_;
+    my $path
+        = blessed($m_ctx) && $m_ctx->can('get_dest') ? $m_ctx->get_dest()
+        : defined($m_ctx)                            ? $m_ctx
+        :                                              cwd()
+        ;
+    catfile($path, split(q{/}, $attrib_ref->{path_of}{$key}), @paths);
+}
+
+# Returns an ARRAY reference containing the search paths of a named item
+# relative to the destinations of the context and its inherited contexts.
+sub _paths {
+    my ($attrib_ref, $m_ctx, $key, @paths) = @_;
+    my @dests;
+    my @ctx_list = ($m_ctx);
+    # Adds destinations from inherited contexts recursively
+    # Note: if A inherits from B and C, B from B1 and B2, and C from C1 and C2,
+    #       the search path will be A, C, C2, C1, B, B2, B1.
+    while (my $current_ctx = pop(@ctx_list)) {
+        push(@ctx_list, @{$current_ctx->get_inherit_ctx_list()});
+        push(@dests, _path($attrib_ref, $current_ctx, $key, @paths));
+    }
+    return \@dests;
+}
+
+# Saves $item in a path given by _path($attrib_ref, $m_ctx, $key, @paths).
+sub _save {
+    my ($attrib_ref, $item, $m_ctx, $key, @paths) = @_;
+    my $path = _path($attrib_ref, $m_ctx, $key, @paths);
+    my @contents
+        = (ref($item) && ref($item) eq 'ARRAY') ? (map {$_ . "\n"} @{$item})
+        :                                         ($item . "\n")
+        ;
+    $attrib_ref->{util}->file_save($path, \@contents);
+}
+
+# Removes empty directories in a tree.
+sub _tidy {
+    my ($attrib_ref, @paths) = @_;
+    # Selects only directories which are not symbolic links
+    my @items = map {[$_, undef, undef]} grep {-d && !-l} @paths;
+    while (my $item = pop(@items)) {
+        my ($path, $n_children_ref, $n_siblings_ref) = @{$item};
+        if (!defined($n_children_ref)) {
+            opendir(my $handle, $path)
+                || return $E->throw($E->DEST_CLEAN, $path, $!);
+            my @children = grep {$_ ne q{.} && $_ ne q{..}} (readdir($handle));
+            closedir($handle);
+            $n_children_ref = \scalar(@children);
+            if (@children) {
+                # Descends into directories
+                my @sub_dirs
+                    = grep {-d && !-l} map {catfile($path, $_)} @children;
+                if (@sub_dirs == @children) {
+                    # If all children are directories, it may be possible to
+                    # remove this directory later if all children are empty
+                    push(@items, [$path, $n_children_ref, $n_siblings_ref]);
+                }
+                push(@items, (map {[$_, undef, $n_children_ref]} @sub_dirs));
+            }
+        }
+        if (!${$n_children_ref}) { # i.e. directory is empty
+            rmdir($path) || return $E->throw($E->DEST_CLEAN, $path, $!);
+            if (defined($n_siblings_ref)) {
+                --${$n_siblings_ref};
+            }
+        }
+    }
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Share::Dest
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Share::Dest;
+    my $helper = FCM::System::Make::Share::Dest->new(\%attrib);
+    my $ctx = $helper->ctx_load($path, $expected_class);
+    my $path = $helper->path($m_ctx, $key);
+    # ...
+
+=head1 DESCRIPTION
+
+A helper class for manipulating the destination of a context in a FCM make
+sub-system, e.g. extract.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Returns a new instance. The %attrib should contain the following:
+
+=over 4
+
+=item dest_items
+
+An ARRAY containing the names of the items that can be created at the context
+destination.
+
+=item path_of
+
+A HASH to map the (keys) names of the items and (values) their relative paths
+(as ARRAY) in a context destination.
+
+=back
+
+=item $instance->ctx_load($path,$expected_class)
+
+Loads a storable context from $path and returns the context. The $expected_class
+is the expected class of the loaded context. The method die() if it fails to
+load the context or if the loaded context does not belong to the expected class.
+
+=item $instance->dest_done($ctx)
+
+Finalises the destination of $ctx by freezing the $ctx in the system directory,
+removing the lock file, and tidying up any empty directories created by the
+system.
+
+=item $instance->dest_init($ctx)
+
+Initialises the destination of $ctx by checking for a lock directory in the
+destination, creating a lock if possible, cleaning up items created by the
+previous make of the system if necessary, and setting up the system directory.
+
+=item $instance->path($ctx,$key, at paths)
+
+Returns the path of a named item ($key) relative to $ctx, which can either be a
+blessed object with a $ctx->get_dest() method, a scalar path, or undef (in which
+case, cwd() is used). If @paths are specified, they are concatenated at the end
+of the path.
+
+=item $instance->path_of($key)
+
+Returns the value of the named item in a make destination.
+
+=item $instance->paths($ctx,$key, at paths)
+
+Returns an ARRAY reference containing the search paths of a named item ($key)
+relative to the destinations of $ctx and its inherited contexts. If @paths are
+specified, they are concatenated at the end of each returned path.
+
+=item $instance->save($item,$ctx,$key, at paths)
+
+Saves $item in a path given by $instance->path($ctx,$key, at paths). $item can be a
+string or a reference to an ARRAY of strings. A "\n" is added to the end of each
+string.
+
+=item $instance->tidy(@paths)
+
+Recursively removes empty directories in @paths.
+
+=back
+
+=head1 CONSTANTS
+
+=over 4
+
+=item %FCM::System::Make::PATH_OF
+
+A HASH containing the default values of named paths in a make destination. The
+following keys are used by the system:
+
+=over 4
+
+=item config
+
+The standard path to the configuration file.
+
+=item sys
+
+The path to the system directory.
+
+=item sys-cache
+
+The path to the system cache directory.
+
+=item sys-config-as-parsed
+
+The path to the as-parsed configuration file.
+
+=item sys-config-on-success
+
+The path to the on-success configuration file.
+
+=item sys-ctx
+
+The path to the frozen make context (for retrieval by incremental makes).
+
+=item sys-ctx-uncompressed
+
+The path to the uncompressed form of sys-ctx.
+
+=item sys-lock
+
+The path to the lock directory.
+
+=item sys-lock-info
+
+The path to the lock info file.
+
+=item target
+
+The target destination of a make.
+
+=back
+
+=back
+
+=head1 DIAGNOSTICS
+
+=head2 FCM::System::Exception
+
+The methods of this class throws this exception on errors.
+
+=head1 TODO
+
+Time-stamp the as-parsed and the on-success configuration files.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Make/Share/Subsystem.pm b/lib/FCM/System/Make/Share/Subsystem.pm
new file mode 100644
index 0000000..3cda0c3
--- /dev/null
+++ b/lib/FCM/System/Make/Share/Subsystem.pm
@@ -0,0 +1,322 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::System::Make::Share::Subsystem;
+use base qw{Exporter};
+
+our @EXPORT = qw{
+    _config_parse
+    _config_parse_class_prop
+    _config_parse_prop
+    _config_parse_inherit_hook_prop
+    _config_unparse_class_prop
+    _config_unparse_join
+    _config_unparse_prop
+    _prop
+    _prop0
+    _props
+};
+
+use FCM::Context::ConfigEntry;
+use FCM::Context::Make::Share::Property;
+use FCM::System::Exception;
+use Storable qw{dclone};
+use Text::ParseWords qw{shellwords};
+
+use constant {PROP_DEFAULT => 0, PROP_NS_OK => 1};
+
+# Aliases
+my $E = 'FCM::System::Exception';
+
+# Parses a configuration entry into the context.
+sub _config_parse {
+    my ($attrib_ref, $ctx, $entry, $label) = @_;
+    my %config_parser_of = (
+        'prop' => \&_config_parse_prop,
+        %{$attrib_ref->{config_parser_of}},
+    );
+    if (!$label || !exists($config_parser_of{$label})) {
+        return;
+    }
+    $config_parser_of{$label}->($attrib_ref, $ctx, $entry);
+    1;
+}
+
+# Parses a configuration entry into the subsystem property.
+sub _config_parse_class_prop {
+    my ($attrib_ref, $entry, $label) = @_;
+    if ($label ne 'prop') {
+        return;
+    }
+    if (@{$entry->get_ns_list()}) {
+        return $E->throw($E->CONFIG_NS, $entry);
+    }
+    my @keys = grep {$_ ne 'class'} keys(%{$entry->get_modifier_of()});
+    if (grep {!exists($attrib_ref->{prop_of}{$_})} @keys) {
+        return $E->throw($E->CONFIG_MODIFIER, $entry);
+    }
+    for my $key (@keys) {
+        $attrib_ref->{prop_of}{$key}[PROP_DEFAULT] = $entry->get_value();
+    }
+    1;
+}
+
+# Reads the ?.prop declaration from a config entry.
+sub _config_parse_prop {
+    my ($attrib_ref, $ctx, $entry) = @_;
+    for my $key (keys(%{$entry->get_modifier_of()})) {
+        my $prop = $ctx->get_prop_of($key);
+        if (!defined($prop)) {
+            if (!defined(_prop_default($attrib_ref, $key))) {
+                return $E->throw($E->CONFIG_MODIFIER, $entry);
+            }
+            $prop = FCM::Context::Make::Share::Property->new({id => $key});
+            $ctx->get_prop_of()->{$key} = $prop;
+        }
+        my $prop_ctx;
+        if (defined($entry->get_value())) {
+            $prop_ctx = $prop->CTX_VALUE->new({value => $entry->get_value()});
+        }
+        if (!@{$entry->get_ns_list()}) {
+            @{$entry->get_ns_list()} = (q{});
+        }
+        for my $ns (@{$entry->get_ns_list()}) {
+            if ($ns && !_prop_ns_ok($attrib_ref, $key)) {
+                return $E->throw($E->CONFIG_NS, $entry);
+            }
+            if (defined($prop_ctx)) {
+                $prop->get_ctx_of()->{$ns} = $prop_ctx;
+            }
+            elsif (exists($prop->get_ctx_of()->{$ns})) {
+                delete($prop->get_ctx_of()->{$ns});
+            }
+        }
+    }
+}
+
+# A hook command for the "inherit/use" declaration, inherit properties.
+sub _config_parse_inherit_hook_prop {
+    my ($attrib_ref, $ctx, $i_ctx) = @_;
+    while (my ($key, $i_prop) = each(%{$i_ctx->get_prop_of()})) {
+        if (!defined($ctx->get_prop_of($key))) {
+            $ctx->get_prop_of()->{$key} = dclone($i_prop);
+        }
+        my %prop_ctx_of = %{$ctx->get_prop_of($key)->get_ctx_of()};
+        while (my ($ns, $i_prop_ctx) = each(%{$i_prop->get_ctx_of()})) {
+            if (    !exists($prop_ctx_of{$ns})
+                ||  $prop_ctx_of{$ns}->get_inherited()
+            ) {
+                my $prop_ctx = dclone($i_prop_ctx);
+                $prop_ctx->set_inherited(1);
+                $ctx->get_prop_of($key)->get_ctx_of()->{$ns} = $prop_ctx;
+            }
+        }
+    }
+}
+
+# Serializes a list of words.
+sub _config_unparse_join {
+    join(
+        q{ },
+        (map {my $s = $_; $s =~ s{(["'\s])}{\\$1}gxms; $s} grep {defined()} @_),
+    );
+}
+
+# Entries of the class prop settings.
+sub _config_unparse_class_prop {
+    my ($attrib_ref, $id) = @_;
+    map {
+        my $key = $_;
+        FCM::Context::ConfigEntry->new({
+            label       => join(q{.}, $id, 'prop'),
+            modifier_of => {'class' => 1, $key => 1},
+            value       => $attrib_ref->{prop_of}{$key}[PROP_DEFAULT],
+        });
+    } sort keys(%{$attrib_ref->{prop_of}});
+}
+
+# Entries of the prop settings.
+sub _config_unparse_prop {
+    my ($attrib_ref, $ctx) = @_;
+    my $label = join(q{.}, $ctx->get_id(), 'prop');
+    my %prop_of = %{$ctx->get_prop_of()};
+    map {
+        my $key = $_;
+        my $setting = $prop_of{$key};
+        map {
+            my $ns = $_;
+            my $prop_ctx = $setting->get_ctx_of()->{$ns};
+            $prop_ctx->get_inherited()
+            ? ()
+            : FCM::Context::ConfigEntry->new({
+                label       => $label,
+                modifier_of => {$key => 1},
+                ns_list     => ($ns ? [$ns] : []),
+                value       => $prop_ctx->get_value(),
+            });
+        } sort(keys(%{$setting->get_ctx_of()}));
+    } sort(keys(%prop_of));
+}
+
+# Returns the value of a named property (for a given $ns).
+sub _prop {
+    my ($attrib_ref, $id, $ctx, $ns) = @_;
+    my $setting = defined($ctx) ? $ctx->get_prop_of()->{$id} : undef;
+    if (!defined($ctx) || !defined($setting)) {
+        return _prop_default($attrib_ref, $id);
+    }
+    if (!_prop_ns_ok($attrib_ref, $id) || !$ns) {
+        my $prop_ctx = $setting->get_ctx();
+        return (
+              defined($prop_ctx) ? $prop_ctx->get_value()
+            :                      _prop_default($attrib_ref, $id)
+        );
+    }
+    my %prop_ctx_of = %{$setting->get_ctx_of()};
+    my $iter_ref
+        = $attrib_ref->{util}->ns_iter($ns, $attrib_ref->{util}->NS_ITER_UP);
+    while (defined(my $item = $iter_ref->())) {
+        if (exists($prop_ctx_of{$item}) && defined($prop_ctx_of{$item})) {
+            return $prop_ctx_of{$item}->get_value();
+        }
+    }
+    return _prop_default($attrib_ref, $id);
+}
+
+# Returns the first non-space value of a $setting for a given $ns.
+sub _prop0 {
+    (_props(@_))[0];
+}
+
+# Returns all suitable values of a $setting for a given $ns.
+sub _props {
+    my $prop = _prop(@_);
+    shellwords($prop ? $prop : q{});
+}
+
+# Returns the default value of a named property.
+sub _prop_default {
+    my ($attrib_ref, $id) = @_;
+    if (!exists($attrib_ref->{prop_of}{$id})) {
+        return;
+    }
+    $attrib_ref->{prop_of}{$id}[PROP_DEFAULT];
+}
+
+# Returns true if the given property can accept a name-space.
+sub _prop_ns_ok {
+    my ($attrib_ref, $id) = @_;
+        exists($attrib_ref->{prop_of}{$id})
+    &&  exists($attrib_ref->{prop_of}{$id}[PROP_NS_OK])
+    &&  $attrib_ref->{prop_of}{$id}[PROP_NS_OK]
+    ;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Make::Share::Subsystem
+
+=head1 SYNOPSIS
+
+    use FCM::System::Make::Share::Subsystem;
+
+=head1 DESCRIPTION
+
+Provides common "local" functions for a make subsystem.
+
+=head1 FUNCTIONS
+
+The following functions are automatically exported by this module.
+
+=over 4
+
+=item _config_parse(\%attrib,$ctx,$entry,$label)
+
+Reads a configuration $entry into the $ctx context. The $label is the label of
+the $entry, but with the prefix (which should be the same as $ctx->get_id() plus
+a dot) removed.
+
+=item _config_parse_class_prop(\%attrib,$entry,$label)
+
+Reads a configuration $entry into the subsystem default property
+$attrib{prop_of}. The $label is the label of the $entry, but with the prefix
+(the subsystem ID plus a dot) removed.
+
+=item _config_parse_prop(\%attrib,$ctx,$entry)
+
+Reads a property configuration $entry into the $ctx context. This method may
+die() with a FCM::System::Exception on error. If the property modifier is
+invalid for the given subsystem, it returns an exception with the CODE
+FCM::System::Exception->CONFIG_MODIFIER. If the property does not support a
+namespace, it returns an exception with the CODE
+FCM::System::Exception->CONFIG_NS.
+
+=item _config_parse_inherit_hook_prop(\%attrib,$ctx,$i_ctx)
+
+The $ctx context is the current subsystem context and the $i_ctx context is the
+inherited subsystem context. Inherits property settings from $i_ctx into $ctx.
+
+=item _config_unparse_join(@list)
+
+Joins the @list into a string that can be parsed again by shellwords.
+
+=item _config_unparse_class_prop(\%attrib,$id)
+
+Turns the default properties in the current subsystem into a list of
+configuration entries. $id is the ID of the current subsystem.
+
+=item _config_unparse_prop(\%attrib,$ctx)
+
+Turns the properties in $ctx into a list of configuration entries.
+
+=item _prop(\%attrib,$id,$ctx,$ns)
+
+Returns the value of property $id. If the property does not exist, it returns
+undef. If the property is not defined in $ctx, it returns the default value. If
+the property is defined in $ctx, it returns the defined value in $ctx. If $ns is
+set and a name-space is allowed for the property, it walks the name-space to
+attempt to return the nearest value of the property for the given name-space.
+
+=item _prop0(\%attrib,$id,$ctx,$ns)
+
+Shorthand for (_props(\%attrib,$id,$ctx,$ns))[0].
+
+=item _props(\%attrib,$id,$ctx,$ns)
+
+Shorthand for shellwords(_prop(\%attrib,$id,$ctx,$ns)).
+
+=back
+
+=head1 DEPENDENCIES
+
+The %attrib argument to the functions in this module may require the following
+keys to be set correctly: {config_parser_of}, {prop_of}, {util}.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Misc.pm b/lib/FCM/System/Misc.pm
new file mode 100644
index 0000000..3315f64
--- /dev/null
+++ b/lib/FCM/System/Misc.pm
@@ -0,0 +1,354 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Misc;
+use base qw{FCM::Class::CODE};
+
+use Cwd qw{cwd};
+use FCM::Context::Event;
+use FCM::Context::Locator;
+use FCM::System::Exception;
+use FCM::Util::ConfigReader;
+use File::Path qw{mkpath rmtree};
+use File::Spec::Functions qw{catfile};
+use List::Util qw{max};
+use Text::ParseWords qw{shellwords};
+
+# The (keys) named actions of this class and (values) their implementations.
+our %ACTION_OF = (
+    browse       => \&_browse,
+    config_parse => \&_config_parse,
+    export_items => \&_export_items,
+    keyword_find => \&_keyword_find,
+    version      => \&_version,
+);
+# Alias to exception class
+my $E = 'FCM::System::Exception';
+
+# Creates the class.
+__PACKAGE__->class({util => '&'}, {action_of => \%ACTION_OF});
+
+# Launches a web browser to display some version controlled resources.
+sub _browse {
+    my ($attrib_ref, $option_ref, @args) = @_;
+    my $UTIL = $attrib_ref->{util};
+    my @command = shellwords(
+          exists($option_ref->{browser}) ? $option_ref->{browser}
+        :                                  $UTIL->external_cfg_get('browser')
+    );
+    if (!@args) {
+        @args = (cwd());
+    }
+    for my $value (@args) {
+        my $locator = FCM::Context::Locator->new($value);
+        my $url = $UTIL->loc_browser_url($locator);
+        my %value_of = %{$UTIL->shell_simple([@command, $url])};
+        if ($value_of{rc}) {
+            return $E->throw(
+                $E->SHELL,
+                {command_list => [@command, $url], %value_of},
+                $value_of{e},
+            );
+        }
+        $attrib_ref->{util}->event(FCM::Context::Event->OUT, $value_of{o});
+    }
+    return;
+}
+
+# Parses and displays the content of a FCM configuration file.
+sub _config_parse {
+    my ($attrib_ref, $option_ref, @args) = @_;
+    my $reader_attrib_ref;
+    if (exists($option_ref->{'fcm1'})) {
+        $reader_attrib_ref = \%FCM::Util::ConfigReader::FCM1_ATTRIB;
+    }
+    for my $value (@args) {
+        my $locator = FCM::Context::Locator->new($value);
+        my $iter = $attrib_ref->{util}->config_reader(
+            $locator, $reader_attrib_ref,
+        );
+        while (my $entry = $iter->()) {
+            $attrib_ref->{util}->event(
+                FCM::Context::Event->CONFIG_ENTRY,
+                $entry,
+                exists($option_ref->{'fcm1'}),
+            );
+        }
+    }
+    return;
+}
+
+# Exports directories in a project as sequential versioned items.
+sub _export_items {
+    my ($attrib_ref, $option_ref, $location) = @_;
+    if (!$location) {
+        return $E->throw($E->EXPORT_ITEMS_SRC);
+    }
+    $location ||= q{.};
+    my $UTIL = $attrib_ref->{util};
+    # Options and arguments
+    $option_ref->{directory} ||= cwd();
+    $option_ref->{'config-file'} ||= ['fcm-export-items.cfg'];
+    my $locator = FCM::Context::Locator->new($location);
+    $UTIL->loc_as_invariant($locator);
+    # Timer
+    my $time_start = time();
+    my $timer = $UTIL->timer();
+    my %EVENT = (
+        'create' => sub {
+            $UTIL->event(FCM::Context::Event->EXPORT_ITEM_CREATE, @_);
+        },
+        'delete' => sub {
+            $UTIL->event(FCM::Context::Event->EXPORT_ITEM_DELETE, @_);
+        },
+        'timer' => sub {
+            $UTIL->event(
+                FCM::Context::Event->TIMER, 'export-items', $time_start, @_,
+            );
+        },
+    );
+    $EVENT{'timer'}->();
+    # Reads configuration file
+    my $config_reader = $attrib_ref->{util}->config_reader(
+        FCM::Context::Locator->new($option_ref->{'config-file'}->[0]),
+        {   %FCM::Util::ConfigReader::FCM1_ATTRIB,
+            event_level => $attrib_ref->{util}->util_of_report()->LOW,
+        },
+    );
+    my %conditions_of;
+    while (defined(my $entry = $config_reader->())) {
+        # Value: conditions
+        my @conditions;
+        for my $word (shellwords($entry->get_value())) {
+            my ($operator, $rev) = $word =~ qr{\A ([<>]=?|[!=]=) (.+) \z}imsx;
+            if (!$operator || !$rev) {
+                return $E->throw($E->CONFIG_VALUE, $entry);
+            }
+            push(@conditions, $operator . $rev); # FIXME: keyword?
+        }
+        # Label: targets and namespaces
+        my ($target) = $entry->get_label() =~ qr{\A (.+) / \*\z}msx;
+        if ($target) {
+            my $l_target = $UTIL->loc_cat($locator, $target);
+            $UTIL->loc_find(
+                $l_target,
+                sub {
+                    my ($l_child, $attrib_of_child_ref) = @_;
+                    if (!$attrib_of_child_ref->{is_dir}) {
+                        my $ns_of_child = $attrib_of_child_ref->{ns};
+                        my $iter
+                            = $UTIL->ns_iter($ns_of_child, $UTIL->NS_ITER_UP);
+                        $iter->(); # discard
+                        my $ns = $UTIL->ns_cat($target, $iter->());
+                        if (!exists($conditions_of{$ns})) {
+                            $conditions_of{$ns} = \@conditions;
+                        }
+                    }
+                },
+            );
+        }
+        else {
+            $conditions_of{$entry->get_label()} = \@conditions;
+        }
+    }
+    # Export
+    NS:
+    while (my ($ns, $conditions_ref) = each(%conditions_of)) {
+        # FIXME: this should be encapsulated by the locator util.
+        my @command_list = (
+            qw{svn log -q},
+            $UTIL->loc_cat($locator, $ns)->get_value(),
+        );
+        my %value_of = %{$UTIL->shell_simple(\@command_list)};
+        if ($value_of{rc}) {
+            return $E->throw(
+                $E->SHELL,
+                {command_list => \@command_list, %value_of},
+                $value_of{e},
+            );
+        }
+        my @revs = map {($_ =~ qr{\Ar(\d+)})} split("\n", $value_of{o});
+        my %v_of;
+        my $v = 0;
+        for my $rev (reverse(@revs)) {
+            $v_of{$rev} = 'v' . ++$v;
+        }
+        my %cur_v_of = %v_of;
+        # Exports only revisions matching the conditions
+        for my $condition (@{$conditions_ref}) {
+            for my $rev (keys(%cur_v_of)) {
+                if (!eval($rev . $condition)) {
+                    delete($cur_v_of{$rev});
+                }
+            }
+        }
+        # Destination directory
+        my $path = catfile($option_ref->{directory}, $ns);
+        if (-d $path) {
+            if ($option_ref->{new} || !keys(%cur_v_of)) {
+                rmtree($path);
+            }
+            else {
+                # Delete excluded revisions if they exist in incremental mode
+                if (opendir(my $handle, $path)) {
+                    while (my $item = readdir($handle)) {
+                        if (exists($v_of{$item}) && !exists($cur_v_of{$item})) {
+                            for (($item, $v_of{$item})) {
+                                my $p = catfile($path, $_);
+                                rmtree($p);
+                                $EVENT{'delete'}->($ns, $item, $p);
+                            }
+                        }
+                    }
+                    closedir($handle);
+                }
+            }
+        }
+        if (!keys(%cur_v_of)) {
+            next NS;
+        }
+        if (!-d $path) {
+            mkpath($path);
+        }
+
+        # Exports each revision, and creates symlink for each v
+        while (my ($rev, $v) = each(%cur_v_of)) {
+            my $target = catfile($option_ref->{directory}, $ns, $v);
+            if (-l $target || -f $target) {
+                unlink($target);
+                $EVENT{'delete'}->($ns, $v, $target);
+            }
+            if (!-d $target) {
+                my $url_peg_rev = $UTIL->loc_cat($locator, $ns)->get_value();
+                my ($url) = $url_peg_rev =~ qr{\A(.*?)(?:@[^@/]+)?\z}msx;
+                my @command_list = (qw{svn export -q -r}, $rev, $url, $target);
+                my %value_of = %{$UTIL->shell_simple(\@command_list)};
+                if ($value_of{rc} || !-d $target) {
+                    return $E->throw(
+                        $E->SHELL,
+                        {command_list => \@command_list, %value_of},
+                        $value_of{e},
+                    );
+                }
+                $EVENT{'create'}->($ns, $v, $target);
+            }
+            my $link = catfile($option_ref->{directory}, $ns, $rev);
+            if (-e $link && !-l $link) {
+                rmtree($link);
+                $EVENT{'delete'}->($ns, $rev, $link);
+            }
+            elsif (-l $link && readlink($link) ne $v) {
+                unlink($link);
+                $EVENT{'delete'}->($ns, $rev, $link);
+            }
+            if (!-e $link) {
+                symlink($v, $link);
+                $EVENT{'create'}->($ns, $rev, $link);
+            }
+        }
+
+        # Symbolic link to the "latest" version directory
+        my $link_of_latest = catfile($option_ref->{directory}, $ns, 'latest');
+        my $v_of_latest = $cur_v_of{max(keys(%cur_v_of))};
+        if (-e $link_of_latest && !-l $link_of_latest) {
+            rmtree($link_of_latest);
+            $EVENT{'delete'}->($ns, 'latest', $link_of_latest);
+        }
+        elsif (-l $link_of_latest && readlink($link_of_latest) ne $v_of_latest) {
+            unlink($link_of_latest);
+            $EVENT{'delete'}->($ns, 'latest', $link_of_latest);
+        }
+        if (!-l $link_of_latest) {
+            symlink($v_of_latest, $link_of_latest);
+            $EVENT{'create'}->($ns, 'latest', $link_of_latest);
+        }
+    }
+    $EVENT{'timer'}->($timer->());
+}
+
+# Searches FCM keywords.
+sub _keyword_find {
+    my ($attrib_ref, $option_ref, @args) = @_;
+    my $UTIL = $attrib_ref->{util};
+    my @entries;
+    if (@args) {
+        for my $key (@args) {
+            my $iter = $UTIL->loc_kw_iter(FCM::Context::Locator->new($key));
+            while (my $entry = $iter->()) {
+                if (!$entry->get_implied()) {
+                    $UTIL->loc_kw_load_rev_prop($entry);
+                    push(@entries, $entry);
+                }
+            }
+        }
+    }
+    else {
+        @entries = values(%{$UTIL->loc_kw_ctx()->get_entry_by_key()});
+    }
+    for my $entry (sort {$a->get_key() cmp $b->get_key()} @entries) {
+        $UTIL->event(FCM::Context::Event->KEYWORD_ENTRY, $entry);
+    }
+    return;
+}
+
+# Emit an FCM::Context::Event->OUT event to print FCM's version.
+sub _version {
+    my ($attrib_ref, $option_ref, @args) = @_;
+    my $UTIL = $attrib_ref->{util};
+    $UTIL->event(FCM::Context::Event->OUT, $UTIL->version(@_) . "\n");
+    return;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Misc
+
+=head1 SYNOPSIS
+
+    use FCM::System::Misc;
+    my $system = FCM::System::Misc->new(\%attrib);
+    $system->keyword_find(@args);
+
+=head1 DESCRIPTION
+
+The rest of the FCM system.
+
+=head1 METHODS
+
+Implements the browse(), config_parse(), export_items(), keyword_find() and
+version() methods for L<FCM::System|FCM::System>. See L<FCM::System|FCM::System>
+for a description of the calling interfaces of these functions.
+
+=head1 DIAGNOSTICS
+
+=head2 FCM::System::Exception
+
+Methods of this class may throw a FCM::System::Exception.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/System/Old.pm b/lib/FCM/System/Old.pm
new file mode 100644
index 0000000..f62b846
--- /dev/null
+++ b/lib/FCM/System/Old.pm
@@ -0,0 +1,142 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::System::Old;
+use base qw{FCM::Class::CODE};
+
+use Cwd qw{cwd};
+use FCM1::Build;
+use FCM1::Config;
+use FCM1::Extract;
+#use FCM1::ExtractConfigComparator;
+use FCM1::Keyword;
+
+my %CLASS_OF = (build => 'FCM1::Build', extract => 'FCM1::Extract');
+
+my %KEY_OF = (
+    'archive'     => 'ARCHIVE',
+    'clean'       => 'CLEAN',
+    'full'        => 'FULL',
+    'ignore-lock' => 'IGNORE_LOCK',
+    'jobs'        => 'JOBS',
+    'stage'       => 'STAGE',
+    'targets'     => 'TARGETS',
+);
+
+__PACKAGE__->class(
+    {util => '&'},
+    {   init => \&_init,
+        action_of => {
+            build          => sub {_run('build', @_)},
+            config_compare => \&_config_compare,
+            extract        => sub {_run('extract', @_)},
+        },
+    },
+);
+
+sub _init {
+    my ($attrib_ref) = @_;
+    if (!defined(FCM1::Keyword::get_util())) {
+        FCM1::Keyword::set_util($attrib_ref->{util});
+    }
+}
+
+sub _config_compare {
+    my ($attrib_ref, $option_hash_ref, @args) = @_;
+    $attrib_ref->{util}->class_load('FCM1::CmUrl');
+    $attrib_ref->{util}->class_load('FCM1::ExtractConfigComparator');
+    if (exists($option_hash_ref->{verbosity})) {
+        FCM1::Config->instance()->verbose($option_hash_ref->{verbosity});
+    }
+    my %option = %{$option_hash_ref};
+    if (exists($option{'wiki-format'})) {
+        $option{'wiki'} = delete($option{'wiki-format'});
+    }
+    my $system = FCM1::ExtractConfigComparator->new({files => \@args, %option});
+    $system->invoke();
+}
+
+sub _run {
+    my ($key, $attrib_ref, $option_hash_ref, @args) = @_;
+    if (exists($option_hash_ref->{targets})) {
+        @{$option_hash_ref->{targets}}
+            = split(qr{:}msx, join(':', @{$option_hash_ref->{targets}}));
+    }
+    if (exists($option_hash_ref->{verbosity})) {
+        FCM1::Config->instance()->verbose($option_hash_ref->{verbosity});
+    }
+    my $system = $CLASS_OF{$key}->new();
+    my $path_to_cfg = @args ? $args[0] : cwd();
+    $system->cfg()->src($path_to_cfg);
+    my %option_of;
+    while (my ($key, $value) = each(%{$option_hash_ref})) {
+        if (exists($KEY_OF{$key})) {
+            $option_of{$KEY_OF{$key}} = $value;
+        }
+    }
+    $system->invoke(%option_of);
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::System::Old
+
+=head1 SYNOPSIS
+
+    use FCM::System::Old;
+    my $system = FCM::System::Old->new();
+    $system->('extract', \%option, \@args);
+
+=head1 DESCRIPTION
+
+Provides a compatibility layer for obsolete FCM 1 functionalities.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new()
+
+Creates and returns an instance of this class.
+
+=item $instance->build(\%option, at args)
+
+Invokes the FCM 1 build system.
+
+=item $instance->config_compare(\%option, at args)
+
+Invokes the FCM 1 cmp-ext-cfg application.
+
+=item $instance->extract(\%option, at args)
+
+Invokes the FCM 1 extract system.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Util.pm b/lib/FCM/Util.pm
new file mode 100644
index 0000000..2019eaa
--- /dev/null
+++ b/lib/FCM/Util.pm
@@ -0,0 +1,1023 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+
+package FCM::Util;
+use base qw{FCM::Class::CODE};
+
+use Digest::MD5;
+use FCM::Context::Event;
+use FCM::Context::Locator;
+use FCM::Util::ConfigReader;
+use FCM::Util::ConfigUpgrade;
+use FCM::Util::Event;
+use FCM::Util::Exception;
+use FCM::Util::Locator;
+use FCM::Util::Reporter;
+use FCM::Util::Shell;
+use FCM::Util::TaskRunner;
+use File::Basename qw{basename dirname};
+use File::Path qw{mkpath};
+use File::Spec::Functions qw{catfile};
+use FindBin;
+use Scalar::Util qw{blessed reftype};
+use Text::ParseWords qw{shellwords};
+use Time::HiRes qw{gettimeofday tv_interval};
+
+use constant {NS_ITER_UP => 1};
+
+# The (keys) named actions of this class and (values) their implementations.
+our %ACTION_OF = (
+    cfg_init             => \&_cfg_init,
+    class_load           => \&_class_load,
+    config_reader        => _util_of_func('config_reader', 'main'),
+    external_cfg_get     => \&_external_cfg_get,
+    event                => \&_event,
+    file_ext             => \&_file_ext,
+    file_head            => \&_file_head,
+    file_load            => \&_file_load,
+    file_load_handle     => \&_file_load_handle,
+    file_md5             => \&_file_md5,
+    file_save            => \&_file_save,
+    file_tilde_expand    => \&_file_tilde_expand,
+    hash_cmp             => \&_hash_cmp,
+    loc_as_invariant     => _util_of_loc_func('as_invariant'),
+    loc_as_keyword       => _util_of_loc_func('as_keyword'),
+    loc_as_normalised    => _util_of_loc_func('as_normalised'),
+    loc_as_parsed        => _util_of_loc_func('as_parsed'),
+    loc_browser_url      => _util_of_loc_func('browser_url'),
+    loc_cat              => _util_of_loc_func('cat'),
+    loc_dir              => _util_of_loc_func('dir'),
+    loc_export           => _util_of_loc_func('export'),
+    loc_export_ok        => _util_of_loc_func('export_ok'),
+    loc_exists           => _util_of_loc_func('test_exists'),
+    loc_find             => _util_of_loc_func('find'),
+    loc_kw_ctx           => _util_of_loc_func('kw_ctx'),
+    loc_kw_ctx_load      => _util_of_loc_func('kw_ctx_load'),
+    loc_kw_iter          => _util_of_loc_func('kw_iter'),
+    loc_kw_load_rev_prop => _util_of_loc_func('kw_load_rev_prop'),
+    loc_kw_prefix        => _util_of_func('locator', 'kw_prefix'),
+    loc_origin           => _util_of_loc_func('origin'),
+    loc_reader           => _util_of_loc_func('reader'),
+    loc_rel2abs          => _util_of_loc_func('rel2abs'),
+    loc_trunk_at_head    => _util_of_loc_func('trunk_at_head'),
+    loc_what_type        => _util_of_loc_func('what_type'),
+    loc_up_iter          => _util_of_loc_func('up_iter'),
+    ns_cat               => \&_ns_cat,
+    ns_common            => \&_ns_common,
+    ns_in_set            => \&_ns_in_set,
+    ns_iter              => \&_ns_iter,
+    ns_sep               => sub {$_[0]->{ns_sep}},
+    report               => _util_of_func('reporter', 'report'),
+    shell                => _util_of_func('shell', 'invoke'),
+    shell_simple         => _util_of_func('shell', 'invoke_simple'),
+    shell_which          => _util_of_func('shell', 'which'),
+    task_runner          => _util_of_func('task_runner', 'main'),
+    timer                => \&_timer,
+    uri_match            => \&_uri_match,
+    util_of_event        => _util_impl_func('event'),
+    util_of_report       => _util_impl_func('reporter'),
+    version              => \&_version,
+);
+# The default paths to the configuration files.
+our @FCM1_KEYWORD_FILES = (
+    catfile((getpwuid($<))[7], qw{.fcm}),
+);
+our @CONF_PATHS = (
+    catfile($FindBin::Bin, qw{.. etc fcm}),
+    catfile((getpwuid($<))[7], qw{.met-um fcm}),
+    catfile((getpwuid($<))[7], qw{.metomi fcm}),
+);
+our %CFG_BASENAME_OF = (
+    external => 'external.cfg',
+    keyword  => 'keyword.cfg',
+);
+# Values of external commands
+our %EXTERNAL_VALUE_OF = (
+    'browser'       => 'firefox',
+    'diff3'         => 'diff3',
+    'diff3.flags'   => '-E -m',
+    'graphic-diff'  => 'xxdiff',
+    'graphic-merge' => 'xxdiff',
+    'ssh'           => 'ssh',
+    'ssh.flags'     => '-n -oBatchMode=yes',
+    'rsync'         => 'rsync',
+    'rsync.flags'   => '-a --exclude=.* --delete-excluded --timeout=900'
+                       . ' --rsh="ssh -oBatchMode=yes"',
+);
+# The name-space separator
+our $NS_SEP = '/';
+# The (keys) named utilities and their implementation classes.
+our %UTIL_CLASS_OF = (
+    config_reader => 'FCM::Util::ConfigReader',
+    event         => 'FCM::Util::Event',
+    locator       => 'FCM::Util::Locator',
+    reporter      => 'FCM::Util::Reporter',
+    shell         => 'FCM::Util::Shell',
+    task_runner   => 'FCM::Util::TaskRunner',
+);
+
+# Alias
+my $E = 'FCM::Util::Exception';
+
+# Regular expression: match a URI
+my $RE_URI = qr/
+    \A              (?# start)
+    (               (?# capture 1, scheme, start)
+        [A-Za-z]    (?# alpha)
+        [\w\+\-\.]* (?# optional alpha, numeric, plus, minus and dot)
+    )               (?# capture 1, scheme, end)
+    :               (?# colon)
+    (.*)            (?# capture 2, opaque, rest of string)
+    \z              (?# end)
+/xms;
+
+# Creates the class.
+__PACKAGE__->class(
+    {   cfg_basename_of   => {isa => '%', default => {%CFG_BASENAME_OF}},
+        conf_paths        => {isa => '@', default => [@CONF_PATHS]},
+        event             => '&',
+        external_value_of => {isa => '%', default => {%EXTERNAL_VALUE_OF}},
+        ns_sep            => {isa => '$', default => $NS_SEP},
+        util_class_of     => {isa => '%', default => {%UTIL_CLASS_OF}},
+        util_of           => '%',
+    },
+    {init => \&_init, action_of => \%ACTION_OF},
+);
+
+# Initialises attributes.
+sub _init {
+    my ($attrib_ref, $self) = @_;
+    # Initialise the utilities
+    while (my ($key, $util_class) = each(%{$attrib_ref->{util_class_of}})) {
+        if (!defined($attrib_ref->{util_of}{$key})) {
+            _class_load($attrib_ref, $util_class);
+            $attrib_ref->{util_of}{$key} = $util_class->new({util => $self});
+        }
+    }
+    if (exists($ENV{FCM_CONF_PATH})) {
+        $attrib_ref->{conf_paths} = [shellwords($ENV{FCM_CONF_PATH})];
+    }
+}
+
+# Loads the named configuration from its configuration files.
+sub _cfg_init {
+    my ($attrib_ref, $basename, $action_ref) = @_;
+    if (exists($ENV{FCM_CONF_PATH})) {
+        $attrib_ref->{conf_paths} = [shellwords($ENV{FCM_CONF_PATH})];
+    }
+    for my $path (
+        grep {-f} map {catfile($_, $basename)} @{$attrib_ref->{conf_paths}}
+    ) {
+        my $config_reader = $ACTION_OF{config_reader}->(
+            $attrib_ref, FCM::Context::Locator->new($path),
+        );
+        $action_ref->($config_reader);
+    }
+}
+
+# Loads a class/package.
+sub _class_load {
+    my ($attrib_ref, $name, $test_method) = @_;
+    $test_method ||= 'new';
+    if (!UNIVERSAL::can($name, $test_method)) {
+        eval('require ' . $name);
+        if (my $e = $@) {
+            return $E->throw($E->CLASS_LOADER, $name, $e);
+        }
+    }
+    return $name;
+}
+
+# Invokes an event.
+sub _event {
+    my ($attrib_ref, $event, @args) = @_;
+    if (!blessed($event)) {
+        $event = FCM::Context::Event->new({code => $event, args => \@args}),
+    }
+    $attrib_ref->{'util_of'}{'event'}->main($event);
+}
+
+# Returns the value of an external tool.
+{   my $EXTERNAL_CFG_INIT;
+    sub _external_cfg_get {
+        my ($attrib_ref, $key) = @_;
+        my $value_hash_ref = $attrib_ref->{external_value_of};
+        if (!$EXTERNAL_CFG_INIT) {
+            $EXTERNAL_CFG_INIT = 1;
+            _cfg_init(
+                $attrib_ref,
+                $attrib_ref->{cfg_basename_of}{external},
+                sub {
+                    my $config_reader = shift();
+                    while (defined(my $entry = $config_reader->())) {
+                        my $k = $entry->get_label();
+                        if ($k && exists($value_hash_ref->{$k})) {
+                            $value_hash_ref->{$k} = $entry->get_value();
+                        }
+                    }
+                }
+            );
+        }
+        if (!$key || !exists($value_hash_ref->{$key})) {
+            return;
+        }
+        return $value_hash_ref->{$key};
+    }
+}
+
+# Returns the file extension of a file system path.
+sub _file_ext {
+    my ($attrib_ref, $path) = @_;
+    my $pos_of_dot = rindex($path, q{.});
+    if ($pos_of_dot == -1) {
+        return (wantarray() ? (undef, $path) : undef);
+    }
+    my $ext = substr($path, $pos_of_dot + 1);
+    wantarray() ? ($ext, substr($path, 0, $pos_of_dot)) : $ext;
+}
+
+# Loads the first $n lines from a file system path.
+sub _file_head {
+    my ($attrib_ref, $path, $n) = @_;
+    $n ||= 1;
+    my $handle = _file_load_handle(@_);
+    my $content = q{};
+    for (1 .. $n) {
+        $content .= readline($handle);
+    }
+    close($handle);
+    (wantarray() ? (map {$_ . "\n"} split("\n", $content)) : $content);
+}
+
+# Loads the contents from a file system path.
+sub _file_load {
+    my ($attrib_ref, $path) = @_;
+    my $handle = _file_load_handle(@_);
+    my $content = do {local($/); readline($handle)};
+    close($handle);
+    (wantarray() ? (map {$_ . "\n"} split("\n", $content)) : $content);
+}
+
+# Opens a file handle to read from a file system path.
+sub _file_load_handle {
+    my ($attrib_ref, $path) = @_;
+    open(my($handle), '<', $path) || return $E->throw($E->IO, $path, $!);
+    $handle;
+}
+
+# Returns the MD5 checksum of the content in a file system path.
+sub _file_md5 {
+    my ($attrib_ref, $path) = @_;
+    my $handle = _file_load_handle($attrib_ref, $path);
+    binmode($handle);
+    my $digest = Digest::MD5->new();
+    $digest->addfile($handle);
+    my $checksum = $digest->hexdigest();
+    close($handle);
+    return $checksum;
+}
+
+# Saves content to a file system path.
+sub _file_save {
+    my ($attrib_ref, $path, $content) = @_;
+    if (!-e dirname($path)) {
+        eval {mkpath(dirname($path))};
+        if (my $e = $@) {
+            return $E->throw($E->IO, $path, $e);
+        }
+    }
+    open(my($handle), '>', $path) || return $E->throw($E->IO, $path, $!);
+    if (ref($content) && ref($content) eq 'ARRAY') {
+        print($handle @{$content}) || return $E->throw($E->IO, $path, $!);
+    }
+    else {
+        print($handle $content) || return $E->throw($E->IO, $path, $!);
+    }
+    close($handle) || return $E->throw($E->IO, $path, $!);
+}
+
+# Expand leading ~ and ~USER syntax in $path and return the resulting string.
+sub _file_tilde_expand {
+    my ($attrib_ref, $path) = @_;
+    $path =~ s{\A~([^/]*)}{$1 ? (getpwnam($1))[7] : (getpwuid($<))[7]}exms;
+    return $path;
+}
+
+# Compares contents of 2 HASH references.
+sub _hash_cmp {
+    my ($attrib_ref, $hash_1_ref, $hash_2_ref, $keys_only) = @_;
+    my %hash_2 = %{$hash_2_ref};
+    my %modified;
+    while (my ($key, $v1) = each(%{$hash_1_ref})) {
+        if (exists($hash_2{$key})) {
+            my $v2 = $hash_2{$key};
+            if (    !$keys_only
+                &&  (
+                        defined($v1) && defined($v2) && $v1 ne $v2
+                    ||  defined($v1) && !defined($v2)
+                    ||  !defined($v1) && defined($v2)
+                )
+            ) {
+                $modified{$key} = 0;
+            }
+            delete($hash_2{$key});
+        }
+        else {
+            $modified{$key} = -1;
+        }
+    }
+    while (my $key = each(%hash_2)) {
+        if (!exists($hash_1_ref->{$key})) {
+            $modified{$key} = 1;
+        }
+    }
+    return %modified;
+}
+
+# Concatenates 2 name-spaces.
+sub _ns_cat {
+    my ($attrib_ref, @ns_list) = @_;
+    join(
+        $attrib_ref->{ns_sep},
+        grep {$_ && $_ ne $attrib_ref->{ns_sep}} @ns_list,
+    );
+}
+
+# Returns the common parts of 2 name-spaces.
+sub _ns_common {
+    my ($attrib_ref, $ns1, $ns2) = @_;
+    my $iter1 = _ns_iter($attrib_ref, $ns1);
+    my $iter2 = _ns_iter($attrib_ref, $ns2);
+    my $common_ns = q{};
+    while (defined(my $s1 = $iter1->()) && defined(my $s2 = $iter2->())) {
+        if ($s1 ne $s2) {
+            return $common_ns;
+        }
+        $common_ns = $s1;
+    }
+    return $common_ns;
+}
+
+# Returns true if $ns is in one of the name-spaces given by keys(%set).
+sub _ns_in_set {
+    my ($attrib_ref, $ns, $ns_set_ref) = @_;
+    if (!keys(%{$ns_set_ref})) {
+        return;
+    }
+    my @ns_list;
+    my $ns_iter = _ns_iter($attrib_ref, $ns);
+    while (defined(my $n = $ns_iter->())) {
+        push(@ns_list, $n);
+    }
+    grep {exists($ns_set_ref->{$_})} @ns_list;
+}
+
+# Returns an iterator to walk up/down a name-space.
+sub _ns_iter {
+    my ($attrib_ref, $ns, $up) = @_;
+    if ($ns eq $attrib_ref->{ns_sep}) {
+        $ns = q{};
+    }
+    my @give = split($attrib_ref->{ns_sep}, $ns);
+    my @take = ();
+    my $next = q{};
+    if ($up) {
+        @give = reverse(@give);
+        $next = $ns;
+    }
+    sub {
+        my $ret = $next;
+        $next = undef;
+        if (@give) {
+            push(@take, shift(@give));
+            $next = join($attrib_ref->{ns_sep}, ($up ? reverse(@give) : @take));
+        }
+        return $ret;
+    };
+}
+
+# Returns a timer.
+sub _timer {
+    my ($attrib_ref, $start_ref) = @_;
+    $start_ref ||= [gettimeofday()];
+    sub {tv_interval($start_ref)};
+}
+
+# Matches a URI.
+sub _uri_match {
+    my ($attrib_ref, $string) = @_;
+    $string =~ $RE_URI;
+}
+
+# Returns a function to return/set the object in the "util_of" basket.
+sub _util_impl_func {
+    my ($id) = @_;
+    sub {
+        my ($attrib_ref, $value) = @_;
+        if (defined($value) && ref($value) && reftype($value) eq 'CODE') {
+            $attrib_ref->{'util_of'}{$id} = $value;
+        }
+        $attrib_ref->{'util_of'}{$id};
+    };
+}
+
+# Returns a function to delegate a method to a utility in the "util_of" basket.
+sub _util_of_func {
+    my ($id, $method) = @_;
+    sub {
+        my $attrib_ref = shift();
+        $attrib_ref->{util_of}{$id}->(($method ? ($method) : ()), @_);
+    };
+}
+
+# Returns a function to delegate a method to the locator utility.
+{   my $KEYWORD_CFG_INIT;
+    sub _util_of_loc_func {
+        my ($method) = @_;
+        sub {
+            my $attrib_ref = shift();
+            if (!$KEYWORD_CFG_INIT) {
+                $KEYWORD_CFG_INIT = 1;
+                my $config_upgrade = FCM::Util::ConfigUpgrade->new();
+                for my $path (grep {-f} @FCM1_KEYWORD_FILES) {
+                    my $config_reader = $ACTION_OF{config_reader}->(
+                        $attrib_ref,
+                        FCM::Context::Locator->new($path),
+                        \%FCM::Util::ConfigReader::FCM1_ATTRIB,
+                    );
+                    $ACTION_OF{loc_kw_ctx_load}->(
+                        $attrib_ref,
+                        sub {$config_upgrade->upgrade($config_reader->())},
+                    );
+                }
+                _cfg_init(
+                    $attrib_ref,
+                    $attrib_ref->{cfg_basename_of}{keyword},
+                    sub {$ACTION_OF{loc_kw_ctx_load}->($attrib_ref, @_)},
+                );
+            }
+            $attrib_ref->{util_of}{locator}->($method, @_);
+        };
+    }
+}
+
+# Returns the FCM version string.
+sub _version {
+    my ($attrib_ref) = @_;
+    # Try "git describe"
+    my $value_hash_ref = eval {
+        $ACTION_OF{shell_simple}->(
+            $attrib_ref,
+            ['git', "--git-dir=$FindBin::Bin/../.git", 'describe'],
+        );
+    };
+    if (my $e = $@) {
+        if (!$E->caught($e)) {
+            die($e);
+        }
+        $@ = undef;
+    }
+    if ($value_hash_ref->{o} && !$value_hash_ref->{rc}) {
+        chomp($value_hash_ref->{o});
+        return "FCM " . $value_hash_ref->{o};
+    }
+    # Read fcm-version.js file
+    my $path = catfile($FindBin::Bin, qw{.. doc etc fcm-version.js});
+    open(my($handle), '<', $path) || die("$path: $!");
+    my $content = do {local($/); readline($handle)};
+    close($handle);
+    my ($version) = $content =~ qr{\AFCM\.VERSION="(.*)";}msx;
+    return "FCM " . $version;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Util
+
+=head1 SYNOPSIS
+
+    use FCM::Util;
+    $u = FCM::Util->new();
+    $u->class_load('Foo');
+
+=head1 DESCRIPTION
+
+Utilities used by the FCM system.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Returns a new instance. The %attrib hash can be used configure the behaviour of
+the instance:
+
+=over 4
+
+=item conf_paths
+
+The search paths to the configuration files. The default is the value in
+ at FCM::Util::CONF_PATHS.
+
+=item cfg_basename_of
+
+A HASH to map the named configuration with the base names of their paths.
+(default=%CFG_BASENAME_OF)
+
+=item external_value_of
+
+A HASH to map the named external tools with their default values.
+(default=%EXTERNAL_VALUE_OF)
+
+=item event
+
+A CODE to handle event.
+
+=item ns_sep
+
+The name space separator. (default=/)
+
+=item util_class_of
+
+A HASH to map (keys) utility names to (values) their implementation classes. See
+%FCM::System::UTIL_CLASS_OF.
+
+=item util_of
+
+A HASH to map (keys) utility names to (values) their implementation instances.
+
+=back
+
+=item $u->cfg_init($basename,\&action)
+
+Search site/user configuration given by $basename. Invoke the callback
+&action($config_reader) for each configuration file found.
+
+=item $u->class_load($name,$test_method)
+
+If $name can call $test_method, returns $name. (If $test_method is not defined,
+the default is "new".) Otherwise, calls require($name). Returns $name.
+
+=item $u->config_reader($locator,\%reader_attrib)
+
+Returns an iterator for getting the configuration entries from $locator (which
+should be an instance of L<FCM::Context::Locator|FCM::Context::Locator>.
+
+The iterator returns the next useful entry of the configuration file as an
+object of L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry>. It returns
+under if there is no more useful entry to return.
+
+The %reader_attrib may be used to override the default attributes. The HASH
+should contain a {parser} and a {processor}. The {parser} is a CODE reference to
+parse a declaration in the configuration file into an entry. The {processor} is
+a CODE reference to process the entry. If the {processor} returns true, the
+entry is considered a special entry (e.g. a variable declaration or an
+C<include> declaration) that is processed, and will not be returned by the
+iterator.
+
+The %reader_attrib can be defined using the following pre-defined sets:
+
+=over 4
+
+=item %FCM::Util::ConfigReader::FCM1_ATTRIB
+
+Using this will generate a reader for configuration files written in the FCM 1
+format.
+
+=item %FCM::Util::ConfigReader::FCM2_ATTRIB
+
+Using this will generate a reader for configuration files written in the FCM 2
+format. (default)
+
+=back
+
+In addition, $reader_attrib{event_level} can be used to adjust the event
+verbosity level.
+
+The parser and the processor are called with a %state, which contains the
+current state of the reader, and has the following elements:
+
+=over 4
+
+=item cont
+
+This is set to true if there is a continue marker at the end of the current
+line. The next line should be parsed as part of the current context.
+
+=item ctx
+
+The context of the current entry, which should be an instance of
+L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry>.
+
+=item line
+
+The content of the current line.
+
+=item stack
+
+An ARRAY reference that represents an include stack. The top of the stack
+(the final element) represents the most current file being read. An include file
+will be put on top of the stack, and removed when EOF is reached. When the stack
+is empty, the iterator is exhausted.
+
+Each element of the stack is an 4-element ARRAY reference. Element 1 is the
+L<FCM::Context::Locator|FCM::Context::Locator> object that represents the
+current file. Element 2 is the line number of the current file. Element 3 is the
+file handle for reading the current file. Element 4 is a CODE reference with an
+interface $f->($path), for turning $path from a relative location under the
+container of the current file into an absolute location.
+
+=item var
+
+A HASH reference containing the variables (from the environment and local to the
+configuration file) that can be used for substitution.
+
+=back
+
+=item $u->external_cfg_get($key)
+
+Returns the value of a named tool.
+
+=item $u->event($event, at args)
+
+Raises an event. The 1st argument $event can either be a blessed reference of
+L<FCM::Context::Event|FCM::Context::Event> or a valid event code. If the former
+is true, @args is not used, otherwise, @args should be the event arguments for
+the specified event code.
+
+=item $u->file_ext($path)
+
+Returns file extension of $path. E.g.:
+
+    my $path = '/foo/bar.baz';
+    my $extension = $u->file_ext($path); # 'baz'
+    my ($extension, $root) = $u->file_ext($path); # ('baz', '/foo/bar')
+
+=item $u->file_head($path, $n)
+
+Loads $n lines (or 1 line if $n not specified) from a $path in the file system.
+In scalar context, returns the content in a scalar. In list context, separate
+the content by the new line character "\n", and returns the resulting list.
+
+=item $u->file_load($path)
+
+Loads contents from a $path in the file system. In scalar context, returns the
+content in a scalar. In list context, separate the content by the new line
+character "\n", and returns the resulting list.
+
+=item $u->file_load_handle($path)
+
+Returns a file handle for loading contents from $path.
+
+=item $u->file_md5($path)
+
+Returns the MD5 checksum of $path.
+
+=item $u->file_save($path, $content)
+
+Saves $content to a $path in the file system.
+
+=item $u->file_tilde_expand($path)
+
+Expand any leading "~" or "~USER" syntax to the HOME directory of the current
+user or the HOME directory of USER. Return the modified string.
+
+=item $u->hash_cmp(\%hash_1,\%hash_2,$keys_only)
+
+Compares the contents of 2 HASH references. If $keys_only is specified, only
+compares the keys. Returns a HASH where each element represents a difference
+between %hash_1 and %hash_2 - if the value is positive, the key exists in
+%hash_2 but not %hash_1, if the value is negative, the key exists in %hash_1 but
+not %hash_2, and if the value is zero, the key exists in both, but the values
+are different.
+
+=item $u->loc_as_invariant($locator)
+
+If the $locator->get_value_level() is below FCM::Context::Locator->L_INVARIANT,
+determines the invariant value of $locator, and sets its value to the result.
+Returns $locator->get_value(). 
+
+See L<FCM::Context::Locator|FCM::Context::Locator> for information on locator
+value level.
+
+=item $u->loc_as_keyword($locator)
+
+Calls $u->loc_as_normalised($locator) if $locator->get_value_level() is below
+FCM::Context::Locator->L_NORMALISED. Returns the value of the locator as an FCM
+keyword, where possible.
+
+=item $u->loc_as_normalised($locator)
+
+If the $locator->get_value_level() is below FCM::Context::Locator->L_NORMALISED,
+determines the normalised value of $locator, and sets its value to the result.
+Returns $locator->get_value().
+
+See L<FCM::Context::Locator|FCM::Context::Locator> for information on locator
+value level.
+
+=item $u->loc_as_parsed($locator)
+
+If the $locator->get_value_level() is below FCM::Context::Locator->L_PARSED,
+determines the parsed value of $locator, and sets its value to the result.
+Returns $locator->get_value().
+
+See L<FCM::Context::Locator|FCM::Context::Locator> for information on locator
+value level.
+
+=item $u->loc_browser_url($locator)
+
+Calls $u->loc_as_normalised($locator) if $locator->get_value_level() is below
+FCM::Context::Locator->L_NORMALISED. Returns the value of the locator as a
+browser URL, where possible.
+
+=item $u->loc_cat($locator, at paths)
+
+Calls $u->loc_as_parsed($locator) if $locator->get_value_level() is below
+FCM::Context::Locator->L_PARSED. Concatenates the value of the $locator with the
+given @paths according to the $locator type. Returns a new FCM::Context::Locator
+that represents the concatenated value.
+
+=item $u->loc_dir($locator)
+
+Calls $u->loc_as_parsed($locator) if $locator->get_value_level() is below
+FCM::Context::Locator->L_PARSED. Determines the "directory" name of the value of
+the $locator according to the $locator type. Returns a new FCM::Context::Locator
+that represents the resulting value.
+
+=item $u->loc_exists($locator)
+
+Calls $u->loc_as_normalised($locator) if $locator->get_value_level() is below
+FCM::Context::Locator->L_NORMALISED. Return a true value if the location
+represented by $locator exists.
+
+=item $u->loc_export($locator,$dest)
+
+Calls $u->loc_as_normalised($locator) if $locator->get_value_level() is below
+FCM::Context::Locator->L_NORMALISED. Exports the file or directory tree
+represented by $locator to a file system $dest.
+
+=item $u->loc_export_ok($locator)
+
+Calls $u->loc_as_parsed($locator) if $locator->get_value_level() is below
+FCM::Context::Locator->L_PARSED. Returns true if it is possible and safe to
+call $u->loc_export($locator).
+
+=item $u->loc_find($locator,\&callback)
+
+Searches the directory tree of $locator. Invokes &callback for each node with
+the following interface:
+
+    $callback_ref->($locator_of_child_node, \%target_attrib);
+
+where %target_attrib contains the keys:
+
+=over 4
+
+=item {is_dir}
+
+This is set to true if the child node is a directory.
+
+=item {last_modified_rev}
+
+This is set to the last modified revision of the child node, if relevant.
+
+=item {last_modified_time}
+
+This is set to the last modified time of the child node.
+
+=item {ns}
+
+This is set to the relative name-space (i.e. the relative path) of the child
+node.
+
+=back
+
+=item $u->loc_kw_ctx()
+
+Returns the keyword context (an instance of FCM::Context::Keyword).
+
+=item $u->loc_kw_ctx_load(@config_entry_iterators)
+
+Loads configuration entries into the keyword context. The
+ at config_entry_iterators should be a list of CODE references, with the following
+calling interfaces:
+
+    while (my $config_entry = $config_entry_iterator->()) {
+        # ... $config_entry should be an instance of FCM::Context::ConfigEntry
+    }
+
+=item $u->loc_kw_iter($locator)
+
+Returns an iterator. When called, the iterator returns location keyword entry
+context (as an instance of
+L<FCM::Context::Keyword::Entry::Location|FCM::Context::Keyword>) for $locator
+until exhausted.
+
+    my $iterator = $u->loc_kw_iter($locator)
+    while (my $kw_ctx_entry = $iterator->()) {
+        # ... do something with $kw_ctx_entry
+    }
+
+=item $u->loc_kw_load_rev_prop($entry)
+
+Loads the revision keywords to $entry
+(L<FCM::Context::Keyword::Entry::Location|FCM::Context::Keyword>), assuming that
+$entry is not an implied location keyword, and that the keyword locator points
+to a VCS location that supports setting up revision keywords in properties.
+
+=item $u->loc_kw_prefix()
+
+Returns the prefix of a FCM keyword. This should be "fcm".
+
+=item $u->loc_origin($locator)
+
+Calls $u->loc_as_parsed($locator) if $locator->get_value_level() is below
+FCM::Context::Locator->L_PARSED. Determines the origin of $locator, and returns
+a new FCM::Context::Locator that represents the result. E.g. if $locator points
+to a Subversion working copy, it returns a new locator that represents the URL
+of the working copy.
+
+=item $u->loc_reader($locator)
+
+Calls $u->loc_as_normalised($locator) if $locator->get_value_level() is below
+FCM::Context::Locator->L_NORMALISED. Returns a file handle for reading the
+content from $locator.
+
+=item $u->loc_rel2abs($locator,$locator_base)
+
+If the value of $locator is a relative path, sets it to an absolute path base on
+the $locator_base, provided that $locator and $locator_base is the same type.
+
+=item $u->loc_trunk_at_head($locator)
+
+Returns a string to represent the relative path to the latest main tree, if it
+is relevant for $locator.
+
+=item $u->loc_what_type($locator)
+
+Sets $locator->get_type() and returns its value. Currently, this can either be
+"svn" for a locator pointing to a Subversion resource or "fs" for a locator
+pointing to a file system resource.
+
+=item $u->loc_up_iter($locator)
+
+Returns an iterator that walks up the hierarchy of the $locator, according to
+its type.
+
+=item $u->ns_cat(@name_spaces)
+
+Concatenates name-spaces and returns the result.
+
+=item $u->ns_common($ns1,$ns2)
+
+Returns the common parts of 2 name-spaces. For example, if $ns1 is
+"egg/ham/bacon" and $ns2 is "egg/ham/sausage", it should return "egg/ham".
+
+=item $u->ns_in_set($ns,\%set)
+
+Returns true if $ns is in a name-space given by the keys of %set.
+
+=item $u->ns_iter($ns,$up)
+
+Returns an iterator that walks up or down a name-space. E.g.:
+
+    $iter_ref = $u->ns_iter('a/bee/cee', $u->NS_ITER_UP);
+    while (defined(my $item = $iter_ref->())) {
+        print("[$item]");
+    }
+    # should print: [a/bee/cee][a/bee][a][]
+
+    $iter_ref = $u->ns_iter('a/bee/cee');
+    while (defined(my $item = $iter_ref->())) {
+        print("[$item]");
+    }
+    # should print: [][a][a/bee][a/bee/cee]
+
+=item $u->ns_sep()
+
+Returns the name-space separator, (i.e. normally "/").
+
+=item $u->report(\%option,$message)
+
+Reports messages using $u->util_of_report(). The default is an instance of
+L<FCM::Util::Reporter|FCM::Util::Reporter>. See
+L<FCM::Util::Reporter|FCM::Util::Reporter> for detail.
+
+=item $u->shell($command,\%action_of)
+
+Invokes the $command, which can be scalar or a reference to an ARRAY. If a
+scalar is specified, it will be separated into an array using the shellwords()
+function in L<Text::ParseWords|Text::ParseWords>. If it is a reference to an
+ARRAY, the ARRAY will be passed to open3() as is.
+
+The %action_of should contain the actions for i: standard input, e: standard
+error output and o: standard output. The default for each of these is an
+anonymous subroutinue that does nothing.
+
+Each time the pipe to the child standard input is available for writing, it will
+call $action_of{i}->(). If it returns a defined value, the value will be written
+to the pipe. If it returns undef, the pipe will be closed.
+
+Each time the pipe from the child standard (error) output is available for
+reading, it will read some values to a buffer, and invoke the callback
+$action_of{o}->($buffer) (or $action_of{e}->($buffer)). The return value of the
+callback will be ignored.
+
+On normal completion, it returns the status code of the command and raises an
+FCM::Context::Event->SHELL event:
+
+Any abnormal failure will cause an instance of FCM::Util::Exception to be
+thrown. (The return of a non-zero status code by the child is considered a
+normal completion.)
+
+=item $u->shell_simple($command)
+
+Wraps $u->shell(), and returns a HASH reference containing {e} (the
+standard error), {o} (the standard output) and {rc} (the return code).
+
+=item $u->shell_which($name)
+
+Returns the full path of an executable command $name if it can be found in the
+system PATH.
+
+=item $u->task_runner($action_code_ref,$n_workers)
+
+Returns a runner of tasks. It can be configured to work in serial (default) or
+parallel. The runner has the following methods:
+
+    $n_done = $runner->main($get_code_ref,$put_code_ref);
+    $runner->destroy();
+
+For each $task (L<FCM::Context::Task|FCM::Context::Task>) returned by the
+$get_code_ref->() iterator, invokes $action_ref->($task->get_ctx()). When
+$action_ref returns, send the $task back to the caller by calling
+$put_code_ref->($task). When it is done, the runner returns the number of tasks
+it has done.
+
+The $runner->destroy() method should be called to destroy the $runner when it is
+not longer used.
+
+=item $u->timer(\@start)
+
+Returns a CODE reference, which can be called to return the elapsed time. The
+ at start argument is optional. If specified, it should be in a format as returned
+by Time::HiRes::gettimeofday(). If not specified, the current gettimeofday() is
+used.
+
+=item $u->uri_match($string)
+
+Returns true if $string is a URI. In array context, returns the scheme and the
+opague part of the URI if $string is a URI, or an empty list otherwise.
+
+=item $u->util_of_event($value)
+
+Returns and/or sets the L<FCM::Util::Event|FCM::Util::Event> object that is used
+to handle the $u->report() method.
+
+=item $u->util_of_report($value)
+
+Returns and/or sets the L<FCM::Util::Reporter|FCM::Util::Reporter> object that
+is used to handle the $u->report() method.
+
+=item $u->version()
+
+Returns the FCM version string.
+
+=back
+
+=head1 DIAGNOSTICS
+
+=head2 FCM::Util::Exception
+
+This exception is a sub-class of L<FCM::Exception|FCM::Exception> and is thrown
+by methods of this class on error.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Util/ConfigReader.pm b/lib/FCM/Util/ConfigReader.pm
new file mode 100644
index 0000000..624f3ef
--- /dev/null
+++ b/lib/FCM/Util/ConfigReader.pm
@@ -0,0 +1,610 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::Util::ConfigReader;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::ConfigEntry;
+use FCM::Context::Event;
+use FCM::Context::Locator;
+use FCM::Util::Exception;
+use File::Spec::Functions qw{file_name_is_absolute};
+use Text::Balanced   qw{extract_bracketed};
+use Text::ParseWords qw{parse_line shellwords};
+
+# Alias
+our $UTIL;
+# Alias to exception class
+my $E = 'FCM::Util::Exception';
+# The variable name, which means the container of the current configuration file
+my $HERE = 'HERE';
+# Element indices in a stack item
+my ($I_LOCATOR, $I_LINE_NUM, $I_HANDLE, $I_HERE_LOCATOR) = (0 .. 3);
+# Patterns for extracting/matching strings
+my %PATTERN_OF = (
+    # Config: comment delimiter, e.g. "... #comment"
+    comment => qr/\s+ \#/xms,
+    # Config: continue, start of next line
+    cont_next => qr/
+        \A (.*?)        (?# start and capture 1, shortest of anything)
+        (\\*)           (?# capture 2, a number of backslashes)
+        \s* \z          (?# optional space until the end)
+    /xms,
+    # Config: continue, end of previous line
+    cont_prev => qr/\A \s* \\? (.*) \z/xms,
+    # Config: removal of the assignment operator at start of string
+    fcm2_equal => qr/
+        \A \s*          (?# start and optional spaces)
+        (=)             (?# capture 1, equal sign)
+        (.*) \z         (?# capture 2, rest of string)
+    /xms,
+    # Config: label of an inc statement
+    fcm1_include => qr/\Ainc\z/ixms,
+    # Config: label of an include statement
+    fcm2_include => qr/\Ainclude\z/ixms,
+    # Config: label of an include-path statement
+    fcm2_include_path => qr/\Ainclude-path\z/ixms,
+    # Config: label
+    fcm2_label => qr/
+        \A \s*          (?# start and optional spaces)
+        (\$?[\w\-\.]+)  (?# capture 1, optional dollar, then valid label)
+        (.*) \z         (?# capture 2, rest of string)
+    /xms,
+    # Config: a variable identifier in a value, e.g. "... ${var}", "$var"
+    fcm1_var => qr/
+        \A              (?# start)
+        (.*?)           (?# capture 1, shortest of anything)
+        ([\$\%])        (?# capture 2, variable sigil, dollar or percent)
+        (\{)?           (?# capture 3, curly brace start, optional)
+        ([A-z]\w+(?:::[A-z]\w+)*) (?# capture 4, variable name)
+        ((?(3)\}))      (?# capture 5, curly brace end, if started in capture 4)
+        (.*)            (?# capture 6, rest of string)
+        \z              (?# end)
+    /xms,
+    # Config: a variable identifier in a value, e.g. "... ${var}", "$var"
+    fcm2_var => qr/
+        \A              (?# start)
+        (.*?)           (?# capture 1, shortest of anything)
+        (\\*)           (?# capture 2, escapes)
+        (\$)            (?# capture 3, variable sigil, dollar)
+        (\{)?           (?# capture 4, curly brace start, optional)
+        ([A-z]\w+)      (?# capture 5, variable name)
+        ((?(4)\}))      (?# capture 6, curly brace end, if started in capture 4)
+        (.*)            (?# capture 7, rest of string)
+        \z              (?# end)
+    /xms,
+    # Config: a $HERE, ${HERE} in the beginning of a string
+    here => qr/
+        \A                  (?# start)
+        (\$HERE|\$\{HERE\}) (?# capture 1, \$HERE)
+        (\/.*)?             (?# capture 2, rest of string)
+        \z                  (?# end)
+    /xms,
+    # Config: an empty or comment line
+    ignore => qr/\A \s* (?:\#|\z)/xms,
+    # Config: comma separator
+    delim_csv => qr/\s*,\s*/xms,
+    # Config: modifier key:value separator
+    delim_mod => qr/\s*:\s*/xms,
+    # A variable name
+    var_name => qr/\A [A-Za-z_]\w* \z/xms,
+    # Config: trim value
+    trim => qr/\A \s* (.*?) \s* \z/xms,
+    # Config: trim value within braces
+    trim_brace => qr/\A [\[\{] \s* (.*?) \s* [\]\}] \z/xms,
+);
+# Default (post-)processors for a configuration entry
+our %FCM1_ATTRIB = (
+    parser    => _parse_func(\&_parse_fcm1_label, \&_parse_fcm1_var),
+    processor => sub {
+        _process_assign_func('%')->(@_)
+        ||
+        _process_include_func('fcm1_include')->(@_)
+        ||
+        _process_fcm1_label(@_)
+        ;
+    },
+);
+# Default (post-)processors for a configuration entry
+our %FCM2_ATTRIB = (
+    parser    => _parse_func(\&_parse_fcm2_label, \&_parse_fcm2_var),
+    processor => sub {
+        _process_assign_func('$', '?')->(@_)
+        ||
+        _process_include_path_func('fcm2_include_path')->(@_)
+        ||
+        _process_include_func('fcm2_include')->(@_)
+        ;
+    },
+);
+
+# Creates the class.
+__PACKAGE__->class(
+    {   event_level => '$',
+        parser      => {isa => '&', default => sub {$FCM2_ATTRIB{parser}}   },
+        processor   => {isa => '&', default => sub {$FCM2_ATTRIB{processor}}},
+        util        => '&',
+    },
+    {action_of => {main => \&_main}},
+);
+
+# Returns a configuration reader.
+sub _main {
+    my ($attrib_ref, $locator, $reader_attrib_ref) = @_;
+    if (!defined($locator)) {
+        return;
+    }
+    my %reader_attrib
+        = defined($reader_attrib_ref) ? %{$reader_attrib_ref} : ();
+    my @include_paths = exists($reader_attrib{include_paths})
+        ?  @{$reader_attrib{include_paths}} : ();
+    my %state = (
+        cont  => undef,
+        ctx   => undef,
+        line  => undef,
+        include_paths => \@include_paths,
+        stack => [[$locator, 0]],
+        var   => {},
+    );
+    my %attrib = (%{$attrib_ref}, %reader_attrib);
+    sub {_read(\%attrib, \%state)};
+}
+
+# Returns a parser for a configuration line (FCM 1 or FCM 2 format).
+sub _parse_func {
+    my ($parse_label_func, $parse_var_func) = @_;
+    sub {
+        my ($state_ref) = @_;
+        my $line
+            = $state_ref->{cont} ? $state_ref->{line}
+            :                      $parse_label_func->($state_ref)
+            ;
+        my $value
+            = $parse_var_func->($state_ref, _parse_value($state_ref, $line));
+        if ($state_ref->{ctx}->get_value()) {
+            $value = $state_ref->{ctx}->get_value() . $value;
+        }
+        $state_ref->{ctx}->set_value($value);
+        if (!$state_ref->{cont}) {
+            _parse_var_here($state_ref);
+        }
+    };
+}
+
+# Parses a configuration line label (FCM 1 format).
+sub _parse_fcm1_label {
+    my ($state_ref) = @_;
+    my ($label, $line) = split(qr{\s+}xms, $state_ref->{line}, 2);
+    $state_ref->{ctx}->set_label($label);
+    return $line;
+}
+
+# Parses a configuration line label (FCM 2 format).
+sub _parse_fcm2_label {
+    my ($state_ref) = @_;
+    my %EXTRACTOR_OF = (
+        equal    => sub {($_[0] =~ $PATTERN_OF{fcm2_equal})},
+        label    => sub {($_[0] =~ $PATTERN_OF{fcm2_label})},
+        modifier => sub {extract_bracketed($_[0], '{}')} ,
+        ns       => sub {extract_bracketed($_[0], '["]')},
+    );
+    my %ACTION_OF = (
+        equal    => sub {$_[1] || $E->throw($E->CONFIG_SYNTAX, $_[0])},
+        label    => sub {$_[0]->set_label($_[1])},
+        modifier => \&_parse_fcm2_label_modifier,
+        ns       => \&_parse_fcm2_label_ns,
+    );
+    my %EXPAND_VAR_IN = (modifier => 1, ns => 1);
+    my $line = $state_ref->{line};
+    for my $key (qw{label modifier ns equal}) {
+        $line ||= q{};
+        (my $content, $line) = $EXTRACTOR_OF{$key}->($line);
+        if ($EXPAND_VAR_IN{$key}) {
+            $content = _parse_fcm2_var($state_ref, $content);
+        }
+        $ACTION_OF{$key}->($state_ref->{ctx}, $content);
+    }
+    return $line;
+}
+
+# Parses the modifier part in a configuration line label (FCM 2 format).
+sub _parse_fcm2_label_modifier {
+    my ($ctx, $content) = @_;
+    if ($content) {
+        my ($str) = $content =~ $PATTERN_OF{trim_brace};
+        my %hash;
+        for my $item (parse_line($PATTERN_OF{delim_csv}, 0, $str)) {
+            my ($key, $value) = split($PATTERN_OF{delim_mod}, $item, 2);
+            # Note: "key1, key2: value2, ..." == "key1: 1, key2: value2, ..."
+            $hash{$key} = ($value ? $value : 1);
+        }
+        $ctx->set_modifier_of(\%hash);
+    }
+}
+
+# Parses the ns part in a configuration line label (FCM 2 format).
+sub _parse_fcm2_label_ns {
+    my ($ctx, $content) = @_;
+    if ($content) {
+        my ($str) = $content =~ $PATTERN_OF{trim_brace};
+        my @ns = map {$_ eq q{/} ? q{} : $_} parse_line(q{ }, 0, $str);
+        $ctx->set_ns_list(\@ns);
+    }
+}
+
+# Expands variables in a string in a FCM 1 configuration file.
+sub _parse_fcm1_var {
+    my ($state_ref, $value) = @_;
+    my %V = %{$state_ref->{var}};
+    my $lead = q{};
+    my $tail = $value;
+    MATCH:
+    while (defined($tail) && length($tail) > 0) {
+        my ($pre, $sigil, $br_open, $name, $br_close, $post)
+            = map {defined($_) ? $_ : q{}} ($tail =~ $PATTERN_OF{fcm1_var});
+        if (!$name) {
+            return $lead . $tail;
+        }
+        $tail = $post;
+        my $symbol = $sigil . $br_open . $name . $br_close;
+        my $substitute
+            = $name eq $HERE                       ? $symbol
+            : $sigil eq '$' && exists($ENV{$name}) ? $ENV{$name}
+            : $sigil eq '%' && exists($V{$name})   ? $V{$name}
+            :                                        undef
+            ;
+        if (!defined($substitute)) {
+            $UTIL->event(
+                FCM::Context::Event->CONFIG_VAR_UNDEF,
+                $state_ref->{ctx},
+                $symbol,
+            );
+        }
+        $substitute ||= $symbol;
+        $lead .= $pre . $substitute;
+    }
+    return $lead;
+}
+
+# Expands variables in a string in a FCM 2 configuration file.
+sub _parse_fcm2_var {
+    my ($state_ref, $value) = @_;
+    my %V = (%ENV, %{$state_ref->{var}});
+    my $lead = q{};
+    my $tail = $value;
+    while (defined($tail) && length($tail) > 0) {
+        my ($pre, $esc, $sigil, $br_open, $name, $br_close, $post)
+            = map {defined($_) ? $_ : q{}} ($tail =~ $PATTERN_OF{fcm2_var});
+        if (!$name) {
+            return $lead . $tail;
+        }
+        $tail = $post;
+        my $symbol = $sigil . $br_open . $name . $br_close;
+        my $substitute
+            = $name eq $HERE           ? $symbol
+            : $esc && length($esc) % 2 ? $symbol
+            : exists($V{$name})        ? $V{$name}
+            :                            undef
+            ;
+        if (!defined($substitute)) {
+            return $E->throw(
+                $E->CONFIG_VAR_UNDEF, $state_ref->{ctx}, "undef($symbol)",
+            );
+        }
+        $substitute ||= q{};
+        $lead .= $pre . substr($esc, 0, length($esc) / 2) . $substitute;
+    }
+    return $lead;
+}
+
+# Parses the value part of a configuration line.
+sub _parse_value {
+    my ($state_ref, $line) = @_;
+    $line ||= q{};
+    my ($value) = parse_line($PATTERN_OF{comment}, 1, $line);
+    $value ||= q{};
+    chomp($value);
+    ($value) = $value =~ $PATTERN_OF{$state_ref->{cont} ? 'cont_prev' : 'trim'};
+    $state_ref->{cont} = q{};
+    if ($value) {
+        my ($lead, $tail) = $value =~ $PATTERN_OF{cont_next};
+        if ($tail && length($tail) % 2) {
+            $value = $lead;
+            $state_ref->{cont} = $tail;
+        }
+    }
+    return $value;
+}
+
+# Expands the leading $HERE variable in the value of a configuration entry.
+sub _parse_var_here {
+    my ($state_ref) = @_;
+    my @values = shellwords($state_ref->{ctx}->get_value());
+    if (!grep {$_ =~ $PATTERN_OF{here}} @values) {
+        return;
+    }
+    VALUE:
+    for my $value (@values) {
+        my ($head, $tail)
+            = map {defined($_) ? $_ : q{}} $value =~ $PATTERN_OF{here};
+        if (!$head) {
+            next VALUE;
+        }
+        $tail = index($tail, '/') == 0 ? substr($tail, 1) : q{}; # FIXME
+        my $here = $state_ref->{stack}->[-1]->[$I_HERE_LOCATOR];
+        $value = $UTIL->loc_cat($here, $tail)->get_value();
+    }
+    $state_ref->{ctx}->set_value(join(
+        q{ },
+        map {my $s = $_; $s =~ s{(['"\s])}{\\$1}gmsx; $s} @values,
+    ));
+}
+
+# Returns a function to process a variable assignment. If
+# $assign_if_undef_modifier is specified and is present in the declaration, only
+# assign a variable if it is not yet defined.
+sub _process_assign_func {
+    my ($sigil, $assign_if_undef_modifier) = @_;
+    sub {
+        my ($state_ref) = @_;
+        my $ctx = $state_ref->{ctx};
+        if (index($ctx->get_label(), $sigil) != 0) { # not a variable assignment
+            return;
+        }
+        my $name = substr($ctx->get_label(), length($sigil));
+        if ($name !~ $PATTERN_OF{var_name}) {
+            return $E->throw($E->CONFIG_SYNTAX, $state_ref->{ctx});
+        }
+        if ($name eq $HERE) {
+            return $E->throw($E->CONFIG_USAGE, $state_ref->{ctx});
+        }
+        if (    !$assign_if_undef_modifier
+            ||  !exists($ctx->get_modifier_of()->{$assign_if_undef_modifier})
+            ||  !exists($ENV{$name}) && !exists($state_ref->{var}{$name})
+        ) {
+            $state_ref->{var}{$name} = $ctx->get_value();
+        }
+        return 1;
+    }
+}
+
+# Processes a FCM 1 label.
+sub _process_fcm1_label {
+    my ($state_ref) = @_;
+    $state_ref->{var}{$state_ref->{ctx}->get_label()}
+        = $state_ref->{ctx}->get_value();
+    return;
+}
+
+# Processes an include-path declaration.
+sub _process_include_path_func {
+    my ($key) = @_;
+    my $PATTERN = $PATTERN_OF{$key};
+    sub {
+        my ($state_ref) = @_;
+        if ($state_ref->{ctx}->get_label() !~ $PATTERN) {
+            return;
+        }
+        my $M = $state_ref->{ctx}->get_modifier_of();
+        my $type = exists($M->{type}) ? $M->{type} : undef;
+        if (exists($M->{'+'})) {
+            push(@{$state_ref->{include_paths}}, (
+                map {
+                    FCM::Context::Locator->new($_, {type => $type});
+                } shellwords($state_ref->{ctx}->get_value()),
+            )),
+        }
+        else {
+            $state_ref->{include_paths} = [
+                map {
+                    FCM::Context::Locator->new($_, {type => $type});
+                } shellwords($state_ref->{ctx}->get_value())
+            ],
+        }
+        return 1;
+    };
+}
+
+# Processes an include declaration.
+sub _process_include_func {
+    my ($key) = @_;
+    my $PATTERN = $PATTERN_OF{$key};
+    sub {
+        my ($state_ref) = @_;
+        if ($state_ref->{ctx}->get_label() !~ $PATTERN) {
+            return;
+        }
+        my $M = $state_ref->{ctx}->get_modifier_of();
+        my $type = exists($M->{type}) ? $M->{type} : undef;
+        push(@{$state_ref->{stack}}, (map {
+            my $name = $_;
+            my $locator;
+            if (    $UTIL->uri_match($name)
+                ||  file_name_is_absolute($name)
+            ) {
+                $locator = FCM::Context::Locator->new($name, {type => $type});
+            }
+            if (!defined($locator)) {
+                HEAD:
+                for my $head (
+                    $state_ref->{stack}->[-1]->[$I_HERE_LOCATOR],
+                    @{$state_ref->{include_paths}},
+                ) {
+                    my $locator_at_head = $UTIL->loc_cat($head, $name);
+                    if ($UTIL->loc_exists($locator_at_head)) {
+                        $locator = $locator_at_head;
+                        last HEAD;
+                    }
+                }
+            }
+            if (!defined($locator)) {
+                return $E->throw(
+                    $E->CONFIG_LOAD, $state_ref->{stack}, "include=$name",
+                );
+            }
+            [$locator, 0, undef, undef];
+        } shellwords($state_ref->{ctx}->get_value())));
+        return 1;
+    };
+}
+
+# Reads the next entry of a configuration file.
+sub _read {
+    my ($attrib_ref, $state_ref) = @_;
+    local($UTIL) = $attrib_ref->{util};
+    STACK:
+    while (@{$state_ref->{stack}}) {
+        my $S = $state_ref->{stack}->[-1];
+        # Open a file handle for the top of the stack, if necessary
+        if (!defined($S->[$I_HANDLE])) {
+            eval {
+                # Check for cyclic dependency
+                for my $i (-scalar(@{$state_ref->{stack}}) .. -2) {
+                    my $value = $UTIL->loc_as_invariant(
+                        $state_ref->{stack}->[$i]->[$I_LOCATOR],
+                    );
+                    if ($value eq $UTIL->loc_as_invariant($S->[$I_LOCATOR])) {
+                        return $E->throw($E->CONFIG_CYCLIC, $state_ref->{stack});
+                    }
+                }
+                $S->[$I_HANDLE] = $UTIL->loc_reader($S->[$I_LOCATOR]);
+                $S->[$I_HERE_LOCATOR] = $UTIL->loc_dir($S->[$I_LOCATOR]);
+            };
+            if (my $e = $@) {
+                if ($E->caught($e) && $e->get_code() eq $E->CONFIG_CYCLIC) {
+                    die($e);
+                }
+                return $E->throw($E->CONFIG_LOAD, $state_ref->{stack}, $e);
+            }
+            $UTIL->event(
+                FCM::Context::Event->CONFIG_OPEN,
+                _stack_cp($state_ref->{stack}),
+                $attrib_ref->{event_level},
+            );
+        }
+        # Read a line and parse it
+        LINE:
+        while ($state_ref->{line} = readline($S->[$I_HANDLE])) {
+            if ($state_ref->{line} =~ $PATTERN_OF{ignore}) {
+                next LINE;
+            }
+            $S->[$I_LINE_NUM] = $.;
+            if (!$state_ref->{cont}) {
+                $state_ref->{ctx} = FCM::Context::ConfigEntry->new({
+                    stack => _stack_cp($state_ref->{stack}),
+                });
+            }
+            $attrib_ref->{parser}->($state_ref);
+            if (!$state_ref->{cont}) {
+                if ($attrib_ref->{processor}->($state_ref)) {
+                    next STACK;
+                }
+                return $state_ref->{ctx};
+            }
+        }
+        # At end of file
+        if ($state_ref->{cont}) {
+            return $E->throw($E->CONFIG_CONT_EOF, $state_ref->{ctx});
+        }
+        close($state_ref->{stack}->[-1]->[$I_HANDLE]);
+        $state_ref->{stack}->[-1]->[$I_HANDLE] = undef; # free the memory
+        pop(@{$state_ref->{stack}});
+    }
+    return;
+}
+
+# Copies a stack, selecting only the and the line number.
+sub _stack_cp {
+    [map {[@{$_}[$I_LOCATOR, $I_LINE_NUM]]} @{$_[0]}];
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Util::Config
+
+=head1 SYNOPSIS
+
+    use FCM::Util;
+    my $util = FCM::Util->new(\%attrib);
+    # ... time passes, and now we want to read a FCM 1 config
+    my ($locator, $reader);
+    $locator = FCM::Context::Locator->new($path_to_an_fcm1_config);
+    $reader
+        = $util->config_reader($locator, \%FCM::Util::ConfigReader::FCM1_ATTRIB);
+    while (my $entry = $reader->()) {
+        # ...
+    }
+    # ... time passes, and now we want to read a FCM 2 config
+    $locator = FCM::Context::Locator->new($path_to_an_fcm2_config);
+    $reader = $util->config_reader($locator);
+    while (my $entry = $reader->()) {
+        # ...
+    }
+
+=head1 DESCRIPTION
+
+This module is part of L<FCM::Util|FCM::Util>. Provides a function to generate
+configuration file readers.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Returns a new new instance. The %attrib must contain the following:
+
+=over 4
+
+=item {parser}
+
+A CODE reference to parse the lines in a configuration file into entry contexts.
+It should have a calling interface $f->(\%state). (See L</STATE> for a
+description of %state.) The return value is ignored.
+
+=item {processor}
+
+A CODE reference to post-process each entry context. It should have a calling
+interface $f->(\%state). (See L</STATE> for a description of %state.) The
+processor should return true if the current entry has been processed and is no
+longer considered useful for the user.
+
+=item {util}
+
+The L<FCM::Util|FCM::Util> object, which initialises this class.
+
+=back
+
+=back
+
+See the description of the config_reader() method in L<FCM::Util|FCM::Util> for
+detail.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Util/ConfigUpgrade.pm b/lib/FCM/Util/ConfigUpgrade.pm
new file mode 100644
index 0000000..fa4f86f
--- /dev/null
+++ b/lib/FCM/Util/ConfigUpgrade.pm
@@ -0,0 +1,136 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::Util::ConfigUpgrade;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::ConfigEntry;
+
+my %DECL_PATTERN_OF = (
+    browser_mapping => qr{\A set::browser_mapping(?:_default|:: ([^:]+)):: (.+) \z}ixms,
+    keyword_loc     => qr{\A set::(?:repos|url):: (.+) \z}ixms,
+    keyword_rev     => qr{\A set::revision:: ([^:]+) :: (.+) \z}ixms,
+);
+my @UPGRADE_FUNCS = (
+    \&_upgrade_browser_mapping_decl,
+    \&_upgrade_keyword_loc_decl,
+    \&_upgrade_keyword_rev_decl,
+);
+
+# Creates the class.
+__PACKAGE__->class({}, {action_of => {upgrade => \&_upgrade}});
+
+sub _upgrade {
+    my ($attrib_ref, $config_entry) = @_;
+    if (!defined($config_entry)) {
+        return;
+    }
+    for my $func (@UPGRADE_FUNCS) {
+        $func->($config_entry);
+        if ($func->($config_entry)) {
+            return $config_entry;
+        }
+    }
+    return $config_entry;
+}
+
+# Upgrades a browser mapping declaration.
+sub _upgrade_browser_mapping_decl {
+    my ($config_entry) = @_;
+    my ($ns, $key)
+        = $config_entry->get_label() =~ $DECL_PATTERN_OF{browser_mapping};
+    if (!$key) {
+        return;
+    }
+    $config_entry->set_label(
+          $key eq 'browser_url_template' ? 'browser.loc-tmpl'
+        : $key eq 'browser_rev_template' ? 'browser.rev-tmpl'
+        :                                  'browser.comp-pat'
+    );
+    if ($ns) {
+        $config_entry->set_ns_list([$ns]);
+    }
+}
+
+# Upgrades a location keyword declaration.
+sub _upgrade_keyword_loc_decl {
+    my ($config_entry) = @_;
+    my ($ns) = $config_entry->get_label() =~ $DECL_PATTERN_OF{keyword_loc};
+    if (!$ns) {
+        return;
+    }
+    $config_entry->set_label('location');
+    $config_entry->get_modifier_of()->{primary} = 1;
+    $config_entry->set_ns_list([$ns]);
+}
+
+# Upgrades a revision keyword declaration.
+sub _upgrade_keyword_rev_decl {
+    my ($config_entry) = @_;
+    my ($ns, $key) = $config_entry->get_label() =~ $DECL_PATTERN_OF{keyword_rev};
+    if (!$ns || !$key) {
+        return;
+    }
+    $config_entry->set_label('revision');
+    $config_entry->set_ns_list([$ns, $key]);
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Util::ConfigUpgrade
+
+=head1 SYNOPSIS
+
+    use FCM::Util::ConfigUpgrade;
+    $upgrade = FCM::Util::ConfigUpgrade->new();
+    if (!$upgrade->($entry)) {
+        die($entry->get_label(), ": cannot upgrade.\n");
+    }
+    # ... do something with $entry
+
+=head1 DESCRIPTION
+
+Provides a utility to upgrade FCM 1 configuration to FCM 2 configuration.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new()
+
+Creates and returns a new instance of this utility.
+
+=item $util->($entry)
+
+Upgrades the content of $entry, where possible. Only keyword related
+declarations in the FCM 1 common configuration files are currently supported.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Util/Event.pm b/lib/FCM/Util/Event.pm
new file mode 100644
index 0000000..7a16f54
--- /dev/null
+++ b/lib/FCM/Util/Event.pm
@@ -0,0 +1,1244 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::Util::Event;
+use base qw{FCM::Class::CODE};
+
+use Data::Dumper qw{Dumper};
+use FCM::Context::Event;
+use File::Basename qw{basename};
+use List::Util qw{first};
+use POSIX qw{strftime};
+use Scalar::Util qw{blessed};
+
+my $CTX = 'FCM::Context::Event';
+my $IS_MULTI_LINE = 1;
+
+# Event keys and their actions.
+my %ACTION_OF = (
+    $CTX->CM_ABORT                      => \&_event_cm_abort,
+    $CTX->CM_BRANCH_CREATE_SOURCE       => _func('cm_branch_create_source'),
+    $CTX->CM_BRANCH_LIST                => \&_event_cm_branch_list,
+    $CTX->CM_COMMIT_MESSAGE             => \&_event_cm_commit_message,
+    $CTX->CM_CONFLICT_TEXT              => _func('cm_conflict_text'),
+    $CTX->CM_CONFLICT_TEXT_SKIP         => \&_event_cm_conflict_text_skip,
+    $CTX->CM_CONFLICT_TREE              => _func('cm_conflict_tree'),
+    $CTX->CM_CONFLICT_TREE_SKIP         => \&_event_cm_conflict_tree_skip,
+    $CTX->CM_CONFLICT_TREE_TIME_WARN    => \&_event_cm_conflict_tree_time_warn,
+    $CTX->CM_CREATE_TARGET              => _func('cm_create_target'),
+    $CTX->CM_LOG_EDIT                   => _func('cm_log_edit'),
+    $CTX->CONFIG_OPEN                   => \&_event_config_open,
+    $CTX->CONFIG_ENTRY                  => \&_event_config_entry,
+    $CTX->CONFIG_VAR_UNDEF              => \&_event_config_var_undef,
+    $CTX->E                             => \&_event_e,
+    $CTX->EXPORT_ITEM_CREATE            => _func('export_item_create'),
+    $CTX->EXPORT_ITEM_DELETE            => _func('export_item_delete'),
+    $CTX->FCM_VERSION                   => _func('fcm_version'),
+    $CTX->KEYWORD_ENTRY                 => \&_event_keyword_entry,
+    $CTX->MAKE_BUILD_SHELL_OUT          => \&_event_make_build_shell_out,
+    $CTX->MAKE_BUILD_SOURCE_ANALYSE     => \&_event_make_build_source_analyse,
+    $CTX->MAKE_BUILD_SOURCE_SUMMARY     => _func('make_build_source_summary'),
+    $CTX->MAKE_BUILD_TARGET_DONE        => \&_event_make_build_target_done,
+    $CTX->MAKE_BUILD_TARGET_FAIL        => \&_event_make_build_target_fail,
+    $CTX->MAKE_BUILD_TARGET_FROM_NS     => \&_event_make_build_target_from_ns,
+    $CTX->MAKE_BUILD_TARGET_SELECT      => \&_event_make_build_target_select,
+    $CTX->MAKE_BUILD_TARGET_SELECT_TIMER=> _func('make_build_target_select_t'),
+    $CTX->MAKE_BUILD_TARGET_MISSING_DEP => \&_event_make_build_target_missing_dep,
+    $CTX->MAKE_BUILD_TARGET_STACK       => \&_event_make_build_target_stack,
+    $CTX->MAKE_BUILD_TARGET_SUMMARY     => _func('make_build_target_sum'),
+    $CTX->MAKE_BUILD_TARGET_TASK_SUMMARY=> _func('make_build_target_task_sum'),
+    $CTX->MAKE_BUILD_TARGETS_FAIL       => \&_event_make_build_targets_fail,
+    $CTX->MAKE_DEST                     => \&_event_make_dest,
+    $CTX->MAKE_EXTRACT_PROJECT_TREE     => \&_event_make_extract_project_tree,
+    $CTX->MAKE_EXTRACT_RUNNER_SUMMARY   => \&_event_make_extract_runner_summary,
+    $CTX->MAKE_EXTRACT_SYMLINK          => \&_event_make_extract_symlink,
+    $CTX->MAKE_EXTRACT_TARGET           => \&_event_make_extract_target,
+    $CTX->MAKE_EXTRACT_TARGET_SUMMARY   => \&_event_make_extract_target_summary,
+    $CTX->MAKE_MIRROR                   => \&_event_make_mirror,
+    $CTX->OUT                           => \&_event_out,
+    $CTX->SHELL                         => \&_event_shell,
+    $CTX->TASK_WORKERS                  => \&_event_task_workers,
+    $CTX->TIMER                         => \&_event_timer,
+);
+# Helper for "_event_e", list of exception classes and their formatters.
+our @E_FORMATTERS = (
+    ['FCM1::Cm::Exception'    , \&_format_e_cm            ],
+    ['FCM1::CLI::Exception'   , sub {$_[0]->get_message()}],
+    ['FCM::Class::Exception' , \&_format_e_class         ],
+    ['FCM::CLI::Exception'   , \&_format_e_cli           ],
+    ['FCM::System::Exception', \&_format_e_sys           ],
+    ['FCM::Util::Exception'  , \&_format_e_util          ],
+);
+# Error format strings for FCM1::Cm::Exception.
+our %E_CM_FORMAT_FOR = (
+    DIFF_PROJECTS     => "%s (target) and %s (source) are not related.\n",
+    INVALID_BRANCH    => "%s: not a valid URL of a standard FCM branch.\n",
+    INVALID_PROJECT   => "%s: not a valid URL of a standard FCM project.\n",
+    INVALID_TARGET    => "%s: not a valid working copy or URL.\n",
+    INVALID_URL       => "%s: not a valid URL.\n",
+    INVALID_WC        => "%s: not a valid working copy.\n",
+    MERGE_REV_INVALID => "%s: not a revision in the available merge list.\n",
+    MERGE_SELF        => "%s: cannot be merged to its own working copy: %s.\n",
+    MERGE_UNRELATED   => "%s: target and %s: source not directly related.\n",
+    MERGE_UNSAFE      => "%s: source contains changes outside the target"
+                         . " sub-directory. Please merge with a full tree.\n",
+    MKPATH            => "%s: cannot create directory.\n",
+    NOT_EXIST         => "%s: does not exist.\n",
+    PARENT_NOT_EXIST  => "%s: parent %s no longer exists.\n",
+    RMTREE            => "%s: cannot remove.\n",
+    ST_CONFLICT       => "File(s) in conflicts:\n%s",
+    ST_MISSING        => "File(s) missing:\n%s",
+    ST_OUT_OF_DATE    => "File(s) out of date:\n%s",
+    SWITCH_UNSAFE     => "%s: merge template exists."
+                         . " Please remove before retrying.\n",
+    WC_EXIST          => "%s: working copy already exists.\n",
+    WC_INVALID_BRANCH => "%s: not a working copy of a standard FCM branch.\n",
+    WC_URL_NOT_EXIST  => "%s: working copy URL does not exists at HEAD.\n",
+);
+# Helper for "_format_e_sys", formatters based on exception code.
+our %E_SYS_FORMATTER_FOR = (
+    BUILD_SOURCE     => _format_e_func('e_sys_build_source'),
+    BUILD_SOURCE_SYN => _format_e_func('e_sys_build_source_syn'),
+    BUILD_TARGET     => \&_format_e_sys_build_target,
+    BUILD_TARGET_BAD => _format_e_func('e_sys_build_target_bad', $IS_MULTI_LINE),
+    BUILD_TARGET_CYC => \&_format_e_sys_build_target_cyc,
+    BUILD_TARGET_DEP => \&_format_e_sys_build_target_dep,
+    BUILD_TARGET_DUP => \&_format_e_sys_build_target_dup,
+    CACHE_LOAD       => _format_e_func('e_sys_cache_load'),
+    CACHE_TYPE       => _format_e_func('e_sys_cache_type'),
+    CM_ALREADY_EXIST => _format_e_func('e_sys_cm_already_exist'),
+    CM_ARG           => _format_e_func('e_sys_cm_arg'),
+    CM_BRANCH_NAME   => _format_e_func('e_sys_cm_branch_name'),
+    CM_BRANCH_SOURCE => _format_e_func('e_sys_cm_branch_source'),
+    CM_CHECKOUT      => _format_e_func('e_sys_cm_checkout'),
+    CM_LOG_EDIT_NULL => _format_e_func('e_sys_cm_log_edit_null'),
+    CM_LOG_EDIT_DELIMITER => _format_e_func('e_sys_cm_log_edit_delimiter'),
+    CM_OPT_ARG       => _format_e_func('e_sys_cm_opt_arg'),
+    CM_PROJECT_NAME  => _format_e_func('e_sys_cm_project_name'),
+    CM_REPOSITORY    => _format_e_func('e_sys_cm_repository'),
+    CONFIG_CONFLICT  => _format_e_sys_config_func('conflict'),
+    CONFIG_INHERIT   => _format_e_sys_config_func('inherit'),
+    CONFIG_MODIFIER  => _format_e_sys_config_func('modifier'),
+    CONFIG_NS        => _format_e_sys_config_func('ns'),
+    CONFIG_NS_VALUE  => _format_e_sys_config_func('ns_value'),
+    CONFIG_UNKNOWN   => _format_e_sys_config_func('unknown'),
+    CONFIG_VALUE     => _format_e_sys_config_func('value'),
+    COPY             => _format_e_func('e_sys_copy'),
+    DEST_CLEAN       => _format_e_func('e_sys_dest_clean'),
+    DEST_CREATE      => _format_e_func('e_sys_dest_create'),
+    DEST_LOCK        => _format_e_func('e_sys_dest_lock'),
+    DEST_LOCKED      => _format_e_func('e_sys_dest_locked'),
+    EXPORT_ITEMS_SRC => _format_e_func('e_sys_export_items_src'),
+    EXTRACT_LOC_BASE => _format_e_func('e_sys_extract_loc_base'),
+    EXTRACT_MERGE    => \&_format_e_sys_extract_merge,
+    EXTRACT_NS       => _format_e_func('e_sys_extract_ns', $IS_MULTI_LINE),
+    MIRROR           => \&_format_e_sys_mirror,
+    MIRROR_NULL      => _format_e_func('e_sys_mirror_null'),
+    MIRROR_SOURCE    => _format_e_func('e_sys_mirror_source', $IS_MULTI_LINE),
+    MIRROR_TARGET    => _format_e_func('e_sys_mirror_target'),
+    MAKE             => _format_e_func('e_sys_make'),
+    MAKE_ARG         => \&_format_e_sys_make_arg,
+    MAKE_CFG         => _format_e_func('e_sys_make_cfg'),
+    MAKE_PROP_NS     => \&_format_e_sys_make_prop_ns,
+    MAKE_PROP_VALUE  => \&_format_e_sys_make_prop_value,
+    SHELL            => \&_format_e_sys_shell,
+);
+# Helper for "_format_e_util", formatters based on exception code.
+our %E_UTIL_FORMATTER_FOR = (
+    CLASS_LOADER         => _format_e_func('e_util_class_loader'),
+    CONFIG_CONT_EOF      => _format_e_util_config_func('eof'),
+    CONFIG_CYCLIC        => _format_e_util_config_stack_func('cyclic'),
+    CONFIG_LOAD          => _format_e_util_config_stack_func('load'),
+    CONFIG_SYNTAX        => _format_e_util_config_func('syntax'),
+    CONFIG_USAGE         => _format_e_util_config_func('usage'),
+    CONFIG_VAR_UNDEF     => _format_e_util_config_func('var_undef'),
+    IO                   => _format_e_func('e_util_io'),
+    LOCATOR_AS_INVARIANT => _format_e_util_locator_func(''),
+    LOCATOR_BROWSER_URL  => _format_e_util_locator_func('_browser_url'),
+    LOCATOR_FIND         => _format_e_util_locator_func(''),
+    LOCATOR_KEYWORD_LOC  => _format_e_util_locator_func('_keyword_loc'),
+    LOCATOR_KEYWORD_REV  => _format_e_util_locator_func('_keyword_rev'),
+    LOCATOR_READER       => _format_e_util_locator_func('_reader'),
+    LOCATOR_TYPE         => _format_e_util_locator_func('_type'),
+    SHELL_OPEN3          => _format_e_util_shell_func('_open3'),
+    SHELL_OS             => _format_e_util_shell_func('_os'),
+    SHELL_SIGNAL         => _format_e_util_shell_func('_signal'),
+    SHELL_WHICH          => _format_e_util_shell_func('_which'),
+);
+# Alias
+our $R;
+# Named diagnostic strings
+our %S = (
+    # ERROR DIAGNOSTICS
+    e_class                      => '%s: %s => %s: internal error at %s:%d',
+    e_cli_app                    => '%s: unknown command,'
+                                    . ' type \'%s help\' for help',
+    e_cli_opt                    => '%s: incorrect usage,'
+                                    . ' type \'%s help %1$s\' for help',
+    e_sys_build_source           => '%s: source does not exist',
+    e_sys_build_source_syn       => '%s(%d): syntax error',
+    e_sys_build_target           => '%s: target not found after an update:',
+    e_sys_build_target_1         => '%s: expect target file',
+    e_sys_build_target_bad       => '%s: don\'t know how to build specified'
+                                    . ' target',
+    e_sys_build_target_cyclic    => '%s: target depends on itself',
+    e_sys_build_target_dep       => '%s: bad or missing dependency (type=%s)',
+    e_sys_build_target_dup       => '%s: same target from [%s]',
+    e_sys_build_target_stack     => '    required by: %s',
+    e_sys_cache_load             => '%s: cannot retrieve cache',
+    e_sys_cache_type             => '%s: unexpected cache type',
+    e_sys_cm_already_exist       => '%s: already exists',
+    e_sys_cm_arg                 => '%s: bad argument',
+    e_sys_cm_branch_name         => '%s: invalid branch name',
+    e_sys_cm_branch_source       => '%s: invalid branch source',
+    e_sys_cm_checkout            => '%s: is already a working copy of %s',
+    e_sys_cm_log_edit_delimiter  => '%sthe above log delimiter is altered',
+    e_sys_cm_log_edit_null       => 'log message is empty',
+    e_sys_cm_opt_arg             => '%s=%s: bad option argument',
+    e_sys_cm_project_name        => '%s: invalid project name',
+    e_sys_cm_repository          => '%s: invalid repository',
+    e_sys_config_conflict        => '%s: cannot modify, value is inherited',
+    e_sys_config_inherit         => '%s: cannot inherit from an incomplete make',
+    e_sys_config_modifier        => '%s: incorrect modifier in declaration',
+    e_sys_config_ns              => '%s: incorrect name-space declaration',
+    e_sys_config_ns_value        => '%s: mismatch between name-space and value',
+    e_sys_config_unknown         => '%s: unknown declaration',
+    e_sys_config_value           => '%s: incorrect value in declaration',
+    e_sys_copy                   => '%s -> %s: copy failed',
+    e_sys_dest_clean             => '%s: cannot remove',
+    e_sys_dest_create            => '%s: cannot create',
+    e_sys_dest_locked            => '%s: lock exists at the destination',
+    e_sys_export_items_src       => 'source location not specified',
+    e_sys_extract_loc_base       => '%s: cannot determine base location',
+    e_sys_extract_merge          => '%s: merge results in conflict',
+    e_sys_extract_merge_output   => '    merge output: %s',
+    e_sys_extract_merge_source   => '    source from location %2d: %s',
+    e_sys_extract_merge_source_0 => '(none)',
+    e_sys_extract_merge_source_x => '!!! source from location %2d: %s',
+    e_sys_extract_ns             => '%s: name-spaces declared but not used',
+    e_sys_mirror                 => '%s <- %s: mirror failed',
+    e_sys_mirror_null            => 'mirror target not specified',
+    e_sys_mirror_source          => '%s: cannot mirror this step',
+    e_sys_mirror_target          => '%s: cannot create mirror target',
+    e_sys_make                   => '%s: step is not implemented',
+    e_sys_make_arg               => 'arg %d (%s): invalid config declaration',
+    e_sys_make_cfg               => 'no configuration specified or found',
+    e_sys_make_arg_more          => 'did you mean "%s"?',
+    e_sys_make_prop_ns           => '%s.prop{%s}[%s] = %s: bad name-space',
+    e_sys_make_prop_value        => '%s.prop{%s}[%s] = %s: bad value',
+    e_sys_shell                  => '%s # rc=%d',
+    e_unknown                    => 'command failed',
+    e_util_class_loader          => '%s: required package cannot be loaded',
+    e_util_config                => '%s:%d: %s',
+    e_util_config_eof            => 'continuation at eof',
+    e_util_config_syntax         => 'syntax error',
+    e_util_config_usage          => 'incorrect usage',
+    e_util_config_var_undef      => 'reference to undefined variable',
+    e_util_config_stack_cyclic   => '%s: cannot load config file,'
+                                    . ' cyclic dependency',
+    e_util_config_stack_load     => '%s: cannot load config file',
+    e_util_io                    => '%s: I/O error',
+    e_util_locator               => '%s: not found',
+    e_util_locator_browser_url   => '%s: cannot determine browser URL',
+    e_util_locator_keyword_loc   => '%s: location keyword not defined',
+    e_util_locator_keyword_rev   => '%s: revision keyword not defined',
+    e_util_locator_reader        => '%s: cannot be read',
+    e_util_locator_type          => '%s: unsupported type of location',
+    e_util_shell_open3           => '%s: command failed to invoke',
+    e_util_shell_os              => '%s: command failed due to OS error',
+    e_util_shell_signal          => '%s: command received a signal',
+    e_util_shell_which           => '%s: command not found',
+
+    # NORMAL DIAGNOSTICS
+    cm_abort_null                => 'command will result in no change',
+    cm_abort_user                => 'by user',
+    cm_branch_create_source      => 'Source: %s (%d)',
+    cm_branch_list               => '%s: %d match(es)',
+    cm_commit_message            => 'Change summary:' . "\n"
+                                    . '-' x 80 . "\n" . '%s'
+                                    . '-' x 80 . "\n"
+                                    . 'Commit message is as follows:' . "\n"
+                                    . '-' x 80 . "\n" . '%s%s'
+                                    . '-' x 80,
+    cm_conflict_text             => '%s: in text conflict.',
+    cm_conflict_text_skip        => '%s: skipped binary file in text conflict.',
+    cm_conflict_tree             => '%s: in tree conflict.',
+    cm_conflict_tree_skip        => '%s: skipped unhandled tree conflict.',
+    cm_conflict_tree_time_warn   => '%s: looking for a rename operation,'
+                                    . ' please wait...',
+    cm_create_target             => 'Created: %s',
+    cm_log_edit                  => '%s: starting commit message editor...',
+    config_open                  => 'config-file=%s%s',
+    config_var_undef             => '%s:%d: %s: variable not defined',
+    event                        => '%s: event raised',
+    export_item_create           => 'A %s@%s -> %s',
+    export_item_delete           => 'D %s@%s -> %s',
+    fcm_version                  => '%s',
+    keyword_loc                  => 'location[%s] = %s',
+    keyword_loc_primary          => 'location{primary}[%s] = %s',
+    keyword_rev                  => 'revision[%s:%s] = %s',
+    make_build_shell_out_1       => '[>>&1] ',
+    make_build_shell_out_2       => '[>>&2] ',
+    make_build_source_analyse    => 'analyse %4.1f %s',
+    make_build_source_analyse_1  => '             -> (%9s) %s',
+    make_build_source_summary    => 'sources: total=%d, analysed=%d,'
+                                    . ' elapsed-time=%.1fs, total-time=%.1fs',
+    make_build_target_done_0     => '%-9s ---- %s %-20s <- %s',
+    make_build_target_done_1     => '%-9s %4.1f %s %-20s <- %s',
+    make_build_target_from_ns    => 'source->target %s -> (%s) %s/ %s',
+    make_build_target_select     => 'required-target: %-9s %-7s %s',
+    make_build_target_select_t   => 'target-tree-analysis: elapsed-time=%.1fs',
+    make_build_target_stack      => 'target %s%s%s',
+    make_build_target_stack_more => ' (n-deps=%d)',
+    make_build_target_missing_dep=> '%-30s: ignore-missing-dep: (%3$9s) %2$s',
+    make_build_target_sum        => 'TOTAL     targets:'
+                                    . ' modified=%d, unchanged=%d, failed=%d,'
+                                    . ' elapsed-time=%.1fs',
+    make_build_target_task_sum   => '%-9s targets:'
+                                    . ' modified=%d, unchanged=%d, failed=%d,'
+                                    . ' total-time=%.1fs',
+    make_build_targets_fail_0    => '! %-20s: depends on failed target: %s',
+    make_build_targets_fail_1    => '! %-20s: update task failed',
+    make_dest                    => 'dest=%s',
+    make_dest_use                => 'use=%s',
+    make_extract_project_tree    => 'location %5s:%2d: %s%s',
+    make_extract_project_tree_1  => ' (%s)',
+    make_extract_runner_summary  => '%s: n-tasks=%d,'
+                                    . ' elapsed-time=%.1fs, total-time=%.1fs',
+    make_extract_target          => '%s%s %5s:%-6s %s',
+    make_extract_target_base_yes => '0',
+    make_extract_target_base_no  => '-',
+    make_extract_symlink         => 'symlink ignored: %s',
+    make_extract_target_summary_d=> '  dest: %4d [%1s %s]',
+    make_extract_target_summary_s=> 'source: %4d [%1s %s]',
+    make_mirror                  => '%s <- %s',
+    make_mode                    => 'mode=%s',
+    make_mode_new                => 'new',
+    make_mode_incr               => 'incremental',
+    shell                        => 'shell(%d %4.1f) %s',
+    task_workers_destroy         => '%s worker processes destroyed',
+    task_workers_init            => '%s worker processes started',
+    timer_done                   => '%-20s# %.1fs',
+    timer_init                   => '%-20s# %s',
+);
+# Symbols/Descriptions for a make extract target status.
+my %MAKE_EXTRACT_TARGET_SYM_OF = (
+    ST_ADDED     => ['A', 'added'                        ],
+    ST_DELETED   => ['D', 'deleted'                      ],
+    ST_MODIFIED  => ['M', 'modified'                     ],
+    ST_O_ADDED   => ['a', 'added, overriding inherited'  ],
+    ST_O_DELETED => ['d', 'deleted, overriding inherited'],
+    ST_UNCHANGED => ['U', 'unchanged'                    ],
+    ST_UNKNOWN   => ['?', 'unknown'                      ],
+);
+# Symbols/Descriptions for a make source status.
+my %MAKE_EXTRACT_SOURCE_SYM_OF = (
+    ST_ADDED     => ['A', 'added by a diff source tree'     ],
+    ST_DELETED   => ['D', 'deleted by a diff source tree'   ],
+    ST_MERGED    => ['G', 'merged from 2+ diff source trees'],
+    ST_MODIFIED  => ['M', 'modified by a diff source tree'  ],
+    ST_UNCHANGED => ['U', 'from base'                       ],
+    ST_UNKNOWN   => ['?', 'unknown'                         ],
+);
+
+# Creates the class.
+__PACKAGE__->class({util => '&'}, {action_of => {main => \&_main}});
+
+sub _main {
+    my ($attrib_ref, $event) = @_;
+    local($R) = $attrib_ref->{util}->util_of_report();
+    if (!exists($ACTION_OF{$event->get_code()})) {
+        return $R->report(
+            {level => $R->HIGH}, sprintf($S{event}, $event->get_code()),
+        );
+    }
+    $ACTION_OF{$event->get_code()}->(@{$event->get_args()});
+}
+
+# Formats a stack of configuration files.
+sub _format_config_stack {
+    my ($config_stack_ref) = @_;
+    my @config_stack = @{$config_stack_ref};
+    my $indent_char = q{};
+    my $return = q{};
+    my $i = 0;
+    for my $item (@config_stack) {
+        my ($locator, $line) = @{$item};
+        my $indent = q{ - } x $i++;
+        $return .= sprintf(
+            $S{'config_open'} . "\n",
+            $indent, ($locator->get_value() . ($line ? ':' . $line : q{})),
+        );
+    }
+    return $return;
+}
+
+# Formats a CM exception.
+sub _format_e_cm {
+    my ($e) = @_;
+    sprintf($E_CM_FORMAT_FOR{$e->get_code()}, $e->get_targets());
+}
+
+# Formats a class exception.
+sub _format_e_class {
+    my ($e) = @_;
+    sprintf(
+        $S{e_class},
+        $e->get_package(),
+        $e->get_key(),
+        (defined($e->get_value()) ? $e->get_value() : 'undef'),
+        @{$e->get_caller()}[1, 2],
+    );
+}
+
+# Formats a CLI exception.
+sub _format_e_cli {
+    my ($e) = @_;
+    my $format
+        = $e->get_code() eq $e->APP ? $S{e_cli_app}
+        :                             $S{e_cli_opt}
+        ;
+    sprintf($format, $e->get_ctx()->[0], basename($0));
+}
+
+# Formats a system exception.
+sub _format_e_sys {
+    my ($e) = @_;
+    if (exists($E_SYS_FORMATTER_FOR{$e->get_code()})) {
+        return $E_SYS_FORMATTER_FOR{$e->get_code()}->($e);
+    }
+    $e;
+}
+
+# Formats a system exception - CONFIG_*.
+sub _format_e_sys_config_func {
+    my ($suffix) = @_;
+    my $key = 'e_sys_config_' . $suffix;
+    sub {
+        my ($e) = @_;
+        my @ctx_list
+            = ref($e->get_ctx()) eq 'ARRAY' ? @{$e->get_ctx()}
+            :                                 ($e->get_ctx())
+            ;
+        map {(
+            sprintf($S{$key}, $_->as_string()),
+            _format_config_stack($_->get_stack()),
+        )} @ctx_list;
+    }
+}
+
+# Formats a system exception - BUILD_TARGET.
+sub _format_e_sys_build_target {
+    my ($e) = @_;
+    my $ctx = $e->get_ctx();
+    (   sprintf($S{e_sys_build_target}, $ctx->get_key()),
+        sprintf($S{e_sys_build_target_1}, $ctx->get_path()),
+    );
+}
+
+# Formats a system exception - BUILD_TARGET_CYC.
+sub _format_e_sys_build_target_cyc {
+    my ($e) = @_;
+    my @messages;
+    while (my ($key, $hash_ref) = each(%{$e->get_ctx()})) {
+        my ($head, @stack) = reverse(@{$hash_ref->{'keys'}});
+        push(@messages, sprintf($S{e_sys_build_target_cyclic}, $head));
+        push(@messages, map {sprintf($S{e_sys_build_target_stack}, $_)} @stack);
+    }
+    @messages;
+}
+
+# Formats a system exception - BUILD_TARGET_DEP.
+sub _format_e_sys_build_target_dep {
+    my ($e) = @_;
+    my @messages;
+    while (my ($key, $hash_ref) = each(%{$e->get_ctx()})) {
+        my ($head, @stack) = reverse(@{$hash_ref->{'keys'}});
+        for (@{$hash_ref->{'values'}}) { # [$dep_key, $dep_type]
+            my ($dep_name, $dep_type, $dep_remark) = @{$_};
+            if ($dep_remark) {
+                $dep_type = $dep_remark . '.' . $dep_type;
+            }
+            push(
+                @messages,
+                sprintf($S{e_sys_build_target_dep}, $dep_name, $dep_type),
+            );
+        }
+        push(@messages, map {sprintf($S{e_sys_build_target_stack}, $_)} @stack);
+    }
+    @messages;
+}
+
+# Formats a system exception - BUILD_TARGET_DUP.
+sub _format_e_sys_build_target_dup {
+    my ($e) = @_;
+    my @messages;
+    while (my ($key, $hash_ref) = each(%{$e->get_ctx()})) {
+        my ($head, @stack) = reverse(@{$hash_ref->{'keys'}});
+        my @ns_list = @{$hash_ref->{'values'}};
+        my $ns = _format_shell_words({'delimiter' => q{, }}, sort(@ns_list));
+        push(@messages, sprintf($S{e_sys_build_target_dup}, $key, $ns));
+        push(@messages, map {sprintf($S{e_sys_build_target_stack}, $_)} @stack);
+    }
+    @messages;
+}
+
+# Formats a system exception - EXTRACT_MERGE.
+sub _format_e_sys_extract_merge {
+    my ($e) = @_;
+    my $target = $e->get_ctx()->{'target'};
+    my $source0 = $target->get_source_of()->{0};
+    my $location_of_0 = $S{e_sys_extract_merge_source_0};
+    if ($source0->get_locator()) {
+        $location_of_0 = $source0->get_locator()->get_value();
+    }
+    my $key = $e->get_ctx()->{'key'};
+    my $location_of_key
+        = $target->get_source_of()->{$key}->get_locator()->get_value();
+    (   sprintf($S{e_sys_extract_merge}, $target->get_ns()),
+        sprintf($S{e_sys_extract_merge_output}, $e->get_ctx()->{'output'}),
+        sprintf($S{e_sys_extract_merge_source}, 0, $location_of_0),
+        (   map {sprintf(
+                $S{e_sys_extract_merge_source},
+                $_,
+                $target->get_source_of()->{$_}->get_locator()->get_value(),
+            )} @{$e->get_ctx()->{'keys_done'}}
+        ),
+        sprintf($S{e_sys_extract_merge_source_x}, $key, $location_of_key),
+        (   map {sprintf(
+                $S{e_sys_extract_merge_source},
+                $_,
+                $target->get_source_of()->{$_}->get_locator()->get_value(),
+            )} @{$e->get_ctx()->{'keys_left'}}
+        ),
+    );
+}
+
+# Formats a system exception - MIRROR.
+sub _format_e_sys_mirror {
+    my ($e) = @_;
+    my ($target, @sources) = @{$e->get_ctx()};
+    sprintf($S{e_sys_mirror}, $target, _format_shell_words(@sources));
+}
+
+# Formats a system exception - MAKE_ARG
+sub _format_e_sys_make_arg {
+    my ($e) = @_;
+    my @return;
+    for (@{$e->get_ctx()}) {
+        my ($arg_index, $arg_value) = @{$_};
+        push(@return, sprintf($S{e_sys_make_arg}, $arg_index, $arg_value));
+        my $advice
+            = $arg_value =~ qr{\.cfg\z}msx ? '-f ' . $arg_value
+            : $arg_value eq '0'            ? '-q'
+            : $arg_value eq '2'            ? '-v'
+            : $arg_value eq '3'            ? '-v -v'
+            :                                undef;
+        if (defined($advice)) {
+            push(@return, sprintf($S{e_sys_make_arg_more}, $advice));
+        }
+    }
+    return @return;
+}
+
+# Formats a system exception - MAKE_PROP_NS
+sub _format_e_sys_make_prop_ns {
+    my ($e) = @_;
+    map {sprintf($S{e_sys_make_prop_ns}, @{$_})} @{$e->get_ctx()};
+}
+
+# Formats a system exception - MAKE_PROP_VALUE
+sub _format_e_sys_make_prop_value {
+    my ($e) = @_;
+    map {sprintf($S{e_sys_make_prop_value}, @{$_})} @{$e->get_ctx()};
+}
+
+# Formats a system exception - SHELL.
+sub _format_e_sys_shell {
+    my ($e) = @_;
+    my $command = _format_shell_words(@{$e->get_ctx()->{command_list}});
+    my %value_of = (out => q{}, rc => '?', %{$e->get_ctx()});
+    return (
+        #(map {sprintf($S{e_sys_shell_err}, $_)} split("\n", $value_of{err})),
+        #(map {sprintf($S{e_sys_shell_out}, $_)} split("\n", $value_of{out})),
+        sprintf($S{e_sys_shell}, $command, $value_of{rc}),
+    );
+}
+
+# Formats a util exception.
+sub _format_e_util {
+    my ($e) = @_;
+    if (exists($E_UTIL_FORMATTER_FOR{$e->get_code()})) {
+        return $E_UTIL_FORMATTER_FOR{$e->get_code()}->($e);
+    }
+    $e;
+}
+
+# Returns a CODE to format a util config-reader exception.
+sub _format_e_util_config_func {
+    my ($id) = @_;
+    sub {
+        my ($e) = @_;
+        (   sprintf(
+                $S{'e_util_config'},
+                $e->get_ctx()->get_stack()->[-1][0]->get_value(),
+                $e->get_ctx()->get_stack()->[-1][1],
+                $S{'e_util_config_' . $id},
+            ),
+            $e->get_ctx()->as_string(),
+        );
+    };
+}
+
+# Returns a CODE to format a util config-reader exception where the ctx is the
+# locator stack.
+sub _format_e_util_config_stack_func {
+    my ($id) = @_;
+    sub {
+        my ($e) = @_;
+        my @return = (
+            _format_config_stack($e->get_ctx()),
+            sprintf(
+                $S{'e_util_config_stack_' . $id},
+                $e->get_ctx()->[-1][0]->get_value(),
+            ),
+        );
+        @return;
+    };
+}
+
+# Formats a locator exception.
+sub _format_e_util_locator_func {
+    my ($id) = @_;
+    sub {sprintf($S{'e_util_locator' . $id}, $_[0]->get_ctx()->get_value())};
+}
+
+# Formats a shell exception.
+sub _format_e_util_shell_func {
+    my ($id) = @_;
+    sub {
+        sprintf(
+            $S{'e_util_shell' . $id},
+            _format_shell_words(@{$_[0]->get_ctx()})
+        );
+    };
+}
+
+# Returns a CODE to format a exception context in a single/multi line.
+sub _format_e_func {
+    my ($id, $is_multi_line) = @_;
+    sub {
+        my ($e) = @_;
+        my @args;
+        if (defined($e->get_ctx())) {
+            @args = (ref($e->get_ctx()) || ref($e->get_ctx()) eq 'ARRAY')
+                ? @{$e->get_ctx()} : $e->get_ctx();
+        }
+        $is_multi_line
+            ? (map {sprintf($S{$id}, $_)} @args) : (sprintf($S{$id}, @args));
+    };
+}
+
+# Formats a simple reference.
+sub _format_ref {
+    my ($hash_ref) = @_;
+    local($Data::Dumper::Terse) = 1;
+    local($Data::Dumper::Indent) = 0;
+    Dumper($hash_ref);
+}
+
+# Formats words into a string suitable for used in a shell command.
+sub _format_shell_words {
+    my %option = ('delimiter' => q{ });
+    if (@_ && ref($_[0]) && ref($_[0]) eq 'HASH') {
+        %option = (%option, %{$_[0]});
+        shift();
+    }
+    my (@words) = @_;
+    join(
+        $option{'delimiter'},
+        map {my $s = $_; $s =~ s{(['"\s])}{\\$1}gmsx; $s} @words,
+    );
+}
+
+# Notification on abort of a CM command.
+sub _event_cm_abort {
+    my ($id) = @_;
+    $R->report(
+        {level => $R->QUIET, prefix => $R->PREFIX_QUIT, type => $R->TYPE_ERR},
+        $S{'cm_abort_' . $id},
+    );
+}
+
+# Notification on a project branch listing.
+sub _event_cm_branch_list {
+    my ($project, @branches) = @_;
+    $R->report(sprintf($S{'cm_branch_list'}, $project, scalar(@branches)));
+    for my $branch (@branches) {
+        $R->report({level => $R->QUIET, prefix => $R->PREFIX_NULL}, $branch);
+    }
+}
+
+# Notification on a log message to be used by a commit.
+sub _event_cm_commit_message {
+    my ($ctx) = @_;
+    $R->report(
+        {prefix => $R->PREFIX_NULL},
+        sprintf(
+            $S{'cm_commit_message'},
+            $ctx->get_info_part(), $ctx->get_user_part(), $ctx->get_auto_part(),
+        ),
+    );
+}
+
+# Notification on a skipped file in text conflict.
+sub _event_cm_conflict_text_skip {
+    my ($ctx) = @_;
+    $R->report({type => $R->TYPE_ERR}, sprintf($S{'cm_conflict_text_skip'}, $ctx));
+}
+
+# Notification for an unhandled type of tree conflict.
+sub _event_cm_conflict_tree_skip {
+    my ($ctx) = @_;
+    $R->report({type => $R->TYPE_ERR}, sprintf($S{'cm_conflict_tree_skip'}, $ctx));
+}
+
+# Warning that the tree conflict operation search may take some time.
+sub _event_cm_conflict_tree_time_warn {
+    my ($ctx) = @_;
+    $R->report({type => $R->TYPE_ERR}, sprintf($S{'cm_conflict_tree_time_warn'}, $ctx));
+}
+
+# Notification when a config entry is found.
+sub _event_config_entry {
+    my ($entry, $in_fcm1) = @_;
+    $R->report(
+        {level => $R->QUIET, prefix => $R->PREFIX_NULL},
+        $entry->as_string($in_fcm1),
+    );
+}
+
+# Notification for a configuration file open.
+sub _event_config_open {
+    my ($config_stack_ref, $level) = @_;
+    $R->report(
+        {level => (defined($level) ? $level : $R->DEBUG)},
+        sub {
+            my $value = $config_stack_ref->[-1][0]->get_value();
+            my $indent = q{ - } x (scalar(@{$config_stack_ref}) - 1);
+            sprintf($S{config_open}, $indent, $value);
+        },
+    );
+}
+
+# Notification when a config variable is undefined.
+sub _event_config_var_undef {
+    my ($entry, $symbol) = @_;
+    $R->report(
+        {type => $R->TYPE_ERR},
+        sprintf(
+            $S{'config_var_undef'},
+            $entry->get_stack()->[-1][0]->get_value(),
+            $entry->get_stack()->[-1][1],
+            $symbol,
+        ),
+    );
+}
+
+# Notification for an exception.
+sub _event_e {
+    my ($exception) = @_;
+    my @e_stack = ($exception);
+    while ( blessed($e_stack[-1])
+        &&  $e_stack[-1]->can('get_exception')
+        &&  (my $e = $e_stack[-1]->get_exception())
+    ) {
+        push(@e_stack, $e);
+    }
+    while (my $e = shift(@e_stack)) {
+        my $formatter;
+        if (blessed($e)) {
+            my $item = first {$e->isa($_->[0])} @E_FORMATTERS;
+            if ($item) {
+                $formatter = $item->[1];
+            }
+            if (!$formatter && $e->can('as_string')) {
+                $formatter = sub {$e->as_string()};
+            }
+        }
+        elsif (ref($e)) {
+            $formatter = \&_format_ref;
+        }
+        elsif ($e eq "\n") {
+            chomp($e);
+        }
+        $R->report(
+            {level => $R->FAIL, type => $R->TYPE_ERR},
+            (defined($formatter) ? $formatter->($e) : $e),
+        );
+    }
+    1;
+}
+
+# Notification when a keyword entry is found.
+sub _event_keyword_entry {
+    my ($entry) = @_;
+    if ($entry->is_implied()) {
+        return;
+    }
+    my @implied_entry_list
+        = values(%{$entry->get_ctx_of_implied()->get_entry_by_key()});
+    if (@implied_entry_list) {
+        $R->report(
+            {level => $R->QUIET, prefix => $R->PREFIX_NULL},
+            sprintf(
+                $S{keyword_loc_primary},
+                $entry->get_key(),
+                $entry->get_value(),
+            ),
+        );
+        for my $implied_entry (
+            sort {$a->get_key() cmp $b->get_key()} @implied_entry_list
+        ) {
+            $R->report(
+                {level => $R->MEDIUM, prefix => $R->PREFIX_NULL},
+                sprintf(
+                    $S{keyword_loc},
+                    $implied_entry->get_key(),
+                    $implied_entry->get_value(),
+                ),
+            );
+        }
+    }
+    else {
+        $R->report(
+            {level => $R->QUIET, prefix => $R->PREFIX_NULL},
+            sprintf($S{keyword_loc}, $entry->get_key(), $entry->get_value()),
+        );
+    }
+    my @revision_entry_list
+        = values(%{$entry->get_ctx_of_rev()->get_entry_by_key()});
+    for my $revision_entry (
+        sort {$a->get_key() cmp $b->get_key()} @revision_entry_list
+    ) {
+        $R->report(
+            {level => $R->QUIET, prefix => $R->PREFIX_NULL},
+            sprintf(
+                $S{keyword_rev},
+                $entry->get_key(),
+                $revision_entry->get_key(),
+                $revision_entry->get_value(),
+            ),
+        );
+    }
+    1;
+}
+
+# Notification of the output from a command.
+sub _event_out {
+    my ($out, $err) = @_;
+    my %option = (delimiter => q{}, prefix => $R->PREFIX_NULL);
+    if ($err) {
+        $R->report({level => $R->WARN, type => $R->TYPE_ERR, %option}, $err);
+    }
+    if ($out) {
+        $R->report({level => $R->QUIET, %option}, $out);
+    }
+}
+
+# Notification of the output from a command invoked by make/build.
+sub _event_make_build_shell_out {
+    my ($out, $err) = @_;
+    if ($err) {
+        $R->report(
+            {   level => $R->HIGH,
+                prefix => $S{'make_build_shell_out_2'},
+                type => $R->TYPE_ERR,
+            },
+            $err,
+        );
+    }
+    if ($out) {
+        $R->report(
+            {level => $R->HIGH, prefix => $S{'make_build_shell_out_1'}},
+            $out,
+        );
+    }
+}
+
+# Notification when a make destination is being set up.
+sub _event_make_dest {
+    my ($m_ctx, $authority) = @_;
+    $R->report(sprintf($S{make_dest}, $authority . ':' . $m_ctx->get_dest()));
+    $R->report(sprintf(
+        $S{make_mode},
+        $S{'make_mode_' . ($m_ctx->get_prev_ctx() ? 'incr' : 'new')},
+    ));
+    for my $i_ctx (@{$m_ctx->get_inherit_ctx_list()}) {
+        $R->report(sprintf($S{make_dest_use}, $i_ctx->get_dest()));
+    }
+}
+
+# Notification when performing a mirroring.
+sub _event_make_mirror {
+    my ($target, @sources) = @_;
+    $R->report(sprintf($S{make_mirror}, $target, _format_shell_words(@sources)));
+}
+
+# Notification when the multi-thread task runner initiates its workers.
+sub _event_task_workers {
+    my ($id, $n_workers) = @_;
+    my $key = 'task_workers_' . $id;
+    if (exists($S{$key})) {
+        $R->report({level => $R->HIGH}, sprintf($S{$key}, $n_workers));
+    }
+}
+
+# Notification when invoking a shell command.
+sub _event_shell {
+    my ($names_ref, $rc, $elapsed) = @_;
+    my $name = _format_shell_words(@{$names_ref});
+    my $message = sprintf($S{shell}, $rc, $elapsed, $name);
+    $R->report({level => $R->HIGH}, $message);
+}
+
+# Notification when a timer starts/ends.
+sub _event_timer {
+    my ($name, $start, $elapsed, $failed) = @_;
+    my $message;
+    if (defined($elapsed)) {
+        $message = sprintf($S{timer_done}, $name, $elapsed);
+    }
+    else {
+        my $format = '%Y-%m-%dT%H:%M:%SZ';
+        $message = sprintf(
+            $S{timer_init}, $name, strftime($format, gmtime($start)));
+    }
+    my $prefix
+        = $failed           ? $R->PREFIX_FAIL
+        : defined($elapsed) ? $R->PREFIX_DONE
+        :                     $R->PREFIX_INIT
+        ;
+    $R->report({prefix => $prefix}, $message);
+}
+
+# Notification when make-build analyse a source.
+sub _event_make_build_source_analyse {
+    my ($source, $elapse) = @_;
+    $R->report(
+        {level => $R->MEDIUM},
+        sprintf($S{make_build_source_analyse}, $elapse, $source->get_ns()),
+    );
+    for my $dep (@{$source->get_deps()}) {
+        $R->report(
+            {level => $R->HIGH},
+            sprintf($S{make_build_source_analyse_1}, reverse(@{$dep})),
+        );
+    }
+}
+
+# Notification when make-build has updated or does not need to update a target.
+sub _event_make_build_target_done {
+    my ($target, $elapsed_time) = @_;
+    my $tmpl = defined($elapsed_time)
+        ? $S{make_build_target_done_1} : $S{make_build_target_done_0};
+    $R->report(
+        {level => $R->MEDIUM},
+        sprintf(
+            $tmpl,
+            $target->get_task(),
+            (defined($elapsed_time) ? ($elapsed_time) : ()),
+            $MAKE_EXTRACT_TARGET_SYM_OF{$target->get_status()}[0],
+            $target->get_key(),
+            $target->get_ns(),
+        ),
+    );
+}
+
+# Notification when make-build a target fails to update or is failed by
+# dependencies.
+sub _event_make_build_target_fail {
+    my ($target, $elapsed_time) = @_;
+    my $tmpl = defined($elapsed_time)
+        ? $S{make_build_target_done_1} : $S{make_build_target_done_0};
+    $R->report(
+        {level => $R->FAIL, type => $R->TYPE_ERR},
+        sprintf(
+            $tmpl,
+            $target->get_task(),
+            (defined($elapsed_time) ? ($elapsed_time) : ()),
+            '!',
+            $target->get_key(),
+            $target->get_ns(),
+        ),
+    );
+}
+
+# Notification when make-build ignores a missing dependency from a target.
+sub _event_make_build_target_missing_dep {
+    $R->report(
+        {level => $R->WARN, type => $R->TYPE_ERR},
+        sprintf($S{make_build_target_missing_dep}, @_),
+    );
+}
+
+# Notification when make-build generates a target from source.
+sub _event_make_build_target_from_ns {
+    $R->report(
+        {level => $R->HIGH},
+        sprintf($S{make_build_target_from_ns}, @_),
+    );
+}
+
+# Notification when make-build chooses a list of targets to build.
+sub _event_make_build_target_select {
+    my ($target_set_ref) = @_;
+    $R->report(
+        {level => $R->HIGH},
+        sub {
+            map {
+                my $key = $_;
+                my $target = $target_set_ref->{$key};
+                sprintf(
+                    $S{make_build_target_select},
+                    $target->get_task(), $target->get_category(), $key,
+                );
+            }
+            sort keys(%{$target_set_ref});
+        },
+    );
+}
+
+# Notification when make-build checks a target for cyclic dependency.
+sub _event_make_build_target_stack {
+    my ($key, $rank, $n_deps) = @_;
+    $R->report(
+        {level => $R->HIGH},
+        sub {
+            my $indent = q{ - } x $rank;
+            my $more
+                = $n_deps ? sprintf($S{make_build_target_stack_more}, $n_deps)
+                :           q{}
+                ;
+            sprintf($S{make_build_target_stack}, $indent, $key, $more),
+        },
+    );
+}
+
+# Notification when make-build fails to update some targets.
+sub _event_make_build_targets_fail {
+    my ($targets_ref) = @_;
+    $R->report(
+        {type => $R->TYPE_ERR, level => $R->FAIL},
+        (map {
+            my $target = $_;
+            my @failed_by = @{$target->get_failed_by()};
+            my @lines;
+            if (grep {$_ eq $target->get_key()} @failed_by) {
+                push(
+                    @lines,
+                    sprintf($S{make_build_targets_fail_1}, $target->get_key()),
+                );
+            }
+            for my $failed_by_key (grep {$_ ne $target->get_key()} @failed_by) {
+                push(
+                    @lines,
+                    sprintf(
+                        $S{make_build_targets_fail_0},
+                        $target->get_key(),
+                        $failed_by_key,
+                    ),
+                );
+            }
+            @lines;
+        } sort {$a->get_key() cmp $b->get_key()} @{$targets_ref}),
+    );
+}
+
+# Notification when make-extract finished gathering information for its project
+# source trees.
+sub _event_make_extract_project_tree {
+    my %locators_of = %{$_[0]};
+    for my $ns (sort(keys(%locators_of))) {
+        my $i = 0;
+        for my $locator (@{$locators_of{$ns}}) {
+            my $format_last_mod_rev = q{};
+            if ($locator->get_last_mod_rev()) {
+                $format_last_mod_rev = sprintf(
+                    $S{'make_extract_project_tree_1'},
+                    $locator->get_last_mod_rev(),
+                );
+            }
+            $R->report(
+                sprintf(
+                    $S{'make_extract_project_tree'},
+                    $ns, $i++, $locator->get_value(), $format_last_mod_rev
+                ),
+            );
+        }
+    }
+}
+
+# Notification when make-extract used the task runner to perform tasks.
+sub _event_make_extract_runner_summary {
+    $R->report(
+        {level => $R->HIGH},
+        sprintf($S{'make_extract_runner_summary'}, @_),
+    );
+}
+
+# Notification when make-extract completes updating its targets.
+sub _event_make_extract_target_summary {
+    my ($basket) = @_;
+    for (
+        [   'status',
+            'make_extract_target_summary_d',
+            \%MAKE_EXTRACT_TARGET_SYM_OF,
+        ],
+        [   'status_of_source',
+            'make_extract_target_summary_s',
+            \%MAKE_EXTRACT_SOURCE_SYM_OF,
+        ],
+    ) {
+        my ($name, $format_name, $sym_hash_ref) = @{$_};
+        for my $key (sort keys(%{$basket->{$name}})) {
+            $R->report(sprintf(
+                $S{$format_name},
+                $basket->{$name}{$key},
+                $sym_hash_ref->{$key}[0],
+                $sym_hash_ref->{$key}[1],
+            ));
+        }
+    }
+}
+
+# Notification when make-extract ignores a symlink.
+sub _event_make_extract_symlink {
+    my ($source) = @_;
+    $R->report(
+        {type => $R->TYPE_ERR},
+        sprintf($S{make_extract_symlink}, $source->get_locator()->get_value()),
+    );
+}
+
+# Notification when make-extract updates a target.
+sub _event_make_extract_target {
+    my ($target) = @_;
+    if (!exists($MAKE_EXTRACT_TARGET_SYM_OF{$target->get_status()})) {
+        return;
+    }
+    $R->report(
+        {level => $R->MEDIUM},
+        sub {
+            my ($verbosity) = @_;
+            if ($verbosity < $R->DEBUG && $target->is_unchanged()) {
+                return;
+            }
+            my ($ns, $path) = split(qr{/}msx, $target->get_ns(), 2);
+            my %source_of = %{$target->get_source_of()};
+            my $base = delete($source_of{0});
+            my @diff_keys
+                = grep {!$source_of{$_}->is_unchanged()} keys(%source_of);
+            my @st_missing_diff_keys
+                = grep {$source_of{$_}->is_missing()} @diff_keys;
+            if (@st_missing_diff_keys) {
+                @diff_keys = @st_missing_diff_keys;
+            }
+            sprintf(
+                $S{make_extract_target},
+                $MAKE_EXTRACT_TARGET_SYM_OF{$target->get_status()}[0],
+                $MAKE_EXTRACT_SOURCE_SYM_OF{$target->get_status_of_source()}[0],
+                $ns,
+                join(
+                q{,},
+                    (   defined($base) && defined($base->get_locator())
+                            ? ($S{make_extract_target_base_yes})
+                            : ($S{make_extract_target_base_no})
+                    ),
+                    sort({$a <=> $b} @diff_keys),
+                ),
+                $path,
+            );
+        },
+    );
+}
+
+# Returns a CODE to perform a simple notification with sprintf format.
+sub _func {
+    my ($id) = @_;
+    sub {$R->report(sprintf($S{$id}, @_))};
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Util::Event
+
+=head1 SYNOPSIS
+
+    use FCM::Util::Event;
+    $event_handler = FCM::Util::Event->new(\%attrib);
+    $event_handler->($event);
+
+=head1 DESCRIPTION
+
+Handles events wrapped as L<FCM::Context::Event|FCM::Context::Event> objects by
+stringifying and reporting them.
+
+This module is part of L<FCM::Util|FCM::Util>. See also the description of the
+$u->report() method in L<FCM::Util|FCM::Util>.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Returns a new instance. The %attrib HASH can have the following elements:
+
+=over 4
+
+=item util
+
+The parent L<FCM::Util|FCM::Util> object.
+
+=back
+
+=item $util->event($event_ctx)
+
+Notification of an $event_ctx, which should be a blessed reference of
+L<FCM::Context::Event|FCM::Context::Event>.
+
+=back
+
+=head1 TODO
+
+Modularise?
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Util/Exception.pm b/lib/FCM/Util/Exception.pm
new file mode 100644
index 0000000..92fe05b
--- /dev/null
+++ b/lib/FCM/Util/Exception.pm
@@ -0,0 +1,219 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+
+package FCM::Util::Exception;
+use base qw{FCM::Exception};
+
+use constant {
+    CLASS_LOADER         => 'CLASS_LOADER',
+    CONFIG_CONT_EOF      => 'CONFIG_CONT_EOF',
+    CONFIG_CYCLIC        => 'CONFIG_CYCLIC',
+    CONFIG_LOAD          => 'CONFIG_LOAD',
+    CONFIG_SYNTAX        => 'CONFIG_SYNTAX',
+    CONFIG_USAGE         => 'CONFIG_USAGE',
+    CONFIG_VAR_UNDEF     => 'CONFIG_VAR_UNDEF',
+    IO                   => 'IO',
+    LOCATOR_AS_INVARIANT => 'LOCATOR_AS_INVARIANT',
+    LOCATOR_BROWSER_URL  => 'LOCATOR_BROWSER_URL',
+    LOCATOR_FIND         => 'LOCATOR_FIND',
+    LOCATOR_KEYWORD_LOC  => 'LOCATOR_KEYWORD_LOC',
+    LOCATOR_KEYWORD_REV  => 'LOCATOR_KEYWORD_REV',
+    LOCATOR_READER       => 'LOCATOR_READER',
+    LOCATOR_TYPE         => 'LOCATOR_TYPE',
+    SHELL_OPEN3          => 'SHELL_OPEN3',
+    SHELL_OS             => 'SHELL_OS',
+    SHELL_SIGNAL         => 'SHELL_SIGNAL',
+    SHELL_WHICH          => 'SHELL_WHICH',
+};
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Util::Exception
+
+=head1 SYNOPSIS
+
+    use FCM::Util::Exception;
+    eval {
+        # something does not work ...
+        FCM::Util::Exception->throw($code, $ctx, $exception);
+    };
+    if (my $e = $@) {
+        if (FCM::Util::Exception->caught($e)) {
+            # do something ...
+        }
+        else {
+            # do something else ...
+        }
+    }
+
+=head1 DESCRIPTION
+
+This exception represents an error condition in an FCM utility. It is a
+sub-class of L<FCM::Exception|FCM::Exception>.
+
+=head1 CONSTANTS
+
+The following are known error code:
+
+=over 4
+
+=item CLASS_LOADER
+
+L<FCM::Util::ClassLoader|FCM::Util::ClassLoader>: The utility fails to load the
+specified class. The $e->get_ctx() method returns the name of the class it fails
+to load.
+
+=item CONFIG_CONT_EOF
+
+L<FCM::Util::ConfigReader|FCM::Util::ConfigReader>: The last line of the
+configuration file has a continuation marker. Expects the $e->get_ctx() method
+to return the L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry> object that
+represents the problem entry.
+
+=item CONFIG_CYCLIC
+
+L<FCM::Util::ConfigReader|FCM::Util::ConfigReader>: There is a cyclic dependency
+in the include hierarchy. Expects the $e->get_ctx() method to return an ARRAY
+reference of the locator stack. (The last element of the ARRAY is the top of the
+stack, and each element is a 2-element ARRAY reference, where the first element
+is a L<FCM::Context::Locator|FCM::Context::Locator> object and the second
+element is the line number.)
+
+=item CONFIG_LOAD
+
+L<FCM::Util::ConfigReader|FCM::Util::ConfigReader>: An error occurs when loading
+a configuration file. Expects the $e->get_ctx() method to return an ARRAY
+reference of the locator stack. (The last element of the ARRAY is the top of the
+stack, and each element is a 2-element ARRAY reference, where the first element
+is a L<FCM::Context::Locator|FCM::Context::Locator> object and the second
+element is the line number.)
+
+=item CONFIG_SYNTAX
+
+L<FCM::Util::ConfigReader|FCM::Util::ConfigReader>: A syntax error in the
+declaration. Expects the $e->get_ctx() method to return the
+L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry> object that represents
+the problem entry.
+
+=item CONFIG_USAGE
+
+L<FCM::Util::ConfigReader|FCM::Util::ConfigReader>: An attempt to assign a value
+to a reserved variable. Expects the $e->get_ctx() method to return the
+L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry> object that represents
+the problem entry.
+
+=item CONFIG_VAR_UNDEF
+
+L<FCM::Util::ConfigReader|FCM::Util::ConfigReader>: References to an undefined
+variable. Expects the $e->get_ctx() method to return the
+L<FCM::Context::ConfigEntry|FCM::Context::ConfigEntry> object that represents
+the problem entry, and $e->get_exception() to return a string that looks like
+"undef($symbol)" where $symbol is the symbol that references the variable.
+
+=item IO
+
+L<FCM::Util::IO|FCM::Util::IO>: I/O exception. Expects $e->get_ctx() to return
+the path that triggers the exception, and the $e->get_exception() to return the
+$! string.
+
+=item LOCATOR_AS_INVARIANT
+
+L<FCM::Util::Locator|FCM::Util::Locator>: The invariant value of the locator
+cannot be determined. Expects $e->get_ctx() method to return the associated
+L<FCM::Context::Locator|FCM::Context::Locator> object.
+
+=item LOCATOR_BROWSER_URL
+
+L<FCM::Util::Locator|FCM::Util::Locator>: The locator cannot be mapped to a
+browser URL. Expects $e->get_ctx() method to return the associated
+L<FCM::Context::Locator|FCM::Context::Locator> object.
+
+=item LOCATOR_FIND
+
+L<FCM::Util::Locator|FCM::Util::Locator>: The locator does not exist. Expects
+$e->get_ctx() method to return the associated
+L<FCM::Context::Locator|FCM::Context::Locator> object.
+
+=item LOCATOR_KEYWORD_LOC
+
+L<FCM::Util::Locator|FCM::Util::Locator>: The location keyword as specified in
+the locator is not defined. Expects $e->get_ctx() method to return the
+associated L<FCM::Context::Locator|FCM::Context::Locator> object.
+
+=item LOCATOR_KEYWORD_REV
+
+L<FCM::Util::Locator|FCM::Util::Locator>: The revision keyword as specified in
+the locator is not defined. Expects $e->get_ctx() method to return the
+associated L<FCM::Context::Locator|FCM::Context::Locator> object.
+
+=item LOCATOR_READER
+
+L<FCM::Util::Locator|FCM::Util::Locator>: The locator cannot be read. Expects
+$e->get_ctx() method to return the associated
+L<FCM::Context::Locator|FCM::Context::Locator> object.
+
+=item LOCATOR_TYPE
+
+L<FCM::Util::Locator|FCM::Util::Locator>: The locator type cannot be determined
+or cannot be supported. Expects $e->get_ctx() method to return the associated
+L<FCM::Context::Locator|FCM::Context::Locator> object.
+
+=item LOCATOR_WHEN
+
+L<FCM::Util::Locator|FCM::Util::Locator>: The last modified date and revision of
+the locator cannot be determined. Expects $e->get_ctx() method to return the
+associated L<FCM::Context::Locator|FCM::Context::Locator> object.
+
+=item SHELL_OPEN3
+
+L<FCM::Util::Shell|FCM::Util::Shell>: The utility fails to invoke
+IPC::Open3::open3(). Expects $e->get_ctx() to return an ARRAY reference of the
+command line, and $e->get_exception() to return the error from open3().
+
+=item SHELL_OS
+
+L<FCM::Util::Shell|FCM::Util::Shell>: An OS error occurs when invoking a shell
+command. Expects $e->get_ctx() to return an ARRAY reference of the
+command line, and $e->get_exception() to return $!.
+
+=item SHELL_SIGNAL
+
+L<FCM::Util::Shell|FCM::Util::Shell>: The system receives a signal when invoking
+a shell command. Expects $e->get_ctx() to return an ARRAY reference of the
+command line, and $e->get_exception() to return the signal number.
+
+=item SHELL_WHICH
+
+L<FCM::Util::Shell|FCM::Util::Shell>: The shell command does not exist in the
+PATH. Expects $e->get_ctx() to return an ARRAY reference of the command line.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Util/Locator.pm b/lib/FCM/Util/Locator.pm
new file mode 100644
index 0000000..74153a6
--- /dev/null
+++ b/lib/FCM/Util/Locator.pm
@@ -0,0 +1,763 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::Util::Locator;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::Keyword;
+use FCM::Context::Locator;
+use FCM::Util::Exception;
+use FCM::Util::Locator::FS;
+use FCM::Util::Locator::SSH;
+use FCM::Util::Locator::SVN;
+
+# URI prefix for FCM scheme
+use constant PREFIX => 'fcm';
+
+# Methods of an instance of this class
+my %ACTION_OF = (
+    as_invariant     => \&_as_invariant,
+    as_keyword       => \&_as_keyword,
+    as_normalised    => \&_as_normalised,
+    as_parsed        => \&_as_parsed,
+    browser_url      => \&_browser_url,
+    cat              => _locator_func(sub {$_[0]->cat(@_[1 .. $#_])}),
+    dir              => _locator_func(sub {$_[0]->dir($_[1])}),
+    export           => \&_export,
+    export_ok        => \&_export_ok,
+    find             => \&_find,
+    kw_ctx           => sub {$_[0]->{kw_ctx}},
+    kw_ctx_load      => \&_kw_ctx_load,
+    kw_iter          => \&_kw_iter,
+    kw_load_rev_prop => \&_kw_load_rev_prop,
+    kw_prefix        => sub {PREFIX},
+    origin           => _locator_func(sub {$_[0]->origin($_[1])}),
+    reader           => \&_reader,
+    rel2abs          => \&_rel2abs,
+    test_exists      => \&_test_exists,
+    trunk_at_head    => \&_trunk_at_head,
+    what_type        => \&_what_type,
+    up_iter          => \&_up_iter,
+);
+# Default browser config
+our %BROWSER_CONFIG = (
+    comp_pat => qr{\A // ([^/]+) /+ ([^/]+)_svn /*(.*) \z}xms,
+    rev_tmpl => '@{1}',
+    loc_tmpl => 'http://{1}/projects/{2}/intertrac/source:/{3}{4}',
+);
+# Alias to the exception class
+my $E = 'FCM::Util::Exception';
+# Loaders for keyword context from configuration entries
+my %KEYWORD_CFG_LOADER_FOR = (
+    'location'
+    => \&_kw_ctx_load_loc,
+    'revision'
+    => \&_kw_ctx_load_rev,
+    'browser.comp-pat'
+    => _kw_ctx_load_browser_func(sub {$_[0]->set_comp_pat($_[1])}),
+    'browser.loc-tmpl'
+    => _kw_ctx_load_browser_func(sub {$_[0]->set_loc_tmpl($_[1])}),
+    'browser.rev-tmpl'
+    => _kw_ctx_load_browser_func(sub {$_[0]->set_rev_tmpl($_[1])}),
+);
+my @KEYWORD_IMPLIED_SUFFICES = (
+    [branches => [qw{-br _br}]],
+    [tags     => [qw{-tg _tg}]],
+    [trunk    => [qw{-tr _tr}]],
+);
+# Patterns for parsing keyword configurations, etc
+my %PATTERN_OF = (
+    # Assignment delimiter, e.g. "label = value"
+    delim_of_assign  => qr/\s* = \s*/xms,
+    # Key of a FCM location keyword, e.g. "um" in "fcm:um"
+    parse => qr/
+        \A              (?# start)
+        ([\w\+\-\.]+)   (?# capture 1, 1 or more word, plus, minus or dot)
+        (.*) \z         (?# capture 2, rest of string)
+    /xms,
+);
+# The name of the property where revision keywords are set in primary locations
+our $REV_PROP_NAME = 'fcm:revision';
+# The known types
+our @TYPES = qw{svn ssh fs};
+# The classes for the known types
+our %TYPE_UTIL_CLASS_OF = (
+    fs  => 'FCM::Util::Locator::FS',
+    ssh => 'FCM::Util::Locator::SSH',
+    svn => 'FCM::Util::Locator::SVN',
+);
+
+# Creates the class.
+__PACKAGE__->class(
+    {   types              => {isa => '@', default => [@TYPES]},
+        type_util_class_of => {isa => '%', default => {%TYPE_UTIL_CLASS_OF}},
+        type_util_of       => '%',
+        util               => '&',
+    },
+    {   init => sub {
+            my ($attrib_ref) = @_;
+            my $K = 'FCM::Context::Keyword';
+            $attrib_ref->{browser_config}
+                = $K->BROWSER_CONFIG->new(\%BROWSER_CONFIG);
+            $attrib_ref->{kw_ctx} = $K->new();
+            for my $type (@{$attrib_ref->{types}}) {
+                if (!exists($attrib_ref->{type_util_of}{$type})) {
+                    my $class = $attrib_ref->{type_util_class_of}{$type};
+                    $attrib_ref->{type_util_of}{$type} = $class->new({
+                        type_util_of => $attrib_ref->{type_util_of},
+                        util         => $attrib_ref->{util},
+                    });
+                }
+            }
+        },
+        action_of => \%ACTION_OF,
+    },
+);
+
+# Determines the invariant value of the $locator.
+sub _as_invariant {
+    my ($attrib_ref, $locator) = @_;
+    if ($locator->get_value_level() < $locator->L_INVARIANT) {
+        _as_normalised($attrib_ref, $locator);
+        my $util_of_type = _util_of_type($attrib_ref, $locator);
+        if ($util_of_type->can('as_invariant')) {
+            my $value = eval {
+                $util_of_type->as_invariant($locator->get_value());
+            };
+            if (my $e = $@) {
+                return $E->throw($E->LOCATOR_AS_INVARIANT, $locator, $e);
+            }
+            if ($value) {
+                $locator->set_value($value);
+                $locator->set_value_level($locator->L_INVARIANT);
+            }
+        }
+    }
+    $locator->get_value();
+}
+
+# Determines the keyword value of the $locator.
+sub _as_keyword {
+    my ($attrib_ref, $locator) = @_;
+    _as_normalised($attrib_ref, $locator);
+    my $util_of_type = _util_of_type($attrib_ref, $locator);
+    my ($target, $rev) = $util_of_type->parse($locator->get_value());
+    my $kw_iter = _kw_iter($attrib_ref, $locator);
+    my $entry;
+    while (!defined($entry) && defined($entry = $kw_iter->())) {
+        if ($entry->is_implied()) {
+            $entry = undef;
+        }
+    }
+    if (defined($entry)) {
+        $target
+            = PREFIX . ':' . $entry->get_key()
+            . substr($target, length($entry->get_value()));
+    }
+    if (defined($rev) && $util_of_type->can_work_with_rev($rev)) {
+        my $transformed_rev = _transform_rev_keyword(
+            $attrib_ref, $locator, $rev,
+            sub {$_[0]->get_entry_by_value($_[1])},
+            sub {$_[0]->get_key()},
+        );
+        if ($transformed_rev) {
+            $rev = $transformed_rev;
+        }
+    }
+    scalar($util_of_type->parse($target, $rev));
+}
+
+# Determines the normalised value of the $locator.
+sub _as_normalised {
+    my ($attrib_ref, $locator) = @_;
+    if ($locator->get_value_level() < $locator->L_NORMALISED) {
+        _as_parsed($attrib_ref, $locator);
+        my $util_of_type = _util_of_type($attrib_ref, $locator);
+        my ($target, $rev) = $util_of_type->parse($locator->get_value());
+        if (defined($rev) && !$util_of_type->can_work_with_rev($rev)) {
+            my $origin = $ACTION_OF{origin}->(
+                $attrib_ref, FCM::Context::Locator->new($target),
+            );
+            $rev = _transform_rev_keyword(
+                $attrib_ref, $origin, lc($rev),
+                sub {$_[0]->get_entry_by_key($_[1])},
+                sub {$_[0]->get_value()},
+            );
+            if (!$rev) {
+                return $E->throw($E->LOCATOR_KEYWORD_REV, $locator);
+            }
+        }
+        $locator->set_value(scalar($util_of_type->parse($target, $rev)));
+        $locator->set_value_level($locator->L_NORMALISED);
+    }
+    $locator->get_value();
+}
+
+# Determines the parsed value of the $locator.
+sub _as_parsed {
+    my ($attrib_ref, $locator) = @_;
+    if ($locator->get_value_level() < $locator->L_PARSED) {
+        my $value = $locator->get_value();
+        my ($scheme, $sps) = $attrib_ref->{util}->uri_match($value);
+        if ($scheme && $scheme eq PREFIX) {
+            my ($key, $trail) = $sps =~ $PATTERN_OF{parse};
+            my $entry = $attrib_ref->{kw_ctx}->get_entry_by_key(lc($key));
+            if (!defined($entry)) {
+                return $E->throw($E->LOCATOR_KEYWORD_LOC, $locator);
+            }
+            $value = $entry->get_value() . $trail;
+        }
+        $locator->set_value($value);
+        $locator->set_value_level($locator->L_PARSED);
+    }
+    $locator->get_value();
+}
+
+# Determines the browser URL of the $locator.
+sub _browser_url {
+    my ($attrib_ref, $locator) = @_;
+    _as_normalised($attrib_ref, $locator);
+    my %GET = (
+        comp_pat => sub {$_[0]->get_comp_pat()},
+        loc_tmpl => sub {$_[0]->get_loc_tmpl()},
+        rev_tmpl => sub {$_[0]->get_rev_tmpl()},
+    );
+    my %value_of = map {($_, undef)} keys(%GET);
+    my $iter = _kw_iter($attrib_ref, $locator);
+    while (my $entry = $iter->()) {
+        if (defined($entry->get_browser_config())) {
+            for my $key (keys(%value_of)) {
+                if (!defined($value_of{$key})) {
+                    my $value = $GET{$key}->($entry->get_browser_config());
+                    if (defined($value)) {
+                        $value_of{$key} = $value;
+                    }
+                }
+            }
+        }
+    }
+    for my $key (keys(%value_of)) {
+        if (!$value_of{$key}) {
+            $value_of{$key} = $GET{$key}->($attrib_ref->{browser_config});
+        }
+    }
+    # Extracts components from the locator
+    my $origin = $ACTION_OF{origin}->($attrib_ref, $locator);
+    my ($target, $rev)
+        = _util_of_type($attrib_ref, $origin)->parse($origin->get_value());
+    my ($scheme, $sps) = $attrib_ref->{util}->uri_match($target);
+    if (!$sps) {
+        return $E->throw($E->LOCATOR_BROWSER_URL, $locator);
+    }
+    my @matches = $sps =~ $value_of{comp_pat};
+    if (!@matches) {
+        return $E->throw($E->LOCATOR_BROWSER_URL, $locator);
+    }
+    # Places the components into the template
+    my $result = $value_of{loc_tmpl};
+    for my $field_number (1 .. @matches) {
+        my $match = $matches[$field_number - 1];
+        $result =~ s/\{ $field_number \}/$match/xms;
+    }
+    my $rev_field_number = scalar(@matches) + 1;
+    my $rev_string = q{};
+    if ($rev) {
+        $rev_string = $value_of{rev_tmpl};
+        $rev_string =~ s/\{1\}/$rev/xms;
+    }
+    $result =~ s/\{ $rev_field_number \}/$rev_string/xms;
+    return $result;
+}
+
+# Exports $locator to a $dest.
+sub _export {
+    my ($attrib_ref, $locator, $dest) = @_;
+    if (_util_of_type($attrib_ref, $locator)->can('export')) {
+        _as_normalised($attrib_ref, $locator);
+        my $util_of_type = _util_of_type($attrib_ref, $locator);
+        $util_of_type->export($locator->get_value(), $dest);
+    }
+}
+
+# Returns true if it is possible to safely export $locator.
+sub _export_ok {
+    my ($attrib_ref, $locator) = @_;
+    my $util_of_type = _util_of_type($attrib_ref, $locator);
+    _as_parsed($attrib_ref, $locator);
+    $util_of_type->can('export_ok')
+        && $util_of_type->export_ok($locator->get_value());
+}
+
+# Searches the directory tree of $locator. Calls a function for each node.
+sub _find {
+    my ($attrib_ref, $locator, $callback) = @_;
+    _as_invariant($attrib_ref, $locator);
+    my $type = $locator->get_type();
+    my $util_of_type = _util_of_type($attrib_ref, $locator);
+    my $found = $util_of_type->find(
+        $locator->get_value(),
+        sub {
+            my ($value, $target_attrib_ref) = @_;
+            my $new_locator;
+            if ($value eq $locator->get_value()) {
+                $locator->set_last_mod_rev($target_attrib_ref->{last_mod_rev});
+                $locator->set_last_mod_time($target_attrib_ref->{last_mod_time});
+                $new_locator = $locator;
+            }
+            else {
+                $new_locator = FCM::Context::Locator->new($value, {
+                    last_mod_rev  => $target_attrib_ref->{last_mod_rev},
+                    last_mod_time => $target_attrib_ref->{last_mod_time},
+                    type          => $type,
+                    value_level   => FCM::Context::Locator->L_INVARIANT,
+                });
+            }
+            $callback->($new_locator, $target_attrib_ref);
+        },
+    );
+    return ($found ? $found : $E->throw($E->LOCATOR_FIND, $locator));
+}
+
+# Loads the keyword context from configuration entries.
+sub _kw_ctx_load {
+    my ($attrib_ref, @config_entry_iterators) = @_;
+    for my $config_entry_iterator (@config_entry_iterators) {
+        while (my $config_entry = $config_entry_iterator->()) {
+            my $handler = $KEYWORD_CFG_LOADER_FOR{$config_entry->get_label()};
+            if (defined($handler)) {
+                $handler->($attrib_ref, $config_entry);
+            }
+        }
+    }
+}
+
+# Loads a location keyword browser config from a configuration entry.
+sub _kw_ctx_load_browser_func {
+    my ($setter_ref) = @_;
+    sub {
+        my ($attrib_ref, $c_entry) = @_;
+        my %entry_by_key = %{$attrib_ref->{kw_ctx}->get_entry_by_key()};
+        if (@{$c_entry->get_ns_list()}) {
+            for my $key (@{$c_entry->get_ns_list()}) {
+                if (exists($entry_by_key{$key})) {
+                    $setter_ref->(
+                        $entry_by_key{$key}->get_browser_config(),
+                        $c_entry->get_value(),
+                    );
+                }
+            }
+        }
+        else {
+            $setter_ref->($attrib_ref->{browser_config}, $c_entry->get_value());
+        }
+    }
+}
+
+# Loads the location keyword context from a configuration entry.
+sub _kw_ctx_load_loc {
+    my ($attrib_ref, $c_entry) = @_;
+    my $key   = lc($c_entry->get_ns_list()->[0]);
+    my $value = $c_entry->get_value();
+    my $M     = $c_entry->get_modifier_of();
+    my $type  = (exists($M->{type}) ? $M->{type} : undef);
+    my $entry
+        = $attrib_ref->{kw_ctx}->add_entry($key, $value, {type => $type});
+    if (exists($M->{primary}) && $M->{primary}) {
+        my $locator = FCM::Context::Locator->new($value, {type => $type});
+        my $util_of_type = _util_of_type($attrib_ref, $locator);
+        for (@KEYWORD_IMPLIED_SUFFICES) {
+            my ($value_suffix, $key_suffix_ref) = @{$_};
+            my $locator = $ACTION_OF{cat}->($attrib_ref, $locator, $value_suffix);
+            my $value = $locator->get_value();
+            for my $key_suffix (@{$key_suffix_ref}) {
+                my $implied_entry = $entry->get_ctx_of_implied()->add_entry(
+                    $key . $key_suffix, $value, {implied => 1, type => $type},
+                );
+                $attrib_ref->{kw_ctx}->add_entry($implied_entry);
+            }
+        }
+    }
+}
+
+# Loads the revision keyword context from a configuration entry.
+sub _kw_ctx_load_rev {
+    my ($attrib_ref, $c_entry) = @_;
+    for my $ns (map {lc($_)} @{$c_entry->get_ns_list()}) {
+        my ($key, $r_key) = split(qr{:}msx, $ns);
+        my $entry = $attrib_ref->{kw_ctx}->get_entry_by_key($key);
+        if (defined($entry)) {
+            $entry->get_ctx_of_rev()->add_entry($r_key, $c_entry->get_value());
+        }
+    }
+}
+
+# Returns an iterator that returns location keyword entry context for $locator.
+sub _kw_iter {
+    my ($attrib_ref, $locator, $callback_ref) = @_;
+    my $origin = $ACTION_OF{origin}->($attrib_ref, $locator);
+    my $iter = _up_iter($attrib_ref, $origin);
+    sub {
+        while (my ($leader) = $iter->()) {
+            my $entry = $attrib_ref->{kw_ctx}->get_entry_by_value($leader);
+            if (defined($entry)) {
+                if (defined($callback_ref)) {
+                    $callback_ref->($entry);
+                }
+                return $entry;
+            }
+        }
+        return;
+    }
+}
+
+# Loads revision keywords from the "fcm:revision" property of the locator value
+# of a location keyword entry.
+sub _kw_load_rev_prop {
+    my ($attrib_ref, $entry) = @_;
+    if ($entry->get_loaded_rev_prop() || $entry->get_implied()) {
+        return;
+    }
+    $entry->set_loaded_rev_prop(1);
+    my $locator = FCM::Context::Locator->new(
+        $entry->get_value(), {type => $entry->get_type()},
+    );
+    my $property = _read_property($attrib_ref, $locator, $REV_PROP_NAME);
+    if (!$property) {
+        return;
+    }
+    for my $line (split(qr{\s*\n\s*}xms, $property)) {
+        my ($key, $value) = split($PATTERN_OF{delim_of_assign}, $line, 2);
+        $entry->get_ctx_of_rev()->add_entry($key, $value);
+    }
+}
+
+# Returns a function to "transform" a $locator to another locator.
+sub _locator_func {
+    my ($impl_ref) = @_;
+    sub {
+        my ($attrib_ref, $locator, @args) = @_;
+        _as_parsed($attrib_ref, $locator);
+        my $util_of_type = _util_of_type($attrib_ref, $locator);
+        FCM::Context::Locator->new(
+            scalar($impl_ref->($util_of_type, $locator->get_value(), @args)),
+            {type => $locator->get_type()},
+        );
+    }
+}
+
+# Returns a file handle to read the content of $locator.
+sub _reader {
+    my ($attrib_ref, $locator) = @_;
+    _as_normalised($attrib_ref, $locator);
+    my $reader = eval {
+        _util_of_type($attrib_ref, $locator)->reader($locator->get_value());
+    };
+    if (my $e = $@) {
+        return $E->throw($E->LOCATOR_READER, $locator, $e);
+    }
+    if (!defined($reader)) {
+        return $E->throw($E->LOCATOR_READER, $locator);
+    }
+    return $reader;
+}
+
+# Returns the contents in a named property of $locator
+sub _read_property {
+    my ($attrib_ref, $locator, $prop_name) = @_;
+    _as_normalised($attrib_ref, $locator);
+    my $util_of_type = _util_of_type($attrib_ref, $locator);
+    eval {$util_of_type->read_property($locator->get_value(), $prop_name)};
+}
+
+# If $locator->get_value() is a relative path, set it to a absolute path
+# base on $locator_base->get_value(), if $locator->get_type() does not differ
+# from $locator_base->get_type().
+sub _rel2abs {
+    my ($attrib_ref, $locator, $locator_base) = @_;
+    _as_normalised($attrib_ref, $locator_base);
+    if (    $locator->get_type()
+        &&  $locator->get_type() ne $locator_base->get_type()
+    ) {
+        return $locator;
+    }
+    my $value = $locator->get_value();
+    if (    $attrib_ref->{util}->uri_match($value)
+        ||  index($value, '/') == 0
+        ||  index($value, '~') == 0
+    ) {
+        return $locator;
+    }
+    my $new_locator = $ACTION_OF{cat}->($attrib_ref, $locator_base, $value);
+    $locator->set_value($new_locator->get_value());
+    $locator->set_value_level($new_locator->get_value_level());
+    $locator;
+}
+
+# Test if $locator location exists.
+sub _test_exists {
+    my ($attrib_ref, $locator) = @_;
+    _as_normalised($attrib_ref, $locator);
+    _util_of_type($attrib_ref, $locator)->test_exists($locator->get_value());
+}
+
+# Transforms a revision from/to a keyword, and returns the result.
+sub _transform_rev_keyword {
+    my ($attrib_ref, $locator, $rev, $rev_entry_func, $result_func) = @_;
+    my $iter = _kw_iter($attrib_ref, $locator);
+    while (my $entry = $iter->()) {
+        # $entry->get_ctx_of_rev()->get_entry_by_key($rev)
+        # $entry->get_ctx_of_rev()->get_entry_by_value($rev)
+        if (defined($entry->get_ctx_of_rev())) {
+            if (!$rev_entry_func->($entry->get_ctx_of_rev(), $rev)) {
+                _kw_load_rev_prop($attrib_ref, $entry);
+            }
+        }
+        if (defined($entry->get_ctx_of_rev())) {
+            my $rev_entry = $rev_entry_func->($entry->get_ctx_of_rev(), $rev);
+            if (defined($rev_entry)) {
+                # $rev_entry->get_value()
+                # $rev_entry->get_key()
+                return $result_func->($rev_entry);
+            }
+        }
+    }
+    return;
+}
+
+# Returns a string to represent the relative path to the latest main tree.
+sub _trunk_at_head {
+    my ($attrib_ref, $locator) = @_;
+    my $orig_value = $locator->get_value();
+    my $util_of_type = _util_of_type($attrib_ref, $locator);
+    my $head_value = $util_of_type->trunk_at_head($orig_value);
+    if (!$head_value || $head_value eq $orig_value) {
+        return;
+    }
+    return FCM::Context::Locator->new($head_value, {
+        type => $locator->get_type(),
+    });
+}
+
+# Determines the type of the $locator.
+sub _what_type {
+    my ($attrib_ref, $locator) = @_;
+    if (!defined($locator->get_type())) {
+        _as_parsed($attrib_ref, $locator);
+        TYPE:
+        for my $key (@{$attrib_ref->{types}}) {
+            if (!exists($attrib_ref->{type_util_of}{$key})) {
+                next TYPE;
+            }
+            my $util_of_type = $attrib_ref->{type_util_of}{$key};
+            if ($util_of_type->can_work_with($locator->get_value())) {
+                $locator->set_type($key);
+                last TYPE;
+            }
+        }
+    }
+    return $locator->get_type();
+}
+
+# Returns an iterator that walks up the hierarchy of the $locator.
+sub _up_iter {
+    my ($attrib_ref, $locator) = @_;
+    my $util_of_type = _util_of_type($attrib_ref, $locator);
+    my ($target, $revision) = $util_of_type->parse($locator->get_value());
+    my $leader = $target;
+    sub {
+        if (!defined($leader)) {
+            $leader = $target;
+            return;
+        }
+        my $return = $leader;
+        $leader = $util_of_type->dir($return);
+        if ($return eq $leader) {
+            $leader = undef;
+        }
+        return $util_of_type->parse($return, $revision);
+    };
+}
+
+# Returns the utility that implements the functionality for the $locator's type.
+sub _util_of_type {
+    my ($attrib_ref, $locator) = @_;
+    my $type = _what_type($attrib_ref, $locator);
+    if (exists($attrib_ref->{type_util_of}{$type})) {
+        return $attrib_ref->{type_util_of}{$type};
+    }
+    return $E->throw($E->LOCATOR_TYPE, $locator);
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Util::Locator
+
+=head1 SYNOPSIS
+
+    use FCM::Util;
+    my $util = FCM::Util->new(\%attrib);
+
+    # Usage
+    $ctx = $util->loc_kw_ctx();
+    @location_keyword_ctx_list = $util->loc_kw_ctx($locator);
+
+    $type = $util->loc_what_type($locator);
+    ($time, $rev) = $util->loc_when_modified($locator);
+
+    $locator_value = $util->loc_as_normalised($locator);
+    $locator_value = $util->loc_as_invariant( $locator);
+    $locator_value = $util->loc_as_keyword(   $locator);
+
+    $url = $util->loc_browser_url($locator);
+
+    $locator_of_parent = $util->loc_dir($locator);
+    $locator_of_child  = $util->loc_cat($locator, @paths);
+    $locator_of_origin = $util->loc_origin($locator);
+
+    $iter = $util->loc_up_iter($locator);
+    while (my $value = $iter->()) {
+        # ...
+    }
+
+    $reader = $util->loc_reader($locator);
+
+=head1 DESCRIPTION
+
+This module is part of L<FCM::Util|FCM::Util>. It implements the loc_* methods.
+
+=head1 IMPLEMENTATION
+
+The manipulations of locator values rely on objects with the following
+interface:
+
+=over 4
+
+=item $util_of_type->as_invariant($locator_value)
+
+Should return the invariant form of $locator_value.
+
+=item $util_of_type->can_work_with($locator_value)
+
+Should return true if it can work with $locator_value, i.e. $locator_value is a
+valid type of locator for the utility.
+
+=item $util_of_type->can_work_with_rev($revision_value)
+
+Should return true if it can work with $revision_value, i.e. $revision_value is
+a valid revision for the utility.
+
+=item $util_of_type->cat($locator_value, @paths)
+
+Should concatenate $locator_value and @paths with appropriate separators and
+returns the result.
+
+=item $util_of_type->dir($locator_value)
+
+Should return the parent (directory) of $locator_value.
+
+=item $util_of_type->export($locator_value,$dest)
+
+Optional. Exports a clean directory tree from $locator_value to $dest.
+
+=item $util_of_type->export_ok($locator_value)
+
+Optional. Returns true if it is safe to export $locator_value. E.g. it is not
+safe to export a SVN working copy, because it may contain unversioned items.
+
+=item $util_of_type->find($locator_value,$callback)
+
+Should search the directory tree in $locator_value and for each node (directory
+or file, inclusive of $locator_value), invoke
+$callback->($locator_value_of_child,\%attrib_of_child). %attrib_of_child should
+contain the elements as described by $util->find($locator,$callback).
+
+=item $util_of_type->origin($locator_value)
+
+Should return the origin of $locator_value. E.g. the URL of a Subversion working
+copy.
+
+=item $util_of_type->parse($locator_value)
+
+Should return an absolute and tidied version of $locator_value. In list context,
+should return a 2-element list, separate the scalar context return value into
+the components (PATH,REV).
+
+=item $util_of_type->reader($locator_value)
+
+Should return a file handle for reading the content of $locator_value.
+
+=item $util_of_type->read_property($locator_value,$property_name)
+
+Should return the value of the named property in $locator_value, or undef if
+not relevant for the $locator_value.
+
+=item $util_of_type->test_exists($locator_value)
+
+Should return a true value if the location $locator_value exists.
+
+=item $util_of_type->trunk_at_head($locator_value)
+
+If relevant, should append a string to $locator_value that represents the
+recommended relative path to the latest version of the main tree of a project of
+this type. E.g. for "svn", this should be "$locator_value/trunk at HEAD".
+
+=back
+
+=head1 CONSTANTS
+
+These global variables are for reference only. Their values should not be
+modified. Instead, use the appropriate attributes of the $class->new(\%attrib)
+method to modify the behaviour.
+
+=over 4
+
+=item %FCM::Util::Locator::BROWSER_CONFIG
+
+The default browser configuration.
+
+=item FCM::Util::Locator::PREFIX
+
+The URI prefix for a FCM location keyword.
+
+=item $FCM::Util::Locator::REV_PROP_NAME
+
+The name of the property where revision keywords are set in primary locations.
+
+=item @FCM::Util::Locator::TYPES
+
+The known locator types.
+
+=item %FCM::Util::Locator::TYPE_UTIL_CLASS_OF
+
+Maps the known locator types with their utility classes.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Util/Locator/FS.pm b/lib/FCM/Util/Locator/FS.pm
new file mode 100644
index 0000000..719edce
--- /dev/null
+++ b/lib/FCM/Util/Locator/FS.pm
@@ -0,0 +1,202 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::Util::Locator::FS;
+use base qw{FCM::Class::CODE};
+
+use File::Basename qw{dirname};
+use File::Find qw{};
+use File::Spec;
+
+our %ACTION_OF = (
+    can_work_with     => sub {1},
+    can_work_with_rev => sub {},
+    cat               => \&_cat,
+    dir               => \&_dir,
+    find              => \&_find,
+    origin            => \&_parse,
+    parse             => \&_parse,
+    reader            => \&_reader,
+    read_property     => sub {},
+    test_exists       => \&_test_exists,
+    trunk_at_head     => sub {},
+);
+
+# Creates the class.
+__PACKAGE__->class({util => '&'}, {action_of => \%ACTION_OF});
+
+# Joins @paths to the end of $value.
+sub _cat {
+    my ($attrib_ref, $value, @paths) = @_;
+    _parse(
+        $attrib_ref,
+        File::Spec->catfile(_parse($attrib_ref, $value), @paths),
+    );
+}
+
+# Returns the directory name of $value.
+sub _dir {
+    my ($attrib_ref, $value) = @_;
+    dirname(_parse($attrib_ref, $value));
+}
+
+# Searches directory tree.
+sub _find {
+    my ($attrib_ref, $value, $callback) = @_;
+    my $found;
+    File::Find::find(
+        sub {
+            $found ||= 1;
+            my $path = $File::Find::name;
+            my ($vol, $dir_name, $base) = File::Spec->splitpath($path);
+            for my $name (File::Spec->splitdir($dir_name), $base) {
+                if (index($name, q{.}) == 0) {
+                    return; # ignore Unix hidden/system files
+                }
+            }
+            my $ns = File::Spec->abs2rel($path, $value);
+            if ($ns eq q{.}) {
+                $ns = q{};
+            }
+            my $last_mod_time = (-l $path ? lstat($path) : stat($path))[9];
+            $callback->(
+                $path,
+                {   is_dir        => scalar(-d $path),
+                    last_mod_rev  => undef,
+                    last_mod_time => $last_mod_time,
+                    ns            => $ns,
+                },
+            );
+        },
+        $value,
+    );
+    return $found;
+}
+
+# Returns $value in scalar context, or ($value,undef) in list context.
+sub _parse {
+    my ($attrib_ref, $value) = @_;
+    $value = $attrib_ref->{util}->file_tilde_expand($value);
+    $value = File::Spec->rel2abs($value);
+    my ($vol, $dir_name, $base) = File::Spec->splitpath($value);
+    my @dir_names;
+    my %HANDLER_OF = (
+        q{}   => sub {push(@dir_names, $_[0])},
+        q{.}  => sub {},
+        q{..} => sub {if (@dir_names > 1) {pop(@dir_names)}},
+    );
+    for my $name (File::Spec->splitdir($dir_name)) {
+        my $handler
+            = exists($HANDLER_OF{$name}) ? $HANDLER_OF{$name} : $HANDLER_OF{q{}};
+        $handler->($name);
+    }
+    $value = File::Spec->catpath($vol, File::Spec->catdir(@dir_names), $base);
+    return (wantarray() ? ($value, undef) : $value);
+}
+
+# Returns a reader (file handle) for a given file system value.
+sub _reader {
+    my ($attrib_ref, $value) = @_;
+    $value = _parse($attrib_ref, $value);
+    open(my $handle, '<', $value) || die("$!\n");
+    return $handle;
+}
+
+# Return a true value if the location $value exists.
+sub _test_exists {
+    my ($attrib_ref, $value) = @_;
+    -e $value;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Util::Locator::FS
+
+=head1 SYNOPSIS
+
+    use FCM::Util::Locator::FS;
+    $util = FCM::Util::Locator::FS->new(\%option);
+    $handle = $util->reader($value);
+
+=head1 DESCRIPTION
+
+Provides utilities to manipulate the values of file system locators.
+
+=head1 METHODS
+
+=over 4
+
+=item $util->can_work_with($value)
+
+Dummy. Always returns true.
+
+=item $util->can_work_with_rev($revision)
+
+Dummy. Always returns false.
+
+=item $util->cat($value, at paths)
+
+Joins @paths to the end of $value.
+
+=item $util->dir($value)
+
+Returns the parent directory of $value.
+
+=item $util->find($value,$callback)
+
+Searches directory tree of $value.
+
+=item $util->origin($value)
+
+Alias of $util->parse($value).
+
+=item $util->parse($value)
+
+In scalar context, returns $value. In list context, returns ($value,undef).
+
+=item $util->reader($value)
+
+Returns a file handle for $value, if it is a readable regular file.
+
+=item $util->read_property($value,$property_name)
+
+Dummy. Always returns undef.
+
+=item $util->test_exists($value)
+
+Return a true value if the location $value exists.
+
+=item $util->trunk_at_head($value)
+
+Dummy. Always returns undef.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Util/Locator/SSH.pm b/lib/FCM/Util/Locator/SSH.pm
new file mode 100644
index 0000000..33f4fd0
--- /dev/null
+++ b/lib/FCM/Util/Locator/SSH.pm
@@ -0,0 +1,251 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::Util::Locator::SSH;
+use base qw{FCM::Class::CODE};
+
+use FCM::Util::Exception;
+use File::Temp;
+use Text::ParseWords qw{shellwords};
+
+our %ACTION_OF = (
+    can_work_with     => \&_can_work_with,
+    can_work_with_rev => sub {},
+    cat               => \&_cat,
+    dir               => \&_dir,
+    export            => \&_export,
+    export_ok         => sub {1},
+    find              => \&_find,
+    origin            => \&_parse,
+    parse             => \&_parse,
+    reader            => \&_reader,
+    read_property     => sub {},
+    test_exists       => \&_test_exists,
+    trunk_at_head     => sub {},
+);
+# Alias to the exception class
+my $E = 'FCM::Util::Exception';
+
+# Creates the class.
+__PACKAGE__->class(
+    {type_util_of => '%', util => '&'},
+    {action_of => \%ACTION_OF},
+);
+
+# Returns true if $value looks like a legitimate HOST:PATH.
+sub _can_work_with {
+    my ($attrib_ref, $value) = @_;
+    if (!$value) {
+        return;
+    }
+    my ($auth) = split(':', $value, 2);
+    if (!$auth) {
+        return;
+    }
+    my $host = index($auth, '@') >= 0 ? (split('@', $auth, 2))[1] : $auth;
+    $host ? gethostbyname($host) : undef;
+}
+
+# Joins @paths to the end of $value.
+sub _cat {
+    my ($attrib_ref, $value, @paths) = @_;
+    my ($auth, $path) = split(':', $value, 2);
+    $auth . ':' . $attrib_ref->{type_util_of}{fs}->cat($path, @paths);
+}
+
+# Returns the directory name of $value.
+sub _dir {
+    my ($attrib_ref, $value) = @_;
+    my ($auth, $path) = split(':', $value, 2);
+    $auth . ':' . $attrib_ref->{type_util_of}{fs}->dir($path);
+}
+
+# Rsync $value to $dest.
+sub _export {
+    my ($attrib_ref, $value, $dest) = @_;
+    my ($auth, $path) = _dir($attrib_ref, $value);
+    my $value_hash_ref = $attrib_ref->{util}->shell_simple([
+        _shell_cmd_list($attrib_ref, 'rsync'),
+        $value . '/',
+        $dest,
+    ]);
+    if ($value_hash_ref->{rc}) {
+        die($value_hash_ref);
+    }
+}
+
+# Searches directory tree.
+sub _find {
+    my ($attrib_ref, $value, $callback) = @_;
+    my ($auth, $path) = split(':', $value, 2);
+    my $value_hash_ref = $attrib_ref->{util}->shell_simple([
+        _shell_cmd_list($attrib_ref, 'ssh'),
+        $auth,
+        "find $path -type f -not -path \"*/.*\" -printf \"%T@ %p\\\\n\"",
+    ]);
+    if ($value_hash_ref->{rc}) {
+        die($value_hash_ref);
+    }
+    my $found;
+    LINE:
+    for my $line (grep {$_} split("\n", $value_hash_ref->{o})) {
+        $found ||= 1;
+        my ($mtime, $name) = split(q{ }, $line, 2);
+        my $ns = substr($name, length($path) + 1);
+        $callback->(
+            $auth . ':' . $name,
+            {   is_dir        => undef,
+                last_mod_rev  => undef,
+                last_mod_time => $mtime,
+                ns            => $ns,
+            },
+        );
+    }
+    $found;
+}
+
+# Returns a reader (file handle) for a given file system value.
+sub _reader {
+    my ($attrib_ref, $value) = @_;
+    my ($auth, $path) = split(':', $value, 2);
+    my $handle = File::Temp->new();
+    my $e;
+    my $rc = $attrib_ref->{util}->shell(
+        [_shell_cmd_list($attrib_ref, 'ssh'), $auth, 'cat', $path],
+        {'e' => \$e, 'o' => sub {print($handle $_[0])}},
+    );
+    if ($rc) {
+        die($e);
+    }
+    seek($handle, 0, 0);
+    return $handle;
+}
+
+# Returns $value in scalar context, or ($value,undef) in list context.
+sub _parse {
+    my ($attrib_ref, $value) = @_;
+    my ($auth, $path) = split(':', $value, 2);
+    $value = $auth . ':' . $attrib_ref->{type_util_of}{fs}->parse($path);
+    return (wantarray() ? ($value, undef) : $value);
+}
+
+# Return a true value if the location $value exists.
+sub _test_exists {
+    my ($attrib_ref, $value) = @_;
+    my ($auth, $path) = split(':', $value, 2);
+    my $value_hash_ref = $attrib_ref->{util}->shell_simple([
+        _shell_cmd_list($attrib_ref, 'ssh'), $auth, "test -e '$path'",
+    ]);
+    return !$value_hash_ref->{rc};
+}
+
+# Get a named command and its flags, return a list.
+sub _shell_cmd_list {
+    my ($attrib_ref, $key) = @_;
+    map {shellwords($_)} (
+        $attrib_ref->{util}->external_cfg_get($key),
+        $attrib_ref->{util}->external_cfg_get($key . '.flags'),
+    );
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Util::Locator::SSH
+
+=head1 SYNOPSIS
+
+    use FCM::Util::Locator::SSH;
+    $util = FCM::Util::Locator::SSH->new(\%option);
+    $handle = $util->reader($value);
+
+=head1 DESCRIPTION
+
+Provides utilities to manipulate the values of locators on file systems on
+remote hosts accessible via SSH and RSYNC.
+
+=head1 METHODS
+
+=over 4
+
+=item $util->can_work_with($value)
+
+Returns true if $value is in the form AUTH:PATH and AUTH is a valid user at host.
+
+=item $util->can_work_with_rev($revision)
+
+Dummy. Always returns false.
+
+=item $util->cat($value, at paths)
+
+Joins @paths to the end of $value.
+
+=item $util->dir($value)
+
+Returns the auth:parent-directory of $value.
+
+=item $util->export($value,$dest)
+
+Rsync a clean directory tree of $value to $dest.
+
+=item $util->export_ok($value)
+
+Returns true if $util->can_work_with($value).
+
+=item $util->find($value,$callback)
+
+Searches directory tree of $value.
+
+=item $util->origin($value)
+
+Alias of $util->parse($value).
+
+=item $util->parse($value)
+
+In scalar context, returns $value. In list context, returns ($value,undef).
+
+=item $util->reader($value)
+
+Returns a file handle for $value, if it is a readable regular file.
+
+=item $util->read_property($value,$property_name)
+
+Dummy. Always returns undef.
+
+=item $util->test_exists($value)
+
+Return a true value if the location $value exists.
+
+=item $util->trunk_at_head($value)
+
+Dummy. Always returns undef.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Util/Locator/SVN.pm b/lib/FCM/Util/Locator/SVN.pm
new file mode 100644
index 0000000..3ea6477
--- /dev/null
+++ b/lib/FCM/Util/Locator/SVN.pm
@@ -0,0 +1,458 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::Util::Locator::SVN;
+use base qw{FCM::Class::CODE};
+
+use File::Temp;
+use POSIX qw{setlocale LC_ALL};
+use Time::Piece;
+
+our %ACTION_OF = (
+    as_invariant      => \&_as_invariant,
+    can_work_with     => \&_can_work_with,
+    can_work_with_rev => \&_can_work_with_rev,
+    cat               => \&_cat,
+    dir               => \&_dir,
+    export            => \&_export,
+    export_ok         => \&_export_ok,
+    find              => \&_find,
+    origin            => \&_origin,
+    parse             => \&_parse,
+    reader            => \&_reader,
+    read_property     => \&_read_property,
+    test_exists       => \&_test_exists,
+    trunk_at_head     => \&_trunk_at_head,
+);
+
+my %PATTERN_OF = (
+    REVISION       => qr{\A(?:\d+|HEAD|BASE|COMMITTED|PREV|\{[^\}]+\})\z}ixms,
+    TARGET_PEG     => qr{\A(.+?)(?:@([^/@]+))?\z}xms,
+    URL_COMPONENTS => qr{\A([A-Za-z][\w\.\+\-]*://)([^/]*)(/?.*)\z}xms,
+);
+
+my %INFO_OF = (
+    'Path'                => 'path',
+    'URL'                 => 'URL',
+    'Repository Root'     => 'repos_root_URL',
+    'Repository UUID'     => 'repos_UUID',
+    'Revision'            => 'rev',
+    'Node Kind'           => 'kind',
+    'Last Changed Author' => 'last_changed_author',
+    'Last Changed Rev'    => 'last_changed_rev',
+    'Last Changed Date'   => 'last_changed_date',
+    # FIXME: currently omitting lock info and other WC info.
+);
+
+my %INFO_KIND_OF = (directory => 'dir', file => 'file');
+
+my %INFO_MOD_OF = (
+    last_changed_date => \&_svn_info_last_changed_date,
+    kind => sub {exists($INFO_KIND_OF{$_[0]}) ? $INFO_KIND_OF{$_[0]} : $_[0]},
+);
+
+# Creates the class.
+__PACKAGE__->class(
+    {type_util_of => '%', util => '&'},
+    {action_of => \%ACTION_OF},
+);
+
+# Returns the invariant version of $value.
+sub _as_invariant {
+    my ($attrib_ref, $value) = @_;
+    my ($target, $revision) = _parse($attrib_ref, $value);
+    if (!$attrib_ref->{util}->uri_match($target) && !$revision) {
+        return;
+    }
+    $revision ||= $attrib_ref->{util}->uri_match($target) ? 'HEAD' : 'BASE';
+    my %info_of;
+    _svn_info(
+        $attrib_ref,
+        sub {%info_of = %{$_[0]}},
+        [$value],
+    );
+    return _parse_simple($attrib_ref, $info_of{URL}, $info_of{rev});
+}
+
+# Returns true if $value looks like a legitimate SVN target.
+sub _can_work_with {
+    my ($attrib_ref, $value) = @_;
+    my ($scheme) = $attrib_ref->{util}->uri_match($value);
+    if ($scheme && grep {$_ eq $scheme} qw{svn file svn+ssh http https}) {
+        return $value;
+    }
+    my ($target, $revision) = _parse($attrib_ref, $value);
+    my $url;
+    local($@);
+    eval {_svn_info($attrib_ref, sub {$url = $_[0]->{URL}}, [$target])};
+    return $url;
+}
+
+# Returns true if $revision looks like a legitimate SVN revision specifier.
+sub _can_work_with_rev {
+    my ($attrib_ref, $revision) = @_;
+    if (!defined($revision)) {
+        return;
+    }
+    return $revision =~ $PATTERN_OF{REVISION};
+}
+
+# Joins @paths to the end of $value.
+sub _cat {
+    my ($attrib_ref, $value, @paths) = @_;
+    my ($target, $rev) = _parse($attrib_ref, $value);
+    my $is_uri = $attrib_ref->{util}->uri_match($target);
+    $target
+        = $is_uri ? join('/', $target, @paths)
+        :           $attrib_ref->{type_util_of}{fs}->cat($target, @paths)
+        ;
+    _parse_simple($attrib_ref, _tidy($target), $rev);
+}
+
+# Returns the directory containing $value.
+sub _dir {
+    my ($attrib_ref, $value) = @_;
+    my ($target, $revision) = _parse($attrib_ref, $value);
+    if ($attrib_ref->{util}->uri_match($target)) {
+        my ($leader, $auth, $trailer) = $target =~ $PATTERN_OF{URL_COMPONENTS};
+        if (!$trailer) {
+            return _parse($attrib_ref, $target, $revision);
+        }
+        $trailer =~ s{/+ [^/]* \z}{}xms;
+        $target = $leader . ($auth ? $auth : q{}) . $trailer;
+    }
+    else {
+        $target = $attrib_ref->{type_util_of}{fs}->dir($target);
+    }
+    _parse_simple($attrib_ref, $target, $revision);
+}
+
+# Export $value to $dest.
+sub _export {
+    my ($attrib_ref, $value, $dest) = @_;
+    _run_svn_simple($attrib_ref, 'export', [$value, $dest], {quiet => undef});
+}
+
+# Returns true if $value is a URL.
+sub _export_ok {
+    my ($attrib_ref, $value) = @_;
+    $attrib_ref->{util}->uri_match($value);
+}
+
+# Searches directory tree of $value.
+sub _find {
+    my ($attrib_ref, $value, $callback) = @_;
+    if (!$attrib_ref->{util}->uri_match($value)) {
+        return $attrib_ref->{type_util_of}{fs}->find($value, $callback);
+    }
+    _svn_info(
+        $attrib_ref,
+        sub {
+            my ($info_ref) = @_;
+            $callback->(
+                $info_ref->{URL} . '@' . $info_ref->{rev},
+                {   is_dir        => $info_ref->{kind} eq 'dir',
+                    last_mod_rev  => $info_ref->{last_changed_rev},
+                    last_mod_time => $info_ref->{last_changed_date},
+                    ns            => $info_ref->{path},
+                },
+            );
+        },
+        [$value],
+        {recursive => undef},
+    );
+    return 1;
+}
+
+# Returns the URL version of $value.
+sub _origin {
+    my ($attrib_ref, $value) = @_;
+    my ($target, $revision) = _parse($attrib_ref, $value);
+    if ($attrib_ref->{util}->uri_match($target)) {
+        return _parse_simple($attrib_ref, $value);
+    }
+    $revision ||= 'BASE';
+    _as_invariant(
+        $attrib_ref,
+        scalar(_parse_simple($attrib_ref, $target, $revision)),
+    );
+}
+
+# In list context, returns ($target, $revision). In scalar context, returns
+# "$target@$revision".
+sub _parse {
+    my ($attrib_ref, $value, $revision) = @_;
+    my ($target, $peg_revision) = $value =~ $PATTERN_OF{TARGET_PEG};
+    if ($peg_revision) {
+        $revision = $peg_revision;
+    }
+    $target
+        = $attrib_ref->{util}->uri_match($value)
+        ? _tidy($target)
+        : $attrib_ref->{type_util_of}{fs}->parse($target)
+        ;
+    _parse_simple($attrib_ref, $target, $revision);
+}
+
+# Same as _parse, but without _tidy.
+sub _parse_simple {
+    my ($attrib_ref, $value, $revision) = @_;
+    (
+        wantarray() ? ($value, $revision)
+        :             $value . ($revision ? q{@} . $revision : q{})
+    );
+}
+
+# Returns a named property of a Subversion target.
+sub _read_property {
+    my ($attrib_ref, $value, $name) = @_;
+    _run_svn_simple($attrib_ref, 'pg', [$name, $value]);
+}
+
+# Returns a reader (file handle) for a given Subversion target.
+sub _reader {
+    my ($attrib_ref, $value) = @_;
+    my ($target, $revision) = _parse($attrib_ref, $value);
+    if ($attrib_ref->{util}->uri_match($target) || $revision) {
+        return _run_svn_handle($attrib_ref, 'cat', [$value]);
+    }
+    else {
+        return $attrib_ref->{type_util_of}{fs}->reader($target);
+    }
+}
+
+# Helper for _run_svn_*, generates the command.
+sub _run_svn_command {
+    my ($attrib_ref, $key, $args_ref, $option_ref) = @_;
+    $args_ref   ||= [];
+    $option_ref ||= {};
+    my @options;
+    while (my ($key, $value) = each(%{$option_ref})) {
+        push(@options, '--' . $key . (defined($value) ? '=' . $value : q{}));
+    }
+    ['svn', $key, @options, @{$args_ref}];
+}
+
+# Runs "svn", sending standard output to a file handle.
+sub _run_svn_handle {
+    my ($attrib_ref, $key, $args_ref, $option_ref) = @_;
+    local($ENV{LANG}) = $ENV{LANG};
+    if (setlocale(LC_ALL, 'en_GB')) {
+        $ENV{LANG} = 'en_GB';
+    }
+    my $handle = File::Temp->new();
+    my $rc = $attrib_ref->{util}->shell(
+        _run_svn_command(@_),
+        {e => \my($err), o => sub {print($handle $_[0])}},
+    );
+    if ($rc || (!tell($handle) && $err)) { # cat, info, etc may return 0 on err
+        chomp($err);
+        die("$err\n");
+    }
+    seek($handle, 0, 0);
+    return $handle;
+}
+
+# Runs a simple "svn" command.
+sub _run_svn_simple {
+    my ($attrib_ref, $key, $args_ref, $option_ref) = @_;
+    local($ENV{LANG}) = $ENV{LANG};
+    if (setlocale(LC_ALL, 'en_GB')) {
+        $ENV{LANG} = 'en_GB';
+    }
+    my $value_hash_ref
+        = $attrib_ref->{util}->shell_simple(_run_svn_command(@_));
+    if ($value_hash_ref->{rc}) {
+        die($value_hash_ref);
+    }
+    $value_hash_ref->{o};
+}
+
+# Runs "svn info".
+sub _svn_info {
+    my ($attrib_ref, $callback_ref, $args_ref, $option_ref) = @_;
+    my $handle = _run_svn_handle($attrib_ref, 'info', $args_ref, $option_ref);
+    my %hash;
+    while (my $line = readline($handle)) {
+        chomp($line);
+        if ($line) {
+            my ($key, $value) = split(qr{:\s*}msx, $line, 2);
+            if (exists($INFO_OF{$key})) {
+                my $id = $INFO_OF{$key};
+                $hash{$id}
+                    = exists($INFO_MOD_OF{$id}) ? $INFO_MOD_OF{$id}->($value)
+                    :                             $value
+                    ;
+            }
+        }
+        else {
+            $callback_ref->(\%hash);
+        }
+    }
+}
+
+# Parse last changed date from "svn info".
+sub _svn_info_last_changed_date {
+    my $text = (split(qr{\s+\(}msx, $_[0], 2))[0];
+    my $head = Time::Piece->strptime(substr($text, 0, -6), '%Y-%m-%d %H:%M:%S');
+    my $tail = substr($text, -5);
+    my ($tz_sign, $tz_h, $tz_m) = $tail =~ qr{([\-\+])(\d\d)(\d\d)}msx;
+    $head->epoch() - int($tz_sign . 1) * ($tz_h * 3600 + $tz_m * 60);
+}
+
+# Return a true value if the location $value exists.
+sub _test_exists {
+    my ($attrib_ref, $value) = @_;
+    my $url;
+    eval {_svn_info($attrib_ref, sub {$url = $_[0]->{URL}}, [$value])};
+    return $url;
+}
+
+# Returns a tidied version of a Subversion URL.
+sub _tidy {
+    my ($url) = @_;
+    my ($leader, $auth, $trailer) = $url =~ $PATTERN_OF{URL_COMPONENTS};
+    if (!$trailer) {
+        return $url;
+    }
+    my @tidied_names;
+    my %handler_of = (
+        q{}   => sub {push(@tidied_names, $_[0])},
+        q{.}  => sub {},
+        q{..} => sub {if (@tidied_names > 1) {pop(@tidied_names)}},
+    );
+    for my $name (split(qr{/+}xms, $trailer)) {
+        my $handler
+            = exists($handler_of{$name}) ? $handler_of{$name} : $handler_of{q{}};
+        $handler->($name);
+    }
+    return $leader . ($auth ? $auth : q{}) . join(q{/}, @tidied_names);
+}
+
+# Returns trunk at HEAD for a URL.
+sub _trunk_at_head {
+    my ($attrib_ref, $target) = @_;
+    if (!$attrib_ref->{util}->uri_match($target)) {
+        return;
+    }
+    _cat($attrib_ref, $target, 'trunk at HEAD');
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Util::Locator::SVN
+
+=head1 SYNOPSIS
+
+    use FCM::Util;
+    $util = FCM::Util->new(\%attrib);
+    $reader = $util->loc_reader($locator);
+
+
+=head1 DESCRIPTION
+
+This is part of L<FCM::Util|FCM::Util>. Provides utilities for Subversion
+targets.
+
+=head1 ATTRIBUTES
+
+=over 4
+
+=item util
+
+The L<FCM::Util|FCM::Util> object that initialised this object.
+
+=head1 METHODS
+
+=over 4
+
+=item $util->as_invariant($value)
+
+Returns the invariant version of $value. For example, if the current HEAD
+revision is 1234, and $value is C<svn://foo/bar/baz> or
+C<svn://foo/bar/baz@HEAD>, it will return C<svn://foo/bar/baz@1234>.
+
+=item $util->can_work_with($value)
+
+Returns the URL form of $value (true) if $value is a valid SVN target.
+
+=item $util->can_work_with_rev($revision)
+
+Returns true if $revision looks like a legitimate SVN revision specifier.
+
+=item $util->cat($value, at paths)
+
+Join @paths to the end of $value.
+
+=item $util->dir($value)
+
+Returns the directory name of $value.
+
+=item $util->export($value,$dest)
+
+Exports a clean directory tree of $value to $dest.
+
+=item $util->export_ok($value)
+
+Returns true if $value is a URL. (It is not safe to export a working copy.)
+
+=item $util->find($value,$callback)
+
+Searches directory tree of $value.
+
+=item $util->origin($value)
+
+Returns the URL version of $value.
+
+=item $util->parse($value,$revision)
+
+In scalar context, returns a string in C<TARGET at REV> for $value. In list
+context, given C<TARGET at REV> returns (C<TARGET>, C<REV>). If $value has a peg
+revision, it overrides the specified $revision.
+
+=item $util->reader($value)
+
+Returns a file handle for reading the content in $value, if possible.
+
+=item $util->read_property($value,$name)
+
+Returns the value of a property $name of $value.
+
+=item $util->test_exists($value)
+
+Return a true value if the location $value exists.
+
+=item $util->trunk_at_head($value)
+
+Returns "$value/trunk at HEAD' if $value is a URI or undef otherwise.
+
+=back
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Util/Reporter.pm b/lib/FCM/Util/Reporter.pm
new file mode 100644
index 0000000..492187c
--- /dev/null
+++ b/lib/FCM/Util/Reporter.pm
@@ -0,0 +1,382 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::Util::Reporter;
+use base qw{FCM::Class::CODE};
+
+use Scalar::Util qw{reftype};
+
+use constant {TYPE_OUT => 1, TYPE_ERR => 2};
+
+use constant {  DEFAULT => 1,
+    FAIL  => 0, WARN    => 1,
+    QUIET => 0, LOW     => 1, MEDIUM => 2, HIGH => 3, DEBUG => 4,
+};
+
+use constant {
+    PREFIX_DONE => q{[done] },
+    PREFIX_FAIL => q{[FAIL] },
+    PREFIX_INFO => q{[info] },
+    PREFIX_INIT => q{[init] },
+    PREFIX_NULL => q{},
+    PREFIX_QUIT => q{[quit] },
+    PREFIX_WARN => q{[WARN] },
+};
+
+# Creates the class.
+__PACKAGE__->class(
+    {ctx_of => '%'},
+    {   init => sub {
+            my ($attrib_ref) = @_;
+            %{$attrib_ref->{ctx_of}} = (
+                stderr => FCM::Util::Reporter::Context->new_err(),
+                stdout => FCM::Util::Reporter::Context->new(),
+            );
+        },
+        action_of => {
+            add_ctx           => \&_add_ctx,
+            del_ctx           => \&_del_ctx,
+            get_ctx           => \&_get_ctx,
+            get_ctx_of_stderr => sub {$_[0]->{ctx_of}->{stderr}},
+            get_ctx_of_stdout => sub {$_[0]->{ctx_of}->{stdout}},
+            report            => \&_report,
+        }
+    },
+);
+
+# Adds a named reporter context.
+sub _add_ctx {
+    my ($attrib_ref, $key, @args) = @_;
+    if (exists($attrib_ref->{ctx_of}->{$key})) {
+        return;
+    }
+    $attrib_ref->{ctx_of}->{$key} = FCM::Util::Reporter::Context->new(@args);
+}
+
+# Deletes a named reporter context.
+sub _del_ctx {
+    my ($attrib_ref, $key) = @_;
+    if (!exists($attrib_ref->{ctx_of}->{$key})) {
+        return;
+    }
+    delete($attrib_ref->{ctx_of}->{$key});
+}
+
+# Returns a named reporter context.
+sub _get_ctx {
+    my ($attrib_ref, $key) = @_;
+    if (!exists($attrib_ref->{ctx_of}->{$key})) {
+        return;
+    }
+    $attrib_ref->{ctx_of}->{$key};
+}
+
+# Reports message.
+sub _report {
+    my ($attrib_ref, @args) = @_;
+    if (!@args) {
+        return;
+    }
+    my %option = (
+        delimiter => "\n",
+        level     => DEFAULT,
+        prefix    => undef,
+        type      => TYPE_OUT,
+    );
+    if (ref($args[0]) && reftype($args[0]) eq 'HASH') {
+        %option = (%option, %{shift(@args)});
+    }
+    # Auto remove ctx with closed file handle
+    while (my ($key, $ctx) = each(%{$attrib_ref->{ctx_of}})) {
+        if (!defined(fileno($ctx->get_handle()))) {
+            delete($attrib_ref->{ctx_of}->{$key});
+        }
+    }
+    # Selects handles
+    my @ctx_and_prefix_list
+        =   map  {
+                my $prefix = defined($option{prefix})
+                    ? $option{prefix} : $_->get_prefix();
+                if (ref($prefix) && reftype($prefix) eq 'CODE') {
+                    $prefix = $prefix->($option{level}, $option{type});
+                }
+                [$_, $prefix],
+            }
+            grep {  (!$_->get_type() || $_->get_type() eq $option{type})
+                &&  $_->get_verbosity() >= $option{level}
+            }
+            values(%{$attrib_ref->{ctx_of}});
+    if (!@ctx_and_prefix_list) {
+        return;
+    }
+    for my $arg (@args) {
+        for (@ctx_and_prefix_list) {
+            my ($ctx, $prefix) = @{$_};
+            my $handle = $ctx->get_handle();
+            if ($option{delimiter}) {
+                for my $item (
+                    map {grep {$_ ne "\n"} split(qr{(\n)}msx)} (
+                          !ref($arg)               ? ($arg)
+                        : reftype($arg) eq 'ARRAY' ? @{$arg}
+                        : reftype($arg) eq 'CODE'  ? $arg->($ctx->get_verbosity())
+                        :                            ($arg)
+                    )
+                ) {
+                    print({$handle} $prefix . $item . $option{delimiter});
+                }
+            }
+            else {
+                print({$handle} $arg);
+            }
+        }
+    }
+    1;
+}
+
+# ------------------------------------------------------------------------------
+package FCM::Util::Reporter::Context;
+use base qw{FCM::Class::HASH};
+
+# Creates the class.
+__PACKAGE__->class(
+    {   handle    => {isa => '*', default => \*STDOUT                     },
+        prefix    => {            default => sub {\&_prefix}              },
+        type      => {isa => '$', default => FCM::Util::Reporter->TYPE_OUT},
+        verbosity => {isa => '$', default => FCM::Util::Reporter->DEFAULT },
+    },
+);
+
+# Returns a new reporter context to STDERR.
+sub new_err {
+    my ($class, $attrib_ref) = @_;
+    $class->new({
+        handle => \*STDERR,
+        type   => FCM::Util::Reporter->TYPE_ERR,
+        (defined($attrib_ref) ? %{$attrib_ref} : ()),
+    });
+}
+
+# The default prefix function.
+sub _prefix {
+    my ($level, $type) = @_;
+      $type eq FCM::Util::Reporter->TYPE_OUT ? FCM::Util::Reporter->PREFIX_INFO
+    : $level > FCM::Util::Reporter->FAIL     ? FCM::Util::Reporter->PREFIX_WARN
+    :                                          FCM::Util::Reporter->PREFIX_FAIL
+    ;
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Reporter
+
+=head1 SYNOPSIS
+
+    use FCM::Util::Reporter;
+    $reporter = FCM::Util::Reporter->new({verbosity => $verbosity});
+    $reporter->($message);
+    $reporter->(\@messages);
+    $reporter->(sub {return @some_strings});
+    $reporter->({level => $reporter->MEDIUM}, $message);
+
+=head1 DESCRIPTION
+
+A simple message reporter.
+
+This module is part of L<FCM::Util|FCM::Util>. See also the description of the
+$u->report() method in L<FCM::Util|FCM::Util>.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Returns a new instance of this class, which is a CODE reference. %attrib can
+contain the following:
+
+=over 4
+
+=item ctx_of
+
+A HASH containing a map to the named reporter contexts. At initialisation, a new
+ctx for "stdout" and a new ctx for "stderr" is created automatically.
+
+=back
+
+=item $reporter->add_ctx($key,%option)
+
+Creates a new reporter context, and adds it to the ctx_of HASH, if a context
+with the same $key does not already exist. The %option is given to the
+constructir of L</FCM::Util::Reporter::Context>. Return the context on success.
+
+=item $reporter->del_ctx($key)
+
+Removes a new reporter context named $key. Return the context on success.
+
+=item $reporter->get_ctx($key)
+
+Returns a named reporter context L</FCM::Util::Reporter::Context>.
+
+=item $reporter->get_ctx_of_stderr()
+
+Shorthand for $reporter->get_ctx('stderr').
+
+=item $reporter->get_ctx_of_stdout()
+
+Shorthand for $reporter->get_ctx('stdout').
+
+=item $reporter->report(\%option,$message)
+
+Reports the message. If %option is not given, reports using the default options.
+In the form, the following %options can be specified:
+
+=over 4
+
+=item delimiter
+
+The delimiter of each message in the list. The default is "\n". If the delimiter
+is set to the empty string, the items in $message will be treated as raw
+strings, i.e. it will also ignore any "prefix" options.
+
+=item level
+
+The level of the current message. The default is DEFAULT.
+
+=item prefix
+
+The message prefix. It can be a string or a CODE reference. If it is a string,
+it is simply preprended to the message. If it is a code reference, it is calls
+as $prefix_ref->($option{level}, $option{type}), and its result (if defined) is
+prepended to the message.
+
+=item type
+
+The message type. It can be REPORT_ERR or REPORT_OUT (default).
+
+=back
+
+=back
+
+=head1 CONSTANTS
+
+=over 4
+
+=item $reporter->FAIL, $reporter->QUIET
+
+The verbosity level 0.
+
+=item $reporter->DEFAULT, $reporter->LOW, $reporter->WARN
+
+The verbosity level 1.
+
+=item $reporter->MEDIUM
+
+The verbosity level 2.
+
+=item $reporter->HIGH
+
+The verbosity level 3.
+
+=item $reporter->DEBUG
+
+The verbosity level 4.
+
+=item $reporter->PREFIX_DONE
+
+The prefix for a task "done" message.
+
+=item $reporter->PREFIX_FAIL
+
+The prefix for a fatal error message.
+
+=item $reporter->PREFIX_INFO
+
+The prefix for an "info" message.
+
+=item $reporter->PREFIX_INIT
+
+The prefix for a task "init" message.
+
+=item $reporter->PREFIX_NULL
+
+An empty string.
+
+=item $reporter->PREFIX_QUIT
+
+The prefix for a quit/abort message.
+
+=item $reporter->PREFIX_WARN
+
+The prefix for a warning message.
+
+=item $reporter->REPORT_ERR
+
+The message type for exception message.
+
+=item $reporter->REPORT_OUT
+
+The message type for info message.
+
+=back
+
+=head1 FCM::Util::Reporter::Context
+
+An instance of this class represents the context for a reporter for the
+L<FCM::Util->report()|FCM::Util>. This class is a sub-class of
+L<FCM::Class::HASH|FCM::Class::HASH>. It has the following attributes:
+
+=over 4
+
+=item handle
+
+The file handle for info messages. (Default=\*STDOUT)
+
+=item prefix
+
+The message prefix. It can be a string or a CODE reference. If it is a string,
+it is simply preprended to the message. If it is a code reference, it is calls
+as $prefix_ref->($option{level}, $option{type}), and its result (if defined) is
+prepended to the message. The default is a CODE that returns PREFIX_INFO for
+TYPE_OUT messages, PREFIX_WARN for TYPE_ERR messages at WARN level or above or
+PREFIX_FAIL for TYPE_ERR messages at FAIL level.
+
+=item type
+
+Reporter type. (Default=TYPE_OUT)
+
+=item verbosity
+
+The verbosity of the reporter. Only messages at a level above or equal to the
+verbosity will be reported. The default is DEFAULT.
+
+=back
+
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Util/Shell.pm b/lib/FCM/Util/Shell.pm
new file mode 100644
index 0000000..1b50c22
--- /dev/null
+++ b/lib/FCM/Util/Shell.pm
@@ -0,0 +1,306 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM::Util::Shell;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::Event;
+use FCM::Util::Exception;
+use File::Spec::Functions qw{catfile file_name_is_absolute path};
+use IPC::Open3 qw{open3};
+use List::Util qw{first};
+use Scalar::Util qw{reftype};
+use Text::ParseWords qw{shellwords};
+
+our $BUFFER_SIZE = 4096;  # default buffer size
+our $TIME_OUT    = 0.005; # default time out for selecting a file handle
+
+my $E = 'FCM::Util::Exception';
+my %FUNCTION_OF = (e => \&_do_r, i => \&_do_w, o => \&_do_r);
+my @IOE = qw{i o e};
+my %ACTION_FUNC_FOR
+    = (e => \&_action_func_r, i => \&_action_func_w, o => \&_action_func_r);
+
+# Creates the class.
+__PACKAGE__->class(
+    {   buffer_size => {isa => '$', default => $BUFFER_SIZE},
+        time_out    => {isa => '$', default => $TIME_OUT},
+        util        => '&',
+    },
+    {   action_of => {
+            invoke        => \&_invoke,
+            invoke_simple => \&_invoke_simple,
+            which         => \&_which,
+        },
+    },
+);
+
+# Returns a CODE to deal with non-CODE read action.
+sub _action_func_r {
+    my ($arg_ref) = @_;
+    ${$arg_ref} ||= q{};
+    sub {${$arg_ref} .= $_[0]};
+}
+
+# Returns a CODE to deal with non-CODE write action.
+sub _action_func_w {
+    my ($arg_ref) = @_;
+    my @inputs
+        = ref($arg_ref) && reftype($arg_ref) eq 'ARRAY'  ? @{$arg_ref}
+        : ref($arg_ref) && reftype($arg_ref) eq 'SCALAR' ? (${$arg_ref})
+        :                                                  ()
+        ;
+    sub {shift(@inputs)};
+}
+
+# Gets output $value from a selected handle, and invokes $action->($value).
+sub _do_r {
+    my ($attrib_ref, $ctx) = @_;
+    my $n_bytes;
+    while (
+        my @handles = $ctx->get_select()->can_read($attrib_ref->{time_out})
+    ) {
+        my ($handle) = @handles;
+        my $buffer = q{};
+        my $n = sysread($handle, $buffer, $attrib_ref->{buffer_size});
+        if (!defined($n)) {
+            return;
+        }
+        $n_bytes += $n;
+        if ($n == 0) {
+            close($handle) || return;
+            return 0;
+        }
+        $ctx->get_action()->($buffer);
+    }
+    defined($n_bytes) ? $n_bytes : -1;
+}
+
+# Gets input from $action->() and writes to a selected handle if possible.
+# Handles buffering of STDIN to the command.
+sub _do_w {
+    my ($attrib_ref, $ctx) = @_;
+    my $n_bytes;
+    while (
+        my @handles = $ctx->get_select()->can_write($attrib_ref->{time_out})
+    ) {
+        my ($handle) = @handles;
+        if (!$ctx->get_buf()) {
+            $ctx->set_buf($ctx->get_action()->());
+            if (!defined($ctx->get_buf())) {
+                close($handle) || return;
+                return 0;
+            };
+            $ctx->set_buf_length(length($ctx->get_buf()));
+            $ctx->set_buf_offset(0);
+        }
+        my $n = syswrite(
+            $handle,
+            $ctx->get_buf(),
+            $attrib_ref->{buffer_size},
+            $ctx->get_buf_offset(),
+        );
+        if (!defined($n)) {
+            return;
+        }
+        $n_bytes += $n;
+        $ctx->set_buf_offset($ctx->get_buf_offset() + $n);
+        if ($ctx->get_buf_offset() >= $ctx->get_buf_length()) {
+            $ctx->set_buf(undef);
+            $ctx->set_buf_length(0);
+            $ctx->set_buf_offset(0);
+        }
+    }
+    defined($n_bytes) ? $n_bytes : -1;
+}
+
+# Invokes a command.
+sub _invoke {
+    my ($attrib_ref, $command_ref, $action_ref) = @_;
+    # Ensure that the command is an ARRAY
+    if (!ref($command_ref)) {
+        $command_ref = [shellwords($command_ref)];
+    }
+    # Check that the command exists in the PATH
+    if (!_which($attrib_ref, $command_ref->[0])) {
+        return $E->throw($E->SHELL_WHICH, $command_ref);
+    }
+    # Sets up the STDIN, STDOUT and STDERR to the command
+    my %ctx_of = map {($_, FCM::Util::Shell::Context->new())} @IOE;
+    $action_ref ||= {};
+    while (my ($key, $action) = each(%{$action_ref})) {
+        if (exists($ctx_of{$key})) {
+            if (reftype($action) eq 'CODE') {
+                $ctx_of{$key}->set_action($action);
+            }
+            else {
+                $ctx_of{$key}->set_action($ACTION_FUNC_FOR{$key}->($action));
+            }
+        }
+    }
+    # Calls the command with open3
+    my $timer = $attrib_ref->{util}->timer();
+    my $pid = eval {
+        open3((map {$ctx_of{$_}->get_handle()} @IOE), @{$command_ref});
+    };
+    if (my $e = $@) {
+        return $E->throw($E->SHELL_OPEN3, $command_ref, $e);
+    }
+    # Handles input/output of the command
+    for my $ctx (values(%ctx_of)) {
+        $ctx->get_select()->add($ctx->get_handle());
+    }
+    while (keys(%ctx_of)) {
+        while (my ($key, $ctx) = each(%ctx_of)) {
+            my $status = $FUNCTION_OF{$key}->($attrib_ref, $ctx);
+            if (!defined($status)) {
+                return $E->throw($E->SHELL_OS, $command_ref, $!);
+            }
+            if (!$status) {
+                delete($ctx_of{$key});
+            }
+        }
+    }
+    # Wait for command to finish
+    waitpid($pid, 0);
+    my $rc = $?;
+    $attrib_ref->{util}->event(
+        FCM::Context::Event->SHELL, $command_ref, $rc, $timer->(),
+    );
+    # Handles exceptions and signals
+    if ($rc) {
+        if ($rc == -1) {
+            return $E->throw($E->SHELL_OS, $command_ref, $!);
+        }
+        if ($rc & 127) {
+            return $E->throw($E->SHELL_SIGNAL, $command_ref, $rc & 127);
+        }
+    }
+    return $rc >> 8;
+}
+
+# Wraps _invoke.
+sub _invoke_simple {
+    my ($attrib_ref, $command_ref) = @_;
+    my ($e, $o);
+    my $rc = _invoke($attrib_ref, $command_ref, {e => \$e, o => \$o});
+    return {e => $e, o => $o, rc => $rc};
+}
+
+# Returns the full path to the command $name, if it exists in the PATH.
+sub _which {
+    my ($attrib_ref, $name) = @_;
+    if (file_name_is_absolute($name)) {
+        return $name;
+    }
+    use filetest 'access';
+    first {-f $_ && -x _} map {catfile($_, $name)} path();
+    no filetest 'access';
+}
+
+# ------------------------------------------------------------------------------
+package FCM::Util::Shell::Context;
+use base qw{FCM::Class::HASH};
+
+use IO::Select;
+use Symbol qw{gensym};
+
+# A context to hold the information for the command's STDIN, STDOUT or STDERR.
+# action => CODE to call to get more STDIN for the command or to send
+#           STDOUT/STDERR to when possible.
+# buf*   => A buffer (and its length and the current offset) to hold the STDIN
+#           that is yet to be written to the command.
+# handle => The command STDIN, STDOUT or STDERR.
+# select => The IO::Select object that tells us whether the handle is ready for
+#           I/O or not.
+__PACKAGE__->class(
+    {   action     => {isa => '&'},
+        buf        => {isa => '$'},
+        buf_length => {isa => '$'},
+        buf_offset => {isa => '$'},
+        handle     => {isa => '*', default => \&gensym},
+        'select'   => {isa => 'IO::Select', default => sub {IO::Select->new()}},
+    },
+);
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Util::Shell
+
+=head1 SYNOPSIS
+
+    use FCM::Util;
+    $util = FCM::Util->new(\%attrib);
+    %action_of = {e => \&e_handler, i => \&i_handler, o => \&o_handler};
+    $rc = $util->shell(\@command, \%action_of);
+    %value_of = %{$util->shell_simple(\@command)};
+
+=head1 DESCRIPTION
+
+Wraps L<IPC::Open3|IPC::Open3> to provide an interface driven by callbacks.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new(\%attrib)
+
+Returns a new instance. The attributes that can be specified in %attrib are:
+
+=over 4
+
+=item {buffer_size}
+
+The size of the read buffer for reading from the standard output and standard
+error output of the command. The default is 4096.
+
+=item {time_out}
+
+The time to wait when selecting a file handle. The default is 0.001.
+
+=item {util}
+
+A CODE reference. The L<FCM::Util|FCM::Util> object that initialised this
+instance.
+
+=back
+
+=back
+
+See the description of the shell(), shell_simpl() and shell_which() methods in
+L<FCM::Util|FCM::Util> for detail.
+
+=head1 SEE ALSO
+
+L<IPC::Open3|IPC::Open3>
+
+Inspired by the CPAN module L<IPC::Cmd|IPC::Cmd> and friends.
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM/Util/TaskRunner.pm b/lib/FCM/Util/TaskRunner.pm
new file mode 100644
index 0000000..09cc7cb
--- /dev/null
+++ b/lib/FCM/Util/TaskRunner.pm
@@ -0,0 +1,380 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+# ------------------------------------------------------------------------------
+package FCM::Util::TaskRunner;
+use base qw{FCM::Class::CODE};
+
+my $P = 'FCM::Util::TaskRunner::Parallel';
+my $S = 'FCM::Util::TaskRunner::Serial';
+
+__PACKAGE__->class({util => '&'}, {action_of => {main => \&_main}});
+
+sub _main {
+    my ($attrib_ref, $action_ref, $n_workers) = @_;
+    $n_workers ||= 1;
+    my $class = $n_workers > 1 ? $P : $S;
+    $attrib_ref->{runner} = $class->new({
+        action    => $action_ref,
+        n_workers => $n_workers,
+        util      => $attrib_ref->{util},
+    });
+}
+
+# ------------------------------------------------------------------------------
+package FCM::Util::TaskRunner::Serial;
+use base qw{FCM::Class::CODE};
+
+__PACKAGE__->class(
+    {action => '&', util => '&'},
+    {action_of => {destroy => sub {}, main => \&_main}},
+);
+
+sub _main {
+    my ($attrib_ref, $get_ref, $put_ref) = @_;
+    my $n_done = 0;
+    while (my $task = $get_ref->()) {
+        my $timer = $attrib_ref->{util}->timer();
+        eval {
+            $task->set_state($task->ST_WORKING);
+            $attrib_ref->{action}->($task->get_ctx());
+            $task->set_state($task->ST_OK);
+        };
+        if ($@) {
+            $task->set_error($@);
+            $task->set_state($task->ST_FAILED);
+        }
+        $task->set_elapse($timer->());
+        $put_ref->($task);
+        ++$n_done;
+    }
+    $n_done;
+}
+
+# ------------------------------------------------------------------------------
+package FCM::Util::TaskRunner::Parallel;
+use base qw{FCM::Class::CODE};
+
+use FCM::Context::Event;
+use IO::Select;
+use IO::Socket;
+use List::Util qw{first};
+use POSIX qw{WNOHANG};
+use Socket qw{AF_UNIX SOCK_STREAM PF_UNSPEC};
+use Storable qw{freeze thaw};
+
+# Package name of worker event and state
+my $CTX_EVENT = 'FCM::Context::Event';
+my $CTX_STATE = 'FCM::Util::TaskRunner::WorkerState';
+
+# Length of a packed long integer
+my $LEN_OF_LONG = length(pack('N', 0));
+
+# Time out for polling sockets to child processes
+my $TIME_OUT = 0.05;
+
+# Creates the class.
+__PACKAGE__->class(
+    {   action        => '&',
+        n_workers     => '$',
+        worker_states => '@',
+        util          => '&',
+    },
+    {init => \&_init, action_of => {destroy => \&_destroy, main => \&_main}},
+);
+
+# Destroys the child processes.
+sub _destroy {
+    my $attrib_ref = shift();
+    local($SIG{CHLD}) = 'IGNORE';
+    my $select = IO::Select->new();
+    my @worker_states = @{$attrib_ref->{worker_states}};
+    for my $worker_state (@worker_states) {
+        $select->add($worker_state->get_socket());
+    }
+    # TBD: reads $socket for any left over event etc?
+    for my $socket ($select->can_write(0)) {
+        my $worker_state = first {$_->get_socket() eq $socket} @worker_states;
+        _item_send($socket);
+        close($socket);
+        waitpid($worker_state->get_pid(), 0);
+    }
+    while (waitpid(-1, WNOHANG) > 0) {
+    }
+    $attrib_ref->{util}->event(
+        FCM::Context::Event->TASK_WORKERS, 'destroy', $attrib_ref->{n_workers},
+    );
+    1;
+}
+
+# On initialisation.
+sub _init {
+    my $attrib_ref = shift();
+    for my $i (1 .. $attrib_ref->{n_workers}) {
+        my ($from_boss, $from_worker)
+            = IO::Socket->socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC);
+        if (!defined($from_boss) || !defined($from_worker)) {
+            die("socketpair: $!");
+        }
+        $from_worker->autoflush(1);
+        $from_boss->autoflush(1);
+        if (my $pid = fork()) {
+            # I am the boss
+            if ($pid < 0) {
+                die("fork: $!");
+            }
+            local($SIG{CHLD}, $SIG{INT}, $SIG{KILL}, $SIG{TERM}, $SIG{XCPU});
+            for my $key (qw{CHLD INT KILL TERM XCPU}) {
+                local($SIG{$key}) = sub {_destroy($attrib_ref, @_); die($!)};
+            }
+            close($from_worker);
+            push(
+                @{$attrib_ref->{worker_states}},
+                $CTX_STATE->new($pid, $from_boss),
+            );
+        }
+        elsif (defined($pid)) {
+            # I am a worker
+            close($from_boss);
+            $attrib_ref->{worker_states} = [];
+            open(STDIN, '/dev/null');
+            # Ensures that events are sent back to the boss process
+            my $util_of_event = bless(
+                sub {_item_send($from_worker, @_)},
+                __PACKAGE__ . '::WorkerEvent',
+            );
+            no strict 'refs';
+            *{__PACKAGE__ . '::WorkerEvent::main'}
+                = sub {my $self = shift(); $self->(@_)};
+            use strict 'refs';
+            $attrib_ref->{util}->util_of_event($util_of_event);
+            _worker(
+                $from_worker,
+                $attrib_ref->{action},
+                $attrib_ref->{util},
+            );
+            close($from_worker);
+            exit();
+        }
+        else {
+            die("fork: $!");
+        }
+    }
+    $attrib_ref->{util}->event(
+        FCM::Context::Event->TASK_WORKERS, 'init', $attrib_ref->{n_workers},
+    );
+}
+
+# Main function of the class.
+sub _main {
+    my ($attrib_ref, $get_ref, $put_ref) = @_;
+    my $n_done = 0;
+    my $n_wait = 0;
+    my $done_something = 1;
+    my $get_task_ref = _get_task_func($get_ref, $attrib_ref->{n_workers});
+    my $select = IO::Select->new();
+    my @worker_states = @{$attrib_ref->{worker_states}};
+    for my $worker_state (@worker_states) {
+        $select->add($worker_state->get_socket());
+    }
+    while ($n_wait || $done_something) {
+        $done_something = 0;
+        # Handles tasks back from workers
+        while (my @sockets = $select->can_read($TIME_OUT)) {
+            for my $socket (@sockets) {
+                my $worker_state
+                    = first {$socket eq $_->get_socket()} @worker_states;
+                my $item = _item_receive($socket);
+                if (defined($item)) {
+                    $done_something = 1;
+                    if ($item->isa('FCM::Context::Event')) {
+                        # Item is only an event, handles it
+                        $attrib_ref->{util}->event($item);
+                    }
+                    else {
+                        # Sends something back to the worker immediately
+                        if (defined(my $task = $get_task_ref->())) {
+                            _item_send($socket, $task);
+                        }
+                        else {
+                            --$n_wait;
+                            $worker_state->set_idle(1);
+                        }
+                        $put_ref->($item);
+                        ++$n_done;
+                    }
+                }
+            }
+        }
+        # Sends something to the idle workers
+        my @idle_worker_states = grep {$_->get_idle()} @worker_states;
+        if (@idle_worker_states) {
+            for my $worker_state (@idle_worker_states) {
+                if (defined(my $task = $get_task_ref->())) {
+                    _item_send($worker_state->get_socket(), $task);
+                    ++$n_wait;
+                    $done_something = 1;
+                    $worker_state->set_idle(0);
+                }
+            }
+        }
+        else {
+            $get_task_ref->(); # only adds more tasks to queue
+        }
+    }
+    $n_done;
+}
+
+# Returns a function to fetch more tasks into a queue.
+sub _get_task_func {
+    my ($get_ref, $n_workers) = @_;
+    my $max_n_in_queue = $n_workers * 2;
+    my @queue;
+    sub {
+        while (@queue < $max_n_in_queue && defined(my $task = $get_ref->())) {
+            push(@queue, $task);
+        }
+        if (!defined(wantarray())) {
+            return;
+        }
+        shift(@queue);
+    };
+}
+
+# Receives an item from a socket.
+sub _item_receive {
+    my ($socket) = @_;
+    my $len_of_data = unpack('N', _item_travel($socket, $LEN_OF_LONG));
+    $len_of_data ? thaw(_item_travel($socket, $len_of_data)) : undef;
+}
+
+# Sends an item to a socket.
+sub _item_send {
+    my ($socket, $item) = @_;
+    my $item_as_data = $item ? freeze($item) : q{};
+    my $message = pack('N', length($item_as_data)) . $item_as_data;
+    _item_travel($socket, length($message), $message);
+}
+
+# Helper for _item_receive/_item_send.
+sub _item_travel {
+    my ($socket, $len_to_travel, $data) = @_;
+    my $action
+        = defined($data) ? sub {syswrite($socket, $data, $_[0], $_[1])}
+        :                  sub {sysread( $socket, $data, $_[0], $_[1])}
+        ;
+    $data ||= q{};
+    my $n_bytes = 0;
+    while ($n_bytes < $len_to_travel) {
+        my $len_remain = $len_to_travel - $n_bytes;
+        my $n = $action->($len_remain, $n_bytes);
+        if (!defined($n)) {
+            die($!);
+        }
+        $n_bytes += $n;
+    }
+    $data;
+}
+
+# Performs the function of a worker. Receives a task. Actions it. Sends it back.
+sub _worker {
+    my ($socket, $action, $util) = @_;
+    while (defined(my $task = _item_receive($socket))) {
+        my $timer = $util->timer();
+        eval {
+            $task->set_state($task->ST_WORKING);
+            $action->($task->get_ctx());
+            $task->set_state($task->ST_OK);
+        };
+        if ($@) {
+            $task->set_state($task->ST_FAILED);
+            $task->set_error($@);
+        }
+        $task->set_elapse($timer->());
+        _item_send($socket, $task);
+    }
+    1;
+}
+
+# ------------------------------------------------------------------------------
+# The state of a worker.
+package FCM::Util::TaskRunner::WorkerState;
+use base qw{FCM::Class::HASH};
+
+__PACKAGE__->class(
+    {   'idle'   => {isa => '$', default => 1}, # worker is idle?
+        'pid'    => '$',                        # worker's PID
+        'socket' => '*',                        # socket to worker
+    },
+    {   init_attrib => sub {
+            my ($pid, $socket) = @_;
+            {'pid' => $pid, 'socket' => $socket};
+        },
+    },
+);
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM::Util::TaskRunner
+
+=head1 SYNOPSIS
+
+    use FCM::Context::Task;
+    use FCM::Util;
+    my $util = FCM::Util->new(\%attrib);
+    # ... time passes
+    my $runner = $util->task_runner(\&do_task, 4); # run with 4 workers
+    # ... time passes
+    my $get_ref = sub {
+        # ... an iterator to return an FCM::Context::Task object
+        # one at a time, returns undef if there is no currently available task
+    };
+    my $put_ref = sub {
+        my ($task) = @_;
+        # ... callback at end of each task
+    };
+    my $n_done = $runner->main($get_ref, $put_ref);
+
+=head1 DESCRIPTION
+
+This module is part of L<FCM::Util|FCM::Util>. See the description of the
+task_runner() method for details.
+
+An instance of this class is a runner of tasks. It can be configured to work in
+serial (default) or parallel. The class is a sub-class of
+L<FCM::Class::CODE|FCM::Class::CODE>.
+
+=head1 SEE ALSO
+
+This module is inspired by the CPAN modules Parallel::Fork::BossWorker and
+Parallel::Fork::BossWorkerAsync.
+
+L<FCM::Context::Task|FCM::Context::Task>,
+L<FCM::Util::TaskManager|FCM::Util::TaskManager>
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM1/Base.pm b/lib/FCM1/Base.pm
new file mode 100644
index 0000000..eb60c97
--- /dev/null
+++ b/lib/FCM1/Base.pm
@@ -0,0 +1,125 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::Base
+#
+# DESCRIPTION
+#   This is base class for all FCM OO packages.
+#
+# ------------------------------------------------------------------------------
+
+package FCM1::Base;
+
+# Standard pragma
+use strict;
+use warnings;
+
+use FCM1::Config;
+
+my @scalar_properties = (
+  'config', # instance of FCM1::Config, configuration setting
+);
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::Base->new;
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::Base class.
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  my $self  = {};
+  for (@scalar_properties) {
+    $self->{$_} = exists $args{uc ($_)} ? $args{uc ($_)} : undef;
+  }
+
+  bless $self, $class;
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @scalar_properties.
+# ------------------------------------------------------------------------------
+
+for my $name (@scalar_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    # Argument specified, set property to specified argument
+    if (@_) {
+      $self->{$name} = $_[0];
+    }
+
+    # Default value for property
+    if (not defined $self->{$name}) {
+      if ($name eq 'config') {
+        # Configuration setting of the main program
+        $self->{$name} = FCM1::Config->instance();
+      }
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $self->setting (@args); # $self->config->setting
+#   $value = $self->verbose (@args); # $self->config->verbose
+# ------------------------------------------------------------------------------
+
+for my $name (qw/setting verbose/) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+    return $self->config->$name (@_);
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $self->cfglabel (@args);
+#
+# DESCRIPTION
+#   This is an alias to $self->config->setting ('CFG_LABEL', @args);
+# ------------------------------------------------------------------------------
+
+sub cfglabel {
+  my $self = shift;
+  return $self->setting ('CFG_LABEL', @_);
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/Build.pm b/lib/FCM1/Build.pm
new file mode 100644
index 0000000..adb465b
--- /dev/null
+++ b/lib/FCM1/Build.pm
@@ -0,0 +1,1630 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::Build
+#
+# DESCRIPTION
+#   This is the top level class for the FCM build system.
+#
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+package FCM1::Build;
+use base qw(FCM1::ConfigSystem);
+
+use Carp             qw{croak}                                       ;
+use Cwd              qw{cwd}                                         ;
+use FCM1::BuildSrc                                                   ;
+use FCM1::BuildTask                                                  ;
+use FCM1::Config                                                     ;
+use FCM1::Dest                                                       ;
+use FCM1::CfgLine                                                    ;
+use FCM1::Timer      qw{timestamp_command}                           ;
+use FCM1::Util       qw{expand_tilde run_command touch_file w_report};
+use File::Basename   qw{dirname}                                     ;
+use File::Spec                                                       ;
+use List::Util       qw{first}                                       ;
+use Text::ParseWords qw{shellwords}                                  ;
+
+# List of scalar property methods for this class
+my @scalar_properties = (
+  'name',    # name of this build
+  'target',  # targets of this build
+);
+
+# List of hash property methods for this class
+my @hash_properties = (
+  'srcpkg',      # source packages of this build
+  'dummysrcpkg', # dummy for handling package inheritance with file extension
+);
+
+# List of compare_setting_X methods
+my @compare_setting_methods = (
+  'compare_setting_bld_blockdata', # program executable blockdata dependency
+  'compare_setting_bld_dep',       # custom dependency setting
+  'compare_setting_bld_dep_excl',  # exclude dependency setting
+  'compare_setting_bld_dep_n',     # no dependency check
+  'compare_setting_bld_dep_pp',    # custom PP dependency setting
+  'compare_setting_bld_dep_exe',   # program executable extra dependency
+  'compare_setting_bld_exe_name',  # program executable rename
+  'compare_setting_bld_pp',        # PP flags
+  'compare_setting_infile_ext',    # input file extension
+  'compare_setting_outfile_ext',   # output file extension
+  'compare_setting_tool',          # build tool settings
+);
+
+my $DELIMITER_LIST = $FCM1::Config::DELIMITER_LIST;
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::Build->new;
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::Build class.
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  my $self = FCM1::ConfigSystem->new (%args);
+
+  $self->{$_} = undef for (@scalar_properties);
+
+  $self->{$_} = {} for (@hash_properties);
+
+  bless $self, $class;
+
+  # List of sub-methods for parse_cfg
+  push @{ $self->cfg_methods }, (qw/target source tool dep misc/);
+
+  # Optional prefix in configuration declaration
+  $self->cfg_prefix ($self->setting (qw/CFG_LABEL BDECLARE/));
+
+  # System type
+  $self->type ('bld');
+
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @scalar_properties.
+# ------------------------------------------------------------------------------
+
+for my $name (@scalar_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    # Argument specified, set property to specified argument
+    if (@_) {
+      $self->{$name} = $_[0];
+    }
+
+    # Default value for property
+    if (not defined $self->{$name}) {
+      if ($name eq 'target') {
+        # Reference to an array
+        $self->{$name} = [];
+
+      } elsif ($name eq 'name') {
+        # Empty string
+        $self->{$name} = '';
+      }
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   %hash = %{ $obj->X () };
+#   $obj->X (\%hash);
+#
+#   $value = $obj->X ($index);
+#   $obj->X ($index, $value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @hash_properties.
+#
+#   If no argument is set, this method returns a hash containing a list of
+#   objects. If an argument is set and it is a reference to a hash, the objects
+#   are replaced by the specified hash.
+#
+#   If a scalar argument is specified, this method returns a reference to an
+#   object, if the indexed object exists or undef if the indexed object does
+#   not exist. If a second argument is set, the $index element of the hash will
+#   be set to the value of the argument.
+# ------------------------------------------------------------------------------
+
+for my $name (@hash_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my ($self, $arg1, $arg2) = @_;
+
+    # Ensure property is defined as a reference to a hash
+    $self->{$name} = {} if not defined ($self->{$name});
+
+    # Argument 1 can be a reference to a hash or a scalar index
+    my ($index, %hash);
+
+    if (defined $arg1) {
+      if (ref ($arg1) eq 'HASH') {
+        %hash = %$arg1;
+
+      } else {
+        $index = $arg1;
+      }
+    }
+
+    if (defined $index) {
+      # A scalar index is defined, set and/or return the value of an element
+      $self->{$name}{$index} = $arg2 if defined $arg2;
+
+      return (
+        exists $self->{$name}{$index} ? $self->{$name}{$index} : undef
+      );
+
+    } else {
+      # A scalar index is not defined, set and/or return the hash
+      $self->{$name} = \%hash if defined $arg1;
+      return $self->{$name};
+    }
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   ($rc, $new_lines) = $self->X ($old_lines);
+#
+# DESCRIPTION
+#   This method compares current settings with those in the cache, where X is
+#   one of @compare_setting_methods.
+#
+#   If setting has changed:
+#   * For bld_blockdata, bld_dep_ext and bld_exe_name, it sets the re-generate
+#     make-rule flag to true.
+#   * For bld_dep_excl, in a standalone build, the method will remove the
+#     dependency cache files for affected sub-packages. It returns an error if
+#     the current build inherits from previous builds.
+#   * For bld_pp, it updates the PP setting for affected sub-packages.
+#   * For infile_ext, in a standalone build, the method will remove all the
+#     sub-package cache files and trigger a re-build by removing most
+#     sub-directories created by the previous build. It returns an error if the
+#     current build inherits from previous builds.
+#   * For outfile_ext, in a standalone build, the method will remove all the
+#     sub-package dependency cache files. It returns an error if the current
+#     build inherits from previous builds.
+#   * For tool, it updates the "flags" files for any changed tools.
+# ------------------------------------------------------------------------------
+
+for my $name (@compare_setting_methods) {
+  no strict 'refs';
+
+  *$name = sub {
+    my ($self, $old_lines) = @_;
+
+    (my $prefix = uc ($name)) =~ s/^COMPARE_SETTING_//;
+
+    my ($changed, $new_lines) =
+      $self->compare_setting_in_config ($prefix, $old_lines);
+
+    my $rc = scalar (keys %$changed);
+
+    if ($rc and $old_lines) {
+      $self->srcpkg ('')->is_updated (1);
+
+      if ($name =~ /^compare_setting_bld_dep(?:_excl|_n|_pp)?$/) {
+        # Mark affected packages as being updated
+        for my $key (keys %$changed) {
+          for my $pkg (values %{ $self->srcpkg }) {
+            next unless $pkg->is_in_package ($key);
+            $pkg->is_updated (1);
+          }
+        }
+
+      } elsif ($name eq 'compare_setting_bld_pp') {
+        # Mark affected packages as being updated
+        for my $key (keys %$changed) {
+          for my $pkg (values %{ $self->srcpkg }) {
+            next unless $pkg->is_in_package ($key);
+            next unless $self->srcpkg ($key)->is_type_any (
+              keys %{ $self->setting ('BLD_TYPE_DEP_PP') }
+            ); # Is a type requiring pre-processing
+
+            $pkg->is_updated (1);
+          }
+        }
+
+      } elsif ($name eq 'compare_setting_infile_ext') {
+        # Re-set input file type if necessary
+        for my $key (keys %$changed) {
+          for my $pkg (values %{ $self->srcpkg }) {
+            next unless $pkg->src and $pkg->ext and $key eq $pkg->ext;
+
+            $pkg->type (undef);
+          }
+        }
+
+        # Mark affected packages as being updated
+        for my $pkg (values %{ $self->srcpkg }) {
+          $pkg->is_updated (1);
+        }
+
+      } elsif ($name eq 'compare_setting_outfile_ext') {
+        # Mark affected packages as being updated
+        for my $pkg (values %{ $self->srcpkg }) {
+          $pkg->is_updated (1);
+        }
+
+      } elsif ($name eq 'compare_setting_tool') {
+        # Update the "flags" files for changed tools
+        for my $name (sort keys %$changed) {
+          my ($tool, @names) = split /__/, $name;
+          my $pkg  = join ('__', @names);
+          my @srcpkgs
+            = $self->srcpkg($pkg)      ? ($self->srcpkg($pkg))
+            : $self->dummysrcpkg($pkg) ? @{$self->dummysrcpkg($pkg)->children()}
+            :                            ()
+            ;
+          for my $srcpkg (@srcpkgs) {
+            my $file = File::Spec->catfile (
+              $self->dest->flagsdir, $srcpkg->flagsbase ($tool)
+            );
+            &touch_file ($file) or croak $file, ': cannot update, abort';
+
+            print $file, ': updated', "\n" if $self->verbose > 2;
+          }
+        }
+      }
+    }
+
+    return ($rc, $new_lines);
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   ($rc, $new_lines) = $self->compare_setting_dependency ($old_lines, $flag);
+#
+# DESCRIPTION
+#   This method uses the previous settings to determine the dependencies of
+#   current source files.
+# ------------------------------------------------------------------------------
+
+sub compare_setting_dependency {
+  my ($self, $old_lines, $flag) = @_;
+
+  my $prefix = $flag ? 'DEP_PP' : 'DEP';
+  my $method = $flag ? 'ppdep'  : 'dep';
+
+  my $rc = 0;
+  my $new_lines = [];
+
+  # Separate old lines
+  my %old;
+  if ($old_lines) {
+    for my $line (@$old_lines) {
+      next unless $line->label_starts_with ($prefix);
+      $old{$line->label_from_field (1)} = $line;
+    }
+  }
+
+  # Go through each source to see if the cache is up to date
+  my $count = 0;
+  my %mtime;
+  for my $srcpkg (values %{ $self->srcpkg }) {
+    next unless $srcpkg->cursrc and $srcpkg->type;
+
+    my $key = $srcpkg->pkgname;
+    my $out_of_date = $srcpkg->is_updated;
+
+    # Check modification time of cache and source file if not out of date
+    if (exists $old{$key}) {
+      if (not $out_of_date) {
+        $mtime{$old{$key}->src} = (stat ($old{$key}->src))[9]
+          if not exists ($mtime{$old{$key}->src});
+
+        $out_of_date = 1 if $mtime{$old{$key}->src} < $srcpkg->curmtime;
+      }
+    }
+    else {
+      $out_of_date = 1;
+    }
+
+    if ($out_of_date) {
+      # Re-scan dependency
+      $srcpkg->is_updated(1);
+      my ($source_is_read, $dep_hash_ref) = $srcpkg->get_dep($flag);
+      if ($source_is_read) {
+        $count++;
+      }
+      $srcpkg->$method($dep_hash_ref);
+      $rc = 1;
+    }
+    else {
+      # Use cached dependency
+      my ($progname, %hash) = split (
+        /$FCM1::Config::DELIMITER_PATTERN/, $old{$key}->value
+      );
+      $srcpkg->progname ($progname) if $progname and not $flag;
+      $srcpkg->$method (\%hash);
+    }
+
+    # New lines values: progname[::dependency-name::type][...]
+    my @value = ((defined $srcpkg->progname ? $srcpkg->progname : ''));
+    for my $name (sort keys %{ $srcpkg->$method }) {
+      push @value, $name, $srcpkg->$method ($name);
+    }
+
+    push @$new_lines, FCM1::CfgLine->new (
+      LABEL => $prefix . $FCM1::Config::DELIMITER . $key,
+      VALUE => join ($FCM1::Config::DELIMITER, @value),
+    );
+  }
+
+  print 'No. of file', ($count > 1 ? 's' : ''), ' scanned for',
+        ($flag ? ' PP': ''), ' dependency: ', $count, "\n"
+    if $self->verbose and $count;
+
+  return ($rc, $new_lines);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   ($rc, $new_lines) = $self->compare_setting_srcpkg ($old_lines);
+#
+# DESCRIPTION
+#   This method uses the previous settings to determine the type of current
+#   source files.
+# ------------------------------------------------------------------------------
+
+sub compare_setting_srcpkg {
+  my ($self, $old_lines) = @_;
+
+  my $prefix = 'SRCPKG';
+
+  # Get relevant items from old lines, stripping out $prefix
+  my %old;
+  if ($old_lines) {
+    for my $line (@$old_lines) {
+      next unless $line->label_starts_with ($prefix);
+      $old{$line->label_from_field (1)} = $line;
+    }
+  }
+
+  # Check for change, use previous setting if exist
+  my $out_of_date = 0;
+  my %mtime;
+  for my $key (keys %{ $self->srcpkg }) {
+    if (exists $old{$key}) {
+      next unless $self->srcpkg ($key)->cursrc;
+
+      my $type = defined $self->setting ('BLD_TYPE', $key)
+                 ? $self->setting ('BLD_TYPE', $key) : $old{$key}->value;
+
+      $self->srcpkg ($key)->type ($type);
+
+      if ($type ne $old{$key}->value) {
+        $self->srcpkg ($key)->is_updated (1);
+        $out_of_date = 1;
+      }
+
+      if (not $self->srcpkg ($key)->is_updated) {
+        $mtime{$old{$key}->src} = (stat ($old{$key}->src))[9]
+          if not exists ($mtime{$old{$key}->src});
+
+        $self->srcpkg ($key)->is_updated (1)
+          if $mtime{$old{$key}->src} < $self->srcpkg ($key)->curmtime;
+      }
+
+    } else {
+      $self->srcpkg ($key)->is_updated (1);
+      $out_of_date = 1;
+    }
+  }
+
+  # Check for deleted keys
+  for my $key (keys %old) {
+    next if $self->srcpkg ($key);
+
+    $out_of_date = 1;
+  }
+
+  # Return reference to an array of new lines
+  my $new_lines = [];
+  for my $key (keys %{ $self->srcpkg }) {
+    push @$new_lines, FCM1::CfgLine->new (
+      LABEL => $prefix . $FCM1::Config::DELIMITER . $key,
+      VALUE => $self->srcpkg ($key)->type,
+    );
+  }
+
+  return ($out_of_date, $new_lines);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   ($rc, $new_lines) = $self->compare_setting_target ($old_lines);
+#
+# DESCRIPTION
+#   This method compare the previous target settings with current ones.
+# ------------------------------------------------------------------------------
+
+sub compare_setting_target {
+  my ($self, $old_lines) = @_;
+
+  my $prefix = 'TARGET';
+  my $old;
+  if ($old_lines) {
+    for my $line (@$old_lines) {
+      next unless $line->label_starts_with ($prefix);
+      $old = $line->value;
+      last;
+    }
+  }
+
+  my $new = join (' ', sort @{ $self->target });
+
+  return (
+    (defined ($old) ? $old ne $new : 1),
+    [FCM1::CfgLine->new (LABEL => $prefix, VALUE => $new)],
+  );
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->invoke_fortran_interface_generator ();
+#
+# DESCRIPTION
+#   This method invokes the Fortran interface generator for all Fortran free
+#   format source files. It returns true on success.
+# ------------------------------------------------------------------------------
+
+sub invoke_fortran_interface_generator {
+  my $self = shift;
+
+  my $pdoneext = $self->setting (qw/OUTFILE_EXT PDONE/);
+
+  # Set up build task to generate interface files for all selected Fortran 9x
+  # sources
+  my %task = ();
+  SRC_FILE:
+  for my $srcfile (values %{ $self->srcpkg }) {
+    if (!defined($srcfile->interfacebase())) {
+      next SRC_FILE;
+    }
+    my $target  = $srcfile->interfacebase . $pdoneext;
+
+    $task{$target} = FCM1::BuildTask->new (
+      TARGET     => $target,
+      TARGETPATH => $self->dest->donepath,
+      SRCFILE    => $srcfile,
+      DEPENDENCY => [$srcfile->flagsbase ('GENINTERFACE')],
+      ACTIONTYPE => 'GENINTERFACE',
+    );
+
+    # Set up build tasks for each source file/package flags file for interface
+    # generator tool
+    for my $i (1 .. @{ $srcfile->pkgnames }) {
+      my $target = $srcfile->flagsbase ('GENINTERFACE', -$i);
+      my $depend = $i < @{ $srcfile->pkgnames }
+                   ? $srcfile->flagsbase ('GENINTERFACE', -$i - 1)
+                   : undef;
+
+      $task{$target} = FCM1::BuildTask->new (
+        TARGET     => $target,
+        TARGETPATH => $self->dest->flagspath,
+        DEPENDENCY => [defined ($depend) ? $depend : ()],
+        ACTIONTYPE => 'UPDATE',
+      ) if not exists $task{$target};
+    }
+  }
+
+  # Set up build task to update the flags file for interface generator tool
+  $task{$self->srcpkg ('')->flagsbase ('GENINTERFACE')} = FCM1::BuildTask->new (
+    TARGET     => $self->srcpkg ('')->flagsbase ('GENINTERFACE'),
+    TARGETPATH => $self->dest->flagspath,
+    ACTIONTYPE => 'UPDATE',
+  );
+
+  my $count = 0;
+
+  # Performs task
+  for my $task (values %task) {
+    next unless $task->actiontype eq 'GENINTERFACE';
+
+    my $rc = $task->action (TASKLIST => \%task);
+    $count++ if $rc;
+  }
+
+  print 'No. of generated Fortran interface', ($count > 1 ? 's' : ''), ': ',
+        $count, "\n"
+    if $self->verbose and $count;
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->invoke_make (%args);
+#
+# DESCRIPTION
+#   This method invokes the make stage of the build system. It returns true on
+#   success.
+#
+# ARGUMENTS
+#   ARCHIVE - If set to "true", invoke the "archive" mode. Most build files and
+#             directories created by this build will be archived using the
+#             "tar" command.  If not set, the default is not to invoke the
+#             "archive" mode.
+#   JOBS    - Specify number of jobs that can be handled by "make". If set, the
+#             value must be a natural integer. If not set, the default value is
+#             1 (i.e.  run "make" in serial mode).
+#   TARGETS - Specify targets to be built. If set, these targets will be built
+#             instead of the ones specified in the build configuration file.
+# ------------------------------------------------------------------------------
+
+sub invoke_make {
+  my ($self, %args) = @_;
+  $args{TARGETS} ||= ['all'];
+  $args{JOBS}    ||= 1;
+  my @command = (
+    $self->setting(qw/TOOL MAKE/),
+    shellwords($self->setting(qw/TOOL MAKEFLAGS/)),
+    # -f Makefile
+    ($self->setting(qw/TOOL MAKE_FILE/), $self->dest()->bldmakefile()),
+    # -j N
+    ($args{JOBS} ? ($self->setting(qw/TOOL MAKE_JOB/), $args{JOBS}) : ()),
+    # -s
+    ($self->verbose() < 3 ? $self->setting(qw/TOOL MAKE_SILENT/) : ()),
+    @{$args{TARGETS}}
+  );
+  my $old_cwd = $self->_chdir($self->dest()->rootdir());
+  run_command(
+    \@command, ERROR => 'warn', RC => \my($code), TIME => $self->verbose() >= 3,
+  );
+  $self->_chdir($old_cwd);
+
+  my $rc = !$code;
+  if ($rc && $args{ARCHIVE}) {
+    $rc = $self->dest()->archive();
+  }
+  $rc &&= $self->dest()->create_bldrunenvsh();
+  while (my ($key, $source) = each(%{$self->srcpkg()})) {
+    $rc &&= defined($source->write_lib_dep_excl());
+  }
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->invoke_pre_process ();
+#
+# DESCRIPTION
+#   This method invokes the pre-process stage of the build system. It
+#   returns true on success.
+# ------------------------------------------------------------------------------
+
+sub invoke_pre_process {
+  my $self = shift;
+   
+  # Check whether pre-processing is necessary
+  my $invoke = 0;
+  for (values %{ $self->srcpkg }) {
+    next unless $_->get_setting ('BLD_PP');
+    $invoke = 1;
+    last;
+  }
+  return 1 unless $invoke;
+
+  # Scan header dependency
+  my $rc = $self->compare_setting (
+    METHOD_LIST => ['compare_setting_dependency'],
+    METHOD_ARGS => ['BLD_TYPE_DEP_PP'],
+    CACHEBASE   => $self->setting ('CACHE_DEP_PP'),
+  );
+
+  return $rc if not $rc;
+
+  my %task     = ();
+  my $pdoneext = $self->setting (qw/OUTFILE_EXT PDONE/);
+
+  # Set up tasks for each source file
+  for my $srcfile (values %{ $self->srcpkg }) {
+    if ($srcfile->is_type_all (qw/CPP INCLUDE/)) {
+      # Set up a copy build task for each include file
+      $task{$srcfile->base} = FCM1::BuildTask->new (
+        TARGET     => $srcfile->base,
+        TARGETPATH => $self->dest->incpath,
+        SRCFILE    => $srcfile,
+        DEPENDENCY => [keys %{ $srcfile->ppdep }],
+        ACTIONTYPE => 'COPY',
+      );
+
+    } elsif ($srcfile->lang ('TOOL_SRC_PP')) {
+      next unless $srcfile->get_setting ('BLD_PP');
+
+      # Set up a PP build task for each source file
+      my $target = $srcfile->base . $pdoneext;
+
+      # Issue warning for duplicated tasks
+      if (exists $task{$target}) {
+        w_report 'WARNING: ', $target, ': unable to create task for: ',
+                 $srcfile->src, ': task already exists for: ',
+                 $task{$target}->srcfile->src;
+        next;
+      }
+
+      $task{$target} = FCM1::BuildTask->new (
+        TARGET     => $target,
+        TARGETPATH => $self->dest->donepath,
+        SRCFILE    => $srcfile,
+        DEPENDENCY => [$srcfile->flagsbase ('PPKEYS'), keys %{ $srcfile->ppdep }],
+        ACTIONTYPE => 'PP',
+      );
+
+      # Set up update ppkeys/flags build tasks for each source file/package
+      my $ppkeys = $self->setting (
+        'TOOL_SRC_PP', $srcfile->lang ('TOOL_SRC_PP'), 'PPKEYS'
+      );
+
+      for my $i (1 .. @{ $srcfile->pkgnames }) {
+        my $target = $srcfile->flagsbase ($ppkeys, -$i);
+        my $depend = $i < @{ $srcfile->pkgnames }
+                     ? $srcfile->flagsbase ($ppkeys, -$i - 1)
+                     : undef;
+
+        $task{$target} = FCM1::BuildTask->new (
+          TARGET     => $target,
+          TARGETPATH => $self->dest->flagspath,
+          DEPENDENCY => [defined ($depend) ? $depend : ()],
+          ACTIONTYPE => 'UPDATE',
+        ) if not exists $task{$target};
+      }
+    }
+  }
+
+  # Set up update global ppkeys build tasks
+  for my $lang (keys %{ $self->setting ('TOOL_SRC_PP') }) {
+    my $target = $self->srcpkg ('')->flagsbase (
+      $self->setting ('TOOL_SRC_PP', $lang, 'PPKEYS')
+    );
+
+    $task{$target} = FCM1::BuildTask->new (
+      TARGET     => $target,
+      TARGETPATH => $self->dest->flagspath,
+      ACTIONTYPE => 'UPDATE',
+    );
+  }
+
+  # Build all PP tasks
+  my $count = 0;
+  for my $task (values %task) {
+    next unless $task->actiontype eq 'PP';
+
+    my $rc = $task->action (TASKLIST => \%task);
+    $task->srcfile->is_updated ($rc);
+    $count++ if $rc;
+  }
+
+  print 'No. of pre-processed file', ($count > 1 ? 's' : ''), ': ', $count, "\n"
+    if $self->verbose and $count;
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->invoke_scan_dependency ();
+#
+# DESCRIPTION
+#   This method invokes the scan dependency stage of the build system. It
+#   returns true on success.
+# ------------------------------------------------------------------------------
+
+sub invoke_scan_dependency {
+  my $self = shift;
+
+  # Scan/retrieve dependency
+  # ----------------------------------------------------------------------------
+  my $rc = $self->compare_setting (
+    METHOD_LIST => ['compare_setting_dependency'],
+    CACHEBASE   => $self->setting ('CACHE_DEP'),
+  );
+
+  # Check whether make file is out of date
+  # ----------------------------------------------------------------------------
+  my $out_of_date = ! -f $self->dest->bldmakefile;
+
+  if ($rc and not $out_of_date) {
+    for (qw/CACHE CACHE_DEP/) {
+      my $cache_mtime = (stat (File::Spec->catfile (
+        $self->dest->cachedir, $self->setting ($_),
+      )))[9];
+      my $mfile_mtime = (stat ($self->dest->bldmakefile))[9];
+
+      next if not defined $cache_mtime;
+      next if $cache_mtime < $mfile_mtime;
+      $out_of_date = 1;
+      last;
+    }
+  }
+
+  if ($rc and not $out_of_date) {
+    for (values %{ $self->srcpkg }) {
+      next unless $_->is_updated;
+      $out_of_date = 1;
+      last;
+    }
+  }
+
+  if ($rc and $out_of_date) {
+    # Write Makefile
+    # --------------------------------------------------------------------------
+    # Register non-word package name
+    my $unusual = 0;
+    for my $key (sort keys %{ $self->srcpkg }) {
+      next if $self->srcpkg ($key)->src;
+      next if $key =~ /^\w*$/;
+
+      $self->setting (
+        ['FCM_PCK_OBJECTS', $key], 'FCM_PCK_OBJECTS' . $unusual++,
+      );
+    }
+
+    # Write different parts in the Makefile
+    my $makefile = '# Automatic Makefile' . "\n\n";
+    $makefile .= 'FCM_BLD_NAME = ' . $self->name . "\n" if $self->name;
+    $makefile .= 'FCM_BLD_CFG = ' . $self->cfg->actual_src . "\n";
+    $makefile .= 'export FCM_VERBOSE ?= ' . $self->verbose . "\n\n";
+    $makefile .= "export OBJECTS\n";
+    $makefile .= $self->dest->write_rules;
+    $makefile .= $self->_write_makefile_perl5lib;
+    $makefile .= $self->_write_makefile_tool;
+    $makefile .= $self->_write_makefile_vpath;
+    $makefile .= $self->_write_makefile_target;
+
+    # Write rules for each source package
+    # Ensure that container packages come before files - this allows $(OBJECTS)
+    # and its dependent variables to expand correctly
+    my @srcpkg = sort {
+      if ($self->srcpkg ($a)->libbase and $self->srcpkg ($b)->libbase) {
+        $b cmp $a;
+
+      } elsif ($self->srcpkg ($a)->libbase) {
+        -1;
+
+      } elsif ($self->srcpkg ($b)->libbase) {
+        1;
+
+      } else {
+        $a cmp $b;
+      }
+    } keys %{ $self->srcpkg };
+
+    for (@srcpkg) {
+      $makefile .= $self->srcpkg ($_)->write_rules if $self->srcpkg ($_)->rules;
+    }
+    $makefile .= '# EOF' . "\n";
+
+    # Update Makefile
+    open OUT, '>', $self->dest->bldmakefile
+      or croak $self->dest->bldmakefile, ': cannot open (', $!, '), abort';
+    print OUT $makefile;
+    close OUT
+      or croak $self->dest->bldmakefile, ': cannot close (', $!, '), abort';
+
+    print $self->dest->bldmakefile, ': updated', "\n" if $self->verbose;
+
+    # Check for duplicated targets
+    # --------------------------------------------------------------------------
+    # Get list of types that cannot have duplicated targets
+    my @no_duplicated_target_types = split (
+      /$DELIMITER_LIST/,
+      $self->setting ('BLD_TYPE_NO_DUPLICATED_TARGET'),
+    );
+
+    my %targets;
+    for my $name (sort keys %{ $self->srcpkg }) {
+      next unless $self->srcpkg ($name)->rules;
+
+      for my $key (sort keys %{ $self->srcpkg ($name)->rules }) {
+        if (exists $targets{$key}) {
+          # Duplicated target: warning for most file types
+          my $status = 'WARNING';
+
+          # Duplicated target: error for the following file types
+          if (@no_duplicated_target_types and
+              $self->srcpkg ($name)->is_type_any (@no_duplicated_target_types) and
+              $targets{$key}->is_type_any (@no_duplicated_target_types)) {
+            $status = 'ERROR';
+            $rc = 0;
+          }
+
+          # Report the warning/error
+          w_report $status, ': ', $key, ': duplicated targets for building:';
+          w_report '       ', $targets{$key}->src;
+          w_report '       ', $self->srcpkg ($name)->src;
+
+        } else {
+          $targets{$key} = $self->srcpkg ($name);
+        }
+      }
+    }
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->invoke_setup_build ();
+#
+# DESCRIPTION
+#   This method invokes the setup_build stage of the build system. It returns
+#   true on success.
+# ------------------------------------------------------------------------------
+
+sub invoke_setup_build {
+  my $self = shift;
+
+  my $rc = 1;
+
+  # Extract archived sub-directories if necessary
+  $rc = $self->dest->dearchive if $rc;
+
+  # Compare cache
+  $rc = $self->compare_setting (METHOD_LIST => [
+    'compare_setting_target', # targets
+    'compare_setting_srcpkg', # source package type
+    @compare_setting_methods,
+  ]) if $rc;
+
+  # Set up runtime dependency scan patterns
+  my %dep_pattern = %{ $self->setting ('BLD_DEP_PATTERN') };
+  for my $key (keys %dep_pattern) {
+    my $pattern = $dep_pattern{$key};
+
+    while ($pattern =~ /##([\w:]+)##/g) {
+      my $match = $1;
+      my $val   = $self->setting (split (/$FCM1::Config::DELIMITER/, $match));
+
+      last unless defined $val;
+      $val =~ s/\./\\./;
+
+      $pattern =~ s/##$match##/$val/;
+    }
+
+    $self->setting (['BLD_DEP_PATTERN', $key], $pattern)
+      unless $pattern eq $dep_pattern{$key};
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->invoke_system (%args);
+#
+# DESCRIPTION
+#   This method invokes the build system. It returns true on success. See also
+#   the header for invoke_make for further information on arguments.
+#
+# ARGUMENTS
+#   STAGE - If set, it should be an integer number or a recognised keyword or
+#           abbreviation. If set, the build is performed up to the named stage.
+#           If not set, the default is to perform all stages of the build.
+#           Allowed values are:
+#             1, setup or s
+#             2, pre_process or pp
+#             3, generate_dependency or gd
+#             4, generate_interface or gi
+#             5, all, a, make or m
+# ------------------------------------------------------------------------------
+
+sub invoke_system {
+  my $self = shift;
+  my %args = @_;
+
+  # Parse arguments
+  # ----------------------------------------------------------------------------
+  # Default: run all 5 stages
+  my $stage = (exists $args{STAGE} and $args{STAGE}) ? $args{STAGE} : 5;
+
+  # Resolve named stages
+  if ($stage !~ /^\d$/) {
+    my %stagenames = (
+      'S(?:ETUP)?'                      => 1,
+      'P(?:RE)?_?P(?:ROCESS)?'          => 2,
+      'G(?:ENERATE)?_?D(?:ENPENDENCY)?' => 3,
+      'G(?:ENERATE)?_?I(?:NTERFACE)?'   => 4,
+      '(?:A(?:LL)|M(?:AKE)?)'           => 5,
+    );
+
+    # Does it match a recognised stage?
+    for my $name (keys %stagenames) {
+      next unless $stage =~ /$name/i;
+
+      $stage = $stagenames{$name};
+      last;
+    }
+
+    # Specified stage name not recognised, default to 5
+    if ($stage !~ /^\d$/) {
+      w_report 'WARNING: ', $stage, ': invalid build stage, default to 5.';
+      $stage = 5;
+    }
+  }
+
+  # Run the method associated with each stage
+  # ----------------------------------------------------------------------------
+  my $rc = 1;
+
+  my @stages = (
+    ['Setup build'               , 'invoke_setup_build'],
+    ['Pre-process'               , 'invoke_pre_process'],
+    ['Scan dependency'           , 'invoke_scan_dependency'],
+    ['Generate Fortran interface', 'invoke_fortran_interface_generator'],
+    ['Make'                      , 'invoke_make'],
+  );
+
+  for my $i (1 .. 5) {
+    last if (not $rc) or $i > $stage;
+
+    my ($name, $method) = @{ $stages[$i - 1] };
+    $rc = $self->invoke_stage ($name, $method, %args) if $rc and $stage >= $i;
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->parse_cfg_dep (\@cfg_lines);
+#
+# DESCRIPTION
+#   This method parses the dependency settings in the @cfg_lines.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg_dep {
+  my ($self, $cfg_lines) = @_;
+
+  my $rc = 1;
+
+  # EXCL_DEP, EXE_DEP and BLOCKDATA declarations
+  # ----------------------------------------------------------------------------
+  for my $name (qw/BLD_BLOCKDATA BLD_DEP BLD_DEP_EXCL BLD_DEP_EXE/) {
+    for my $line (grep {$_->slabel_starts_with_cfg ($name)} @$cfg_lines) {
+      # Separate label into a list, delimited by double-colon, remove 1st field
+      my @flds = $line->slabel_fields;
+      shift @flds;
+
+      if ($name =~ /^(?:BLD_DEP|BLD_DEP_EXCL|BLD_DEP_PP)$/) {
+        # BLD_DEP_*: label fields may contain sub-package
+        my $pk = @flds ? join ('__', @flds) : '';
+
+        # Check whether sub-package is valid
+        if ($pk and not ($self->srcpkg ($pk) or $self->dummysrcpkg ($pk))) {
+          $line->error ($line->label . ': invalid sub-package in declaration.');
+          $rc = 0;
+          next;
+        }
+
+        # Setting is stored in an array reference
+        $self->setting ([$name, $pk], [])
+          if not defined $self->setting ($name, $pk);
+
+        # Add current declaration to the array if necessary
+        my $list  = $self->setting ($name, $pk);
+        my $value = $name eq 'BLD_DEP_EXCL' ? uc ($line->value) : $line->value;
+        push @$list, $value if not grep {$_ eq $value} @$list;
+
+      } else {
+        # EXE_DEP and BLOCKDATA: label field may be an executable target
+        my $target = @flds ? $flds[0] : '';
+
+        # The value contains a list of objects and/or sub-package names
+        my @deps   = split /\s+/, $line->value;
+
+        if (not @deps) {
+          if ($name eq 'BLD_BLOCKDATA') {
+            # The objects containing a BLOCKDATA program unit must be declared
+            $line->error ($line->label . ': value not set.');
+            $rc = 0;
+            next;
+
+          } else {
+            # If $value is a null string, target(s) depends on all objects
+            push @deps, '';
+          }
+        }
+
+        for my $dep (@deps) {
+          $dep =~ s/$FCM1::Config::DELIMITER_PATTERN/__/g;
+        }
+
+        $self->setting ([$name, $target], join (' ', sort @deps));
+      }
+
+      $line->parsed (1);
+    }
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->parse_cfg_dest (\@cfg_lines);
+#
+# DESCRIPTION
+#   This method parses the build destination settings in the @cfg_lines.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg_dest {
+  my ($self, $cfg_lines) = @_;
+
+  my $rc = $self->SUPER::parse_cfg_dest ($cfg_lines);
+
+  # Set up search paths
+  for my $name (@FCM1::Dest::paths) {
+    (my $label = uc ($name)) =~ s/PATH//;
+
+    $self->setting (['PATH', $label], $self->dest->$name);
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->parse_cfg_misc (\@cfg_lines);
+#
+# DESCRIPTION
+#   This method parses misc build settings in the @cfg_lines.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg_misc {
+    my ($self, $cfg_lines_ref) = @_;
+    my $rc = 1;
+    my %item_of = (
+        BLD_DEP_N    => [\&_parse_cfg_misc_dep_n   , 1   ], # boolean
+        BLD_EXE_NAME => [\&_parse_cfg_misc_exe_name      ],
+        BLD_LIB      => [\&_parse_cfg_misc_dep_n         ],
+        BLD_PP       => [\&_parse_cfg_misc_dep_n   , 1   ], # boolean
+        BLD_TYPE     => [\&_parse_cfg_misc_dep_n         ],
+        INFILE_EXT   => [\&_parse_cfg_misc_file_ext, 0, 1], # uc($value)
+        OUTFILE_EXT  => [\&_parse_cfg_misc_file_ext, 1, 0], # uc($ns)
+    );
+    while (my ($key, $item) = each(%item_of)) {
+        my ($handler, @extra_arguments) = @{$item};
+        for my $line (@{$cfg_lines_ref}) {
+            if ($line->slabel_starts_with_cfg($key)) {
+                if ($handler->($self, $key, $line, @extra_arguments)) {
+                    $line->parsed(1);
+                }
+                else {
+                    $rc = 0;
+                }
+            }
+        }
+    }
+    return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# parse_cfg_misc: handler of BLD_EXE_NAME or similar.
+sub _parse_cfg_misc_exe_name {
+    my ($self, $key, $line) = @_;
+    my ($prefix, $name, @fields) = $line->slabel_fields();
+    if (!$name || @fields) {
+        $line->error(sprintf('%s: expects a single label name field.', $key));
+        return 0;
+    }
+    $self->setting([$key, $name], $line->value());
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# parse_cfg_misc: handler of BLD_DEP_N or similar.
+sub _parse_cfg_misc_dep_n {
+    my ($self, $key, $line, $value_is_boolean) = @_;
+    my ($prefix, @fields) = $line->slabel_fields();
+    my $ns = @fields ? join(q{__}, @fields) : q{};
+    if ($ns && !$self->srcpkg($ns) && !$self->dummysrcpkg($ns)) {
+        $line->error($line->label() . ': invalid sub-package in declaration.');
+        return 0;
+    }
+    my @srcpkgs
+        = $self->dummysrcpkg($ns) ? @{$self->dummysrcpkg($ns)->children()}
+        :                           $self->srcpkg($ns)
+        ;
+    my $value = $value_is_boolean ? $line->bvalue() : $line->value();
+    for my $srcpkg (@srcpkgs) {
+        $self->setting([$key, $srcpkg->pkgname()], $value);
+    }
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# parse_cfg_misc: handler of INFILE_EXT/OUTFILE_EXT or similar.
+sub _parse_cfg_misc_file_ext {
+    my ($self, $key, $line, $ns_in_uc, $value_in_uc) = @_;
+    my ($prefix, $ns) = $line->slabel_fields();
+    my $value = $value_in_uc ? uc($line->value()) : $line->value();
+    $self->setting([$key, ($ns_in_uc ? uc($ns) : $ns)], $value);
+    return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->parse_cfg_source (\@cfg_lines);
+#
+# DESCRIPTION
+#   This method parses the source package settings in the @cfg_lines.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg_source {
+  my ($self, $cfg_lines) = @_;
+
+  my $rc  = 1;
+  my %src = ();
+
+  # Automatic source directory search?
+  # ----------------------------------------------------------------------------
+  my $search = 1;
+
+  for my $line (grep {$_->slabel_starts_with_cfg ('SEARCH_SRC')} @$cfg_lines) {
+    $search = $line->bvalue;
+    $line->parsed (1);
+  }
+
+  # Search src/ sub-directory if necessary
+  %src = %{ $self->dest->get_source_files } if $search;
+
+  # SRC declarations
+  # ----------------------------------------------------------------------------
+  for my $line (grep {$_->slabel_starts_with_cfg ('FILE')} @$cfg_lines) {
+    # Expand ~ notation and path relative to srcdir of destination
+    my $value = $line->value;
+    $value = File::Spec->rel2abs (&expand_tilde ($value), $self->dest->srcdir);
+
+    if (! -e $value) {
+      $line->error ($value . ': source does not exist or is not readable.');
+      next;
+    }
+
+    # Package name
+    my @names = $line->slabel_fields;
+    shift @names;
+
+    # If package name not set, determine using the path if possible
+    if (not @names) {
+      my $package = $self->dest->get_pkgname_of_path ($value);
+      @names = @$package if defined $package;
+    }
+
+    if (not @names) {
+      $line->error ($self->cfglabel ('FILE') .
+                    ': package not specified/cannot be determined.');
+      next;
+    }
+
+    $src{join ('__', @names)} = $value;
+
+    $line->parsed (1);
+  }
+
+  # For directories, get non-recursive file listing, and add to %src
+  # ----------------------------------------------------------------------------
+  for my $key (keys %src) {
+    next unless -d $src{$key};
+
+    opendir DIR, $src{$key} or die $src{$key}, ': cannot read directory';
+    while (my $base = readdir 'DIR') {
+      next if $base =~ /^\./;
+
+      my $file = File::Spec->catfile ($src{$key}, $base);
+      next if ! -f $file;
+
+      my $name = join ('__', ($key, $base));
+      $src{$name} = $file unless exists $src{$name};
+    }
+    closedir DIR;
+
+    delete $src{$key};
+  }
+
+  # Set up source packages
+  # ----------------------------------------------------------------------------
+  my %pkg = ();
+  for my $name (keys %src) {
+    $pkg{$name} = FCM1::BuildSrc->new (PKGNAME => $name, SRC => $src{$name});
+  }
+
+  # INHERIT::SRC declarations
+  # ----------------------------------------------------------------------------
+  my %can_inherit = ();
+  for my $line (
+    grep {$_->slabel_starts_with_cfg(qw/INHERIT FILE/)} @{$cfg_lines}
+  ) {
+    my ($key1, $key2, @ns) = $line->slabel_fields();
+    $can_inherit{join('__', @ns)} = $line->bvalue();
+    $line->parsed(1);
+  }
+
+  # Inherit packages, if it is OK to do so
+  for my $inherited_build (reverse(@{$self->inherit()})) {
+    SRCPKG:
+    while (my ($key, $srcpkg) = each(%{$inherited_build->srcpkg()})) {
+      if (exists($pkg{$key}) || !$srcpkg->src()) {
+        next SRCPKG;
+      }
+      my $known_key = first {exists($can_inherit{$_})} @{$srcpkg->pkgnames()};
+      if (defined($known_key) && !$can_inherit{$known_key}) {
+        next SRCPKG;
+      }
+      $pkg{$key} = $srcpkg;
+    }
+  }
+
+  # Get list of intermediate "packages"
+  # ----------------------------------------------------------------------------
+  for my $name (keys %pkg) {
+    # Name of current package
+    my @names = split /__/, $name;
+
+    my $cur = $name;
+
+    while ($cur) {
+      # Name of parent package
+      pop @names;
+      my $parent = @names ? join ('__', @names) : '';
+
+      # If parent package does not exist, create it
+      $pkg{$parent} = FCM1::BuildSrc->new (PKGNAME => $parent)
+        unless exists $pkg{$parent};
+
+      # Current package is a child of the parent package
+      push @{ $pkg{$parent}->children }, $pkg{$cur}
+        unless grep {$_->pkgname eq $cur} @{ $pkg{$parent}->children };
+
+      # Go up a package
+      $cur = $parent;
+    }
+  }
+
+  $self->srcpkg (\%pkg);
+
+  # Dummy: e.g. "foo/bar/baz.egg" belongs to the "foo/bar/baz" dummy.
+  # ----------------------------------------------------------------------------
+  SRCPKG:
+  while (my ($name, $srcpkg) = each(%pkg)) {
+    if (!$srcpkg->src()) { # ensure that $srcpkg represents a source file
+      next SRCPKG;
+    }
+    my @names = split('__', $name);
+    if (@names) {
+      $names[-1] =~ s{\.\w+ \z}{}msx;
+    }
+    my $dummy_name = join('__', @names);
+    if ($dummy_name eq $name || defined($self->srcpkg($dummy_name))) {
+      next SRCPKG;
+    }
+    if (!defined($self->dummysrcpkg($dummy_name))) {
+      $self->dummysrcpkg($dummy_name, FCM1::BuildSrc->new(PKGNAME => $dummy_name));
+    }
+    push(@{$self->dummysrcpkg($dummy_name)->children()}, $srcpkg);
+  }
+
+  # Make sure a package is defined
+  # ----------------------------------------------------------------------------
+  if (not %{$self->srcpkg}) {
+    w_report 'ERROR: ', $self->cfg->actual_src, ': no source file to build.';
+    $rc = 0;
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->parse_cfg_target (\@cfg_lines);
+#
+# DESCRIPTION
+#   This method parses the target settings in the @cfg_lines.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg_target {
+  my ($self, $cfg_lines) = @_;
+
+  # NAME declaraions
+  # ----------------------------------------------------------------------------
+  for my $line (grep {$_->slabel_starts_with_cfg ('NAME')} @$cfg_lines) {
+    $self->name ($line->value);
+    $line->parsed (1);
+  }
+
+  # TARGET declarations
+  # ----------------------------------------------------------------------------
+  for my $line (grep {$_->slabel_starts_with_cfg ('TARGET')} @$cfg_lines) {
+    # Value is a space delimited list
+    push @{ $self->target }, split (/\s+/, $line->value);
+    $line->parsed (1);
+  }
+
+  # INHERIT::TARGET declarations
+  # ----------------------------------------------------------------------------
+  # By default, do not inherit target
+  my $inherit_flag = 0;
+
+  for (grep {$_->slabel_starts_with_cfg (qw/INHERIT TARGET/)} @$cfg_lines) {
+    $inherit_flag = $_->bvalue;
+    $_->parsed (1);
+  }
+
+  # Inherit targets from inherited build, if $inherit_flag is set to true
+  # ----------------------------------------------------------------------------
+  if ($inherit_flag) {
+    for my $use (reverse @{ $self->inherit }) {
+      unshift @{ $self->target }, @{ $use->target };
+    }
+  }
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->parse_cfg_tool (\@cfg_lines);
+#
+# DESCRIPTION
+#   This method parses the tool settings in the @cfg_lines.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg_tool {
+  my ($self, $cfg_lines) = @_;
+
+  my $rc = 1;
+
+  my %tools         = %{ $self->setting ('TOOL') };
+  my @package_tools = split(/$DELIMITER_LIST/, $self->setting('TOOL_PACKAGE'));
+
+  # TOOL declaration
+  # ----------------------------------------------------------------------------
+  for my $line (grep {$_->slabel_starts_with_cfg ('TOOL')} @$cfg_lines) {
+    # Separate label into a list, delimited by double-colon, remove TOOL
+    my @flds = $line->slabel_fields;
+    shift @flds;
+
+    # Check that there is a field after TOOL
+    if (not @flds) {
+      $line->error ('TOOL: not followed by a valid label.');
+      $rc = 0;
+      next;
+    }
+
+    # The first field is the tool iteself, identified in uppercase
+    $flds[0] = uc ($flds[0]);
+
+    # Check that the tool is recognised
+    if (not exists $tools{$flds[0]}) {
+      $line->error ($flds[0] . ': not a valid TOOL.');
+      $rc = 0;
+      next;
+    }
+
+    # Check sub-package declaration
+    if (@flds > 1 and not grep {$_ eq $flds[0]} @package_tools) {
+      $line->error ($flds[0] . ': sub-package not accepted with this TOOL.');
+      $rc = 0;
+      next;
+    }
+
+    # Name of declared package
+    my $pk = join ('__', @flds[1 .. $#flds]);
+
+    # Check whether package exists
+    if (not ($self->srcpkg ($pk) or $self->dummysrcpkg ($pk))) {
+      $line->error ($line->label . ': invalid sub-package in declaration.');
+      $rc = 0;
+      next;
+    }
+
+    $self->setting (['TOOL', join ('__', @flds)], $line->value);
+    $line->parsed (1);
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $self->_write_makefile_perl5lib ();
+#
+# DESCRIPTION
+#   This method returns a makefile $string for defining $PERL5LIB.
+# ------------------------------------------------------------------------------
+
+sub _write_makefile_perl5lib {
+  my $self = shift;
+
+  my $classpath = File::Spec->catfile (split (/::/, ref ($self))) . '.pm';
+
+  my $libdir  = dirname (dirname ($INC{$classpath}));
+  my @libpath = split (/:/, (exists $ENV{PERL5LIB} ? $ENV{PERL5LIB} : ''));
+
+  my $string = ((grep {$_ eq $libdir} @libpath)
+                ? ''
+                : 'export PERL5LIB := ' . $libdir .
+                  (exists $ENV{PERL5LIB} ? ':$(PERL5LIB)' : '') . "\n\n");
+
+  return $string;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $self->_write_makefile_target ();
+#
+# DESCRIPTION
+#   This method returns a makefile $string for defining the default targets.
+# ------------------------------------------------------------------------------
+
+sub _write_makefile_target {
+  my $self = shift;
+
+  # Targets of the build
+  # ----------------------------------------------------------------------------
+  my @targets = @{ $self->target };
+  if (not @targets) {
+    # Build targets not specified by user, default to building all main programs
+    my @programs = ();
+
+    # Get all main programs from all packages
+    for my $pkg (values %{ $self->srcpkg }) {
+      push @programs, $pkg->exebase if $pkg->exebase;
+    }
+
+    @programs = sort (@programs);
+
+    if (@programs) {
+      # Build main programs, if there are any
+      @targets = @programs;
+
+    } else {
+      # No main program in source tree, build the default library
+      @targets = ($self->srcpkg ('')->libbase);
+    }
+  }
+
+  my $return = 'FCM_BLD_TARGETS = ' . join (' ', @targets) . "\n\n";
+
+  # Default targets
+  $return .= '.PHONY : all' . "\n\n";
+  $return .= 'all : $(FCM_BLD_TARGETS)' . "\n\n";
+
+  # Targets for copy dummy
+  $return .= sprintf("%s:\n\ttouch \$@\n\n", $self->setting(qw/BLD_CPDUMMY/));
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $self->_write_makefile_tool ();
+#
+# DESCRIPTION
+#   This method returns a makefile $string for defining the build tools.
+# ------------------------------------------------------------------------------
+
+sub _write_makefile_tool {
+  my $self = shift;
+
+  # List of build tools
+  my $tool = $self->setting ('TOOL');
+
+  # List of tools local to FCM, (will not be exported)
+  my %localtool = map {($_, 1)} split ( # map into a hash table
+    /$DELIMITER_LIST/, $self->setting ('TOOL_LOCAL'),
+  );
+
+  # Export required tools
+  my $count = 0;
+  my $return = '';
+  for my $name (sort keys %$tool) {
+    # Ignore local tools
+    next if exists $localtool{(split (/__/, $name))[0]};
+
+    if ($name =~ /^\w+$/) {
+      # Tools with normal name, just export it as an environment variable
+      $return .= 'export ' . $name . ' = ' . $tool->{$name} . "\n";
+
+    } else {
+      # Tools with unusual characters, export using a label/value pair
+      $return .= 'export FCM_UNUSUAL_TOOL_LABEL' . $count . ' = ' . $name . "\n";
+      $return .= 'export FCM_UNUSUAL_TOOL_VALUE' . $count . ' = ' .
+                 $tool->{$name} . "\n";
+      $count++;
+    }
+  }
+
+  $return .= "\n";
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $self->_write_makefile_vpath ();
+#
+# DESCRIPTION
+#   This method returns a makefile $string for defining vpath directives.
+# ------------------------------------------------------------------------------
+
+sub _write_makefile_vpath {
+  my $self = shift();
+  my $FMT = 'vpath %%%s $(FCM_%sPATH)';
+  my %SETTING_OF = %{$self->setting('BLD_VPATH')};
+  my %EXT_OF = %{$self->setting('OUTFILE_EXT')};
+  # Note: each setting can be either an empty string or a comma-separated list
+  # of output file extension keys.
+  join(
+    "\n",
+    (
+      map
+      {
+        my $key = $_;
+        my @types = split(qr{$DELIMITER_LIST}msx, $SETTING_OF{$key});
+          @types ? (map {sprintf($FMT, $EXT_OF{$_}, $key)} sort @types)
+        :          sprintf($FMT, q{}, $key)
+        ;
+      }
+      sort keys(%SETTING_OF)
+    ),
+  ) . "\n\n";
+}
+
+# Wraps chdir. Returns the old working directory.
+sub _chdir {
+  my ($self, $path) = @_;
+  if ($self->verbose() >= 3) {
+    printf("cd %s\n", $path);
+  }
+  my $old_cwd = cwd();
+  chdir($path) || croak(sprintf("%s: cannot change directory ($!)\n", $path));
+  $old_cwd;
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/Build/Fortran.pm b/lib/FCM1/Build/Fortran.pm
new file mode 100644
index 0000000..452e32f
--- /dev/null
+++ b/lib/FCM1/Build/Fortran.pm
@@ -0,0 +1,549 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package FCM1::Build::Fortran;
+
+use Text::Balanced qw{extract_bracketed extract_delimited};
+
+# Actions of this class
+my %ACTION_OF = (extract_interface => \&_extract_interface);
+
+# Regular expressions
+# Matches a variable attribute
+my $RE_ATTR = qr{
+    allocatable|dimension|external|intent|optional|parameter|pointer|save|target
+}imsx;
+# Matches a name
+my $RE_NAME = qr{[A-Za-z]\w*}imsx;
+# Matches a specification type
+my $RE_SPEC = qr{
+    character|complex|double\s*precision|integer|logical|real|type
+}imsx;
+# Matches the identifier of a program unit that does not have arguments
+my $RE_UNIT_BASE = qr{block\s*data|module|program}imsx;
+# Matches the identifier of a program unit that has arguments
+my $RE_UNIT_CALL = qr{function|subroutine}imsx;
+# Matches the identifier of any program unit
+my $RE_UNIT      = qr{$RE_UNIT_BASE|$RE_UNIT_CALL}msx;
+my %RE = (
+    # A comment line
+    COMMENT     => qr{\A\s*(?:!|\z)}msx,
+    # A trailing comment, capture the expression before the comment
+    COMMENT_END => qr{\A([^'"]*?)\s*!.*\z}msx,
+    # A contination marker, capture the expression before the marker
+    CONT        => qr{\A(.*)&\s*\z}msx,
+    # A contination marker at the beginning of a line, capture the marker and
+    # the expression after the marker
+    CONT_LEAD   => qr{\A(\s*&)(.*)\z}msx,
+    # Capture a variable identifier, removing any type component expression
+    NAME_COMP   => qr{\b($RE_NAME)(?:\s*\%\s*$RE_NAME)*\b}msx,
+    # Matches the first identifier in a line
+    NAME_LEAD   => qr{\A\s*$RE_NAME\s*}msx,
+    # Captures a name identifier after a comma, and the expression after
+    NAME_LIST   => qr{\A(?:.*?)\s*,\s*($RE_NAME)\b(.*)\z}msx,
+    # Captures the next quote character
+    QUOTE       => qr{\A[^'"]*(['"])}msx,
+    # Matches an attribute declaration
+    TYPE_ATTR   => qr{\A\s*($RE_ATTR)\b}msx,
+    # Matches a type declaration
+    TYPE_SPEC   => qr{\A\s*($RE_SPEC)\b}msx,
+    # Captures the expression after one or more program unit attributes
+    UNIT_ATTR   => qr{\A\s*(?:(?:elemental|recursive|pure)\s+)+(.*)\z}imsx,
+    # Captures the identifier and the symbol of a program unit with no arguments
+    UNIT_BASE   => qr{\A\s*($RE_UNIT_BASE)\s+($RE_NAME)\s*\z}imsx,
+    # Captures the identifier and the symbol of a program unit with arguments
+    UNIT_CALL   => qr{\A\s*($RE_UNIT_CALL)\s+($RE_NAME)\b}imsx,
+    # Captures the end of a program unit, its identifier and its symbol
+    UNIT_END    => qr{\A\s*(end)(?:\s+($RE_NAME)(?:\s+($RE_NAME))?)?\s*\z}imsx,
+    # Captures the expression after a program unit type specification
+    UNIT_SPEC   => qr{\A\s*$RE_SPEC\b(.*)\z}imsx,
+);
+
+# Keywords in type declaration statements
+my %TYPE_DECL_KEYWORD_SET = map { ($_, 1) } qw{
+    allocatable
+    dimension
+    in
+    inout
+    intent
+    kind
+    len
+    optional
+    out
+    parameter
+    pointer
+    save
+    target
+};
+
+# Creates and returns an instance of this class.
+sub new {
+    my ($class) = @_;
+    bless(
+        sub {
+            my $key = shift();
+            if (!exists($ACTION_OF{$key})) {
+                return;
+            }
+            $ACTION_OF{$key}->(@_);
+        },
+        $class,
+    );
+}
+
+# Methods.
+for my $key (keys(%ACTION_OF)) {
+    no strict qw{refs};
+    *{$key} = sub { my $self = shift(); $self->($key, @_) };
+}
+
+# Extracts the calling interfaces of top level subroutines and functions from
+# the $handle for reading Fortran sources.
+sub _extract_interface {
+    my ($handle) = @_;
+    map { _present_line($_) } @{_reduce_to_interface(_load($handle))};
+}
+
+# Reads $handle for the next Fortran statement, handling continuations.
+sub _load {
+    my ($handle) = @_;
+    my $ctx = {signature_token_set_of => {}, statements => []};
+    my $state = {
+        in_contains  => undef, # in a "contains" section of a program unit
+        in_interface => undef, # in an "interface" block
+        in_quote     => undef, # in a multi-line quote
+        stack        => [],    # program unit stack
+    };
+    my $NEW_STATEMENT = sub {
+        {   name        => q{}, # statement name, e.g. function, integer, ...
+            lines       => [],  # original lines in the statement
+            line_number => 0,   # line number (start) in the original source
+            symbol      => q{}, # name of a program unit (signature, end)
+            type        => q{}, # e.g. signature, use, type, attr, end
+            value       => q{}, # the actual value of the statement
+        };
+    };
+    my $statement;
+LINE:
+    while (my $line = readline($handle)) {
+        if (!defined($statement)) {
+            $statement = $NEW_STATEMENT->();
+        }
+        my $value = $line;
+        chomp($value);
+        # Pre-processor directives and continuation
+        if (!$statement->{line_number} && index($value, '#') == 0) {
+            $statement->{line_number} = $.;
+            $statement->{name}        = 'cpp';
+        }
+        if ($statement->{name} eq 'cpp') {
+            push(@{$statement->{lines}}, $line);
+            $statement->{value} .= $value;
+            if (rindex($value, '\\') != length($value) - 1) {
+                $statement = undef;
+            }
+            next LINE;
+        }
+        # Normal Fortran
+        if ($value =~ $RE{COMMENT}) {
+            next LINE;
+        }
+        if (!$statement->{line_number}) {
+            $statement->{line_number} = $.;
+        }
+        my ($cont_head, $cont_tail);
+        if ($statement->{line_number} != $.) { # is a continuation
+            ($cont_head, $cont_tail) = $value =~ $RE{CONT_LEAD};
+            if ($cont_head) {
+                $value = $cont_tail;
+            }
+        }
+        # Correctly handle ! and & in quotes
+        my ($head, $tail) = (q{}, $value);
+        if ($state->{in_quote} && index($value, $state->{in_quote}) >= 0) {
+            my $index = index($value, $state->{in_quote});
+            $head = substr($value, 0, $index + 1);
+            $tail
+                = length($value) > $index + 1
+                ? substr($value, $index + 2)
+                : q{};
+            $state->{in_quote} = undef;
+        }
+        if (!$state->{in_quote}) {
+            while ($tail) {
+                if (index($tail, q{!}) >= 0) {
+                    if (!($tail =~ s/$RE{COMMENT_END}/$1/)) {
+                        ($head, $tail, $state->{in_quote})
+                            = _load_extract_quote($head, $tail);
+                    }
+                }
+                else {
+                    while (index($tail, q{'}) > 0
+                        || index($tail, q{"}) > 0)
+                    {
+                        ($head, $tail, $state->{in_quote})
+                            = _load_extract_quote($head, $tail);
+                    }
+                    $head .= $tail;
+                    $tail = q{};
+                }
+            }
+        }
+        $cont_head ||= q{};
+        push(@{$statement->{lines}}, $cont_head . $head . $tail . "\n");
+        $statement->{value} .= $head . $tail;
+        # Process a statement only if it is marked with a continuation
+        if (!($statement->{value} =~ s/$RE{CONT}/$1/)) {
+            $statement->{value} =~ s{\s+\z}{}msx;
+            if (_process($statement, $ctx, $state)) {
+                push(@{$ctx->{statements}}, $statement);
+            }
+            $statement = undef;
+        }
+    }
+    return $ctx;
+}
+
+# Helper, removes a quoted string from $tail.
+sub _load_extract_quote {
+    my ($head, $tail) = @_;
+    my ($extracted, $remainder, $prefix)
+        = extract_delimited($tail, q{'"}, qr{[^'"]*}msx, q{});
+    if ($extracted) {
+        return ($head . $prefix . $extracted, $remainder);
+    }
+    else {
+        my ($quote) = $tail =~ $RE{QUOTE};
+        return ($head . $tail, q{}, $quote);
+    }
+}
+
+# Study statements and put attributes into array $statements
+sub _process {
+    my ($statement, $ctx, $state) = @_;
+    my $name;
+
+    # End Interface
+    if ($state->{in_interface}) {
+        if ($statement->{value} =~ qr{\A\s*end\s*interface\b}imsx) {
+            $state->{in_interface} = 0;
+        }
+        return;
+    }
+
+    # End Program Unit
+    if (@{$state->{stack}} && $statement->{value} =~ qr{\A\s*end\b}imsx) {
+        my ($end, $type, $symbol) = lc($statement->{value}) =~ $RE{UNIT_END};
+        if (!$end) {
+            return;
+        }
+        my ($top_type, $top_symbol) = @{$state->{stack}->[-1]};
+        if (!$type
+            || $top_type eq $type && (!$symbol || $top_symbol eq $symbol))
+        {
+            pop(@{$state->{stack}});
+            if ($state->{in_contains} && !@{$state->{stack}}) {
+                $state->{in_contains} = 0;
+            }
+            if (!$state->{in_contains}) {
+                $statement->{name}   = $top_type;
+                $statement->{symbol} = $top_symbol;
+                $statement->{type}   = 'end';
+                return $statement;
+            }
+        }
+        return;
+    }
+
+    # Interface/Contains
+    ($name) = $statement->{value} =~ qr{\A\s*(contains|interface)\b}imsx;
+    if ($name) {
+        $state->{'in_' . lc($name)} = 1;
+        return;
+    }
+
+    # Program Unit
+    my ($type, $symbol, @tokens) = _process_prog_unit($statement->{value});
+    if ($type) {
+        push(@{$state->{stack}}, [$type, $symbol]);
+        if ($state->{in_contains}) {
+            return;
+        }
+        $statement->{name}   = lc($type);
+        $statement->{type}   = 'signature';
+        $statement->{symbol} = lc($symbol);
+        $ctx->{signature_token_set_of}{$symbol}
+            = {map { (lc($_) => 1) } @tokens};
+        return $statement;
+    }
+    if ($state->{in_contains}) {
+        return;
+    }
+
+    # Use
+    if ($statement->{value} =~ qr{\A\s*(use)\b}imsx) {
+        $statement->{name} = 'use';
+        $statement->{type} = 'use';
+        return $statement;
+    }
+
+    # Type Declarations
+    ($name) = $statement->{value} =~ $RE{TYPE_SPEC};
+    if ($name) {
+        $name =~ s{\s}{}gmsx;
+        $statement->{name} = lc($name);
+        $statement->{type} = 'type';
+        return $statement;
+    }
+
+    # Attribute Statements
+    ($name) = $statement->{value} =~ $RE{TYPE_ATTR};
+    if ($name) {
+        $statement->{name} = $name;
+        $statement->{type} = 'attr';
+        return $statement;
+    }
+}
+
+# Parse a statement for program unit header. Returns a list containing the type,
+# the symbol and the signature tokens of the program unit.
+sub _process_prog_unit {
+    my ($string) = @_;
+    my ($type, $symbol, @args) = (q{}, q{});
+    # Is it a blockdata, module or program?
+    ($type, $symbol) = $string =~ $RE{UNIT_BASE};
+    if ($type) {
+        $type = lc($type);
+        $type =~ s{\s*}{}gmsx;
+        return ($type, $symbol);
+    }
+    # Remove the attribute and type declaration of a procedure
+    $string =~ s/$RE{UNIT_ATTR}/$1/;
+    my ($match) = $string =~ $RE{UNIT_SPEC};
+    if ($match) {
+        $string = $match;
+        extract_bracketed($string);
+    }
+    # Is it a function or subroutine?
+    ($type, $symbol) = lc($string) =~ $RE{UNIT_CALL};
+    if (!$type) {
+        return;
+    }
+    my $extracted = extract_bracketed($string, q{()}, qr{[^(]*}msx);
+
+    # Get signature tokens from SUBROUTINE/FUNCTION
+    if ($extracted) {
+        $extracted =~ s{\s}{}gmsx;
+        @args = split(q{,}, substr($extracted, 1, length($extracted) - 2));
+        if ($type eq 'function') {
+            my $result = extract_bracketed($string, q{()}, qr{[^(]*}msx);
+            if ($result) {
+                $result =~ s{\A\(\s*(.*?)\s*\)\z}{$1}msx; # remove braces
+                push(@args, $result);
+            }
+            else {
+                push(@args, $symbol);
+            }
+        }
+    }
+    return (lc($type), lc($symbol), map { lc($_) } @args);
+}
+
+# Reduces the list of statements to contain only the interface block.
+sub _reduce_to_interface {
+    my ($ctx) = @_;
+    my (%token_set, @interface_statements);
+STATEMENT:
+    for my $statement (reverse(@{$ctx->{statements}})) {
+        if ($statement->{type} eq 'end'
+            && grep { $_ eq $statement->{name} } qw{subroutine function})
+        {
+            push(@interface_statements, $statement);
+            %token_set
+                = %{$ctx->{signature_token_set_of}{$statement->{symbol}}};
+            next STATEMENT;
+        }
+        if ($statement->{type} eq 'signature'
+            && grep { $_ eq $statement->{name} } qw{subroutine function})
+        {
+            push(@interface_statements, $statement);
+            %token_set = ();
+            next STATEMENT;
+        }
+        if ($statement->{type} eq 'use') {
+            my ($head, $tail)
+                = split(qr{\s*:\s*}msx, lc($statement->{value}), 2);
+            if ($tail) {
+                my @imports = map { [split(qr{\s*=>\s*}msx, $_, 2)] }
+                    split(qr{\s*,\s*}msx, $tail);
+                my @useful_imports
+                    = grep { exists($token_set{$_->[0]}) } @imports;
+                if (!@useful_imports) {
+                    next STATEMENT;
+                }
+                if (@imports != @useful_imports) {
+                    my @token_strings
+                        = map { $_->[0] . ($_->[1] ? ' => ' . $_->[1] : q{}) }
+                        @useful_imports;
+                    my ($last, @rest) = reverse(@token_strings);
+                    my @token_lines
+                        = (reverse(map { $_ . q{,&} } @rest), $last);
+                    push(
+                        @interface_statements,
+                        {   lines => [
+                                sprintf("%s:&\n", $head),
+                                (map { sprintf(" & %s\n", $_) } @token_lines),
+                            ]
+                        },
+                    );
+                    next STATEMENT;
+                }
+            }
+            push(@interface_statements, $statement);
+            next STATEMENT;
+        }
+        if ($statement->{type} eq 'attr') {
+            my ($spec, @tokens) = ($statement->{value} =~ /$RE{NAME_COMP}/g);
+            if (grep { exists($token_set{$_}) } @tokens) {
+                for my $token (@tokens) {
+                    $token_set{$token} = 1;
+                }
+                push(@interface_statements, $statement);
+                next STATEMENT;
+            }
+        }
+        if ($statement->{type} eq 'type') {
+            my ($variable_string, $spec_string)
+                = reverse(split('::', lc($statement->{value}), 2));
+            if ($spec_string) {
+                $spec_string =~ s{$RE{NAME_LEAD}}{}msx;
+            }
+            else {
+                # The first expression in the statement is the type + attrib
+                $variable_string =~ s{$RE{NAME_LEAD}}{}msx;
+                $spec_string = extract_bracketed($variable_string, '()',
+                    qr{[\s\*]*}msx);
+            }
+            # Useful tokens are those that comes after a comma
+            my $tail = q{,} . lc($variable_string);
+            my @tokens;
+            while ($tail) {
+                if ($tail =~ qr{\A\s*['"]}msx) {
+                    extract_delimited($tail, q{'"}, qr{\A[^'"]*}msx, q{});
+                }
+                elsif ($tail =~ qr{\A\s*\(}msx) {
+                    extract_bracketed($tail, '()', qr{\A[^(]*}msx);
+                }
+                else {
+                    my $token;
+                    ($token, $tail) = $tail =~ $RE{NAME_LIST};
+                    if ($token && $token_set{$token}) {
+                        @tokens = ($variable_string =~ /$RE{NAME_COMP}/g);
+                        $tail = q{};
+                    }
+                }
+            }
+            if (@tokens && $spec_string) {
+                my @spec_tokens = (lc($spec_string) =~ /$RE{NAME_COMP}/g);
+                push(
+                    @tokens,
+                    (   grep { !exists($TYPE_DECL_KEYWORD_SET{$_}) }
+                            @spec_tokens
+                    ),
+                );
+            }
+            if (grep { exists($token_set{$_}) } @tokens) {
+                for my $token (@tokens) {
+                    $token_set{$token} = 1;
+                }
+                push(@interface_statements, $statement);
+                next STATEMENT;
+            }
+        }
+    }
+    if (!@interface_statements) {
+        return [];
+    }
+    [   {lines => ["interface\n"]},
+        reverse(@interface_statements),
+        {lines => ["end interface\n"]},
+    ];
+}
+
+# Processes and returns the line of the statement.
+sub _present_line {
+    my ($statement) = @_;
+    map {
+        s{\s+}{ }gmsx;      # collapse multiple spaces
+        s{\s+\z}{\n}msx;    # remove trailing spaces
+        $_;
+    } @{$statement->{lines}};
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+FCM1::Build::Fortran
+
+=head1 SYNOPSIS
+
+    use FCM1::Build::Fortran;
+    my $fortran_util = FCM1::Build::Fortran->new();
+    open(my($handle), '<', $path_to_a_fortran_source_file);
+    print($fortran_util->extract_interface($handle)); # prints interface
+    close($handle);
+
+=head1 DESCRIPTION
+
+A class to analyse Fortran source. Currently, it has a single method to extract
+the calling interfaces of top level subroutines and functions in a Fortran
+source.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new()
+
+Creates and returns an instance of this class.
+
+=item $instance->extract_interface($handle)
+
+Extracts the calling interfaces of top level subroutines and functions in a
+Fortran source that can be read from $handle. Returns an interface block as a
+list of lines.
+
+=back
+
+=head1 ACKNOWLEDGEMENT
+
+This module is inspired by the logic developed by the European Centre
+for Medium-Range Weather Forecasts (ECMWF).
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM1/BuildSrc.pm b/lib/FCM1/BuildSrc.pm
new file mode 100644
index 0000000..cd36a52
--- /dev/null
+++ b/lib/FCM1/BuildSrc.pm
@@ -0,0 +1,1508 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::BuildSrc
+#
+# DESCRIPTION
+#   This is a class to group functionalities of source in a build.
+#
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+package FCM1::BuildSrc;
+use base qw{FCM1::Base};
+
+use Carp qw{croak};
+use Cwd qw{cwd};
+use FCM1::Build::Fortran;
+use FCM1::CfgFile;
+use FCM1::CfgLine;
+use FCM1::Config;
+use FCM1::Timer qw{timestamp_command};
+use FCM1::Util qw{find_file_in_path run_command};
+use File::Basename qw{basename dirname};
+use File::Spec;
+
+# List of scalar property methods for this class
+my @scalar_properties = (
+  'children',   # list of children packages
+  'is_updated', # is this source (or its associated settings) updated?
+  'mtime',      # modification time of src
+  'ppmtime',    # modification time of ppsrc
+  'ppsrc',      # full path of the pre-processed source
+  'pkgname',    # package name of the source
+  'progname',   # program unit name in the source
+  'src',        # full path of the source
+  'type',       # type of the source
+);
+
+# List of hash property methods for this class
+my @hash_properties = (
+  'dep',   # dependencies
+  'ppdep', # pre-process dependencies
+  'rules', # make rules
+);
+
+# Error message formats
+my %ERR_MESS_OF = (
+  CHDIR       => '%s: cannot change directory (%s), abort',
+  OPEN        => '%s: cannot open (%s), abort',
+  CLOSE_PIPE  => '%s: failed (%d), abort',
+);
+
+# Event message formats and levels
+my %EVENT_SETTING_OF = (
+  CHDIR            => ['%s: change directory'                   , 2],
+  F_INTERFACE_NONE => ['%s: Fortran interface generation is off', 3],
+  GET_DEPENDENCY   => ['%s: %d line(s), %d auto dependency(ies)', 3],
+);
+
+my %RE_OF = (
+  F_PREFIX => qr{
+    (?:
+      (?:ELEMENTAL|PURE(?:\s+RECURSIVE)?|RECURSIVE(?:\s+PURE)?)
+      \s+
+    )?
+  }imsx,
+  F_SPEC => qr{
+    (?:
+      (?:CHARACTER|COMPLEX|DOUBLE\s*PRECISION|INTEGER|LOGICAL|REAL|TYPE)
+      (?: \s* \( .+ \) | \s* \* \d+ \s*)??
+      \s+
+    )?
+  }imsx,
+);
+
+{
+  # Returns a singleton instance of FCM1::Build::Fortran.
+  my $FORTRAN_UTIL;
+  sub _get_fortran_util {
+    $FORTRAN_UTIL ||= FCM1::Build::Fortran->new();
+    return $FORTRAN_UTIL;
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::BuildSrc->new (%args);
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::BuildSrc class. See
+#   above for allowed list of properties. (KEYS should be in uppercase.)
+# ------------------------------------------------------------------------------
+
+sub new {
+  my ($class, %args) = @_;
+  my $self = bless(FCM1::Base->new(%args), $class);
+  for my $key (@scalar_properties, @hash_properties) {
+    $self->{$key}
+      = exists($args{uc($key)}) ? $args{uc($key)}
+      :                           undef
+      ;
+  }
+  $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @scalar_properties.
+# ------------------------------------------------------------------------------
+
+for my $name (@scalar_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    # Argument specified, set property to specified argument
+    if (@_) {
+      $self->{$name} = $_[0];
+
+      if ($name eq 'ppsrc') {
+        $self->ppmtime (undef);
+
+      } elsif ($name eq 'src') {
+        $self->mtime (undef);
+      }
+    }
+
+    # Default value for property
+    if (not defined $self->{$name}) {
+      if ($name eq 'children') {
+        # Reference to an empty array
+        $self->{$name} = [];
+        
+      } elsif ($name =~ /^(?:is_cur|pkgname|ppsrc|src)$/) {
+        # Empty string
+        $self->{$name} = '';
+        
+      } elsif ($name eq 'mtime') {
+        # Modification time
+        $self->{$name} = (stat $self->src)[9] if $self->src;
+        
+      } elsif ($name eq 'ppmtime') {
+        # Modification time
+        $self->{$name} = (stat $self->ppsrc)[9] if $self->ppsrc;
+        
+      } elsif ($name eq 'type') {
+        # Attempt to get the type if src is set
+        $self->{$name} = $self->get_type if $self->src;
+      }
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   %hash = %{ $obj->X () };
+#   $obj->X (\%hash);
+#
+#   $value = $obj->X ($index);
+#   $obj->X ($index, $value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @hash_properties.
+#
+#   If no argument is set, this method returns a hash containing a list of
+#   objects. If an argument is set and it is a reference to a hash, the objects
+#   are replaced by the specified hash.
+#
+#   If a scalar argument is specified, this method returns a reference to an
+#   object, if the indexed object exists or undef if the indexed object does
+#   not exist. If a second argument is set, the $index element of the hash will
+#   be set to the value of the argument.
+# ------------------------------------------------------------------------------
+
+for my $name (@hash_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my ($self, $arg1, $arg2) = @_;
+
+    # Ensure property is defined as a reference to a hash
+    if (not defined $self->{$name}) {
+      if ($name eq 'rules') {
+        $self->{$name} = $self->get_rules;
+
+      } else {
+        $self->{$name} = {};
+      }
+    }
+
+    # Argument 1 can be a reference to a hash or a scalar index
+    my ($index, %hash);
+
+    if (defined $arg1) {
+      if (ref ($arg1) eq 'HASH') {
+        %hash = %$arg1;
+
+      } else {
+        $index = $arg1;
+      }
+    }
+
+    if (defined $index) {
+      # A scalar index is defined, set and/or return the value of an element
+      $self->{$name}{$index} = $arg2 if defined $arg2;
+
+      return (
+        exists $self->{$name}{$index} ? $self->{$name}{$index} : undef
+      );
+
+    } else {
+      # A scalar index is not defined, set and/or return the hash
+      $self->{$name} = \%hash if defined $arg1;
+      return $self->{$name};
+    }
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   This method returns/sets property X, all derived from src, where X is:
+#     base  - (read-only) basename of src
+#     dir   - (read-only) dirname of src
+#     ext   - (read-only) file extension of src
+#     root  - (read-only) basename of src without the file extension
+# ------------------------------------------------------------------------------
+
+sub base {
+  return &basename ($_[0]->src);
+}
+
+# ------------------------------------------------------------------------------
+
+sub dir {
+  return &dirname ($_[0]->src);
+}
+
+# ------------------------------------------------------------------------------
+
+sub ext {
+  return substr $_[0]->base, length ($_[0]->root);
+}
+
+# ------------------------------------------------------------------------------
+
+sub root {
+  (my $root = $_[0]->base) =~ s/\.\w+$//;
+  return $root;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   This method returns/sets property X, all derived from ppsrc, where X is:
+#     ppbase  - (read-only) basename of ppsrc
+#     ppdir   - (read-only) dirname of ppsrc
+#     ppext   - (read-only) file extension of ppsrc
+#     pproot  - (read-only) basename of ppsrc without the file extension
+# ------------------------------------------------------------------------------
+
+sub ppbase {
+  return &basename ($_[0]->ppsrc);
+}
+
+# ------------------------------------------------------------------------------
+
+sub ppdir {
+  return &dirname ($_[0]->ppsrc);
+}
+
+# ------------------------------------------------------------------------------
+
+sub ppext {
+  return substr $_[0]->ppbase, length ($_[0]->pproot);
+}
+
+# ------------------------------------------------------------------------------
+
+sub pproot {
+  (my $root = $_[0]->ppbase) =~ s/\.\w+$//;
+  return $root;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#
+# DESCRIPTION
+#   This method returns/sets property X, derived from src or ppsrc, where X is:
+#     curbase  - (read-only) basename of cursrc
+#     curdir   - (read-only) dirname of cursrc
+#     curext   - (read-only) file extension of cursrc
+#     curmtime - (read-only) modification time of cursrc
+#     curroot  - (read-only) basename of cursrc without the file extension
+#     cursrc   - ppsrc or src
+# ------------------------------------------------------------------------------
+
+for my $name (qw/base dir ext mtime root src/) {
+  no strict 'refs';
+
+  my $subname = 'cur' . $name;
+
+  *$subname = sub {
+    my $self = shift;
+    my $method = $self->ppsrc ? 'pp' . $name : $name;
+    return $self->$method (@_);
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $base = $obj->X ();
+#
+# DESCRIPTION
+#   This method returns a basename X for the source, where X is:
+#     donebase      - "done" file name
+#     etcbase       - target for copying data files
+#     exebase       - executable name for source containing a main program
+#     interfacebase - Fortran interface file name
+#     libbase       - library file name
+#     objbase       - object name for source containing compilable source
+#   If the source file contains a compilable procedure, this method returns
+#   the name of the object file.
+# ------------------------------------------------------------------------------
+
+sub donebase {
+  my $self   = shift;
+
+  my $return;
+  if ($self->is_type_all ('SOURCE')) {
+    if ($self->objbase and not $self->is_type_all ('PROGRAM')) {
+      $return = ($self->progname ? $self->progname : lc ($self->curroot)) .
+                $self->setting (qw/OUTFILE_EXT DONE/);
+    }
+
+  } elsif ($self->is_type_all ('INCLUDE')) {
+    $return = $self->curbase . $self->setting (qw/OUTFILE_EXT IDONE/);
+  }
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+
+sub etcbase {
+  my $self = shift;
+
+  my $return = @{ $self->children }
+               ? $self->pkgname . $self->setting (qw/OUTFILE_EXT ETC/)
+               : undef;
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+
+sub exebase {
+  my $self = shift;
+
+  my $return;
+  if ($self->objbase and $self->is_type_all ('PROGRAM')) {
+    if ($self->setting ('BLD_EXE_NAME', $self->curroot)) {
+      $return = $self->setting ('BLD_EXE_NAME', $self->curroot);
+
+    } else {
+      $return = $self->curroot . $self->setting (qw/OUTFILE_EXT EXE/);
+    }
+  }
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+
+sub interfacebase {
+  my $self = shift();
+  if (
+        uc($self->get_setting(qw/TOOL GENINTERFACE/)) ne 'NONE'
+    &&  $self->progname()
+    &&  $self->is_type_all(qw/SOURCE/)
+    &&  $self->is_type_any(qw/FORTRAN9X FPP9X/)
+    &&  !$self->is_type_any(qw/PROGRAM MODULE BLOCKDATA/)
+  ) {
+    my $flag = lc($self->get_setting(qw/TOOL INTERFACE/));
+    my $ext  = $self->setting(qw/OUTFILE_EXT INTERFACE/);
+
+    return (($flag eq 'program' ? $self->progname() : $self->curroot()) . $ext);
+  }
+  return;
+}
+
+# ------------------------------------------------------------------------------
+
+sub objbase {
+  my $self = shift;
+
+  my $return;
+
+  if ($self->is_type_all ('SOURCE')) {
+    my $ext = $self->setting (qw/OUTFILE_EXT OBJ/);
+
+    if ($self->is_type_any (qw/FORTRAN FPP/)) {
+      $return = lc ($self->progname) . $ext if $self->progname;
+
+    } else {
+      $return = lc ($self->curroot) . $ext;
+    }
+  }
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->flagsbase ($flag, [$index,]);
+#
+# DESCRIPTION
+#   Returns the base name of the flags file for the current package namespace
+#   for a given $flag. The returned base name should look like
+#   "LABEL___PACKAGE__NAME__SPACE.flags", where "LABEL" is normally the $flag,
+#   and "PACKAGE__NAME__SPACE" is the current package namespace without the file
+#   extension. If $flag is FLAGS or PPKEYS and $self->lang() is defined, it
+#   will attempt to determine the correct label for the language. E.g. If
+#   $self->lang() is 'C', the label will be "CFLAGS". If $index is set, returns
+#   the base name of the flags file for the $index'th element in package name
+#   space (as described in "pkgnames" method) instead of the current package
+#   name space.
+# ------------------------------------------------------------------------------
+
+sub flagsbase {
+  my ($self, $flag, $index) = @_;
+  my $name = $index ? $self->pkgnames()->[$index] : $self->pkgname();
+  my @names = split('__', $name);
+  if (@names && $self->src() && $name eq $self->pkgname()) {
+    $names[-1] =~ s{\.\w+ \z}{}msx;
+  }
+  my $label = $flag;
+  if ($self->lang() && ($flag eq 'FLAGS' || $flag eq 'PPKEYS')) {
+    if (!exists($self->setting('TOOL_SRC')->{$self->lang()}{$flag})) {
+      return;
+    }
+    $label = $self->setting('TOOL_SRC')->{$self->lang()}{$flag};
+  }
+  join('__', $label, @names) . $self->setting(qw/OUTFILE_EXT FLAGS/);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->libbase ([$prefix], [$suffix]);
+#
+# DESCRIPTION
+#   This method returns the property libbase (derived from pkgname) the base
+#   name of the library archive. $prefix and $suffix defaults to 'lib' and '.a'
+#   respectively.
+# ------------------------------------------------------------------------------
+
+sub libbase {
+  my ($self, $prefix, $suffix) = @_;
+  $prefix ||= 'lib';
+  $suffix ||= $self->setting(qw/OUTFILE_EXT LIB/);
+  if ($self->src()) { # applies to directories only
+    return;
+  }
+  my $name = $self->setting('BLD_LIB', $self->pkgname());
+  if (!defined($name)) {
+    $name = $self->pkgname();
+  }
+  $prefix . $name . $suffix;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->lang ([$setting]);
+#
+# DESCRIPTION
+#   This method returns the property lang (derived from type) the programming
+#   language name if type matches one supported in the TOOL_SRC setting. If
+#   $setting is specified, use $setting instead of TOOL_SRC.
+# ------------------------------------------------------------------------------
+
+sub lang {
+  my ($self, $setting) = @_;
+
+  my @keys = keys %{ $self->setting ($setting ? $setting : 'TOOL_SRC') };
+
+  my $return = undef;
+  for my $key (@keys) {
+    next unless $self->is_type_all ('SOURCE', $key);
+    $return = $key;
+    last;
+  }
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->pkgnames;
+#
+# DESCRIPTION
+#   This method returns a list of container packages, derived from pkgname:
+# ------------------------------------------------------------------------------
+
+sub pkgnames {
+  my $self = shift;
+
+  my $return = [];
+  if ($self->pkgname) {
+    my @names = split (/__/, $self->pkgname);
+
+    for my $i (0 .. $#names) {
+      push @$return, join ('__', (@names[0 .. $i]));
+    }
+
+    unshift @$return, '';
+  }
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   %dep = %{$obj->get_dep()};
+#   %dep = %{$obj->get_dep($flag)};
+#
+# DESCRIPTION
+#   This method scans the current source file for dependencies and returns the
+#   dependency hash (keys = dependencies, values = dependency types). If $flag
+#   is specified, the config setting for $flag is used to determine the types of
+#   types. Otherwise, those specified in 'BLD_TYPE_DEP' is used.
+# ------------------------------------------------------------------------------
+
+sub get_dep {
+  my ($self, $flag) = @_;
+  # Work out list of exclude for this file, using its sub-package name
+  my %EXCLUDE_SET = map {($_, 1)} @{$self->get_setting('BLD_DEP_EXCL')};
+  # Determine what dependencies are supported by this known type
+  my %DEP_TYPE_OF = %{$self->setting($flag ? $flag : 'BLD_TYPE_DEP')};
+  my %PATTERN_OF = %{$self->setting('BLD_DEP_PATTERN')};
+  my @dep_types = ();
+  if (!$self->get_setting('BLD_DEP_N')) {
+    DEP_TYPE:
+    while (my ($key, $dep_type_string) = each(%DEP_TYPE_OF)) {
+      # Check if current file is a type of file requiring dependency scan
+      if (!$self->is_type_all($key)) {
+        next DEP_TYPE;
+      }
+      # Get list of dependency type for this file
+      for my $dep_type (split(/$FCM1::Config::DELIMITER/, $dep_type_string)) {
+        if (exists($PATTERN_OF{$dep_type}) && !exists($EXCLUDE_SET{$dep_type})) {
+          push(@dep_types, $dep_type);
+        }
+      }
+    }
+  }
+
+  # Automatic dependencies
+  my %dep_of;
+  my $can_get_symbol # Also scan for program unit name in Fortran source
+      =  !$flag
+      && $self->is_type_all('SOURCE')
+      && $self->is_type_any(qw/FPP FORTRAN/)
+      ;
+  my $has_read_file;
+  if ($can_get_symbol || @dep_types) {
+    my $handle = _open($self->cursrc());
+    LINE:
+    while (my $line = readline($handle)) {
+      chomp($line);
+      if ($line =~ qr{\A \s* \z}msx) { # empty lines
+        next LINE;
+      }
+      if ($can_get_symbol) {
+        my $symbol = _get_dep_symbol($line);
+        if ($symbol) {
+          $self->progname($symbol);
+          $can_get_symbol = 0;
+          next LINE;
+        }
+      }
+      DEP_TYPE:
+      for my $dep_type (@dep_types) {
+        my ($match) = $line =~ /$PATTERN_OF{$dep_type}/i;
+        if (!$match) {
+          next DEP_TYPE;
+        }
+        # $match may contain multiple items delimited by space
+        for my $item (split(qr{\s+}msx, $match)) {
+          my $key = uc($dep_type . $FCM1::Config::DELIMITER . $item);
+          if (!exists($EXCLUDE_SET{$key})) {
+            $dep_of{$item} = $dep_type;
+          }
+        }
+        next LINE;
+      }
+    }
+    $self->_event('GET_DEPENDENCY', $self->pkgname(), $., scalar(keys(%dep_of)));
+    close($handle);
+    $has_read_file = 1;
+  }
+
+  # Manual dependencies
+  my $manual_deps_ref
+      = $self->setting('BLD_DEP' . ($flag ? '_PP' : ''), $self->pkgname());
+  if (defined($manual_deps_ref)) {
+    for (@{$manual_deps_ref}) {
+      my ($dep_type, $item) = split(/$FCM1::Config::DELIMITER/, $_, 2);
+      $dep_of{$item} = $dep_type;
+    }
+  }
+
+  return ($has_read_file, \%dep_of);
+}
+
+# Returns, if possible, the program unit declared in the $line.
+sub _get_dep_symbol {
+  my $line = shift();
+  for my $pattern (
+    qr{\A \s* $RE_OF{F_PREFIX} SUBROUTINE              \s+ ([A-Za-z]\w*)}imsx,
+    qr{\A \s* MODULE (?!\s+PROCEDURE)                  \s+ ([A-Za-z]\w*)}imsx,
+    qr{\A \s* PROGRAM                                  \s+ ([A-Za-z]\w*)}imsx,
+    qr{\A \s* $RE_OF{F_PREFIX} $RE_OF{F_SPEC} FUNCTION \s+ ([A-Za-z]\w*)}imsx,
+    qr{\A \s* BLOCK\s*DATA                             \s+ ([A-Za-z]\w*)}imsx,
+  ) {
+    my ($match) = $line =~ $pattern;
+    if ($match) {
+      return lc($match);
+    }
+  }
+  return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @out = @{ $obj->get_fortran_interface () };
+#
+# DESCRIPTION
+#   This method invokes the Fortran interface block generator to generate
+#   an interface block for the current source file. It returns a reference to
+#   an array containing the lines of the interface block.
+# ------------------------------------------------------------------------------
+
+sub get_fortran_interface {
+  my $self = shift();
+  my %ACTION_OF = (
+    q{}    => \&_get_fortran_interface_by_internal_code,
+    f90aib => \&_get_fortran_interface_by_f90aib,
+    none   => sub {$self->_event('F_INTERFACE_NONE', $self->root()); []},
+  );
+  my $key = lc($self->get_setting(qw/TOOL GENINTERFACE/));
+  if (!$key || !exists($ACTION_OF{$key})) {
+    $key = q{};
+  }
+  $ACTION_OF{$key}->($self->cursrc());
+}
+
+# Generates Fortran interface block using "f90aib".
+sub _get_fortran_interface_by_f90aib {
+  my $path = shift();
+  my $command = sprintf(q{f90aib <'%s' 2>'%s'}, $path, File::Spec->devnull());
+  my $pipe = _open($command, '-|');
+  my @lines = readline($pipe);
+  close($pipe) || croak($ERR_MESS_OF{CLOSE_PIPE}, $command, $?);
+  \@lines;
+}
+
+# Generates Fortran interface block using internal code.
+sub _get_fortran_interface_by_internal_code {
+  my $path = shift();
+  my $handle = _open($path);
+  my @lines = _get_fortran_util()->extract_interface($handle);
+  close($handle);
+  \@lines;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @out = @{ $obj->get_pre_process () };
+#
+# DESCRIPTION
+#   This method invokes the pre-processor on the source file and returns a
+#   reference to an array containing the lines of the pre-processed source on
+#   success.
+# ------------------------------------------------------------------------------
+
+sub get_pre_process {
+  my $self = shift;
+
+  # Supported source files
+  my $lang = $self->lang ('TOOL_SRC_PP');
+  return unless $lang;
+
+  # List of include directories
+  my @inc = @{ $self->setting (qw/PATH INC/) };
+
+  # Build the pre-processor command according to file type
+  my %tool        = %{ $self->setting ('TOOL') };
+  my %tool_src_pp = %{ $self->setting ('TOOL_SRC_PP', $lang) };
+
+  # The pre-processor command and its options
+  my @command = ($tool{$tool_src_pp{COMMAND}});
+  my @ppflags = split /\s+/, $self->get_setting ('TOOL', $tool_src_pp{FLAGS});
+
+  # List of defined macros, add "-D" in front of each macro
+  my @ppkeys  = split /\s+/, $self->get_setting ('TOOL', $tool_src_pp{PPKEYS});
+  @ppkeys     = map {($tool{$tool_src_pp{DEFINE}} . $_)} @ppkeys;
+
+  # Add "-I" in front of each include directories
+  @inc        = map {($tool{$tool_src_pp{INCLUDE}} . $_)} @inc;
+
+  push @command, (@ppflags, @ppkeys, @inc, $self->base);
+
+  # Change to container directory of source file
+  my $old_cwd = $self->_chdir($self->dir());
+
+  # Execute the command, getting the output lines
+  my $verbose = $self->verbose;
+  my @outlines = &run_command (
+    \@command, METHOD => 'qx', PRINT => $verbose > 1, TIME => $verbose > 2,
+  );
+
+  # Change back to original directory
+  $self->_chdir($old_cwd);
+
+  return \@outlines;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rules = %{ $self->get_rules };
+#
+# DESCRIPTION
+#   This method returns a reference to a hash in the following format:
+#     $rules = {
+#       target => {ACTION => action, DEP => [dependencies], ...},
+#       ...    => {...},
+#     };
+#   where the 1st rank keys are the available targets for building this source
+#   file, the second rank keys are ACTION and DEP. The value of ACTION is the
+#   action for building the target, which can be "COMPILE", "LOAD", "TOUCH",
+#   "CP" or "AR". The value of DEP is a refernce to an array containing a list
+#   of dependencies suitable for insertion into the Makefile.
+# ------------------------------------------------------------------------------
+
+sub get_rules {
+  my $self = shift;
+
+  my $rules;
+  my %outfile_ext = %{ $self->setting ('OUTFILE_EXT') };
+
+  if ($self->is_type_all (qw/SOURCE/)) {
+    # Source file
+    # --------------------------------------------------------------------------
+    # Determine whether the language of the source file is supported
+    my %tool_src = %{ $self->setting ('TOOL_SRC') };
+
+    return () unless $self->lang;
+
+    # Compile object
+    # --------------------------------------------------------------------------
+    if ($self->objbase) {
+      # Depends on the source file
+      my @dep = ($self->rule_src);
+
+      # Depends on the compiler flags flags-file
+      my @flags;
+      push @flags, ('FLAGS' )
+        if $self->flagsbase ('FLAGS' );
+      push @flags, ('PPKEYS')
+        if $self->flagsbase ('PPKEYS') and not $self->ppsrc;
+
+      push @dep, $self->flagsbase ($_) for (@flags);
+
+      # Source file dependencies
+      for my $name (sort keys %{ $self->dep }) {
+        # A Fortran 9X module, lower case object file name
+        if ($self->dep ($name) eq 'USE') {
+          (my $root = $name) =~ s/\.\w+$//;
+          push @dep, lc ($root) . $outfile_ext{OBJ};
+
+        # An include file
+        } elsif ($self->dep ($name) =~ /^(?:INC|H|INTERFACE)$/) {
+          push @dep, $name;
+        }
+      }
+
+      $rules->{$self->objbase} = {ACTION => 'COMPILE', DEP => \@dep};
+
+      # Touch flags-files
+      # ------------------------------------------------------------------------
+      for my $flag (@flags) {
+        next unless $self->flagsbase ($flag);
+
+        $rules->{$self->flagsbase ($flag)} = {
+          ACTION => 'TOUCH',
+          DEP    => [
+            $self->flagsbase ($tool_src{$self->lang}{$flag}, -2),
+          ],
+          DEST   => '$(FCM_FLAGSDIR)',
+        };
+      }
+    }
+
+    if ($self->exebase) {
+      # Link into an executable
+      # ------------------------------------------------------------------------
+      my @dep = ();
+      push @dep, $self->objbase               if $self->objbase;
+      push @dep, $self->flagsbase ('LD'     ) if $self->flagsbase ('LD'     );
+      push @dep, $self->flagsbase ('LDFLAGS') if $self->flagsbase ('LDFLAGS');
+
+      # Depends on BLOCKDATA program units, for Fortran programs
+      my %blockdata = %{ $self->setting ('BLD_BLOCKDATA') };
+      my @blkobj    = ();
+
+      if ($self->is_type_any (qw/FPP FORTRAN/) and keys %blockdata) {
+        # List of BLOCKDATA object files
+        if (exists $blockdata{$self->exebase}) {
+          @blkobj = split /\s+/, $blockdata{$self->exebase};
+
+        } elsif (exists $blockdata{''}) {
+          @blkobj = split /\s+/, $blockdata{''};
+        }
+
+        for my $name (@blkobj) {
+          (my $root = $name) =~ s/\.\w+$//;
+          $name = $root . $outfile_ext{OBJ};
+          push @dep, $root . $outfile_ext{DONE};
+        }
+      }
+
+      # Extra executable dependencies
+      my %exe_dep = %{ $self->setting ('BLD_DEP_EXE') };
+      if (keys %exe_dep) {
+        my @exe_deps;
+        if (exists $exe_dep{$self->exebase}) {
+          @exe_deps = split /\s+/, $exe_dep{$self->exebase};
+
+        } elsif (exists $exe_dep{''}) {
+          @exe_deps = $exe_dep{''} ? split (/\s+/, $exe_dep{''}) : ('');
+        }
+
+        my $pattern = '\\' . $outfile_ext{OBJ} . '$';
+
+        for my $name (@exe_deps) {
+          if ($name =~ /$pattern/) {
+            # Extra dependency is an object
+            (my $root = $name) =~ s/\.\w+$//;
+            push @dep, $root . $outfile_ext{DONE};
+
+          } else {
+            # Extra dependency is a sub-package
+            my $var;
+            if ($self->setting ('FCM_PCK_OBJECTS', $name)) {
+              # sub-package name contains unusual characters
+              $var = $self->setting ('FCM_PCK_OBJECTS', $name);
+
+            } else {
+              # sub-package name contains normal characters
+              $var = $name ? join ('__', ('OBJECTS', $name)) : 'OBJECTS';
+            }
+
+            push @dep, '$(' . $var . ')';
+          }
+        }
+      }
+
+      # Source file dependencies
+      for my $name (sort keys %{ $self->dep }) {
+        (my $root = $name) =~ s/\.\w+$//;
+
+        # Lowercase name for object dependency
+        $root = lc ($root) unless $self->dep ($name) =~ /^(?:INC|H)$/;
+
+        # Select "done" file extension
+        if ($self->dep ($name) =~ /^(?:INC|H)$/) {
+          push @dep, $name . $outfile_ext{IDONE};
+
+        } else {
+          push @dep, $root . $outfile_ext{DONE};
+        }
+      }
+
+      $rules->{$self->exebase} = {
+        ACTION => 'LOAD', DEP => \@dep, BLOCKDATA => \@blkobj,
+      };
+
+      # Touch Linker flags-file
+      # ------------------------------------------------------------------------
+      for my $flag (qw/LD LDFLAGS/) {
+        $rules->{$self->flagsbase ($flag)} = {
+          ACTION => 'TOUCH',
+          DEP    => [$self->flagsbase ($flag, -2)],
+          DEST   => '$(FCM_FLAGSDIR)',
+        };
+      }
+
+    }
+
+    if ($self->donebase) {
+      # Touch done file
+      # ------------------------------------------------------------------------
+      my @dep = ($self->objbase);
+
+      for my $name (sort keys %{ $self->dep }) {
+        (my $root = $name) =~ s/\.\w+$//;
+
+        # Lowercase name for object dependency
+        $root = lc ($root) unless $self->dep ($name) =~ /^(?:INC|H)$/;
+
+        # Select "done" file extension
+        if ($self->dep ($name) =~ /^(?:INC|H)$/) {
+          push @dep, $name . $outfile_ext{IDONE};
+
+        } else {
+          push @dep, $root . $outfile_ext{DONE};
+        }
+      }
+
+      $rules->{$self->donebase} = {
+        ACTION => 'TOUCH', DEP => \@dep, DEST => '$(FCM_DONEDIR)',
+      };
+    }
+    
+    if ($self->interfacebase) {
+      # Interface target
+      # ------------------------------------------------------------------------
+      # Source file dependencies
+      my @dep = ();
+      for my $name (sort keys %{ $self->dep }) {
+        # Depends on Fortran 9X modules
+        push @dep, lc ($name) . $outfile_ext{OBJ}
+          if $self->dep ($name) eq 'USE';
+      }
+
+      $rules->{$self->interfacebase} = {ACTION => '', DEP => \@dep};
+    }
+
+  } elsif ($self->is_type_all ('INCLUDE')) {
+    # Copy include target
+    # --------------------------------------------------------------------------
+    my @dep = ($self->rule_src);
+
+    for my $name (sort keys %{ $self->dep }) {
+      # A Fortran 9X module, lower case object file name
+      if ($self->dep ($name) eq 'USE') {
+        (my $root = $name) =~ s/\.\w+$//;
+        push @dep, lc ($root) . $outfile_ext{OBJ};
+
+      # An include file
+      } elsif ($self->dep ($name) =~ /^(?:INC|H|INTERFACE)$/) {
+        push @dep, $name;
+      }
+    }
+
+    $rules->{$self->curbase} = {
+      ACTION => 'CP', DEP => \@dep, DEST => '$(FCM_INCDIR)',
+    };
+
+    # Touch IDONE file
+    # --------------------------------------------------------------------------
+    if ($self->donebase) {
+      my @dep = ($self->rule_src);
+
+      for my $name (sort keys %{ $self->dep }) {
+        (my $root = $name) =~ s/\.\w+$//;
+
+        # Lowercase name for object dependency
+        $root   = lc ($root) unless $self->dep ($name) =~ /^(?:INC|H)$/;
+
+        # Select "done" file extension
+        if ($self->dep ($name) =~ /^(?:INC|H)$/) {
+          push @dep, $name . $outfile_ext{IDONE};
+
+        } else {
+          push @dep, $root . $outfile_ext{DONE};
+        }
+      }
+
+      $rules->{$self->donebase} = {
+        ACTION => 'TOUCH', DEP => \@dep, DEST => '$(FCM_DONEDIR)',
+      };
+    }
+
+  } elsif ($self->is_type_any (qw/EXE SCRIPT/)) {
+    # Copy executable file
+    # --------------------------------------------------------------------------
+    my @dep = ($self->rule_src);
+
+    # Depends on dummy copy file, if file is an "always build type"
+    push @dep, $self->setting (qw/BLD_CPDUMMY/)
+      if $self->is_type_any (split (
+        /$FCM1::Config::DELIMITER_LIST/, $self->setting ('BLD_TYPE_ALWAYS_BUILD')
+      ));
+
+    # Depends on other executable files
+    for my $name (sort keys %{ $self->dep }) {
+      push @dep, $name if $self->dep ($name) eq 'EXE';
+    }
+
+    $rules->{$self->curbase} = {
+      ACTION => 'CP', DEP => \@dep, DEST => '$(FCM_BINDIR)',
+    };
+
+  } elsif (@{ $self->children }) {
+    # Targets for top level and package flags files and dummy dependencies
+    # --------------------------------------------------------------------------
+    my %tool_src   = %{ $self->setting ('TOOL_SRC') };
+    my %flags_tool = (LD => '', LDFLAGS => '');
+
+    for my $key (keys %tool_src) {
+      $flags_tool{$tool_src{$key}{FLAGS}} = $tool_src{$key}{COMMAND}
+        if exists $tool_src{$key}{FLAGS};
+
+      $flags_tool{$tool_src{$key}{PPKEYS}} = ''
+        if exists $tool_src{$key}{PPKEYS};
+    }
+
+    for my $name (sort keys %flags_tool) {
+      my @dep = $self->pkgname eq '' ? () : $self->flagsbase ($name, -2);
+      push @dep, $self->flagsbase ($flags_tool{$name})
+        if $self->pkgname eq '' and $flags_tool{$name};
+
+      $rules->{$self->flagsbase ($flags_tool{$name})} = {
+        ACTION => 'TOUCH',
+        DEST   => '$(FCM_FLAGSDIR)',
+      } if $self->pkgname eq '' and $flags_tool{$name};
+
+      $rules->{$self->flagsbase ($name)} = {
+        ACTION => 'TOUCH',
+        DEP    => \@dep,
+        DEST   => '$(FCM_FLAGSDIR)',
+      };
+    }
+
+    # Package object and library
+    # --------------------------------------------------------------------------
+    {
+      my @dep;
+      # Add objects from children
+      for my $child (sort {$a->pkgname cmp $b->pkgname} @{ $self->children }) {
+        push @dep, $child->rule_obj_var (1)
+          if $child->libbase and $child->rules ($child->libbase);
+        push @dep, $child->objbase
+          if $child->cursrc and $child->objbase and
+             not $child->is_type_any (qw/PROGRAM BLOCKDATA/);
+      }
+
+      if (@dep) {
+        $rules->{$self->libbase} = {ACTION => 'AR', DEP => \@dep};
+      }
+    }
+
+    # Package data files
+    # --------------------------------------------------------------------------
+    {
+      my @dep;
+      for my $child (@{ $self->children }) {
+        push @dep, $child->rule_src if $child->src and not $child->type;
+      }
+
+      if (@dep) {
+        push @dep, $self->setting (qw/BLD_CPDUMMY/);
+        $rules->{$self->etcbase} = {
+          ACTION => 'CP_DATA', DEP => \@dep, DEST => '$(FCM_ETCDIR)',
+        };
+      }
+    }
+  }
+
+  return $rules;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->get_setting ($setting[, @prefix]);
+#
+# DESCRIPTION
+#   This method gets the correct $setting for the current source by following
+#   its package name. If @prefix is set, get the setting with the given prefix.
+# ------------------------------------------------------------------------------
+
+sub get_setting {
+  my ($self, $setting, @prefix) = @_;
+
+  my $val;
+  for my $name (reverse @{ $self->pkgnames }) {
+    my @names = split /__/, $name;
+    $val = $self->setting ($setting, join ('__', (@prefix, @names)));
+
+    $val = $self->setting ($setting, join ('__', (@prefix, @names)))
+      if (not defined $val) and @names and $names[-1] =~ s/\.[^\.]+$//;
+    last if defined $val;
+  }
+
+  return $val;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $type = $self->get_type();
+#
+# DESCRIPTION
+#   This method determines whether the source is a type known to the
+#   build system. If so, it returns the type flags delimited by "::".
+# ------------------------------------------------------------------------------
+
+sub get_type {
+  my $self = shift();
+  my @IGNORE_LIST
+    = split(/$FCM1::Config::DELIMITER_LIST/, $self->setting('INFILE_IGNORE'));
+  if (grep {$self->curbase() eq $_} @IGNORE_LIST) {
+    return q{};
+  }
+  # User defined
+  my $type = $self->setting('BLD_TYPE', $self->pkgname());
+  # Extension
+  if (!defined($type)) {
+    my $ext = $self->curext() ? substr($self->curext(), 1) : q{};
+    $type = $self->setting('INFILE_EXT', $ext);
+  }
+  # Pattern of name
+  if (!defined($type)) {
+    my %NAME_PATTERN_TO_TYPE_HASH = %{$self->setting('INFILE_PAT')};
+    PATTERN:
+    while (my ($pattern, $value) = each(%NAME_PATTERN_TO_TYPE_HASH)) {
+      if ($self->curbase() =~ $pattern) {
+        $type = $value;
+        last PATTERN;
+      }
+    }
+  }
+  # Pattern of #! line
+  if (!defined($type) && -s $self->cursrc() && -T _) {
+    my $handle = _open($self->cursrc());
+    my $line = readline($handle);
+    close($handle);
+    my %SHEBANG_PATTERN_TO_TYPE_HASH = %{$self->setting('INFILE_TXT')};
+    PATTERN:
+    while (my ($pattern, $value) = each(%SHEBANG_PATTERN_TO_TYPE_HASH)) {
+      if ($line =~ qr{^\#!.*$pattern}msx) {
+        $type = $value;
+        last PATTERN;
+      }
+    }
+  }
+  if (!$type) {
+    return $type;
+  }
+  # Extra type information for selected file types
+  my %EXTRA_FOR = (
+    qr{\b (?:FORTRAN|FPP) \b}msx => \&_get_type_extra_for_fortran,
+    qr{\b C \b}msx               => \&_get_type_extra_for_c,
+  );
+  EXTRA:
+  while (my ($key, $code_ref) = each(%EXTRA_FOR)) {
+    if ($type =~ $key) {
+      my $handle = _open($self->cursrc());
+      LINE:
+      while (my $line = readline($handle)) {
+        my $extra = $code_ref->($line);
+        if ($extra) {
+          $type .= $FCM1::Config::DELIMITER . $extra;
+          last LINE;
+        }
+      }
+      close($handle);
+      last EXTRA;
+    }
+  }
+  return $type;
+}
+
+sub _get_type_extra_for_fortran {
+  my ($match) = $_[0] =~ qr{\A \s* (PROGRAM|MODULE|BLOCK\s*DATA) \b}imsx;
+  if (!$match) {
+    return;
+  }
+  $match =~ s{\s}{}g;
+  uc($match)
+}
+
+sub _get_type_extra_for_c {
+  ($_[0] =~ qr{int\s+main\s*\(}msx) ? 'PROGRAM' : undef;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flag = $obj->is_in_package ($name);
+#
+# DESCRIPTION
+#   This method returns true if current package is in the package $name.
+# ------------------------------------------------------------------------------
+
+sub is_in_package {
+  my ($self, $name) = @_;
+  
+  my $return = 0;
+  for (@{ $self->pkgnames }) {
+    next unless /^$name(?:\.\w+)?$/;
+    $return = 1;
+    last;
+  }
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flag = $obj->is_type_all ($arg, ...);
+#   $flag = $obj->is_type_any ($arg, ...);
+#
+# DESCRIPTION
+#   This method returns a flag for the following:
+#     is_type_all - does type match all of the arguments?
+#     is_type_any - does type match any of the arguments?
+# ------------------------------------------------------------------------------
+
+for my $name ('all', 'any') {
+  no strict 'refs';
+
+  my $subname = 'is_type_' . $name;
+
+  *$subname = sub {
+    my ($self, @intypes) = @_;
+
+    my $rc = 0;
+    if ($self->type) {
+      my %types = map {($_, 1)} split /$FCM1::Config::DELIMITER/, $self->type;
+
+      for my $intype (@intypes) {
+        $rc = exists $types{$intype};
+        last if ($name eq 'all' and not $rc) or ($name eq 'any' and $rc);
+      }
+    }
+
+    return $rc;
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $obj->rule_obj_var ([$read]);
+#
+# DESCRIPTION
+#   This method returns a string containing the make rule object variable for
+#   the current package. If $read is set, return $($string)
+# ------------------------------------------------------------------------------
+
+sub rule_obj_var {
+  my ($self, $read) = @_;
+
+  my $return;
+  if ($self->setting ('FCM_PCK_OBJECTS', $self->pkgname)) {
+    # Package name registered in unusual list
+    $return = $self->setting ('FCM_PCK_OBJECTS', $self->pkgname);
+
+  } else {
+    # Package name not registered in unusual list
+    $return = $self->pkgname
+              ? join ('__', ('OBJECTS', $self->pkgname)) : 'OBJECTS';
+  }
+
+  $return = $read ? '$(' . $return . ')' : $return;
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $obj->rule_src ();
+#
+# DESCRIPTION
+#   This method returns a string containing the location of the source file
+#   relative to the build root. This string will be suitable for use in a
+#   "Make" rule file for FCM.
+# ------------------------------------------------------------------------------
+
+sub rule_src {
+  my $self = shift;
+
+  my $return = $self->cursrc;
+  LABEL: for my $name (qw/SRC PPSRC/) {
+    for my $i (0 .. @{ $self->setting ('PATH', $name) } - 1) {
+      my $dir = $self->setting ('PATH', $name)->[$i];
+      next unless index ($self->cursrc, $dir) == 0;
+
+      $return = File::Spec->catfile (
+        '$(FCM_' . $name . 'DIR' . ($i ? $i : '') . ')',
+        File::Spec->abs2rel ($self->cursrc, $dir),
+      );
+      last LABEL;
+    }
+  }
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->write_lib_dep_excl ();
+#
+# DESCRIPTION
+#   This method writes a set of exclude dependency configurations for the
+#   library of this package.
+# ------------------------------------------------------------------------------
+
+sub write_lib_dep_excl {
+  my $self = shift();
+  if (!find_file_in_path($self->libbase(), $self->setting(qw/PATH LIB/))) {
+    return 0;
+  }
+
+  my $ETC_DIR = $self->setting(qw/PATH ETC/)->[0];
+  my $CFG_EXT = $self->setting(qw/OUTFILE_EXT CFG/);
+  my $LABEL_OF_EXCL_DEP = $self->cfglabel('BLD_DEP_EXCL');
+  my @SETTINGS = (
+       #dependency   #source file type list       #dependency name function
+       ['H'        , [qw{INCLUDE CPP          }], sub {$_[0]->base()}         ],
+       ['INTERFACE', [qw{INCLUDE INTERFACE    }], sub {$_[0]->base()}         ],
+       ['INC'      , [qw{INCLUDE              }], sub {$_[0]->base()}         ],
+       ['USE'      , [qw{SOURCE FORTRAN MODULE}], sub {$_[0]->root()}         ],
+       ['INTERFACE', [qw{SOURCE FORTRAN       }], sub {$_[0]->interfacebase()}],
+       ['OBJ'      , [qw{SOURCE               }], sub {$_[0]->root()}         ],
+  );
+
+  my $cfg = FCM1::CfgFile->new();
+  my @stack = ($self);
+  NODE:
+  while (my $node = pop(@stack)) {
+    # Is a directory
+    if (@{$node->children()}) {
+      push(@stack, reverse(@{$node->children()}));
+      next NODE;
+    }
+    # Is a typed file
+    if (
+          $node->cursrc()
+      &&  $node->type()
+      &&  !$node->is_type_any(qw{PROGRAM BLOCKDATA})
+    ) {
+      for (@SETTINGS) {
+        my ($key, $type_list_ref, $name_func_ref) = @{$_};
+        my $name = $name_func_ref->($node);
+        if ($name && $node->is_type_all(@{$type_list_ref})) {
+          push(
+            @{$cfg->lines()},
+            FCM1::CfgLine->new(
+              label => $LABEL_OF_EXCL_DEP,
+              value => $key . $FCM1::Config::DELIMITER . $name,
+            ),
+          );
+          next NODE;
+        }
+      }
+    }
+  }
+
+  # Write to configuration file
+  $cfg->print_cfg(
+    File::Spec->catfile($ETC_DIR, $self->libbase('lib', $CFG_EXT)),
+  );
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $obj->write_rules ();
+#
+# DESCRIPTION
+#   This method returns a string containing the "Make" rules for building the
+#   source file.
+# ------------------------------------------------------------------------------
+
+sub write_rules {
+  my $self  = shift;
+  my $mk    = '';
+
+  for my $target (sort keys %{ $self->rules }) {
+    my $rule = $self->rules ($target);
+    next unless defined ($rule->{ACTION});
+
+    if ($rule->{ACTION} eq 'AR') {
+      my $var = $self->rule_obj_var;
+      $mk .= ($var eq 'OBJECTS' ? 'export ' : '') . $var . ' =';
+      $mk .= ' ' . join (' ', @{ $rule->{DEP} });
+      $mk .= "\n\n";
+    }
+
+    $mk .= $target . ':';
+    
+    if ($rule->{ACTION} eq 'AR') {
+      $mk .= ' ' . $self->rule_obj_var (1);
+
+    } else {
+      for my $dep (@{ $rule->{DEP} }) {
+        $mk .= ' ' . $dep;
+      }
+    }
+
+    $mk .= "\n";
+
+    if (exists $rule->{ACTION}) {
+      if ($rule->{ACTION} eq 'AR') {
+        $mk .= "\t" . 'fcm_internal archive $@ $^' . "\n";
+
+      } elsif ($rule->{ACTION} eq 'CP') {
+        $mk .= "\t" . 'cp $< ' . $rule->{DEST} . "\n";
+        $mk .= "\t" . 'chmod u+w ' .
+               File::Spec->catfile ($rule->{DEST}, '$@') . "\n";
+
+      } elsif ($rule->{ACTION} eq 'CP_DATA') {
+        $mk .= "\t" . 'cp $^ ' . $rule->{DEST} . "\n";
+        $mk .= "\t" . 'touch ' .
+               File::Spec->catfile ($rule->{DEST}, '$@') . "\n";
+
+      } elsif ($rule->{ACTION} eq 'COMPILE') {
+        if ($self->lang) {
+          $mk .= "\t" . 'fcm_internal compile:' . substr ($self->lang, 0, 1) .
+                 ' ' . $self->pkgnames->[-2] . ' $< $@';
+          $mk .= ' 1' if ($self->flagsbase ('PPKEYS') and not $self->ppsrc);
+          $mk .= "\n";
+        }
+
+      } elsif ($rule->{ACTION} eq 'LOAD') {
+        if ($self->lang) {
+          $mk .= "\t" . 'fcm_internal load:' . substr ($self->lang, 0, 1) .
+                 ' ' . $self->pkgnames->[-2] . ' $< $@';
+          $mk .= ' ' . join (' ', @{ $rule->{BLOCKDATA} })
+            if @{ $rule->{BLOCKDATA} };
+          $mk .= "\n";
+        }
+
+      } elsif ($rule->{ACTION} eq 'TOUCH') {
+        $mk .= "\t" . 'touch ' .
+               File::Spec->catfile ($rule->{DEST}, '$@') . "\n";
+      }
+    }
+
+    $mk .= "\n";
+  }
+
+  return $mk;
+}
+
+# Wraps "chdir". Returns old directory.
+sub _chdir {
+  my ($self, $dir) = @_;
+  my $old_cwd = cwd();
+  $self->_event('CHDIR', $dir);
+  chdir($dir) || croak(sprintf($ERR_MESS_OF{CHDIR}, $dir));
+  $old_cwd;
+}
+
+# Wraps an event.
+sub _event {
+  my ($self, $key, @args) = @_;
+  my ($format, $level) = @{$EVENT_SETTING_OF{$key}};
+  $level ||= 1;
+  if ($self->verbose() >= $level) {
+    printf($format . ".\n", @args);
+  }
+}
+
+# Wraps "open".
+sub _open {
+  my ($path, $mode) = @_;
+  $mode ||= '<';
+  open(my $handle, $mode, $path) || croak(sprintf($ERR_MESS_OF{OPEN}, $path, $!));
+  $handle;
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/BuildTask.pm b/lib/FCM1/BuildTask.pm
new file mode 100644
index 0000000..6b9d0ba
--- /dev/null
+++ b/lib/FCM1/BuildTask.pm
@@ -0,0 +1,353 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::BuildTask
+#
+# DESCRIPTION
+#   This class hosts information of a build task in the FCM build system.
+#
+# ------------------------------------------------------------------------------
+
+package FCM1::BuildTask;
+ at ISA = qw(FCM1::Base);
+
+# Standard pragma
+use strict;
+use warnings;
+
+# Standard modules
+use Carp;
+use File::Compare;
+use File::Copy;
+use File::Basename;
+use File::Path;
+use File::Spec::Functions;
+
+# FCM component modules
+use FCM1::Base;
+use FCM1::Timer;
+use FCM1::Util;
+
+# List of property methods for this class
+my @scalar_properties = (
+  'actiontype',  # type of action
+  'dependency',  # list of dependencies for this target
+  'srcfile',     # reference to input FCM1::BuildSrc instance
+  'output',      # output file
+  'outputmtime', # output file modification time
+  'target',      # target name for this task
+  'targetpath',  # search path for the target
+);
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::BuildTask->new (%args);
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::BuildTask class. See
+#   above for allowed list of properties. (KEYS should be in uppercase.)
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  my $self = FCM1::Base->new (%args);
+
+  bless $self, $class;
+
+  for my $name (@scalar_properties) {
+    $self->{$name} = exists $args{uc ($name)} ? $args{uc ($name)} : undef;
+  }
+
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @scalar_properties.
+# ------------------------------------------------------------------------------
+
+for my $name (@scalar_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    # Argument specified, set property to specified argument
+    if (@_) {
+      $self->{$name} = $_[0];
+
+      if ($name eq 'output') {
+        $self->{outputmtime} = $_[0] ? (stat $_[0]) [9] : undef;
+      }
+    }
+
+    # Default value for property
+    if (not defined $self->{$name}) {
+      if ($name eq 'dependency' or $name eq 'targetpath') {
+        # Reference to an array
+        $self->{$name} = [];
+      }
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->action (TASKLIST => \%tasklist);
+#
+# DESCRIPTION
+#   This method performs the task action and sets the output accordingly. The
+#   argument TASKLIST must be a reference to a hash containing the other tasks
+#   of the build, which this task may depend on. The keys of the hash must the
+#   name of the target names of the tasks, and the values of the hash must be
+#   the references to the corresponding FCM1::BuildTask instances. The method
+#   returns true if the task has been performed to create a new version of the
+#   target.
+# ------------------------------------------------------------------------------
+
+sub action {
+  my $self     = shift;
+  my %args     = @_;
+  my $tasklist = exists $args{TASKLIST} ? $args{TASKLIST} : {};
+
+  return unless $self->actiontype;
+
+  my $uptodate     = 1;
+  my $dep_uptodate = 1;
+
+  # Check if dependencies are up to date
+  # ----------------------------------------------------------------------------
+  for my $depend (@{ $self->dependency }) {
+    if (exists $tasklist->{$depend}) {
+      if (not $tasklist->{$depend}->output) {
+        # Dependency task output is not set, performs its task action
+        if ($tasklist->{$depend}->action (TASKLIST => $tasklist)) {
+          $uptodate     = 0;
+          $dep_uptodate = 0;
+        }
+      }
+
+    } elsif ($self->verbose > 1) {
+      w_report 'Warning: Task for "', $depend,
+               '" does not exist, may be required by ', $self->target;
+    }
+  }
+
+  # Check if the target exists in the search path
+  # ----------------------------------------------------------------------------
+  if (@{ $self->targetpath }) {
+    my $output = find_file_in_path ($self->target, $self->targetpath);
+    $self->output ($output) if $output;
+  }
+
+  # Target is out of date if it does not exist
+  if ($uptodate) {
+    $uptodate = 0 if not $self->output;
+  }
+
+  # Check if current target is older than its dependencies
+  # ----------------------------------------------------------------------------
+  if ($uptodate) {
+    for my $depend (@{ $self->dependency }) {
+      next unless exists $tasklist->{$depend};
+
+      if ($tasklist->{$depend}->outputmtime > $self->outputmtime) {
+        $uptodate     = 0;
+        $dep_uptodate = 0;
+      }
+    }
+
+    if ($uptodate and ref $self->srcfile) {
+      $uptodate = 0 if $self->srcfile->mtime > $self->outputmtime;
+    }
+  }
+
+  if ($uptodate) {
+    # Current target and its dependencies are up to date
+    # --------------------------------------------------------------------------
+    if ($self->actiontype eq 'PP') {
+      # "done" file up to date, set name of pre-processed source file
+      # ------------------------------------------------------------------------
+      my $base     = $self->srcfile->root . lc ($self->srcfile->ext);
+      my @pknames  = split '__', (@{ $self->srcfile->pkgnames })[-2];
+      my @path     = map {
+        catfile ($_, @pknames);
+      } @{ $self->setting (qw/PATH PPSRC/) };
+      my $oldfile = find_file_in_path ($base, \@path);
+      $self->srcfile->ppsrc ($oldfile);
+    }
+
+  } else {
+    # Perform action is not up to date
+    # --------------------------------------------------------------------------
+    # (For GENINTERFACE and PP, perform action if "done" file not up to date)
+    my $new_output = @{ $self->targetpath }
+                     ? catfile ($self->targetpath->[0], $self->target)
+                     : $self->target;
+
+    # Create destination container directory if necessary
+    my $destdir = dirname $new_output;
+
+    if (not -d $destdir) {
+      print 'Make directory: ', $destdir, "\n" if $self->verbose > 2;
+      mkpath $destdir;
+    }
+
+    # List of actions
+    if ($self->actiontype eq 'UPDATE') {
+      # Action is UPDATE: Update file
+      # ------------------------------------------------------------------------
+      print 'Update: ', $new_output, "\n" if $self->verbose > 2;
+      touch_file $new_output
+        or croak 'Unable to update "', $new_output, '", abort';
+      $self->output ($new_output);
+
+    } elsif ($self->actiontype eq 'COPY') {
+      # Action is COPY: copy file to destination if necessary
+      # ------------------------------------------------------------------------
+      my $copy_required = ($dep_uptodate and $self->output and -f $self->output)
+                          ? compare ($self->output, $self->srcfile->src)
+                          : 1;
+
+      if ($copy_required) {
+        # Set up copy command
+        my $srcfile = $self->srcfile->src;
+        my $destfile = catfile ($destdir, basename($srcfile));
+        print 'Copy: ', $srcfile, "\n", '  to: ', $destfile, "\n"
+          if $self->verbose > 2;
+        &copy ($srcfile, $destfile)
+          or die $srcfile, ': copy to ', $destfile, ' failed (', $!, '), abort';
+        chmod (((stat ($srcfile))[2] & 07777), $destfile);
+
+        $self->output ($new_output);
+
+      } else {
+        $uptodate = 1;
+      }
+
+    } elsif ($self->actiontype eq 'PP' or $self->actiontype eq 'GENINTERFACE') {
+      # Action is PP or GENINTERFACE: process file
+      # ------------------------------------------------------------------------
+      my ($newlines, $base, @path);
+
+      if ($self->actiontype eq 'PP') {
+        # Invoke the pre-processor on the source file
+        # ----------------------------------------------------------------------
+        # Get lines in the pre-processed source
+        $newlines = $self->srcfile->get_pre_process;
+        $base     = $self->srcfile->root . lc ($self->srcfile->ext);
+
+        # Get search path for the existing pre-processed file
+        my @pknames  = split '__', (@{ $self->srcfile->pkgnames })[-2];
+        @path        = map {
+          catfile ($_, @pknames);
+        } @{ $self->setting (qw/PATH PPSRC/) };
+
+      } else { # if ($self->actiontype eq 'GENINTERFACE')
+        # Invoke the interface generator
+        # ----------------------------------------------------------------------
+        # Get new interface lines
+        $newlines = $self->srcfile->get_fortran_interface;
+
+        # Get search path for the existing interface file
+        $base     = $self->srcfile->interfacebase;
+        @path     = @{ $self->setting (qw/PATH INC/) },
+      }
+
+
+      # If pre-processed or interface file exists,
+      # compare its content with new lines to see if it has been updated
+      my $update_required = 1;
+      my $oldfile = find_file_in_path ($base, \@path);
+
+      if ($oldfile and -f $oldfile) {
+        # Read old file
+        open FILE, '<', $oldfile;
+        my @oldlines = readline 'FILE';
+        close FILE;
+
+        # Compare old contents and new contents
+        if (@oldlines eq @$newlines) {
+          $update_required = grep {
+            $oldlines[$_] ne $newlines->[$_];
+          } (0 .. $#oldlines);
+        }
+      }
+
+      if ($update_required) {
+        # Update the pre-processed source or interface file
+        # ----------------------------------------------------------------------
+        # Determine container directory of the  pre-processed or interface file
+        my $newfile = @path ? catfile ($path[0], $base) : $base;
+
+        # Create the container directory if necessary
+        if (not -d $path[0]) {
+          print 'Make directory: ', $path[0], "\n"
+            if $self->verbose > 1;
+          mkpath $path[0];
+        }
+
+        # Update the pre-processor or interface file
+        open FILE, '>', $newfile
+          or croak 'Cannot write to "', $newfile, '" (', $!, '), abort';
+        print FILE @$newlines;
+        close FILE
+          or croak 'Cannot write to "', $newfile, '" (', $!, '), abort';
+        print 'Generated: ', $newfile, "\n" if $self->verbose > 1;
+
+        # Set the name of the pre-processed file
+        $self->srcfile->ppsrc ($newfile) if $self->actiontype eq 'PP';
+
+      } else {
+        # Content in pre-processed source or interface file is up to date
+        # ----------------------------------------------------------------------
+        $uptodate = 1;
+
+        # Set the name of the pre-processed file
+        $self->srcfile->ppsrc ($oldfile) if $self->actiontype eq 'PP';
+      }
+
+      # Update the "done" file
+      print 'Update: ', $new_output, "\n" if $self->verbose > 2;
+      touch_file $new_output
+        or croak 'Unable to update "', $new_output, '", abort';
+      $self->output ($new_output);
+
+    } else {
+      carp 'Action type "', $self->actiontype, "' not supported";
+    }
+  }
+
+  return not $uptodate;
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/CfgFile.pm b/lib/FCM1/CfgFile.pm
new file mode 100644
index 0000000..8f947f8
--- /dev/null
+++ b/lib/FCM1/CfgFile.pm
@@ -0,0 +1,597 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::CfgFile
+#
+# DESCRIPTION
+#   This class is used for reading and writing FCM config files. A FCM config
+#   file is a line-based text file that provides information on how to perform
+#   a particular task using the FCM system.
+#
+# ------------------------------------------------------------------------------
+
+package FCM1::CfgFile;
+ at ISA = qw(FCM1::Base);
+
+# Standard pragma
+use warnings;
+use strict;
+
+# Standard modules
+use Carp;
+use File::Basename;
+use File::Path;
+use File::Spec;
+
+# FCM component modules
+use FCM1::Base;
+use FCM1::CfgLine;
+use FCM1::Config;
+use FCM1::Keyword;
+use FCM1::Util;
+
+# List of property methods for this class
+my @scalar_properties = (
+  'actual_src', # actual source of configuration file
+  'lines',      # list of lines, FCM1::CfgLine objects
+  'pegrev',     # peg revision of configuration file
+  'src',        # source of configuration file
+  'type',       # type of configuration file
+  'version',    # version of configuration file
+);
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::CfgFile->new (%args);
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::CfgFile class. See above
+#   for allowed list of properties. (KEYS should be in uppercase.)
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  my $self = FCM1::Base->new (%args);
+
+  bless $self, $class;
+
+  for (@scalar_properties) {
+    $self->{$_} = exists $args{uc ($_)} ? $args{uc ($_)} : undef;
+  }
+
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @scalar_properties.
+# ------------------------------------------------------------------------------
+
+for my $name (@scalar_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    if (@_) {
+      $self->{$name} = $_[0];
+    }
+
+    if (not defined $self->{$name}) {
+      if ($name eq 'lines') {
+        $self->{$name} = [];
+      }
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $mtime = $obj->mtime ();
+#
+# DESCRIPTION
+#   This method returns the modified time of the configuration file source.
+# ------------------------------------------------------------------------------
+
+sub mtime {
+  my $self  = shift;
+  my $mtime = undef;
+
+  if (-f $self->src) {
+    $mtime = (stat $self->src)[9];
+  }
+
+  return $mtime;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $read = $obj->read_cfg($is_for_inheritance);
+#
+# DESCRIPTION
+#   This method reads the current configuration file. It returns the number of
+#   lines read from the config file, or "undef" if it fails. The result is
+#   placed in the LINES array of the current instance, and can be accessed via
+#   the "lines" method.
+# ------------------------------------------------------------------------------
+
+sub read_cfg {
+  my ($self, $is_for_inheritance) = @_;
+
+  my @lines = $self->_get_cfg_lines($is_for_inheritance);
+
+  # List of CFG types that need INC declarations expansion
+  my %exp_inc    = ();
+  for (split (/$FCM1::Config::DELIMITER_LIST/, $self->setting ('CFG_EXP_INC'))) {
+    $exp_inc{uc ($_)} = 1;
+  }
+
+  # List of CFG labels that are reserved keywords
+  my %cfg_keywords = ();
+  for (split (/$FCM1::Config::DELIMITER_LIST/, $self->setting ('CFG_KEYWORD'))) {
+    $cfg_keywords{$self->cfglabel ($_)} = 1;
+  }
+
+  # Loop each line, to separate lines into label : value pairs
+  my $cont = undef;
+  my $here = undef;
+  for my $line_num (1 .. @lines) {
+    my $line = $lines[$line_num - 1];
+    chomp $line;
+
+    my $label   = '';
+    my $value   = '';
+    my $comment = '';
+
+    # If this line is a continuation, set $start to point to the line that
+    # starts this continuation. Otherwise, set $start to undef
+    my $start = defined ($cont) ? $self->lines->[$cont] : undef;
+    my $warning = undef;
+
+    if ($line =~ /^(\s*#.*)$/) { # comment line
+      $comment = $1;
+
+    } elsif ($line =~ /\S/) {    # non-blank line
+      if (defined $cont) {
+        # Previous line has a continuation mark
+        $value = $line;
+
+        # Separate value and comment
+        if ($value =~ s/((?:\s+|^)#\s+.*)$//) {
+          $comment = $1;
+        }
+
+        # Remove leading spaces
+        $value =~ s/^\s*\\?//;
+
+        # Expand environment variables
+        my $warn;
+        ($value, $warn) = $self->_expand_variable ($value, 1) if $value;
+        $warning .= ($warning ? ', ' : '') . $warn if $warn;
+
+        # Expand internal variables
+        ($value, $warn) = $self->_expand_variable ($value, 0) if $value;
+        $warning .= ($warning ? ', ' : '') . $warn if $warn;
+
+        # Get "line" that begins the current continuation
+        my $v = $start->value . $value;
+        $v =~ s/\\$//;
+        $start->value ($v);
+
+      } else {
+        # Previous line does not have a continuation mark
+        if ($line =~ /^\s*(\S+)(?:\s+(.*))?$/) {
+          # Check line contains a valid label:value pair
+          $label = $1;
+          $value = defined ($2) ? $2 : '';
+
+          # Separate value and comment
+          if ($value =~ s/((?:\s+|^)#\s+.*)$//) {
+            $comment = $1;
+          }
+
+          # Remove trailing spaces
+          $value =~ s/\s+$//;
+
+          # Value begins with $HERE?
+          $here  = ($value =~ /\$\{?HERE\}?(?:[^A-Z_]|$)/);
+
+          # Expand environment variables
+          my $warn;
+          ($value, $warn) = $self->_expand_variable ($value, 1) if $value;
+          $warning .= ($warning ? ', ' : '') . $warn if $warn;
+
+          # Expand internal variables
+          ($value, $warn) = $self->_expand_variable ($value, 0) if $value;
+          $warning .= ($warning ? ', ' : '') . $warn if $warn;
+        }
+      }
+
+      # Determine whether current line ends with a continuation mark
+      if ($value =~ s/\\$//) {
+        $cont = scalar (@{ $self->lines }) unless defined $cont;
+
+      } else {
+        $cont = undef;
+      }
+    }
+
+    if (exists $exp_inc{uc ($self->type)} and
+        uc ($start ? $start->label : $label) eq $self->cfglabel ('INC') and
+        not defined $cont) {
+      # Current configuration file requires expansion of INC declarations
+      # The start/current line is an INC declaration
+      # The current line is not a continuation or is the end of the continuation
+
+      # Get lines from an "include" configuration file
+      my $src = ($start ? $start->value : $value);
+      $src   .= '@' . $self->pegrev if $here and $self->pegrev;
+
+      if ($src) {
+        # Invoke a new instance to read the source
+        my $cfg = FCM1::CfgFile->new (
+          SRC => expand_tilde ($src), TYPE => $self->type,
+        );
+
+        $cfg->read_cfg;
+
+        # Add lines to the lines array in the current configuration file
+        $comment = 'INC ' . $src . ' ';
+        push @{$self->lines}, FCM1::CfgLine->new (
+          comment => $comment . '# Start',
+          number  => ($start ? $start->number : $line_num),
+          src     => $self->actual_src,
+          warning => $warning,
+        );
+        push @{ $self->lines }, @{ $cfg->lines };
+        push @{$self->lines}, FCM1::CfgLine->new (
+          comment => $comment . '# End',
+          src     => $self->actual_src,
+        );
+
+      } else {
+        push @{$self->lines}, FCM1::CfgLine->new (
+          number  => $line_num,
+          src     => $self->actual_src,
+          warning => 'empty INC declaration.'
+        );
+      }
+
+    } else {
+      # Push label:value pair into lines array
+      push @{$self->lines}, FCM1::CfgLine->new (
+        label   => $label,
+        value   => ($label ? $value : ''),
+        comment => $comment,
+        number  => $line_num,
+        src     => $self->actual_src,
+        warning => $warning,
+      );
+    }
+
+    next if defined $cont; # current line not a continuation
+
+    my $slabel = ($start ? $start->label : $label);
+    my $svalue = ($start ? $start->value : $value);
+    next unless $slabel;
+
+    # Check config file type and version
+    if (index (uc ($slabel), $self->cfglabel ('CFGFILE')) == 0) {
+      my @words = split /$FCM1::Config::DELIMITER_PATTERN/, $slabel;
+      shift @words;
+
+      my $name = @words ? lc ($words[0]) : 'type';
+
+      if ($self->can ($name)) {
+        $self->$name ($svalue);
+      }
+    }
+
+    # Set internal variable
+    $slabel =~ s/^\%//; # Remove leading "%" from label
+
+    $self->config->variable ($slabel, $svalue)
+      unless exists $cfg_keywords{$slabel};
+  }
+
+  # Report and reset warnings
+  # ----------------------------------------------------------------------------
+  for my $line (@{ $self->lines }) {
+    w_report $line->format_warning if $line->warning;
+    $line->warning (undef);
+  }
+
+  return @{ $self->lines };
+
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->print_cfg ($file, [$force]);
+#
+# DESCRIPTION
+#   This method prints the content of current configuration file. If no
+#   argument is specified, it prints output to the standard output. If $file is
+#   specified, and is a writable file name, the output is sent to the file.  If
+#   the file already exists, its content is compared to the current output.
+#   Nothing will be written if the content is unchanged unless $force is
+#   specified. Otherwise, for typed configuration files, the existing file is
+#   renamed using a prefix that contains its last modified time. The method
+#   returns 1 if there is no error.
+# ------------------------------------------------------------------------------
+
+sub print_cfg {
+  my ($self, $file, $force) = @_;
+
+  # Count maximum number of characters in the labels, (for pretty printing)
+  my $max_label_len = 0;
+  for my $line (@{ $self->lines }) {
+    next unless $line->label;
+    my $label_len  = length $line->label;
+    $max_label_len = $label_len if $label_len > $max_label_len;
+  }
+
+  # Output string
+  my $out = '';
+
+  # Append each line of the config file to the output string
+  for my $line (@{ $self->lines }) {
+    $out .= $line->print_line ($max_label_len - length ($line->label) + 1);
+    $out .= "\n";
+  }
+
+  if ($out) {
+    my $out_handle = select();
+
+    # Open file if necessary
+    if ($file) {
+      # Make sure the host directory exists and is writable
+      my $dirname = dirname $file;
+      if (not -d $dirname) {
+        print 'Make directory: ', $dirname, "\n" if $self->verbose;
+        mkpath $dirname;
+      }
+      croak $dirname, ': cannot write to config file directory, abort'
+        unless -d $dirname;
+
+      if (-f $file and not $force) {
+        # Read old config file to see if content has changed
+        open(my $handle, '<', $file) || croak("$file: $!\n");
+        my $in_lines = '';
+        while (my $line = readline($handle)) {
+          $in_lines .= $line;
+        }
+        close($handle);
+
+        # Return if content is up-to-date
+        if ($in_lines eq $out) {
+          print 'No change in ', lc ($self->type), ' cfg: ', $file, "\n"
+            if $self->verbose > 1 and $self->type;
+          return 1;
+        }
+
+        # If config file already exists, make sure it is writable
+        if ($self->type) {
+          # Existing config file writable, rename it using its time stamp
+          my $mtime = (stat $file)[9];
+          my ($sec, $min, $hour, $mday, $mon, $year) = (gmtime $mtime)[0 .. 5];
+          my $timestamp = sprintf '%4d%2.2d%2.2d_%2.2d%2.2d%2.2d_',
+                          $year + 1900, $mon + 1, $mday, $hour, $min, $sec;
+          my $oldfile   = File::Spec->catfile (
+            $dirname, $timestamp . basename ($file)
+          );
+          rename $file, $oldfile;
+          print 'Rename existing ', lc ($self->type), ' cfg: ',
+                $oldfile, "\n" if $self->verbose > 1;
+        }
+      }
+
+      # Open file and select file handle
+      open(my $handle, '>', $file) || croak("$file: $!\n");
+      $out_handle = $handle;
+    }
+
+    # Print output
+    print($out_handle $out);
+
+    # Close file if necessary
+    if ($file) {
+      close($out_handle) || croak("$file: $!\n");
+
+      if ($self->type and $self->verbose > 1) {
+        print 'Generated ', lc ($self->type), ' cfg: ', $file, "\n";
+
+      } elsif ($self->verbose > 2) {
+        print 'Generated cfg: ', $file, "\n";
+      }
+    }
+
+  } else {
+    # Warn if nothing to print
+    my $warning = 'Empty configuration';
+    $warning   .= ' - nothing written to file: ' . $file if $file;
+    carp $warning if $self->type;
+  }
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @lines = $self->_get_cfg_lines($is_for_inheritance);
+#
+# DESCRIPTION
+#   This internal method opens the configuration file and returns its contents
+#   as an array of lines. If the $self->src() is given as a URI, the method
+#   tries to read it with "svn cat". Otherwise, the method tries to read
+#   $self->src() with open() and readline(). If $self->type() is not a known
+#   type, $self->src() can only be a regular file and $is_for_inheritance is
+#   ignored.  Otherwise, $self->src() can be a regular file or a directory and
+#   $is_for_inheritance is used to determine the behaviour for searching the
+#   directory for a configuration file. If $is_for_inheritance is set, the
+#   config file may be located at "$src/cfg/$type.cfg". If $is_for_inheritance
+#   is not set, the config file may be located at "$src/$type.cfg" or
+#   "$src/cfg/$type.cfg".
+# ------------------------------------------------------------------------------
+
+sub _get_cfg_lines {
+  my ($self, $is_for_inheritance) = @_;
+  my $DIAG = sub {};
+  my @paths_refs = ([]);
+  if ($self->type() && exists($self->setting('CFG_NAME')->{uc($self->type())})) {
+    my $base = $self->setting('CFG_NAME')->{uc($self->type())};
+    if (!$is_for_inheritance) {
+      push(@paths_refs, [$base]);
+    }
+    push(@paths_refs, [$self->setting(qw/DIR CFG/), $base]);
+    if ($self->verbose()) {
+      $DIAG = sub {printf("Config file (%s): %s\n", $self->type(), @_)};
+    }
+  }
+  if ($self->src() =~ qr{\A([A-Za-z][\w\+-\.]*):}xms) {
+    # $self->src() is a URI, try "svn cat"
+    my $src = FCM1::Util::tidy_url(FCM1::Keyword::expand($self->src()));
+    my ($uri, $rev) = $src =~ qr{\A(.+?)(?:\@([^\@]+))?\z}msx;
+    $rev ||= 'HEAD';
+    for my $paths_ref (@paths_refs) {
+      my $path = join('/', $uri, @{$paths_ref}) . '@' . $rev;
+      local($@);
+      my @lines = eval {
+        run_command([qw/svn cat/, $path], METHOD => 'qx', DEVNULL => 1);
+      };
+      if (!$@) {
+        $self->pegrev($rev);
+        $self->actual_src($path);
+        $DIAG->($path);
+        return @lines;
+      }
+    }
+  }
+  else {
+    # $self->src() is not a URI, assume that it resides in the file system
+    for my $paths_ref (@paths_refs) {
+      my $path = File::Spec->catfile($self->src(), @{$paths_ref});
+      if (-e $path && !-d $path) { # "-f $path" returns false for "/dev/null"
+        open(my $handle, '<', $path)
+          || croak("$path: cannot open config file, abort: $!");
+        my @lines = readline($handle);
+        close($handle);
+        $self->actual_src($path);
+        $DIAG->($path);
+        return @lines;
+      }
+    }
+  }
+  croak(sprintf("%s: cannot locate config file, abort", $self->src()));
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $self->_expand_variable ($string, $env[, \%recursive_set]);
+#
+# DESCRIPTION
+#   This internal method expands variables in $string. If $env is true, it
+#   expands environment variables. Otherwise, it expands local variables. If
+#   %recursive_set is specified, it indicates that this method is being called
+#   recursively. In which case, it must not attempt to expand a variable that
+#   exists in the keys of %recursive_set.
+# ------------------------------------------------------------------------------
+
+sub _expand_variable {
+  my ($self, $string, $env, $recursive_set_ref) = @_;
+
+  # Pattern for environment/local variable
+  my @patterns = $env
+    ? (qr#\$([A-Z][A-Z0-9_]+)#, qr#\$\{([A-Z][A-Z0-9_]+)\}#)
+    : (qr#%(\w+(?:::[\w\.-]+)*)#, qr#%\{(\w+(?:(?:::|/)[\w\.-]+)*)\}#);
+
+  my $ret = '';
+  my $warning = undef;
+  while ($string) {
+    # Find the first match in $string
+    my ($prematch, $match, $postmatch, $var_label);
+    for my $pattern (@patterns) {
+      next unless $string =~ /$pattern/;
+      if ((not defined $prematch) or length ($`) < length ($prematch)) {
+        $prematch = $`;
+        $match = $&;
+        $var_label = $1;
+        $postmatch = $';
+      }
+    }
+
+    if ($match) {
+      $ret .= $prematch;
+      $string = $postmatch;
+
+      # Get variable value from environment or local configuration
+      my $variable = $env
+                     ? (exists $ENV{$var_label} ? $ENV{$var_label} : undef)
+                     : $self->config->variable ($var_label);
+
+      if ($env and $var_label eq 'HERE' and not defined $variable) {
+        $variable = dirname ($self->actual_src);
+        $variable = File::Spec->rel2abs ($variable) if not &is_url ($variable);
+      }
+
+      # Substitute match with value of variable
+      if (defined $variable) {
+        my %set = (($recursive_set_ref ? %{$recursive_set_ref} : ()));
+        if (exists($set{$var_label})) {
+          $warning .= ', ' if $warning;
+          $warning .= $match . ': cyclic dependency, variable not expanded';
+          $ret .= $variable;
+
+        } else {
+          my ($r, $w)
+            = $self->_expand_variable($variable, $env, {%set, $var_label => 1});
+          $ret .= $r;
+          if ($w) {
+            $warning .= ', ' if $warning;
+            $warning .= $w;
+          }
+        }
+
+      } else {
+        $warning .= ', ' if $warning;
+        $warning .= $match . ': variable not expanded';
+        $ret .= $match;
+      }
+
+    } else {
+      $ret .= $string;
+      $string = "";
+    }
+  }
+
+  return ($ret, $warning);
+}
+
+1;
+
+__END__
diff --git a/lib/FCM1/CfgLine.pm b/lib/FCM1/CfgLine.pm
new file mode 100644
index 0000000..4819024
--- /dev/null
+++ b/lib/FCM1/CfgLine.pm
@@ -0,0 +1,346 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::CfgLine
+#
+# DESCRIPTION
+#   This class is used for grouping the settings in each line of a FCM
+#   configuration file.
+#
+# ------------------------------------------------------------------------------
+
+package FCM1::CfgLine;
+ at ISA = qw(FCM1::Base);
+
+# Standard pragma
+use warnings;
+use strict;
+
+# Standard modules
+use File::Basename;
+
+# In-house modules
+use FCM1::Base;
+use FCM1::Config;
+use FCM1::Util;
+
+# List of property methods for this class
+my @scalar_properties = (
+  'bvalue',  # line value, in boolean
+  'comment', # (in)line comment
+  'error',   # error message for incorrect usage while parsing the line
+  'label',   # line label
+  'line',    # content of the line
+  'number',  # line number in source file
+  'parsed',  # has this line been parsed (by the extract/build system)?
+  'prefix',  # optional prefix for line label
+  'slabel',  # label without the optional prefix
+  'src',     # name of source file
+  'value',   # line value
+  'warning', # warning message for deprecated usage
+);
+
+# Useful variables
+our $COMMENT_RULER = '-' x 78;
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @cfglines = FCM1::CfgLine->comment_block (@comment);
+#
+# DESCRIPTION
+#   This method returns a list of FCM1::CfgLine objects representing a comment
+#   block with the comment string @comment.
+# ------------------------------------------------------------------------------
+
+sub comment_block {
+  my @return = (
+    FCM1::CfgLine->new (comment => $COMMENT_RULER),
+    (map {FCM1::CfgLine->new (comment => $_)} @_),
+    FCM1::CfgLine->new (comment => $COMMENT_RULER),
+    FCM1::CfgLine->new (),
+  );
+
+  return @return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::CfgLine->new (%args);
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::CfgLine class. See above
+#   for allowed list of properties. (KEYS should be in uppercase.)
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  my $self = FCM1::Base->new (%args);
+
+  for (@scalar_properties) {
+    $self->{$_} = exists $args{uc ($_)} ? $args{uc ($_)} : undef;
+    $self->{$_} = $args{$_} if exists $args{$_};
+  }
+
+  bless $self, $class;
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @scalar_properties.
+# ------------------------------------------------------------------------------
+
+for my $name (@scalar_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    if (@_) {
+      $self->{$name} = $_[0];
+
+      if ($name eq 'line' or $name eq 'label') {
+        $self->{slabel} = undef;
+
+      } elsif ($name eq 'line' or $name eq 'value') {
+        $self->{bvalue} = undef;
+      }
+    }
+
+    # Default value for property
+    if (not defined $self->{$name}) {
+      if ($name =~ /^(?:comment|error|label|line|prefix|src|value)$/) {
+        # Blank
+        $self->{$name} = '';
+
+      } elsif ($name eq 'slabel') {
+        if ($self->prefix and $self->label_starts_with ($self->prefix)) {
+          $self->{$name} = $self->label_from_field (1);
+
+        } else {
+          $self->{$name} = $self->label;
+        }
+
+      } elsif ($name eq 'bvalue') {
+        if (defined ($self->value)) {
+          $self->{$name} = ($self->value =~ /^(\s*|false|no|off|0*)$/i)
+                           ? 0 : $self->value;
+        }
+      }
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @fields = $obj->label_fields ();
+#   @fields = $obj->slabel_fields ();
+#
+# DESCRIPTION
+#   These method returns a list of fields in the (s)label.
+# ------------------------------------------------------------------------------
+
+for my $name (qw/label slabel/) {
+  no strict 'refs';
+
+  my $sub_name = $name . '_fields';
+  *$sub_name = sub  {
+    return (split (/$FCM1::Config::DELIMITER_PATTERN/, $_[0]->$name));
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $obj->label_from_field ($index);
+#   $string = $obj->slabel_from_field ($index);
+#
+# DESCRIPTION
+#   These method returns the (s)label from field $index onwards.
+# ------------------------------------------------------------------------------
+
+for my $name (qw/label slabel/) {
+  no strict 'refs';
+
+  my $sub_name = $name . '_from_field';
+  *$sub_name = sub  {
+    my ($self, $index) = @_;
+    my $method = $name . '_fields';
+    my @fields = $self->$method;
+    return join ($FCM1::Config::DELIMITER, @fields[$index .. $#fields]);
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flag = $obj->label_starts_with (@fields);
+#   $flag = $obj->slabel_starts_with (@fields);
+#
+# DESCRIPTION
+#   These method returns a true if (s)label starts with the labels in @fields
+#   (ignore case).
+# ------------------------------------------------------------------------------
+
+for my $name (qw/label slabel/) {
+  no strict 'refs';
+
+  my $sub_name = $name . '_starts_with';
+  *$sub_name = sub  {
+    my ($self, @fields) = @_;
+    my $return = 1;
+
+    my $method = $name . '_fields';
+    my @all_fields = $self->$method;
+
+    for my $i (0 .. $#fields) {
+      next if lc ($fields[$i]) eq lc ($all_fields[$i]);
+      $return = 0;
+      last;
+    }
+
+    return $return;
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flag = $obj->label_starts_with_cfg (@fields);
+#   $flag = $obj->slabel_starts_with_cfg (@fields);
+#
+# DESCRIPTION
+#   These method returns a true if (s)label starts with the configuration file
+#   labels in @fields (ignore case).
+# ------------------------------------------------------------------------------
+
+for my $name (qw/label slabel/) {
+  no strict 'refs';
+
+  my $sub_name = $name . '_starts_with_cfg';
+  *$sub_name = sub  {
+    my ($self, @fields) = @_;
+
+    for my $field (@fields) {
+      $field = $self->cfglabel ($field);
+    }
+
+    my $method = $name . '_starts_with';
+    return $self->$method (@fields);
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $mesg = $obj->format_error ();
+#
+# DESCRIPTION
+#   This method returns a string containing a formatted error message for
+#   anything reported to the current line.
+# ------------------------------------------------------------------------------
+
+sub format_error {
+  my ($self) = @_;
+  my $mesg = '';
+
+  $mesg .= $self->format_warning;
+
+  if ($self->error or not $self->parsed) {
+    $mesg = 'ERROR: ' . $self->src . ': LINE ' . $self->number . ':' . "\n";
+    if ($self->error) {
+      $mesg .= '       ' . $self->error;
+
+    } else {
+      $mesg .= '       ' . $self->label . ': label not recognised.';
+    }
+  }
+
+  return $mesg;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $mesg = $obj->format_warning ();
+#
+# DESCRIPTION
+#   This method returns a string containing a formatted warning message for
+#   any warning reported to the current line.
+# ------------------------------------------------------------------------------
+
+sub format_warning {
+  my ($self) = @_;
+  my $mesg = '';
+
+  if ($self->warning) {
+    $mesg = 'WARNING: ' . $self->src . ': LINE ' . $self->number . ':' . "\n";
+    $mesg .= '         ' . $self->warning;
+  }
+
+  return $mesg;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $line = $obj->print_line ([$space]);
+#
+# DESCRIPTION
+#   This method returns a configuration line using $self->label, $self->value
+#   and $self->comment. The value in $self->line is re-set. If $space is set
+#   and is a positive integer, it sets the spacing between the label and the
+#   value in the line. The default is 1.
+# ------------------------------------------------------------------------------
+
+sub print_line {
+  my ($self, $space) = @_;
+
+  # Set space between label and value, default to 1 character
+  $space = 1 unless $space and $space =~ /^[1-9]\d*$/;
+
+  my $line = '';
+
+  # Add label and value, if label is set
+  if ($self->label) {
+    $line .= $self->label . ' ' x $space;
+    $line .= $self->value if defined $self->value;
+  }
+
+  # Add comment if necessary
+  my $comment = $self->comment;
+  $comment =~ s/^\s*//;
+
+  if ($comment) {
+    $comment = '# ' . $comment if $comment !~ /^#/;
+    $line .= ' ' if $line;
+    $line .= $comment;
+  }
+
+  return $self->line ($line);
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/Cm.pm b/lib/FCM1/Cm.pm
new file mode 100644
index 0000000..6e3c8a6
--- /dev/null
+++ b/lib/FCM1/Cm.pm
@@ -0,0 +1,2264 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::Cm
+#
+# DESCRIPTION
+#   This module contains the FCM code management functionalities and wrappers
+#   to Subversion commands.
+#
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package FCM1::Cm;
+use base qw{Exporter};
+
+our @EXPORT_OK = qw(cm_check_missing cm_check_unknown cm_switch cm_update);
+
+use Cwd qw{cwd};
+use FCM::System::Exception;
+use FCM1::Config;
+use FCM1::CmBranch;
+use FCM1::CmUrl;
+use FCM1::Keyword;
+use FCM1::Util      qw{
+    get_url_of_wc
+    get_url_peg_of_wc
+    is_url
+    is_wc
+    tidy_url
+};
+use File::Basename qw{basename dirname};
+use File::Path qw{mkpath rmtree};
+use File::Spec;
+use Text::ParseWords qw{shellwords};
+
+# ------------------------------------------------------------------------------
+
+# CLI message handler
+our $CLI_MESSAGE = \&_cli_message;
+
+# List of CLI messages
+our %CLI_MESSAGE_FOR = (
+    q{}           => "%s",
+    BRANCH_LIST   => "%s at %s: %d branch(es) found for %s.\n",
+    CHDIR_WCT     => "%s: working directory changed to top of working copy.\n",
+    CF            => "Conflicts in: %s\n",
+    MERGE_ACTUAL  => "-" x 74 . "actual\n%s" . "-" x 74 . "actual\n",
+    MERGE_COMPARE => "Merge: %s\n c.f.: %s\n",
+    MERGE_OK      => "Merge succeeded.\n",
+    MERGE_DRYRUN  => "-" x 73 . "dry-run\n%s" . "-" x 73 . "dry-run\n",
+    MERGE_REVS    => "Eligible merge(s) from %s: %s\n",
+    OUT_DIR       => "Output directory: %s\n",
+    PATCH_DONE    => "%s: patch generated.\n",
+    PATCH_REV     => "Patch created for changeset %s\n",
+    SEPARATOR     => q{-} x 80 . "\n",
+    STATUS        => "%s: status of %s:\n%s\n",
+);
+
+# CLI abort and error messages
+our %CLI_MESSAGE_FOR_ABORT = (
+    FAIL => "%s: command failed.\n",
+    NULL => "%s: command will result in no change.\n",
+    USER => "%s: abort by user.\n",
+);
+
+# CLI abort and error messages
+our %CLI_MESSAGE_FOR_ERROR = (
+    CHDIR               => "%s: cannot change to directory.\n",
+    CLI                 => "%s",
+    CLI_HELP            => "Type 'fcm help %s' for usage.\n",
+    CLI_MERGE_ARG1      => "Arg 1 must be the source in auto/custom mode.\n",
+    CLI_MERGE_ARG2      => "Arg 2 must be the source in custom mode"
+                           . " if --revision not set.\n",
+    CLI_OPT_ARG         => "--%s: invalid argument [%s].\n",
+    CLI_OPT_WITH_OPT    => "--%s: must be specified with --%s.\n",
+    CLI_USAGE           => "incorrect value for the %s argument",
+    DIFF_PROJECTS       => "%s (target) and %s (source) are not related.\n",
+    INVALID_BRANCH      => "%s: not a valid URL of a standard FCM branch.\n",
+    INVALID_PROJECT     => "%s: not a valid URL of a standard FCM project.\n",
+    INVALID_TARGET      => "%s: not a valid working copy or URL.\n",
+    INVALID_URL         => "%s: not a valid URL.\n",
+    INVALID_WC          => "%s: not a valid working copy.\n",
+    MERGE_REV_INVALID   => "%s: not a revision in the available merge list.\n",
+    MERGE_SELF          => "%s: cannot be merged to its own working copy: %s.\n",
+    MERGE_UNRELATED     => "%s: target and %s: source not directly related.\n",
+    MERGE_UNSAFE        => "%s: source contains changes outside the target"
+                           . " sub-directory. Please merge with a full tree.\n",
+    MKPATH              => "%s: cannot create directory.\n",
+    NOT_EXIST           => "%s: does not exist.\n",
+    PARENT_NOT_EXIST    => "%s: parent %s no longer exists.\n",
+    RMTREE              => "%s: cannot remove.\n",
+    ST_CI_MESG_FILE     => "Attempt to add commit message file:\n%s",
+    ST_CONFLICT         => "File(s) in conflicts:\n%s",
+    ST_MISSING          => "File(s) missing:\n%s",
+    ST_OOD              => "File(s) out of date:\n%s",
+    SWITCH_UNSAFE       => "%s: merge template exists."
+                           . " Please remove before retrying.\n",
+    WC_INVALID_BRANCH   => "%s: not a working copy of a standard FCM branch.\n",
+    WC_URL_NOT_EXIST    => "%s: working copy URL does not exists at HEAD.\n",
+);
+
+# List of CLI prompt messages
+our %CLI_MESSAGE_FOR_PROMPT = (
+    CF_OVERWRITE      => qq{%s: existing changes will be overwritten.\n}
+                         . qq{ Do you wish to continue?},
+    CI                => qq{Would you like to commit this change?},
+    CI_BRANCH_SHARED  => qq{\n}
+                         . qq{*** WARNING: YOU ARE COMMITTING TO A %s BRANCH.\n}
+                         . qq{*** Please ensure that you have the}
+                         . qq{ owner's permission.\n\n}
+                         . qq{Would you like to commit this change?},
+    CI_BRANCH_USER    => qq{\n}
+                         . qq{*** WARNING: YOU ARE COMMITTING TO A BRANCH}
+                         . qq{ NOT OWNED BY YOU.\n}
+                         . qq{*** Please ensure that you have the}
+                         . qq{ owner's permission.\n\n}
+                         . qq{Would you like to commit this change?},
+    CI_TRUNK          => qq{\n}
+                         . qq{*** WARNING: YOU ARE COMMITTING TO THE TRUNK.\n}
+                         . qq{*** Please ensure that your change conforms to}
+                         . qq{ your project's working practices.\n\n}
+                         . qq{Would you like to commit this change?},
+    CONTINUE          => qq{%s: continue?},
+    MERGE             => qq{Would you like to go ahead with the merge?},
+    MERGE_REV         => qq{Enter a revision},
+    MKPATCH_OVERWRITE => qq{%s: output location exists. OK to overwrite?},
+    RUN_SVN_COMMAND   => qq{Would you like to run "svn %s"?},
+);
+
+# List of CLI warning messages
+our %CLI_MESSAGE_FOR_WARNING = (
+    BRANCH_SUBDIR   => "%s: is a sub-directory of a branch in a FCM project.\n",
+    CF_BINARY       => "%s: ignoring binary file, please resolve manually.\n",
+    INVALID_BRANCH  => $CLI_MESSAGE_FOR_ERROR{INVALID_BRANCH},
+    ST_IN_TRAC_DIFF => "%s: local changes cannot be displayed in Trac.\n"
+);
+
+# CLI prompt handler and title prefix
+our $CLI_PROMPT = \&_cli_prompt;
+our $CLI_PROMPT_PREFIX = q{fcm };
+
+# Event handlers
+our %CLI_HANDLER_OF = (
+    'WC_STATUS'      => \&_cli_handler_of_wc_status,
+    'WC_STATUS_PATH' => \&_cli_handler_of_wc_status_path,
+);
+
+# Common patterns
+our %PATTERN_OF = (
+    # A CLI option
+    CLI_OPT => qr{
+        \A            (?# beginning)
+        (--\w[\w-]*=) (?# capture 1, a long option label)
+        (.*)          (?# capture 2, the value of the option)
+        \z            (?# end)
+    }xms,
+    # A CLI revision option
+    CLI_OPT_REV => qr{
+        \A                      (?# beginning)
+        (--revision(?:=|\z)|-r) (?# capture 1, --revision, --revision= or -r)
+        (.*)                    (?# capture 2, trailing value)
+        \z                      (?# end)
+    }xms,
+    # A CLI revision option range
+    CLI_OPT_REV_RANGE => qr{
+        \A                  (?# beginning)
+        (                   (?# capture 1, begin)
+            (?:\{[^\}]+\}+) (?# a date in curly braces)
+            |               (?# or)
+            [^:]+           (?# anything but a colon)
+        )                   (?# capture 1, end)
+        (?::(.*))?          (?# colon, and capture 2 til the end)
+        \z                  (?# end)
+    }xms,
+    # A FCM branch path look-alike, should be configurable in the future
+    FCM_BRANCH_PATH => qr{
+        \A                            (?# beginning)
+        /*                            (?# some slashes)
+        (?:                           (?# group 1, begin)
+            (?:trunk/*(?:@\d+)?\z)    (?# trunk at a revision)
+            |                         (?# or)
+            (?:trunk|branches|tags)/+ (?# trunk, branch or tags)
+        )                             (?# group 1, end)
+    }xms,
+    # Last line of output from "svn status -u"
+    ST_AGAINST_REV => qr{
+        \A                           (?# beginning)
+        Status\sagainst\srevision:.* (?# output of svn status -u)
+        \z                           (?# end)
+    }xms,
+    # Extract path from "svn status"
+    ST_PATH => qr{
+        \A   (?# beginning)
+        .{6} (?# 6 columns)
+        \s+  (?# spaces)
+        (.+) (?# capture 1, target path)
+        \z   (?# end)
+    }xms,
+    # A legitimate "svn" revision
+    SVN_REV => qr{
+        \A                                      (?# beginning)
+        (?:\d+|HEAD|BASE|COMMITTED|PREV|\{.+\}) (?# digit, reserved words, date)
+        \z                                      (?# end)
+    }ixms,
+);
+
+# Status matchers
+our %ST_MATCHER_FOR = (
+    CONFLICT => sub {substr($_[0], 0, 1) eq 'C' || substr($_[0], 6, 1) eq 'C'},
+    MISSING  => sub {substr($_[0], 0, 1) eq '!'},
+    MODIFIED => sub {substr($_[0], 0, 7) =~ qr{\S}xms},
+    OOD      => sub {substr($_[0], 8, 1) eq '*'},
+    UNKNOWN  => sub {substr($_[0], 0, 1) eq '?'},
+);
+
+# Set the FCM::Util object by FCM::System::CM.
+our $UTIL;
+sub set_util {
+    $UTIL = shift();
+}
+
+# Set the commit message utility provided by FCM::System::CM.
+our $COMMIT_MESSAGE_UTIL;
+sub set_commit_message_util {
+    $COMMIT_MESSAGE_UTIL = shift();
+    FCM1::CmBranch::set_commit_message_util($COMMIT_MESSAGE_UTIL);
+}
+
+# Set the SVN utility provided by FCM::System::CM.
+our $SVN;
+sub set_svn_util {
+    $SVN = shift();
+    FCM1::CmUrl::set_svn_util($SVN);
+    FCM1::CmBranch::set_svn_util($SVN);
+}
+
+# Returns the branch URL as an instance of FCM1::CmUrl.
+sub _branch_url {
+    my $arg = shift();
+    my $url
+        = $arg && is_url($arg) ? FCM1::CmUrl->new(URL => $arg)
+        : $arg && is_wc($arg)  ? FCM1::CmUrl->new(URL => get_url_of_wc($arg))
+        : is_wc()              ? FCM1::CmUrl->new(URL => get_url_of_wc())
+        :                        undef
+        ;
+    if (!$url) {
+        return _cm_err(FCM1::Cm::Exception->INVALID_TARGET, '.');
+    }
+    $url;
+}
+
+# Branch delete.
+sub cm_branch_delete {
+    my ($option_ref, $arg) = @_;
+    my $branch = cm_branch_info($option_ref, $arg);
+    $branch->del(
+        PASSWORD            => $option_ref->{password},
+        NON_INTERACTIVE     => $option_ref->{'non-interactive'},
+        SVN_NON_INTERACTIVE => $option_ref->{'svn-non-interactive'},
+    );
+    if (!$arg && $option_ref->{'switch'}) {
+        cm_switch($option_ref, $branch->layout()->get_config()->{'dir-trunk'});
+    }
+}
+
+# Branch diff.
+sub cm_branch_diff {
+    my ($option_ref, $target) = @_;
+    local(%ENV) = %ENV;
+    $ENV{FCM_GRAPHIC_DIFF} ||= $UTIL->external_cfg_get('graphic-diff');
+    my @diff_cmd
+        = $option_ref->{graphical}  ? (qw{--diff-cmd fcm_graphic_diff})
+        : $option_ref->{'diff-cmd'} ? ('--diff-cmd', $option_ref->{'diff-cmd'})
+        :                             ()
+        ;
+    if ($option_ref->{extensions}) {
+        push(@diff_cmd, '--extensions', shellwords($option_ref->{extensions}));
+    }
+
+    # Target can be a URL/path, default to $PWD.
+    $target ||= q{.};
+    my $target_is_path = !is_url($target);
+
+    # Get repository and branch information
+    my $url = bless(_branch_url($target), 'FCM1::CmBranch');
+
+    # Check that URL is a standard FCM branch
+    if (!$url->is_branch()) {
+        return _cm_err(FCM1::Cm::Exception->INVALID_BRANCH, $url->url_peg());
+    }
+
+    # Save and remove sub-directory part of the URL
+    my $subdir = $url->subdir();
+    $url->url_peg($url->branch_url_peg());
+
+    # Check that $url exists
+    if (!$url->url_exists()) {
+        return _cm_err(FCM1::Cm::Exception->INVALID_URL, $url->url_peg());
+    }
+
+    # Compare current branch with its parent
+    my $parent = FCM1::CmBranch->new(URL => $url->parent()->url());
+    if ($url->pegrev()) {
+      $parent->url_peg($parent->url() . '@' . $url->pegrev());
+    }
+
+    if (!$parent->url_exists()) {
+        return _cm_err(
+            FCM1::Cm::Exception->PARENT_NOT_EXIST, $url->url_peg(), $parent->url(),
+        );
+    }
+
+    my $base = $parent->base_of_merge_from($url);
+
+    # Ensure the correct diff (syntax) is displayed
+    # Reinstate the sub-tree part into the URL
+    if ($subdir) {
+      $url->url_peg($url->branch_url() . '/' . $subdir . '@' . $url->pegrev());
+      $base->url_peg($base->branch_url() . '/' . $subdir . '@' . $base->pegrev());
+    }
+
+    if ($option_ref->{trac} || $option_ref->{wiki}) {
+        if ($target_is_path && _svn_status_get([$target])) {
+            $CLI_MESSAGE->('ST_IN_TRAC_DIFF', $target);
+        }
+
+        # Trac wiki syntax
+        my $wiki_syntax = 'diff:' . $base->path_peg() . '//' . $url->path_peg();
+
+        if ($option_ref->{wiki}) {
+            $CLI_MESSAGE->(q{}, "$wiki_syntax\n");
+        }
+        else { # if $option_ref->{trac}
+            my $browser = $UTIL->external_cfg_get('browser');
+            my $trac_url = FCM1::Keyword::get_browser_url($url->project_url());
+            # FIXME: assuming that the browser URL uses the InterTrac syntax
+            $trac_url =~ s{/intertrac/.*$}{/intertrac/$wiki_syntax}xms;
+            my %value_of = %{$UTIL->shell_simple([$browser, $trac_url])};
+            if ($value_of{rc}) {
+                return FCM::System::Exception->throw(
+                    FCM::System::Exception->SHELL,
+                    {command_list => [$browser, $trac_url], %value_of},
+                    $value_of{e},
+                );
+            }
+        }
+    }
+    else {
+        $SVN->call(
+            'diff', @diff_cmd,
+            ($option_ref->{summarize} ? ('--summarize') : ()),
+		    ($option_ref->{xml} ? ('--xml') : ()),
+            '--old', $base->url_peg(),
+            '--new', ($target_is_path ? $target : $url->url_peg()),
+        );
+    }
+}
+
+# Branch info.
+sub cm_branch_info {
+    my ($option_ref, $arg) = @_;
+    my $url = _branch_url($arg);
+    FCM1::Config->instance()->verbose($option_ref->{verbose} ? 1 : 0);
+    my $branch = FCM1::CmBranch->new(URL => $url->url_peg());
+    if (!$branch->branch()) {
+        return _cm_err(FCM1::Cm::Exception->INVALID_BRANCH, $branch->url_peg());
+    }
+    if (!$branch->url_exists()) {
+        return _cm_err(FCM1::Cm::Exception->NOT_EXIST, $branch->url_peg());
+    }
+    $branch->url_peg($branch->branch_url_peg());
+    $option_ref->{'show-children'} ||= $option_ref->{'show-all'};
+    $option_ref->{'show-other'   } ||= $option_ref->{'show-all'};
+    $option_ref->{'show-siblings'} ||= $option_ref->{'show-all'};
+    $branch->display_info(
+        SHOW_CHILDREN => $option_ref->{'show-children'},
+        SHOW_OTHER    => $option_ref->{'show-other'   },
+        SHOW_SIBLINGS => $option_ref->{'show-siblings'},
+    );
+    $branch;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   &FCM1::Cm::cm_commit ();
+#
+# DESCRIPTION
+#   This is a FCM wrapper to the "svn commit" command.
+# ------------------------------------------------------------------------------
+
+sub cm_commit {
+  my ($option_ref, $path) = @_;
+  $path ||= cwd();
+  if (!-e $path) {
+    return _cm_err(FCM1::Cm::Exception->NOT_EXIST, $path);
+  }
+
+  # Make sure we are in a working copy
+  if (!is_wc($path)) {
+    return _cm_err(FCM1::Cm::Exception->INVALID_WC, $path);
+  }
+
+  # Make sure we are at the top level of the working copy
+  # (otherwise we might miss any template commit message)
+  my $dir = $SVN->get_wc_root($path);
+
+  if ($dir ne cwd ()) {
+    chdir($dir) || return _cm_err(FCM1::Cm::Exception->CHDIR, $dir);
+    $CLI_MESSAGE->('CHDIR_WCT', $dir);
+  }
+
+  # Get update status of working copy
+  # Check working copy files are not in conflict, missing, or out of date
+  my @status = _svn_status_get([], 1);
+  if (!defined($option_ref->{'dry-run'})) {
+    my %st_lines_of = (CONFLICT => [], MISSING => [], OOD => []);
+
+    LINE:
+    for my $line (@status) {
+      for my $key (keys(%st_lines_of)) {
+        if ($ST_MATCHER_FOR{$key}->($line)) {
+          push(@{$st_lines_of{$key}}, $line);
+          next LINE;
+        }
+      }
+      # Check that all files which have been added have the svn:executable
+      # property set correctly (in case the developer adds a script before they
+      # remember to set the execute bit)
+      my ($file) = $line =~ qr/\AA.{8}\s*\d+\s+(.*)/msx;
+      if (!$file || !-f $file) {
+        next LINE;
+      }
+      my ($command, @arguments)
+        = (-x $file && !-l $file) ? ('propset', '*') : ('propdel');
+      $SVN->call($command, qw{-q svn:executable}, @arguments, $file);
+    }
+
+    # Abort commit if files are in conflict, missing, or out of date
+    my @keys = grep {@{$st_lines_of{$_}}} keys(%st_lines_of);
+    if (@keys) {
+      for my $key (sort(@keys)) {
+        my @lines = map {"$_\n"} @{$st_lines_of{$key}};
+        $CLI_MESSAGE->('ST_' . $key, join(q{}, @lines));
+      }
+      return _cm_abort(FCM1::Cm::Abort->FAIL);
+    }
+  }
+
+  # Read in any existing message
+  my $commit_message_ctx = $COMMIT_MESSAGE_UTIL->load();
+
+  # Execute "svn status" for a list of changed items
+  @status = map {$_ . "\n"} grep {$_ =~ qr/\A[^\?]/msx} _svn_status_get();
+
+  # Abort if there is no change in the working copy
+  if (!@status) {
+    return _cm_abort(FCM1::Cm::Abort->NULL);
+  }
+
+  # Abort if attempt to add commit message file
+  my $ci_mesg_file_base = $COMMIT_MESSAGE_UTIL->path_base();
+  my @bad_status = grep {$_ =~ qr{^A.*?\s$ci_mesg_file_base\n}m} @status;
+  if (@bad_status) {
+    for my $bad_status (@bad_status) {
+      $CLI_MESSAGE->('ST_CI_MESG_FILE', $bad_status);
+    }
+    return _cm_abort(FCM1::Cm::Abort->FAIL);
+  }
+
+  # Get associated URL of current working copy
+  my $layout = $SVN->get_layout($SVN->get_info()->[0]->{url});
+
+  # Include URL, or project, branch and sub-directory info in @status
+  unshift @status, "\n";
+
+  if ($layout->get_branch()) {
+    unshift(@status,
+      map {sprintf("[%-7s: %s]\n", @{$_})} (
+        ['Root'   , $layout->get_root()    ],
+        ['Project', $layout->get_project() ],
+        ['Branch' , $layout->get_branch()  ],
+        ['Sub-dir', $layout->get_sub_tree()],
+      ),
+    );
+  }
+  else {
+    unshift(@status,
+      map {sprintf("[%s: %s]\n", @{$_})} (
+        ['Root', $layout->get_root()],
+        ['Path', $layout->get_path()],
+      ),
+    );
+  }
+
+  # Use a temporary file to store the final commit log message
+  $commit_message_ctx->set_info_part(join(q{}, @status));
+  $COMMIT_MESSAGE_UTIL->edit($commit_message_ctx);
+  $COMMIT_MESSAGE_UTIL->notify($commit_message_ctx);
+
+  # Check with the user to see if he/she wants to go ahead
+  my $reply = 'n';
+  if (!defined($option_ref->{'dry-run'})) {
+    $reply = $CLI_PROMPT->('commit', (
+        $layout->is_trunk()         ? ('CI_TRUNK')
+      : !$layout->get_branch_owner()? ('CI')
+      : $layout->is_owned_by_user() ? ('CI')
+      : $layout->is_shared()        ? ('CI_BRANCH_SHARED',
+                                       $layout->get_branch_owner())
+      :                               ('CI_BRANCH_USER')
+    ));
+  }
+
+  if ($reply eq 'y') {
+    # Commit the change if user replies "y" for "yes"
+    my $temp = $COMMIT_MESSAGE_UTIL->temp($commit_message_ctx);
+    eval {$SVN->call(
+      qw{commit -F}, "$temp",
+      ($option_ref->{'svn-non-interactive'} ? '--non-interactive' : ()),
+      (   defined($option_ref->{password})
+          ? ('--password', $option_ref->{password}) : ()
+      ),
+    )};
+    if ($@) {
+      $COMMIT_MESSAGE_UTIL->save($commit_message_ctx);
+      die($@);
+    }
+
+    # Remove commit message file
+    unlink($COMMIT_MESSAGE_UTIL->path());
+
+    # Update the working copy
+    _svn_update();
+
+  } else {
+    $COMMIT_MESSAGE_UTIL->save($commit_message_ctx);
+    if (!$option_ref->{'dry-run'}) {
+      return _cm_abort();
+    }
+  }
+
+  return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   &FCM1::Cm::cm_merge ();
+#
+# DESCRIPTION
+#   This is a wrapper to "svn merge".
+# ------------------------------------------------------------------------------
+
+sub cm_merge {
+  my ($option_ref, @args) = @_;
+  # Find out the URL of the working copy
+  if (!is_wc()) {
+    return _cm_err(FCM1::Cm::Exception->INVALID_WC, '.');
+  }
+  my $wct = $SVN->get_wc_root();
+  if ($wct ne cwd()) {
+    chdir($wct) || return _cm_err(FCM1::Cm::Exception->CHDIR, $wct);
+    $CLI_MESSAGE->('CHDIR_WCT', $wct);
+  }
+  my $target = FCM1::CmBranch->new(URL => get_url_of_wc($wct));
+  if (!$target->url_exists()) {
+    return _cm_err(FCM1::Cm::Exception->WC_URL_NOT_EXIST, '.');
+  }
+
+  # The target must be at the top of a branch
+  # $subdir will be used later to determine whether the merge is allowed or not
+  my $subdir = $target->subdir();
+  if ($subdir) {
+    $target->url_peg($target->branch_url_peg());
+  }
+
+  # Check for any local modifications
+  # ----------------------------------------------------------------------------
+  if (!$option_ref->{'dry-run'} && !$option_ref->{'non-interactive'}) {
+    _svn_status_checker('merge', 'MODIFIED', $CLI_HANDLER_OF{WC_STATUS})->();
+  }
+
+  # Determine the SOURCE URL
+  # ----------------------------------------------------------------------------
+  my $source;
+
+  if ($option_ref->{reverse}) {
+    # Reverse merge, the SOURCE is the working copy URL
+    $source = FCM1::CmBranch->new (URL => $target->url);
+
+  } else {
+    # Automatic/custom merge, argument 1 is the SOURCE of the merge
+    my $source_url = shift (@args);
+    if (!$source_url) {
+      _cli_err('CLI_MERGE_ARG1');
+    }
+
+    $source = _cm_get_source($source_url, $target);
+  }
+
+  # Parse the revision option
+  # ----------------------------------------------------------------------------
+  if ($option_ref->{reverse} && !$option_ref->{revision}) {
+    _cli_err('CLI_OPT_WITH_OPT', 'revision', 'reverse');
+  }
+  my @revs
+    = (grep {$option_ref->{$_}} qw{reverse custom}) && $option_ref->{revision}
+    ? split(qr{:}xms, $option_ref->{revision})
+    : ();
+
+  # Determine the merge delta and the commit log message
+  # ----------------------------------------------------------------------------
+  my (@delta, $mesg, @logs);
+  my $separator = '-' x 80 . "\n";
+
+  if ($option_ref->{reverse}) {
+    # Reverse merge
+    # --------------------------------------------------------------------------
+    if (@revs == 1) {
+      $revs[1] = ($revs[0] - 1);
+
+    } else {
+      @revs = sort {$b <=> $a} @revs;
+    }
+    $source->url_peg(
+      $source->branch_url() . '/' . $subdir . '@' . $source->pegrev(),
+    );
+
+    # "Delta" of the "svn merge" command
+    @delta = ('-r' . $revs[0] . ':' . $revs[1], $source->url_peg);
+
+    # Template message
+    $mesg = 'Reversed r' . $revs[0] .
+            (($revs[1] < $revs[0] - 1) ? ':' . $revs[1] : '') . ' of ' .
+            $source->path . "\n";
+
+  } elsif ($option_ref->{custom}) {
+    # Custom merge
+    # --------------------------------------------------------------------------
+    if (@revs) {
+      # Revision specified
+      # ------------------------------------------------------------------------
+      # Only one revision N specified, use (N - 1):N as the delta
+      unshift @revs, ($revs[0] - 1) if @revs == 1;
+      $source->url_peg(
+        $source->branch_url() . '/' . $subdir . '@' . $source->pegrev(),
+      );
+      $target->url_peg(
+        $target->branch_url() . '/' . $subdir . '@' . $target->pegrev(),
+      );
+
+      # "Delta" of the "svn merge" command
+      @delta = ('-r' . $revs[0] . ':' . $revs[1], $source->url_peg);
+
+      # Template message
+      $mesg = 'Custom merge into ' . $target->path . ': r' . $revs[1] .
+              ' cf. r' . $revs[0] . ' of ' . $source->path_peg . "\n";
+
+    } else {
+      # Revision not specified
+      # ------------------------------------------------------------------------
+      # Get second source URL
+      my $source2_url = shift (@args);
+      if (!$source2_url) {
+        _cli_err('CLI_MERGE_ARG2');
+      }
+
+      my $source2 = _cm_get_source($source2_url, $target);
+      for my $item ($source, $source2, $target) {
+        $item->url_peg($item->branch_url() . '/' . $subdir . '@' . $item->pegrev());
+      }
+
+      # "Delta" of the "svn merge" command
+      @delta = ($source->url_peg, $source2->url_peg);
+
+      # Template message
+      $mesg = 'Custom merge into ' . $target->path . ': ' . $source->path_peg .
+              ' cf. ' . $source2->path_peg . "\n";
+    }
+
+  } else {
+    # Automatic merge
+    # --------------------------------------------------------------------------
+    # Check to ensure source branch is not the same as the target branch
+    if (!$target->branch()) {
+      return _cm_err(FCM1::Cm::Exception->WC_INVALID_BRANCH, $wct);
+    }
+    if ($source->branch() eq $target->branch()) {
+      return _cm_err(FCM1::Cm::Exception->MERGE_SELF, $target->url_peg(), $wct);
+    }
+
+    # Only allow the merge if the source and target are "directly related"
+    # --------------------------------------------------------------------------
+    my $anc = $target->ancestor ($source);
+    return _cm_err(
+      FCM1::Cm::Exception->MERGE_UNRELATED, $target->url_peg(), $source->url_peg
+    ) unless
+      ($anc->url eq $target->url and $anc->url_peg eq $source->parent->url_peg)
+      or
+      ($anc->url eq $source->url and $anc->url_peg eq $target->parent->url_peg)
+      or
+      ($anc->url eq $source->parent->url and $anc->url eq $target->parent->url);
+
+    # Check for available merges from the source
+    # --------------------------------------------------------------------------
+    my @revs = $target->avail_merge_from ($source, 1);
+
+    if (@revs) {
+      if ($option_ref->{verbose}) {
+        # Verbose mode, print log messages of available merges
+        $CLI_MESSAGE->('MERGE_REVS', $source->path_peg(), q{});
+        for (@revs) {
+          $CLI_MESSAGE->('SEPARATOR');
+          $CLI_MESSAGE->(q{}, $source->display_svnlog($_));
+        }
+        $CLI_MESSAGE->('SEPARATOR');
+      }
+      else {
+        # Normal mode, list revisions of available merges
+        $CLI_MESSAGE->('MERGE_REVS', $source->path_peg(), join(q{ }, @revs));
+      }
+
+    } else {
+      return _cm_abort(FCM1::Cm::Abort->NULL);
+    }
+
+    # If more than one merge available, prompt user to enter a revision number
+    # to merge from, default to $revs [0]
+    # --------------------------------------------------------------------------
+    if ($option_ref->{'non-interactive'} || @revs == 1) {
+      $source->url_peg($source->url() . '@' . $revs[0]);
+    }
+    else {
+      my $reply = $CLI_PROMPT->(
+        {type => q{}, default => $revs[0]}, 'merge', 'MERGE_REV',
+      );
+      if (!defined($reply)) {
+        return _cm_abort();
+      }
+      # Expand revision keyword if necessary
+      if ($reply) {
+        $reply = (FCM1::Keyword::expand($target->project_url(), $reply))[1];
+      }
+      # Check that the reply is a number in the available merges list
+      if (!grep {$_ eq $reply} @revs) {
+        return _cm_err(FCM1::Cm::Exception->MERGE_REV_INVALID, $reply)
+      }
+      $source->url_peg($source->url() . '@' . $reply);
+    }
+
+    # If the working copy top is pointing to a sub-directory of a branch,
+    # we need to check whether the merge will result in losing changes made in
+    # other sub-directories of the source.
+    if ($subdir and not $target->allow_subdir_merge_from ($source, $subdir)) {
+      return _cm_err(FCM1::Cm::Exception->MERGE_UNSAFE, $source->url_peg());
+    }
+
+    # Calculate the base of the merge
+    my $base = $target->base_of_merge_from ($source);
+
+    # $source and $base must take into account the sub-directory
+    my $source_full = FCM1::CmBranch->new (URL => $source->url_peg);
+    my $base_full = FCM1::CmBranch->new (URL => $base->url_peg);
+
+    if ($subdir) {
+      $source_full->url_peg(
+        $source_full->branch_url() . '/' . $subdir . '@' . $source_full->pegrev()
+      );
+      $base_full->url_peg(
+        $base_full->branch_url() . '/' . $subdir . '@' . $base_full->pegrev()
+      );
+    }
+
+    # Diagnostic
+    $CLI_MESSAGE->('SEPARATOR'); 
+    $CLI_MESSAGE->('MERGE_COMPARE', $source->path_peg(), $base->path_peg()); 
+    # Delta of the "svn merge" command
+    @delta = ($base_full->url_peg, $source_full->url_peg);
+
+    # Template message
+    $mesg = sprintf(
+      "Merged into %s: %s cf. %s",
+      $target->path(), $source->path_peg(), $base->path_peg(),
+    );
+
+    if (exists($option_ref->{'auto-log'})) {
+      my $last_merge_from_source = ($target->last_merge_from($source))[1];
+      if (!defined($last_merge_from_source)) {
+        $last_merge_from_source = $target->ancestor($source);
+      }
+      my %log_entries = $source->svnlog(
+        REV => [$last_merge_from_source->pegrev() + 1, $source->pegrev()],
+      );
+      @logs = sort {$b->{'revision'} <=> $a->{'revision'}} values(%log_entries);
+    }
+  }
+
+  # Run "svn merge" in "--dry-run" mode to see the result
+  # ----------------------------------------------------------------------------
+  my $dry_run_output
+    = $SVN->stdout(qw{svn merge --dry-run --non-interactive}, @delta);
+
+  # Abort merge if it will result in no change
+  if (!$dry_run_output) {
+    return _cm_abort(FCM1::Cm::Abort->NULL);
+  }
+
+  # Report result of "svn merge --dry-run"
+  if ($option_ref->{'dry-run'} || !$option_ref->{'non-interactive'}) {
+    $CLI_MESSAGE->('MERGE_DRYRUN', $dry_run_output);
+  }
+
+  return if $option_ref->{'dry-run'};
+
+  # Prompt the user to see if (s)he would like to go ahead
+  # ----------------------------------------------------------------------------
+  # Go ahead with merge only if user replies "y"
+  if (
+    !$option_ref->{'non-interactive'} && $CLI_PROMPT->('merge', 'MERGE') ne 'y'
+  ) {
+    return _cm_abort();
+  }
+  $SVN->call('cleanup');
+  my $output = $SVN->stdout(qw{svn merge --non-interactive}, @delta);
+  $CLI_MESSAGE->('MERGE_OK');
+  if ($output ne $dry_run_output) {
+    $CLI_MESSAGE->('MERGE_ACTUAL', $output);
+  }
+
+  # Prepare the commit log
+  # ----------------------------------------------------------------------------
+  my $commit_message_ctx = $COMMIT_MESSAGE_UTIL->load();
+  my @auto_log = map {
+    my $log_entry = $_;
+    my @msg_list = (
+      map  {q{> } . $_}
+      grep {
+            $_
+        &&  $_ !~ qr{\AMerged\sinto\s\S+:\s(?:\S+)\scf\.\s(?:\S+)\z}msx
+        &&  $_ !~ qr{\A(?:\#\d+(?:,\#\d+)*:\s)?Created\s\S+\sfrom\s\S+\.\z}msx
+        &&  $_ !~ qr{\Ar\d+:\z}msx
+        &&  $_ !~ qr{\A>\s.+\z}msx
+      }
+      split("\n", $log_entry->{'msg'})
+    );
+    @msg_list ? ('----', 'r' . $log_entry->{'revision'} . ':', @msg_list) : ();
+  } @logs;
+  my @messages = (
+    $mesg,
+    (@auto_log ? (@auto_log, '----'): ()),
+    $commit_message_ctx->get_auto_part()
+  );
+  $commit_message_ctx->set_auto_part(join("\n", @messages));
+  $COMMIT_MESSAGE_UTIL->save($commit_message_ctx);
+
+  return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   &FCM1::Cm::cm_mkpatch ();
+#
+# DESCRIPTION
+#   This is a FCM command to create a patching script from particular revisions
+#   of a URL.
+# ------------------------------------------------------------------------------
+
+sub cm_mkpatch {
+  my ($option_ref, $u, $outdir) = @_;
+  # Process command line options and arguments
+  my @exclude = $option_ref->{exclude} ? @{$option_ref->{exclude}} : ();
+  my $organisation = $option_ref->{organisation};
+  my $revision = $option_ref->{revision};
+
+  # Excluded paths, convert glob into regular patterns
+  @exclude = split (/:/, join (':', @exclude));
+  for (@exclude) {
+    s#\*#[^/]*#; # match any number of non-slash character
+    s#\?#[^/]#;  # match a non-slash character
+    s#/*$##;     # remove trailing slash
+  }
+
+  # Organisation prefix
+  $organisation ||= 'original';
+
+  # Make sure revision option is set correctly
+  my @revs = $revision ? split (/:/, $revision) : ();
+  @revs    = @revs [0, 1] if @revs > 2;
+
+  if (!$u) {
+    _cli_err('CLI_USAGE', 'URL');
+  }
+
+  my $url = FCM1::CmUrl->new (URL => $u);
+  if (!$url->is_url()) {
+    return _cm_err(FCM1::Cm::Exception->INVALID_URL, $u);
+  }
+  if (!$url->url_exists()) {
+    return _cm_err(FCM1::Cm::Exception->NOT_EXIST, $u);
+  }
+  if (!$url->branch()) {
+    $CLI_MESSAGE->('INVALID_BRANCH', $u);
+  }
+  elsif ($url->subdir()) {
+    $CLI_MESSAGE->('BRANCH_SUBDIR', $u);
+  }
+
+  if (@revs) {
+    # If HEAD revision is given, convert it into a number
+    # --------------------------------------------------------------------------
+    for my $rev (@revs) {
+      $rev = $url->svninfo(FLAG => 'revision') if uc ($rev) eq 'HEAD';
+    }
+
+  } else {
+    # If no revision is given, use the HEAD
+    # --------------------------------------------------------------------------
+    $revs[0] = $url->svninfo(FLAG => 'revision');
+  }
+
+  $revs[1] = $revs[0] if @revs == 1;
+
+  # Check that output directory is set
+  # ----------------------------------------------------------------------------
+  $outdir = File::Spec->catfile (cwd (), 'fcm-mkpatch-out') if not $outdir;
+
+  if (-e $outdir) {
+    # Ask user to confirm removal of old output directory if it exists
+    if ($CLI_PROMPT->('mkpatch', 'MKPATCH_OVERWRITE', $outdir) ne 'y') {
+      return _cm_abort();
+    }
+
+    rmtree($outdir) || return _cm_err(FCM1::Cm::Exception->RMTREE, $outdir);
+  }
+
+  # (Re-)create output directory
+  mkpath($outdir) || return _cm_err(FCM1::Cm::Exception->MKPATH, $outdir);
+  $CLI_MESSAGE->('OUT_DIR', $outdir);
+
+  # Get and process log of URL
+  # ----------------------------------------------------------------------------
+  my @script   = (); # main output script
+  my %log      = $url->svnlog (REV => \@revs);
+  my $url_path = $url->path;
+
+  for my $rev (sort {$a <=> $b} keys %log) {
+    # Look at the changed paths for each revision
+    my $use_patch     = 1; # OK to use a patch file?
+    my $only_modified = 1; # Change only contains modifications?
+    my @paths;
+    PATH: for my $path (sort keys %{ $log{$rev}{paths} }) {
+      my $file = $path;
+
+      # Skip paths outside of the branch
+      next PATH unless $file =~ s#^$url_path/##;
+
+      # Skip excluded paths
+      for my $exclude (@exclude) {
+        if ($file =~ m#^$exclude(?:/|$)#) {
+          # Can't use a patch file if any files have been excluded
+          $use_patch = 0;
+          next PATH;
+        }
+      }
+
+      # Can't use a patch file if any files have been added or replaced
+      $use_patch = 0 if $log{$rev}{paths}{$path}{action} eq 'A' or
+                        $log{$rev}{paths}{$path}{action} eq 'R';
+
+      $only_modified = 0 unless $log{$rev}{paths}{$path}{action} eq 'M';
+
+      push @paths, $path;
+    }
+
+    # If the change only contains modifications, make sure they aren't
+    # just property changes
+    if ($only_modified) {
+      my @changedpaths;
+      for my $path (@paths) {
+        (my $file = $path) =~ s#^$url_path/*##;
+        my @diff = $SVN->stdout(
+          qw{svn diff --no-diff-deleted --summarize -c}, $rev,
+          sprintf("%s/%s@%s", $url->url(), $file, $rev),
+        );
+        next unless $diff[-1] =~ /^[A-Z]/;
+        push @changedpaths, $path;
+      }
+      @paths = @changedpaths;
+    }
+
+    next unless @paths;
+
+    # Create the patch using "svn diff"
+    my $patch = ();
+    if ($use_patch) {
+      $patch = $SVN->stdout(
+          qw{svn diff --no-diff-deleted -c}, $rev, $url->url(),
+      );
+      if ($patch) {
+        # Don't use the patch if it may contain subversion keywords or
+        # any changes to PDF files or any changes to symbolic links or
+        # any carriage returns in the middle of a line
+        for (split(qr{\n}msx, $patch)) {
+          if (/\$[a-zA-Z:]+ *\$/ or /^--- .+\.pdf\t/ or /^\+link / or /\r.+/) {
+            $use_patch = 0;
+            last;
+          }
+        }
+      } else {
+        $use_patch = 0;
+      }
+    }
+
+    # Create a directory for this revision in the output directory
+    my $outdir_rev = File::Spec->catfile ($outdir, $rev);
+    mkpath($outdir_rev)
+      || return _cm_err(FCM1::Cm::Exception->MKPATH, $outdir_rev);
+
+    # Parse commit log message
+    my @msg = split /\n/, $log{$rev}{msg};
+    for (@msg) {
+      # Re-instate line break
+      $_ .= "\n";
+
+      # Remove line if it matches a merge template
+      $_ = '' if /^Reversed r\d+(?::\d+)? of \S+$/;
+      $_ = '' if /^Custom merge into \S+:.+$/;
+      $_ = '' if /^Merged into \S+: \S+ cf\. \S+$/;
+
+      # Modify Trac ticket link
+      s/(?:#|ticket:)(\d+)/${organisation}_ticket:$1/g;
+
+      # Modify Trac changeset link
+      s/(?:r|changeset:)(\d+)/${organisation}_changeset:$1/g;
+      s/\[(\d+)\]/${organisation}_changeset:$1/g;
+    }
+
+    push @msg, '(' . $organisation . '_changeset:' . $rev . ')' . "\n";
+
+    # Write commit log message in a file
+    my $f_revlog = File::Spec->catfile ($outdir_rev, 'log-message');
+    open FILE, '>', $f_revlog or die $f_revlog, ': cannot open (', $!, ')';
+    print FILE @msg;
+    close FILE or die $f_revlog, ': cannot close (', $!, ')';
+
+    # Handle each changed path
+    my $export_file   = 1;  # name for next exported file (gets incremented)
+    my $patch_needed  = 0;  # is a patch file required?
+    my @before_script = (); # patch script to run before patch applied
+    my @after_script  = (); # patch script to run after patch applied
+    my @copied_dirs   = (); # copied directories
+    CHANGED: for my $path (@paths) {
+      (my $file = $path) =~ s#^$url_path/*##;
+      my $url_file = $url->url . '/' . $file . '@' . $rev;
+
+      # Skip paths within copied directories
+      for my $copied_dir (@copied_dirs) {
+        next CHANGED if $file =~ m#^$copied_dir(?:/|$)#;
+      }
+
+      # Handle deleted files
+      if ($log{$rev}{paths}{$path}{action} eq 'D') {
+        push @after_script, 'svn delete "' . $file . '"';
+
+      } else {
+        # Skip property changes (if not done earlier)
+        if (not $only_modified and $log{$rev}{paths}{$path}{action} eq 'M') {
+          my @diff = $SVN->stdout(
+            qw{svn diff --no-diff-deleted --summarize -c}, $rev, $url_file,
+          );
+          next CHANGED unless $diff[-1] =~ /^[A-Z]/;
+        }
+
+        # Determine if the file is a directory
+        my $is_dir
+          =     $log{$rev}{paths}{$path}{action} ne 'M'
+            &&  $SVN->get_info($url_file)->[0]->{'kind'} eq 'dir';
+
+        # Decide how to treat added files
+        my $export_required = 0;
+        if ($log{$rev}{paths}{$path}{action} eq 'A') {
+          my $is_newfile = 0;
+          # Determine if the file is copied
+          if (exists $log{$rev}{paths}{$path}{'copyfrom-path'}) {
+            if ($is_dir) {
+              # A copied directory needs to be exported and added recursively
+              push @after_script, 'svn add "' . $file . '"';
+              $export_required = 1;
+              push @copied_dirs, $file;
+            } else {
+              # History exists for this file
+              my $copyfrom_path = $log{$rev}{paths}{$path}{'copyfrom-path'};
+              my $copyfrom_rev  = $log{$rev}{paths}{$path}{'copyfrom-rev'};
+              my $cp_url = FCM1::CmUrl->new (
+                URL => $url->root . $copyfrom_path . '@' . $copyfrom_rev,
+              );
+
+              if ($copyfrom_path =~ s#^$url_path/*##) {
+                # File is copied from a file under the specified URL
+                # Check source exists
+                $is_newfile = 1 unless $cp_url->url_exists ($rev - 1);
+              } else {
+                # File copied from outside of the specified URL
+                $is_newfile = 1;
+
+                # Check branches can be determined
+                if ($url->branch and $cp_url->branch) {
+
+                  # Follow its history, stop on copy
+                  my %cp_log = $cp_url->svnlog (STOP_ON_COPY => 1);
+
+                  # "First" revision of the copied file
+                  my $cp_rev = (sort {$a <=> $b} keys %cp_log) [0];
+                  my %attrib = %{ $cp_log{$cp_rev}{paths}{$cp_url->path} }
+                    if $cp_log{$cp_rev}{paths}{$cp_url->path};
+
+                  # Check whether the "first" revision is copied from elsewhere.
+                  if (exists $attrib{'copyfrom-path'}) {
+                    # If source exists in the specified URL, set up the copy
+                    my $cp_cp_url = FCM1::CmUrl->new (
+                      URL => $url->root . $attrib{'copyfrom-path'} . '@' .
+                             $attrib{'copyfrom-rev'},
+                    );
+                    if ($cp_cp_url->subdir()) {
+                      $cp_cp_url->url_peg(
+                        $cp_cp_url->project_url()
+                        . '/' . $url->branch()
+                        . '/' . $cp_cp_url->subdir()
+                        . '@' . $cp_cp_url->pegrev(),
+                      );
+                      if ($cp_cp_url->url_exists ($rev - 1)) {
+                        ($copyfrom_path = $cp_cp_url->path) =~ s#^$url_path/*##;
+                        # Check path is defined - if not it probably means the
+                        # branch doesn't follow the FCM naming convention
+                        $is_newfile = 0 if $copyfrom_path;
+                      }
+                    }
+                  }
+
+                  # Note: The logic above does not cover all cases. However, it
+                  # should do the right thing for the most common case. Even
+                  # where it gets it wrong the file contents should always be
+                  # correct even if the file history is not.
+                }
+              }
+
+              # Check whether file is copied from an excluded or copied path
+              if (not $is_newfile) {
+                for my $path (@exclude, at copied_dirs) {
+                  if ($copyfrom_path =~ m#^$path(?:/|$)#) {
+                    $is_newfile = 1;
+                    last;
+                  }
+                }
+              }
+
+              # Check whether file is copied from a file which has been replaced
+              if (not $is_newfile) {
+                my $copyfrom_fullpath = $url->branch_path . "/" . $copyfrom_path;
+                $is_newfile = 1 if ($log{$rev}{paths}{$copyfrom_fullpath}{action} and
+                                    $log{$rev}{paths}{$copyfrom_fullpath}{action} eq 'R');
+              }
+
+              # Copy the file, if required
+              push @before_script, 'svn copy ' . $copyfrom_path .  ' "' . $file . '"'
+                if not $is_newfile;
+            }
+
+          } else {
+            # History does not exist, must be a new file
+            if ($is_dir) {
+              # If it's a directory then create it and add it immediately
+              # (in case it contains any copied files)
+              push @before_script, 'mkdir "' . $file. '"';
+              push @before_script, 'svn add "' . $file . '"';
+            } else {
+              $is_newfile = 1;
+	    }
+          }
+
+          # Add the file, if required
+          if ($is_newfile) {
+            push @after_script, 'svn add "' . $file . '"';
+          }
+        }
+
+        if ($is_dir and $log{$rev}{paths}{$path}{action} eq 'R') {
+          # Subversion does not appear to support replacing a directory in a
+          # single transaction from a working copy (other than as the result
+          # of a merge). Therefore the delete of the old directory must be
+          # done in advance as a separate commit.
+          push @script, 'svn delete -m "Delete directory in preparation for' .
+            ' replacing it (part of ' . $organisation . '_changeset:' . $rev .
+            ')" $target/' . $file;
+          push @script, 'svn update --non-interactive';
+          # The replaced directory needs to be exported and added recursively
+          push @after_script, 'svn add "' . $file . '"';
+          $export_required = 1;
+          push @copied_dirs, $file;
+        }
+
+        if (not $is_dir and $log{$rev}{paths}{$path}{action} ne 'A') {
+          my ($was_symlink) = $SVN->stdout(
+            qw{svn propget svn:special},
+            sprintf("%s/%s@%d", $url->url(), $file, ($rev - 1)),
+          );
+          my ($is_symlink) = $SVN->stdout(
+            qw{svn propget svn:special}, $url_file,
+          );
+          if ($was_symlink and not $is_symlink) {
+            # A symbolic link has been changed to a normal file
+            push @before_script, 'svn propdel -q svn:special "' . $file . '"';
+            push @before_script, 'rm "' . $file . '"';
+	  } elsif ($log{$rev}{paths}{$path}{action} eq 'R') {
+            # Delete the old file and then add the new file
+            push @before_script, 'svn delete "' . $file . '"';
+            push @after_script, 'svn add "' . $file . '"';
+          } elsif ($is_symlink and not $was_symlink) {
+            # A normal file has been changed to a symbolic link
+            push @after_script, 'svn propset -q svn:special \* "' . $file . '"';
+          } elsif ($is_symlink and $was_symlink) {
+            # If a symbolic link has been modified then remove the old
+            # copy first to allow the copy to work
+            push @before_script, 'rm "' . $file . '"';
+          }
+        }
+
+        # Decide whether the file needs to be exported
+        if (not $is_dir) {
+          if (not $use_patch) {
+            $export_required = 1;
+          } else {
+            # Export the file if it is binary
+            my @file_diff = $SVN->stdout(
+              qw{svn diff --no-diff-deleted -c}, $rev, $url_file,
+            );
+            for (@file_diff) {
+              $export_required = 1 if /Cannot display: file marked as a binary type./;
+            }
+            # Only create a patch file if necessary
+            $patch_needed = 1 if not $export_required;
+          }
+        }
+
+        if ($export_required) {
+          # Download the file using "svn export"
+          my $export = File::Spec->catfile ($outdir_rev, $export_file);
+          $SVN->call(qw{export -q -r}, $rev, $url_file, $export);
+
+          # Copy the exported file into the file
+          push @before_script,
+               'cp -r ${fcm_patch_dir}/' . $export_file . ' "' . $file . '"';
+          $export_file++;
+        }
+      }
+    }
+
+    # Write the patch file
+    if ($patch_needed) {
+      my $patchfile = File::Spec->catfile ($outdir_rev, 'patchfile');
+      open FILE, '>', $patchfile
+        or die $patchfile, ': cannot open (', $!, ')';
+      print FILE $patch;
+      close FILE or die $patchfile, ': cannot close (', $!, ')';
+    }
+
+    # Add line break to each line in @before_script and @after_script
+    @before_script = map {($_ ? $_ . ' || exit 1' . "\n" : "\n")}
+                     @before_script if (@before_script);
+    @after_script  = map {($_ ? $_ . ' || exit 1' . "\n" : "\n")}
+                     @after_script if (@after_script);
+
+    # Write patch script to output
+    my $out = File::Spec->catfile ($outdir_rev, 'apply-patch');
+    open FILE, '>', $out or die $out, ': cannot open (', $!, ')';
+
+    # Script header
+    print FILE <<EOF;
+#!/usr/bin/env bash
+# ------------------------------------------------------------------------------
+# NAME
+#   apply-patch
+#
+# DESCRIPTION
+#   This script is generated automatically by the "fcm mkpatch" command. It
+#   applies the patch to the current working directory which must be a working
+#   copy of a valid project tree that can accept the import of the patches.
+#
+#   Patch created from $organisation URL: $u
+#   Changeset: $rev
+# ------------------------------------------------------------------------------
+
+this=`basename \$0`
+echo "\$this: Applying patch for changeset $rev."
+
+# Location of the patch, base on the location of this script
+cd `dirname \$0` || exit 1
+fcm_patch_dir=\$PWD
+
+# Change directory back to the working copy
+cd \$OLDPWD || exit 1
+
+# Check working copy does not have local changes
+status=`svn status`
+if [[ -n \$status ]]; then
+  echo "\$this: working copy contains changes, abort." >&2
+  exit 1
+fi
+if [[ -a "#commit_message#" ]]; then
+  echo "\$this: existing commit message in "#commit_message#", abort." >&2
+  exit 1
+fi
+
+# Apply the changes
+patch_command=\${patch_command:-"patch --no-backup-if-mismatch -p0"}
+EOF
+
+    # Script content
+    print FILE @before_script if @before_script;
+    print FILE "\$patch_command <\${fcm_patch_dir}/patchfile || exit 1\n"
+      if $patch_needed;
+    print FILE @after_script  if @after_script;
+
+    # Script footer
+    print FILE <<EOF;
+
+# Copy in the commit message
+cp \${fcm_patch_dir}/log-message "#commit_message#"
+
+echo "\$this: finished normally."
+#EOF
+EOF
+
+    close FILE or die $out, ': cannot close (', $!, ')';
+
+    # Add executable permission
+    chmod 0755, $out;
+
+    # Script to commit the change
+    push @script, '${fcm_patches_dir}/' . $rev . '/apply-patch';
+    push @script, 'svn commit -F "#commit_message#"';
+    push @script, 'rm -f "#commit_message#"';
+    push @script, 'svn update --non-interactive';
+    push @script, '';
+
+    $CLI_MESSAGE->('PATCH_REV', $rev);
+  }
+
+  # Write the main output script if necessary. Otherwise remove output directory
+  # ----------------------------------------------------------------------------
+  if (@script) {
+    # Add line break to each line in @script
+    @script = map {($_ ? $_ . ' || exit 1' . "\n" : "\n")} @script;
+
+    # Write script to output
+    my $out = File::Spec->catfile ($outdir, 'fcm-import-patch');
+    open FILE, '>', $out or die $out, ': cannot open (', $!, ')';
+
+    # Script header
+    print FILE <<EOF;
+#!/usr/bin/env bash
+# ------------------------------------------------------------------------------
+# NAME
+#   fcm-import-patch
+#
+# SYNOPSIS
+#   fcm-import-patch TARGET
+#
+# DESCRIPTION
+#   This script is generated automatically by the "fcm mkpatch" command, as are
+#   the revision "patches" created in the same directory. The script imports the
+#   patches into TARGET, which must either be a URL or a working copy of a valid
+#   project tree that can accept the import of the patches.
+#
+#   Patch created from $organisation URL: $u
+# ------------------------------------------------------------------------------
+
+this=`basename \$0`
+
+# Check argument
+target=\$1
+
+# First argument must be a URL or working copy
+if [[ -z \$target ]]; then
+  echo "\$this: the first argument must be a URL or a working copy, abort." >&2
+  exit 1
+fi
+
+if [[ \$target == svn://*  || \$target == svn+ssh://* || \\
+      \$target == http://* || \$target == https://*   || \\
+      \$target == file://* ]]; then
+  # A URL, checkout a working copy in a temporary location
+  fcm_tmp_dir=`mktemp -d \${TMPDIR:=/tmp}/\$this.XXXXXX`
+  fcm_working_copy=\$fcm_tmp_dir
+  svn checkout -q \$target \$fcm_working_copy || exit 1
+else
+  fcm_working_copy=\$target
+  target=`svn info \$fcm_working_copy | grep "^URL: " | sed 's/URL: //'` || exit 1
+fi
+
+# Location of the patches, base on the location of this script
+cd `dirname \$0` || exit 1
+fcm_patches_dir=\$PWD
+
+# Change directory to the working copy
+cd \$fcm_working_copy || exit 1
+
+# Set the language to avoid encoding problems
+if locale -a | grep -q en_GB\$; then
+  export LANG=en_GB
+fi
+
+# Commands to apply patches
+EOF
+
+    # Script content
+    print FILE @script;
+
+    # Script footer
+    print FILE <<EOF;
+# Check working copy does not have local changes
+status=`svn status`
+if [[ -n \$status ]]; then
+  echo "\$this: working copy still contains changes, abort." >&2
+  exit 1
+fi
+
+# Remove temporary working copy, if necessary
+if [[ -d \$fcm_tmp_dir && -w \$fcm_tmp_dir ]]; then
+  rm -rf \$fcm_tmp_dir
+fi
+
+echo "\$this: finished normally."
+#EOF
+EOF
+
+    close FILE or die $out, ': cannot close (', $!, ')';
+
+    # Add executable permission
+    chmod 0755, $out;
+
+    # Diagnostic
+    $CLI_MESSAGE->('PATCH_DONE', $outdir);
+
+  } else {
+    # Remove output directory
+    rmtree $outdir or die $outdir, ': cannot remove';
+
+    # Diagnostic
+    return _cm_abort(FCM1::Cm::Abort->NULL);
+  }
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# CLI error.
+sub _cli_err {
+    my ($key, @args) = @_;
+    my $message = sprintf($CLI_MESSAGE_FOR_ERROR{$key}, @args);
+    die(FCM1::CLI::Exception->new({message => $message}));
+}
+
+# ------------------------------------------------------------------------------
+# The default handler of the "WC_STATUS" event.
+sub _cli_handler_of_wc_status {
+    my ($name, $target_list_ref, $status_list_ref) = @_;
+    $target_list_ref ||= [q{.}];
+    if (@{$status_list_ref}) {
+        $CLI_MESSAGE->(
+            'STATUS',
+            $name,
+            q{"} . join(q{", "}, @{$target_list_ref}) . q{"},
+            join("\n", @{$status_list_ref}),
+        );
+        if ($CLI_PROMPT->($name, 'CONTINUE', $name) ne 'y') {
+            return _cm_abort();
+        }
+    }
+    return @{$status_list_ref};
+}
+
+# ------------------------------------------------------------------------------
+# The default handler of the "WC_STATUS_PATH" event.
+sub _cli_handler_of_wc_status_path {
+    my ($name, $target_list_ref, $status_list_ref) = @_;
+    my $message
+        = @{$status_list_ref} ? (join("\n", @{$status_list_ref}) . "\n") : q{};
+    $CLI_MESSAGE->(q{}, $message);
+    my @paths = map {chomp(); ($_ =~ $PATTERN_OF{ST_PATH})} @{$status_list_ref};
+    my @paths_of_interest;
+    while (my $path = shift(@paths)) {
+        my %handler_of = (
+            a => sub {push(@paths_of_interest, $path, @paths); @paths = ()},
+            n => sub {},
+            y => sub {push(@paths_of_interest, $path)},
+        );
+        my $reply = $CLI_PROMPT->(
+            {type => 'yna'}, $name, 'RUN_SVN_COMMAND', "$name $path",
+        );
+        $handler_of{$reply}->();
+    }
+    return @paths_of_interest;
+}
+
+# ------------------------------------------------------------------------------
+# Expands location keywords in a list.
+sub _cli_keyword_expand_url {
+    my ($arg_list_ref) = @_;
+    ARG:
+    for my $arg (@{$arg_list_ref}) {
+        my ($label, $value) = ($arg =~ $PATTERN_OF{CLI_OPT});
+        if (!$label) {
+            ($label, $value) = (q{}, $arg);
+        }
+        if (!$value) {
+            next ARG;
+        }
+        eval {
+            $value = FCM1::Util::tidy_url(FCM1::Keyword::expand($value));
+        };
+        if ($@) {
+            if ($value ne 'fcm:revision') {
+                die($@);
+            }
+        }
+        $arg = $label . $value;
+    }
+}
+
+# ------------------------------------------------------------------------------
+# Expands revision keywords in -r and --revision options in a list.
+sub _cli_keyword_expand_rev {
+    my ($arg_list_ref) = @_;
+    my @targets;
+    for my $arg (@{$arg_list_ref}) {
+        if (-e $arg && is_wc($arg) || is_url($arg)) {
+            push(@targets, $arg);
+        }
+    }
+    if (!@targets) {
+        push(@targets, get_url_of_wc());
+    }
+    if (!@targets) {
+        return;
+    }
+    my @old_arg_list = @{$arg_list_ref};
+    my @new_arg_list = ();
+    ARG:
+    while (defined(my $arg = shift(@old_arg_list))) {
+        my ($key, $value) = $arg =~ $PATTERN_OF{CLI_OPT_REV};
+        if (!$key) {
+            push(@new_arg_list, $arg);
+            next ARG;
+        }
+        push(@new_arg_list, '--revision');
+        if (!$value) {
+            $value = shift(@old_arg_list);
+        }
+        my @revs = grep {defined()} ($value =~ $PATTERN_OF{CLI_OPT_REV_RANGE});
+        my ($url, @url_list) = @targets;
+        for my $rev (@revs) {
+            if ($rev !~ $PATTERN_OF{SVN_REV}) {
+                $rev = (FCM1::Keyword::expand($url, $rev))[1];
+            }
+            if (@url_list) {
+                $url = shift(@url_list);
+            }
+        }
+        push(@new_arg_list, join(q{:}, @revs));
+    }
+    @{$arg_list_ref} = @new_arg_list;
+}
+
+# ------------------------------------------------------------------------------
+# Prints a message.
+sub _cli_message {
+    my ($key, @args) = @_;
+    for (
+        [\*STDOUT, \%CLI_MESSAGE_FOR        , q{}          ],
+        [\*STDERR, \%CLI_MESSAGE_FOR_WARNING, q{[WARNING] }],
+        [\*STDERR, \%CLI_MESSAGE_FOR_ABORT  , q{[ABORT] }  ],
+        [\*STDERR, \%CLI_MESSAGE_FOR_ERROR  , q{[ERROR] }  ],
+    ) {
+        my ($handle, $hash_ref, $prefix) = @{$_};
+        if (exists($hash_ref->{$key})) {
+            return printf({$handle} $prefix . $hash_ref->{$key}, @args);
+        }
+    }
+}
+
+# ------------------------------------------------------------------------------
+# Wrapper for FCM1::Interactive::get_input.
+sub _cli_prompt {
+    my %option
+        = (type => 'yn', default => 'n', (ref($_[0]) ? %{shift(@_)} : ()));
+    my ($name, $key, @args) = @_;
+    return FCM1::Interactive::get_input(
+        title   => $CLI_PROMPT_PREFIX . $name,
+        message => sprintf($CLI_MESSAGE_FOR_PROMPT{$key}, @args),
+        %option,
+    );
+}
+
+# ------------------------------------------------------------------------------
+# Check missing status and delete.
+sub cm_check_missing {
+    my %option = %{shift()};
+    my $checker
+        = _svn_status_checker('delete', 'MISSING', $option{st_check_handler});
+    my @paths = $checker->(\@_);
+    if (@paths) {
+        $SVN->call('delete', @paths);
+    }
+}
+
+# ------------------------------------------------------------------------------
+# Check unknown status and add.
+sub cm_check_unknown {
+    my %option = %{shift()};
+    my $checker
+        = _svn_status_checker('add', 'UNKNOWN', $option{st_check_handler});
+    my @paths = $checker->(\@_);
+    if (@paths) {
+        $SVN->call('add', @paths);
+    }
+}
+
+# ------------------------------------------------------------------------------
+# FCM wrapper to SVN switch.
+sub cm_switch {
+    my %option = %{shift()};
+    my ($source, $path) = @_;
+    $path ||= cwd();
+    if (!$source) {
+        return _cm_err(FCM1::Cm::Exception->INVALID_TARGET, q{});
+    }
+    if (!-e $path) {
+        return _cm_err(FCM1::Cm::Exception->NOT_EXIST, $path);
+    }
+    if (!is_wc($path)) {
+        return _cm_err(FCM1::Cm::Exception->INVALID_WC, $path);
+    }
+
+    # Check for merge template in the commit log file in the working copy
+    my $path_of_wc = $SVN->get_wc_root($path);
+    my $commit_message_file = $COMMIT_MESSAGE_UTIL->path($path_of_wc);
+    my $commit_message_ctx = $COMMIT_MESSAGE_UTIL->load($commit_message_file);
+    if ($commit_message_ctx->get_auto_part()) {
+        return _cm_err(
+            FCM1::Cm::Exception->SWITCH_UNSAFE,
+            ($path eq $path_of_wc
+                ? File::Spec->abs2rel($commit_message_file)
+                : $commit_message_file
+            ),
+        );
+    }
+
+    # Check for any local modifications
+    if (defined($option{st_check_handler})) {
+        _svn_status_checker('switch', 'MODIFIED', $option{st_check_handler})->(
+            [$path_of_wc],
+        );
+    }
+
+    my @targets = $path_of_wc eq cwd() ? () : ($path_of_wc);
+    $SVN->call('cleanup', @targets);
+    $SVN->call(
+        'switch',
+        '--non-interactive',
+        ($option{revision} ? ('-r', $option{revision}) : ()),
+        ($option{quiet}    ? '--quiet'                 : ()),
+        _cm_get_source(
+            $source,
+            FCM1::CmBranch->new(URL => $path_of_wc),
+        )->url_peg(),
+        @targets,
+    );
+}
+
+# ------------------------------------------------------------------------------
+# FCM wrapper to SVN update.
+sub cm_update {
+    my %option = %{shift()};
+    my @targets = @_;
+    if (!@targets) {
+        @targets = (cwd());
+    }
+    for my $target (@targets) {
+        if (!-e $target) {
+            return _cm_err(FCM1::Cm::Exception->NOT_EXIST, $target);
+        }
+        if (!is_wc($target)) {
+            return _cm_err(FCM1::Cm::Exception->INVALID_WC, $target);
+        }
+        $target = $SVN->get_wc_root($target);
+        if ($target eq cwd()) {
+            $target = q{.};
+        }
+    }
+    if (defined($option{st_check_handler})) {
+        my ($matcher_keys_ref, $show_updates)
+            = defined($option{revision}) ? (['MODIFIED'       ], undef)
+            :                              (['MODIFIED', 'OOD'], 1    )
+            ;
+        my $matcher = sub {
+            for my $key (@{$matcher_keys_ref}) {
+                $ST_MATCHER_FOR{$key}->(@_) && return 1;
+            }
+        };
+        _svn_status_checker(
+            'update', $matcher, $option{st_check_handler}, $show_updates,
+        )->(\@targets);
+    }
+    if ($option{revision} && $option{revision} !~ $PATTERN_OF{SVN_REV}) {
+        $option{revision} = (
+            FCM1::Keyword::expand(get_url_of_wc($targets[0]), $option{revision})
+        )[1];
+    }
+    _svn_update(\@targets, \%option);
+}
+
+# ------------------------------------------------------------------------------
+# Raises an abort exception.
+sub _cm_abort {
+    my ($code) = @_;
+    $code ||= FCM1::Cm::Abort->USER;
+    die(bless({code => $code, message => 'abort'}, 'FCM1::Cm::Abort'));
+}
+
+# ------------------------------------------------------------------------------
+# Raises a failure.
+sub _cm_err {
+    my ($code, @targets) = @_;
+    die(bless(
+        {code => $code, message => "ERROR: $code", targets => \@targets},
+        'FCM1::Cm::Exception',
+    ));
+}
+
+# ------------------------------------------------------------------------------
+# Return a corresponding FCM1::CmBranch instance for $source_url w.r.t. $target.
+sub _cm_get_source {
+    my ($source_url, $target) = @_;
+    if (!$UTIL->uri_match($source_url)) {
+        # Source not full URL, construct source URL based on target URL
+        my ($path, $peg) = $source_url =~ qr{\A(.*?)(@[^@/]+)?\z}msx;
+        my $project = $target->project_path();
+        if (index($path, $project . '/') == 0) {
+            # $path contains the full path under the repository root
+            $path = substr($path, length($project));
+        }
+        my %layout_config = %{$target->layout()->get_config()};
+        if (!grep {!defined($layout_config{"dir-$_"})} qw{trunk branch tag}) {
+            # $path must be under the specified sub-directories for the trunk,
+            # branches and tags
+            my @dirs = map {$layout_config{"dir-$_"}} qw{trunk branch tag};
+            my @paths = split(qr{/+}msx, $path);
+            if (!@paths || !grep {$_ eq $paths[0]} @dirs) {
+                $path = $layout_config{'dir-branch'} . '/' . $path;
+            }
+        }
+        $peg ||= q{};
+        $source_url = join('/', $target->project_url(), $path) . $peg;
+    }
+    my $source = FCM1::CmBranch->new(URL => $source_url);
+    my $layout = eval {$source->layout()};
+    if ($@) {
+        $@ = undef;
+        return _cm_err(FCM1::Cm::Exception->INVALID_URL, $source_url);
+    }
+    if (!$layout->get_branch()) {
+        return _cm_err(FCM1::Cm::Exception->INVALID_BRANCH, $source_url);
+    }
+    $source->url_peg(
+      $source->branch_url() . '/' . $target->subdir() . '@' . $source->pegrev()
+    );
+    # Ensure that the source and target URLs are in the same project
+    if ($source->project_url() ne $target->project_url()) {
+        return _cm_err(
+            FCM1::Cm::Exception->DIFF_PROJECTS,
+            $target->url_peg(),
+            $source->url_peg(),
+        );
+    }
+    return $source;
+}
+
+# ------------------------------------------------------------------------------
+# Returns the results of "svn status".
+sub _svn_status_get {
+    my ($targets_ref, $show_updates) = @_;
+    my @targets = (defined($targets_ref) ? @{$targets_ref} : ());
+    for my $target (@targets) {
+        if ($target eq cwd()) {
+            $target = q{.};
+        }
+    }
+    my @options = ($show_updates ? qw{--show-updates} : ());
+    $SVN->stdout(qw{svn status}, @options, @targets);
+}
+
+# ------------------------------------------------------------------------------
+# Returns a "svn status" checker.
+sub _svn_status_checker {
+    my ($name, $matcher, $handler, $show_updates) = @_;
+    if (!ref($matcher)) {
+        $matcher = $ST_MATCHER_FOR{$matcher};
+    }
+    my $P = $PATTERN_OF{ST_PATH};
+    sub {
+        my ($targets_ref) = @_;
+        my @status = _svn_status_get($targets_ref, $show_updates);
+        if ($show_updates) {
+            @status = map {$_ =~ $PATTERN_OF{ST_AGAINST_REV} ? () : $_} @status;
+        }
+        my @status_of_interest = grep {$matcher->($_)} @status;
+        # Note: for future expansion...
+        #my @paths;
+        #if (!$show_updates) {
+        #    @paths = map {chomp(); $_} map {($_ =~ $P)} @status_of_interest;
+        #}
+        #defined($handler)
+        #? $handler->($name, $targets_ref, \@status_of_interest, \@paths)
+        #: @status_of_interest
+        #;
+        defined($handler)
+        ? $handler->($name, $targets_ref, \@status_of_interest)
+        : @status_of_interest
+        ;
+    };
+}
+
+# ------------------------------------------------------------------------------
+# Runs "svn update".
+sub _svn_update {
+    my ($targets_ref, $option_hash_ref) = @_;
+    my %option = (defined($option_hash_ref) ? %{$option_hash_ref} : ());
+    my @targets = defined($targets_ref) ? @{$targets_ref} : ();
+    $SVN->call('cleanup', @targets);
+    $SVN->call(
+        'update',
+        '--non-interactive',
+        ($option{revision} ? ('-r', $option{revision}) : ()),
+        ($option{quiet}    ? '--quiet'                 : ()),
+        @targets,
+    );
+}
+
+# ------------------------------------------------------------------------------
+# CLI exception.
+package FCM1::CLI::Exception;
+use base qw{FCM1::Exception};
+
+# ------------------------------------------------------------------------------
+# Abort exception.
+package FCM1::Cm::Abort;
+use base qw{FCM1::Exception};
+use constant {FAIL => 'FAIL', NULL => 'NULL', USER => 'USER'};
+
+sub get_code {
+    return $_[0]->{code};
+}
+
+# ------------------------------------------------------------------------------
+# Resource exception.
+package FCM1::Cm::Exception;
+our @ISA = qw{FCM1::Cm::Abort};
+use constant {
+    CHDIR             => 'CHDIR',
+    DIFF_PROJECTS     => 'DIFF_PROJECTS',
+    INVALID_BRANCH    => 'INVALID_BRANCH',
+    INVALID_PROJECT   => 'INVALID_PROJECT',
+    INVALID_TARGET    => 'INVALID_TARGET',
+    INVALID_URL       => 'INVALID_URL',
+    INVALID_WC        => 'INVALID_WC',
+    MERGE_REV_INVALID => 'MERGE_REV_INVALID',
+    MERGE_SELF        => 'MERGE_SELF',
+    MERGE_UNRELATED   => 'MERGE_UNRELATED',
+    MERGE_UNSAFE      => 'MERGE_UNSAFE',
+    MKPATH            => 'MKPATH',
+    NOT_EXIST         => 'NOT_EXIST',
+    PARENT_NOT_EXIST  => 'PARENT_NOT_EXIST',
+    RMTREE            => 'RMTREE',
+    SWITCH_UNSAFE     => 'SWITCH_UNSAFE',
+    WC_INVALID_BRANCH => 'WC_INVALID_BRANCH',
+    WC_URL_NOT_EXIST  => 'WC_URL_NOT_EXIST',
+};
+
+sub get_targets {
+    return @{$_[0]->{targets}};
+}
+
+1;
+__END__
+
+=pod
+
+=head1 NAME
+
+FCM1::Cm
+
+=head1 SYNOPSIS
+
+    use FCM1::Cm qw{cm_check_missing cm_check_unknown cm_switch cm_update};
+
+    # Checks status for "missing" items and "svn delete" them
+    $missing_st_handler = sub {
+        my ($name, $targets_ref, $status_list_ref) = @_;
+        # ...
+        return @paths_of_interest;
+    };
+    cm_check_missing({st_check_handler => $missing_st_handler}, @targets);
+
+    # Checks status for "unknown" items and "svn add" them
+    $unknown_st_handler = sub {
+        my ($name, $targets_ref, $status_list_ref) = @_;
+        # ...
+        return @paths_of_interest;
+    };
+    cm_check_unknown({st_check_handler => $unknown_st_handler}, @targets);
+
+    # Sets up a status checker
+    $st_check_handler = sub {
+        my ($name, $targets_ref, $status_list_ref) = @_;
+        # ...
+    };
+    # Switches a "working copy" at the "root" level to a new URL target
+    cm_switch(
+        {
+            'non-interactive'  => $non_interactive_flag,
+            'quiet'            => $quiet_flag,
+            'revision'         => $revision,
+            'st_check_handler' => $st_check_handler,
+        },
+        $target, $path_of_wc,
+    );
+    # Runs "svn update" on each working copy from their "root" level
+    cm_update(
+        {
+            'non-interactive'  => $non_interactive_flag,
+            'quiet'            => $quiet_flag,
+            'revision'         => $revision,
+            'st_check_handler' => $st_check_handler,
+        },
+        @targets,
+    );
+
+=head1 DESCRIPTION
+
+Wraps the Subversion client and implements other FCM code management
+functionalities.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item cm_check_missing(\%option, at targets)
+
+Use "svn status" to check for missing items in @targets. If @targets is an empty
+list, the function adds the current working directory to it. Expects
+$option{st_check_handler} to be a CODE reference. Calls
+$option{st_check_handler} with ($name, $targets_ref, $status_list_ref) where
+$name is "delete", $targets_ref is \@targets, and $status_list_ref is an
+ARRAY reference to a list of "svn status" output with the "missing" status.
+$option{st_check_handler} should return a list of interesting paths, which will
+be scheduled for removal using "svn delete".
+
+=item cm_check_unknown(\%option, at targets)
+
+Similar to cm_check_missing(\%option, at targets) but checks for "unknown" items,
+which will be scheduled for addition using "svn add".
+
+=item cm_switch(\%option,$target,$path_of_wc)
+
+Invokes "svn switch" at the root of a working copy specified by $path_of_wc (or
+the current working directory if $path_of_wc is not specified).
+$option{'non-interactive'}, $option{quiet}, $option{revision} determines the
+options (of the same name) that are passed to "svn switch". If
+$option{st_check_handler} is set, it should be a CODE reference, and will be
+called with ('switch', [$path_of_wc], $status_list_ref), where $status_list_ref
+is an ARRAY reference to the output returned by "svn status" on $path_of_wc.
+This can be used for the application to display the working copy status to the
+user before prompting him/her to continue. The return value of
+$option{st_check_handler} is ignored.
+
+=item cm_update(\%option, at targets)
+
+Invokes "svn update" at the root of each working copy specified by @targets. If
+ at targets is an empty list, the function adds the current working directory to
+it. $option{'non-interactive'}, $option{quiet}, $option{revision} determines the
+options (of the same name) that are passed to "svn update". If
+$option{st_check_handler} is set, it should be a CODE reference, and will be
+called with ($name, $targets_ref, $status_list_ref), where $name is
+'update', $targets_ref is \@targets and $status_list_ref is an ARRAY
+reference to the output returned by "svn status -u" on the @targets. This can be
+used for the application to display the working copy update status to the user
+before prompting him/her to continue. The return value of
+$option{st_check_handler} is ignored.
+
+=back
+
+=head1 DIAGNOSTICS
+
+The following exceptions can be raised:
+
+=over 4
+
+=item FCM1::Cm::Abort
+
+This exception @ISA L<FCM1::Exception|FCM1::Exception>. It is raised if a command
+is aborted for some reason. The $e->get_code() method can be used to retrieve an
+error code, which can be one of the following:
+
+=over 4
+
+=item $e->FAIL
+
+The command aborts because of a failure.
+
+=item $e->NULL
+
+The command aborts because it will result in no change.
+
+=item $e->USER
+
+The command aborts because of an action by the user.
+
+=back
+
+=item FCM1::Cm::Exception
+
+This exception @ISA L<FCM1::Abort|FCM1::Abort>. It is raised if a command fails
+with a known reason. The $e->get_targets() method can be used to retrieve a list
+of targets/resources associated with this exception. The $e->get_code() method
+can be used to retrieve an error code, which can be one of the following:
+
+=over 4
+
+=item $e->CHDIR
+
+Fails to change directory to a target.
+
+=item $e->INVALID_BRANCH
+
+A target is not a valid branch URL in the standard FCM project layout.
+
+=item $e->INVALID_PROJECT
+
+A target is not a valid project URL in the standard FCM project layout.
+
+=item $e->INVALID_TARGET
+
+A target is not a valid Subversion URL or working copy.
+
+=item $e->INVALID_URL
+
+A target is not a valid Subversion URL.
+
+=item $e->INVALID_WC
+
+A target is not a valid Subversion working copy.
+
+=item $e->MERGE_REV_INVALID
+
+An invalid revision (target element 0) is specified for a merge.
+
+=item $e->MERGE_SELF
+
+Attempt to merge a URL (target element 0) to its own working copy (target
+element 1).
+
+=item $e->MERGE_UNRELATED
+
+The merge target (target element 0) is not directly related to the merge source
+(target element 1).
+
+=item $e->MERGE_UNSAFE
+
+A merge source (target element 0) contains changes outside the target
+sub-directory.
+
+=item $e->MKPATH
+
+Fail to create a directory (target element 0) recursively.
+
+=item $e->NOT_EXIST
+
+A target does not exist.
+
+=item $e->PARENT_NOT_EXIST
+
+The parent of the target no longer exists.
+
+=item $e->RMTREE
+
+Fail to remove a directory (target element 0) recursively.
+
+=item $e->SWITCH_UNSAFE
+
+A merge template exists in the commit message file (target element 0) in a
+working copy target.
+
+=item $e->WC_INVALID_BRANCH
+
+The URL of the target working copy is not a valid branch URL in the standard FCM
+project layout.
+
+=item $e->WC_URL_NOT_EXIST
+
+The URL of the target working copy no longer exists at the HEAD revision.
+
+=back
+
+=back
+
+=head1 TO DO
+
+Reintegrate with L<FCM1::CmUrl|FCM1::CmUrl> and L<FCM1::CmBranch|FCM1::CmBranch>,
+but separate this module into the CLI part and the CM part. Expose the remaining
+CM functions when this is done.
+
+Use L<SVN::Client|SVN::Client> to interface with Subversion.
+
+Move C<mkpatch> out of this module.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
+opy already exists.
+
+=item $e->WC_INVALID_BRANCH
+
+The URL of the target working copy is not a valid branch URL in the standard FCM
+project layout.
+
+=item $e->WC_URL_NOT_EXIST
+
+The URL of the target working copy no longer exists at the HEAD revision.
+
+=back
+
+=back
+
+=head1 TO DO
+
+Reintegrate with L<FCM1::CmUrl|FCM1::CmUrl> and L<FCM1::CmBranch|FCM1::CmBranch>,
+but separate this module into the CLI part and the CM part. Expose the remaining
+CM functions when this is done.
+
+Use L<SVN::Client|SVN::Client> to interface with Subversion.
+
+Move C<mkpatch> out of this module.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
+$e->CHDIR
+
+Fails to change directory to a target.
+
+=item $e->INVALID_BRANCH
+
+A target is not a valid branch URL in the standard FCM project layout.
+
+=item $e->INVALID_PROJECT
+
+A target is not a valid project URL in the standard FCM project layout.
+
+=item $e->INVALID_TARGET
+
+A target is not a valid Subversion URL or working copy.
+
+=item $e->INVALID_URL
+
+A target is not a valid Subversion URL.
+
+=item $e->INVALID_WC
+
+A target is not a valid Subversion working copy.
+
+=item $e->MERGE_REV_INVALID
+
+An invalid revision (target element 0) is specified for a merge.
+
+=item $e->MERGE_SELF
+
+Attempt to merge a URL (target element 0) to its own working copy (target
+element 1).
+
+=item $e->MERGE_UNRELATED
+
+The merge target (target element 0) is not directly related to the merge source
+(target element 1).
+
+=item $e->MERGE_UNSAFE
+
+A merge source (target element 0) contains changes outside the target
+sub-directory.
+
+=item $e->MKPATH
+
+Fail to create a directory (target element 0) recursively.
+
+=item $e->NOT_EXIST
+
+A target does not exist.
+
+=item $e->PARENT_NOT_EXIST
+
+The parent of the target no longer exists.
+
+=item $e->RMTREE
+
+Fail to remove a directory (target element 0) recursively.
+
+=item $e->SWITCH_UNSAFE
+
+A merge template exists in the commit message file (target element 0) in a
+working copy target.
+
+=item $e->WC_INVALID_BRANCH
+
+The URL of the target working copy is not a valid branch URL in the standard FCM
+project layout.
+
+=item $e->WC_URL_NOT_EXIST
+
+The URL of the target working copy no longer exists at the HEAD revision.
+
+=back
+
+=back
+
+=head1 TO DO
+
+Migrate to FCM::System hierarchy.
+
+Move C<mkpatch> out of this module.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM1/CmBranch.pm b/lib/FCM1/CmBranch.pm
new file mode 100644
index 0000000..075d6cf
--- /dev/null
+++ b/lib/FCM1/CmBranch.pm
@@ -0,0 +1,1009 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::CmBranch
+#
+# DESCRIPTION
+#   This class contains methods for manipulating a branch. It is a sub-class of
+#   FCM1::CmUrl, and inherits all methods from that class.
+#
+# ------------------------------------------------------------------------------
+
+package FCM1::CmBranch;
+use base qw{FCM1::CmUrl};
+
+use strict;
+use warnings;
+
+use FCM1::Config;
+use FCM1::Interactive;
+use FCM1::Keyword;
+use FCM1::Util qw/e_report w_report svn_date/;
+
+my @properties = (
+  'CREATE_REV',  # revision at which the branch is created
+  'DELETE_REV',  # revision at which the branch is deleted
+  'PARENT',      # reference to parent branch FCM1::CmBranch
+  'ANCESTOR',    # list of common ancestors with other branches
+                 # key = URL, value = ancestor FCM1::CmBranch
+  'LAST_MERGE',  # list of last merges from branches
+                 # key = URL at REV, value = [TARGET, UPPER, LOWER]
+  'AVAIL_MERGE', # list of available revisions for merging
+                 # key = URL at REV, value = [REV ...]
+  'CHILDREN',    # list of children of this branch
+  'SIBLINGS',    # list of siblings of this branch
+);
+
+# Set the commit message utility provided by FCM::System::CM.
+our $COMMIT_MESSAGE_UTIL;
+sub set_commit_message_util {
+    $COMMIT_MESSAGE_UTIL = shift();
+}
+
+# Set the SVN utility provided by FCM::System::CM.
+our $SVN;
+sub set_svn_util {
+    $SVN = shift();
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $cm_branch = FCM1::CmBranch->new (URL => $url,);
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::CmBranch class.
+#
+# ARGUMENTS
+#   URL    - URL of a branch
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  my $self = FCM1::CmUrl->new (%args);
+
+  $self->{$_} = undef for (@properties);
+
+  bless $self, $class;
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $url = $cm_branch->url_peg;
+#   $cm_branch->url_peg ($url);
+#
+# DESCRIPTION
+#   This method returns/sets the current URL.
+# ------------------------------------------------------------------------------
+
+sub url_peg {
+  my $self = shift;
+
+  if (@_) {
+    if (! $self->{URL} or $_[0] ne $self->{URL}) {
+      # Re-set URL and other essential variables in the SUPER-class
+      $self->SUPER::url_peg (@_);
+
+      # Re-set essential variables
+      $self->{$_} = undef for (@properties);
+    }
+  }
+
+  return $self->{URL};
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rev = $cm_branch->create_rev;
+#
+# DESCRIPTION
+#   This method returns the revision at which the branch was created.
+# ------------------------------------------------------------------------------
+
+sub create_rev {
+  my $self = shift;
+
+  if (not $self->{CREATE_REV}) {
+    return unless $self->url_exists ($self->pegrev);
+
+    # Use "svn log" to find out the first revision of the branch
+    my %log = $self->svnlog (STOP_ON_COPY => 1);
+
+    # Look at log in ascending order
+    my $rev   = (sort {$a <=> $b} keys %log) [0];
+    my $paths = $log{$rev}{paths};
+
+    # Get revision when URL is first added to the repository
+    if (exists $paths->{$self->branch_path}) {
+      $self->{CREATE_REV} = $rev if $paths->{$self->branch_path}{action} eq 'A';
+    }
+  }
+
+  return $self->{CREATE_REV};
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $parent = $cm_branch->parent;
+#
+# DESCRIPTION
+#   This method returns the parent (a FCM1::CmBranch object) of the current
+#   branch.
+# ------------------------------------------------------------------------------
+
+sub parent {
+  my $self = shift;
+
+  if (not $self->{PARENT}) {
+    # Use the log to find out the parent revision
+    my %log = $self->svnlog (REV => $self->create_rev);
+
+    if (exists $log{paths}{$self->branch_path}) {
+      my $path = $log{paths}{$self->branch_path};
+
+      if ($path->{action} eq 'A') {
+        if (exists $path->{'copyfrom-path'}) {
+          # Current branch is copied from somewhere, set the source as the parent
+          my $url = $self->root .  $path->{'copyfrom-path'};
+          my $rev = $path->{'copyfrom-rev'};
+          $self->{PARENT} = FCM1::CmBranch->new (URL => $url . '@' . $rev);
+
+        } else {
+          # Current branch is not copied from somewhere
+          $self->{PARENT} = $self;
+        }
+      }
+    }
+  }
+
+  return $self->{PARENT};
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rev = $cm_branch->delete_rev;
+#
+# DESCRIPTION
+#   This method returns the revision at which the branch was deleted.
+# ------------------------------------------------------------------------------
+
+sub delete_rev {
+  my $self = shift;
+
+  if (not $self->{DELETE_REV}) {
+    return if $self->url_exists ('HEAD');
+
+    # Container of the current URL
+    (my $dir_url = $self->branch_url) =~ s#/+[^/]+/*$##;
+
+    # Use "svn log" on the container between a revision where the branch exists
+    # and the HEAD
+    my $dir = FCM1::CmUrl->new (URL => $dir_url);
+    my %log = $dir->svnlog (
+      REV => ['HEAD', ($self->pegrev ? $self->pegrev : $self->create_rev)],
+    );
+
+    # Go through the log to see when branch no longer exists
+    for my $rev (sort {$a <=> $b} keys %log) {
+      next unless exists $log{$rev}{paths}{$self->branch_path} and
+                  $log{$rev}{paths}{$self->branch_path}{action} eq 'D';
+
+      $self->{DELETE_REV} = $rev;
+      last;
+    }
+  }
+
+  return $self->{DELETE_REV};
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flag = $cm_branch->is_child_of ($branch);
+#
+# DESCRIPTION
+#   This method returns true if the current branch is a child of $branch.
+# ------------------------------------------------------------------------------
+
+sub is_child_of {
+  my ($self, $branch) = @_;
+  !$self->is_trunk()
+    && $self->parent()->url() eq $branch->url()
+    && (!$branch->is_branch() || $self->create_rev() >= $branch->create_rev());
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flag = $cm_branch->is_sibling_of ($branch);
+#
+# DESCRIPTION
+#   This method returns true if the current branch is a sibling of $branch.
+# ------------------------------------------------------------------------------
+
+sub is_sibling_of {
+  my ($self, $branch) = @_;
+
+  # The trunk cannot be a sibling branch
+  return if $branch->is_trunk;
+
+  return if $self->parent->url ne $branch->parent->url;
+
+  # If the parent is a branch, ensure they are actually the same branch
+  return if $branch->parent->is_branch and
+            $self->parent->create_rev != $branch->parent->create_rev;
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $self->_get_relatives ($relation);
+#
+# DESCRIPTION
+#   This method sets the $self->{$relation} variable by inspecting the list of
+#   branches at the current revision of the current branch. $relation can be
+#   either "CHILDREN" or "SIBLINGS".
+# ------------------------------------------------------------------------------
+
+sub _get_relatives {
+  my ($self, $relation) = @_;
+
+  $self->{$relation} = [];
+
+  # If we are searching for CHILDREN, get list of SIBLINGS, and vice versa
+  my $other = ($relation eq 'CHILDREN' ? 'SIBLINGS' : 'CHILDREN');
+  my %other_list;
+  if ($self->{$other}) {
+    %other_list = map {$_->url(), 1} @{$self->{$other}};
+  }
+
+  my @url_peg_list = $self->branch_list();
+  URL:
+  for my $url_peg (@url_peg_list) {
+    my ($url, $peg) = $SVN->split_by_peg($url_peg);
+    # Ignore URL of current branch and its parent
+    if ( $url eq $self->url()
+         # Ignore parent
+      || $self->is_branch() && $url eq $self->parent()->url()
+         # Ignore the other type of relatives
+      || exists($other_list{$url})
+    ) {
+      next URL;
+    }
+
+    my $branch = FCM1::CmBranch->new(URL => $url_peg);
+
+    # Test whether $branch is a relative we are looking for
+    my $can_return = $relation eq 'CHILDREN'
+      ? $branch->is_child_of($self) : $branch->is_sibling_of($self);
+    if ($can_return) {
+      push(@{$self->{$relation}}, $branch);
+    }
+  }
+
+  return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @children = $cm_branch->children;
+#
+# DESCRIPTION
+#   This method returns a list of children (FCM1::CmBranch objects) of the
+#   current branch that exists in the current revision.
+# ------------------------------------------------------------------------------
+
+sub children {
+  my $self = shift;
+
+  $self->_get_relatives ('CHILDREN') if not $self->{CHILDREN};
+
+  return @{ $self->{CHILDREN} };
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @siblings = $cm_branch->siblings;
+#
+# DESCRIPTION
+#   This method returns a list of siblings (FCM1::CmBranch objects) of the
+#   current branch that exists in the current revision.
+# ------------------------------------------------------------------------------
+
+sub siblings {
+  my $self = shift;
+
+  $self->_get_relatives ('SIBLINGS') if not $self->{SIBLINGS};
+
+  return @{ $self->{SIBLINGS} };
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $ancestor = $cm_branch->ancestor ($branch);
+#
+# DESCRIPTION
+#   This method returns the common ancestor (a FCM1::CmBranch object) of a
+#   specified $branch and the current branch. The argument $branch must be a
+#   FCM1::CmBranch object. Both the current branch and $branch are assumed to be
+#   in the same project.
+# ------------------------------------------------------------------------------
+
+sub ancestor {
+  my ($self, $branch) = @_;
+
+  if (not exists $self->{ANCESTOR}{$branch->url_peg}) {
+    if ($self->url_peg eq $branch->url_peg) {
+      $self->{ANCESTOR}{$branch->url_peg} = $self;
+
+    } else {
+      # Get family tree of current branch, from trunk to current branch
+      my @this_family = ($self);
+      while (not $this_family [0]->is_trunk) {
+        unshift @this_family, $this_family [0]->parent;
+      }
+
+      # Get family tree of $branch, from trunk to $branch
+      my @that_family = ($branch);
+      while (not $that_family [0]->is_trunk) {
+        unshift @that_family, $that_family [0]->parent;
+      }
+
+      # Find common ancestor from list of parents
+      my $ancestor = undef;
+
+      while (not $ancestor) {
+        # $this and $that should both start as some revisions on the trunk.
+        # Walk down a generation each time it loops around.
+        my $this = shift @this_family;
+        my $that = shift @that_family;
+
+        if ($this->url eq $that->url) {
+          if ($this->is_trunk or $this->create_rev eq $that->create_rev) {
+            # $this and $that are the same branch
+            if (@this_family and @that_family) {
+              # More generations in both branches, try comparing the next
+              # generations.
+              next;
+
+            } else {
+              # End of lineage in one of the branches, ancestor is at the lower
+              # revision of the current URL.
+              if ($this->pegrev and $that->pegrev) {
+                $ancestor = $this->pegrev < $that->pegrev ? $this : $that;
+
+              } else {
+                $ancestor = $this->pegrev ? $this : $that;
+              }
+            }
+
+          } else {
+            # Despite the same URL, $this and $that are different branches as
+            # they are created at different revisions. The ancestor must be the
+            # parent with the lower revision. (This should not occur at the
+            # start.)
+            $ancestor = $this->parent->pegrev < $that->parent->pegrev
+                        ? $this->parent : $that->parent;
+          }
+
+        } else {
+          # Different URLs, ancestor must be the parent with the lower revision.
+          # (This should not occur at the start.)
+          $ancestor = $this->parent->pegrev < $that->parent->pegrev
+                      ? $this->parent : $that->parent;
+        }
+      }
+
+      $self->{ANCESTOR}{$branch->url_peg} = $ancestor;
+    }
+  }
+
+  return $self->{ANCESTOR}{$branch->url_peg};
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   ($target, $upper, $lower) = $cm_branch->last_merge_from (
+#     $branch, $stop_on_copy,
+#   );
+#
+# DESCRIPTION
+#   This method returns a 3-element list with information of the last merge
+#   into the current branch from a specified $branch. The first element in the
+#   list $target (a FCM1::CmBranch object) is the target at which the merge was
+#   performed. (This can be the current branch or a parent branch up to the
+#   common ancestor with the specified $branch.) The second and third elements,
+#   $upper and $lower, (both FCM1::CmBranch objects), are the upper and lower
+#   ends of the source delta. If there is no merge from $branch into the
+#   current branch from their common ancestor to the current revision, this
+#   method will return an empty list. If $stop_on_copy is specified, it ignores
+#   merges from parents of $branch, and merges into parents of the current
+#   branch.
+# ------------------------------------------------------------------------------
+
+sub last_merge_from {
+  my ($self, $branch, $stop_on_copy) = @_;
+
+  if (not exists $self->{LAST_MERGE}{$branch->url_peg}) {
+    # Get "log" of current branch down to the common ancestor
+    my %log = $self->svnlog (
+      REV => [
+       ($self->pegrev ? $self->pegrev : 'HEAD'),
+       $self->ancestor ($branch)->pegrev,
+      ],
+
+      STOP_ON_COPY => $stop_on_copy,
+    );
+
+    my $cr = $self;
+
+    # Go down the revision log, checking for merge template messages
+    REV: for my $rev (sort {$b <=> $a} keys %log) {
+      # Loop each line of the log message at each revision
+      my @msg = split /\n/, $log{$rev}{msg};
+
+      # Also consider merges into parents of current branch
+      $cr = $cr->parent if ($cr->is_branch and $rev < $cr->create_rev);
+
+      for (@msg) {
+        # Ignore unless log message matches a merge template
+        next unless /Merged into \S+: (\S+) cf\. (\S+)/;
+
+        # Upper $1 and lower $2 ends of the source delta
+        my $u_path = $1;
+        my $l_path = $2;
+
+        # Add the root directory to the paths if necessary
+        $u_path = '/' . $u_path if substr ($u_path, 0, 1) ne '/';
+        $l_path = '/' . $l_path if substr ($l_path, 0, 1) ne '/';
+
+        # Only consider merges with specified branch (and its parent)
+        (my $path = $u_path) =~ s/@(\d+)$//;
+        my $u_rev = $1;
+
+        my $br = $branch;
+        $br    = $br->parent while (
+          $br->is_branch and $u_rev < $br->create_rev and not $stop_on_copy
+        );
+
+        next unless $br->branch_path eq $path;
+
+        # If $br is a parent of branch, ignore those merges with the parent
+        # above the branch point of the current branch
+        next if $br->pegrev and $br->pegrev < $u_rev;
+
+        # Set the return values
+        $self->{LAST_MERGE}{$branch->url_peg} = [
+          FCM1::CmBranch->new (URL => $cr->url . '@' . $rev), # target
+          FCM1::CmBranch->new (URL => $self->root . $u_path), # delta upper
+          FCM1::CmBranch->new (URL => $self->root . $l_path), # delta lower
+        ];
+
+        last REV;
+      }
+    }
+  }
+
+  return (exists $self->{LAST_MERGE}{$branch->url_peg}
+          ? @{ $self->{LAST_MERGE}{$branch->url_peg} } : ());
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @revs = $cm_branch->avail_merge_from ($branch[, $stop_on_copy]);
+#
+# DESCRIPTION
+#   This method returns a list of revisions of a specified $branch, which are
+#   available for merging into the current branch. If $stop_on_copy is
+#   specified, it will not list available merges from the parents of $branch.
+# ------------------------------------------------------------------------------
+
+sub avail_merge_from {
+  my ($self, $branch, $stop_on_copy) = @_;
+
+  if (not exists $self->{AVAIL_MERGE}{$branch->url_peg}) {
+    # Find out the revision of the upper delta at the last merge from $branch
+    # If no merge is found, use revision of common ancestor with $branch
+    my @last_merge = $self->last_merge_from ($branch);
+    my $rev        = $self->ancestor ($branch)->pegrev;
+    $rev           = $last_merge [1]->pegrev
+      if @last_merge and $last_merge [1]->pegrev > $rev;
+
+    # Get the "log" of the $branch down to $rev
+    my %log = $branch->svnlog (
+      REV          => [($branch->pegrev ? $branch->pegrev : 'HEAD'), $rev],
+      STOP_ON_COPY => $stop_on_copy,
+    );
+
+    # No need to include $rev itself, as it has already been merged
+    delete $log{$rev};
+
+    # No need to include the branch create revision
+    delete $log{$branch->create_rev}
+      if $branch->is_branch and exists $log{$branch->create_rev};
+
+    if (keys %log) {
+      # Check whether there is a latest merge from $self into $branch, if so,
+      # all revisions of $branch below that merge should become unavailable
+      my @last_merge_into = $branch->last_merge_from ($self);
+
+      if (@last_merge_into) {
+        for my $rev (keys %log) {
+          delete $log{$rev} if $rev < $last_merge_into [0]->pegrev;
+        }
+      }
+    }
+
+    # Available merges include all revisions above the branch creation revision
+    # or the revision of the last merge
+    $self->{AVAIL_MERGE}{$branch->url_peg} = [sort {$b <=> $a} keys %log];
+  }
+
+  return @{ $self->{AVAIL_MERGE}{$branch->url_peg} };
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $lower = $cm_branch->base_of_merge_from ($branch);
+#
+# DESCRIPTION
+#   This method returns the lower delta (a FCM1::CmBranch object) for the next
+#   merge from $branch.
+# ------------------------------------------------------------------------------
+
+sub base_of_merge_from {
+  my ($self, $branch) = @_;
+
+  # Base is the ancestor if there is no merge between $self and $branch
+  my $return = $self->ancestor ($branch);
+
+  # Get configuration for the last merge from $branch to $self
+  my @merge_from = $self->last_merge_from ($branch);
+
+  # Use the upper delta of the last merge from $branch, as all revisions below
+  # that have already been merged into the $self
+  $return = $merge_from [1]
+    if @merge_from and $merge_from [1]->pegrev > $return->pegrev;
+
+  # Get configuration for the last merge from $self to $branch
+  my @merge_into = $branch->last_merge_from ($self);
+
+  # Use the upper delta of the last merge from $self, as the current revision
+  # of $branch already contains changes of $self up to the peg revision of the
+  # upper delta
+  $return = $merge_into [1]
+    if @merge_into and $merge_into [0]->pegrev > $return->pegrev;
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flag = $cm_branch->allow_subdir_merge_from ($branch, $subdir);
+#
+# DESCRIPTION
+#   This method returns true if a merge from the sub-directory $subdir in
+#   $branch  is allowed - i.e. it does not result in losing changes made in
+#   $branch outside of $subdir.
+# ------------------------------------------------------------------------------
+
+sub allow_subdir_merge_from {
+  my ($self, $branch, $subdir) = @_;
+
+  # Get revision at last merge from $branch or ancestor
+  my @merge_from = $self->last_merge_from ($branch);
+  my $last       = @merge_from ? $merge_from [1] : $self->ancestor ($branch);
+  my $rev        = $last->pegrev;
+
+  my $return = 1;
+  if ($branch->pegrev > $rev) {
+    # Use "svn diff --summarize" to work out what's changed between last
+    # merge/ancestor and current revision
+    my $range = $branch->pegrev . ':' . $rev;
+    my @out = $SVN->stdout(
+        qw{svn diff --summarize -r}, $range, $branch->url_peg(),
+    );
+
+    # Returns false if there are changes outside of $subdir
+    my $url = join ('/', $branch->url, $subdir);
+    for my $line (@out) {
+      chomp $line;
+      $line = substr ($line, 7); # file name begins at column 7
+      if ($line !~ m#^$url(?:/|$)#) {
+        $return = 0;
+        last;
+      }
+    }
+  }
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $cm_branch->delete (
+#     [NON_INTERACTIVE     => 1,]
+#     [PASSWORD            => $password,]
+#     [SVN_NON_INTERACTIVE => 1,]
+#   );
+#
+# DESCRIPTION
+#   This method deletes the current branch from the Subversion repository.
+#
+# OPTIONS
+#   NON_INTERACTIVE     - Do no interactive prompting, set SVN_NON_INTERACTIVE
+#                         to true automatically.
+#   PASSWORD            - specify the password for commit access.
+#   SVN_NON_INTERACTIVE - Do no interactive prompting when running svn commit,
+#                         etc. This option is implied by NON_INTERACTIVE.
+# ------------------------------------------------------------------------------
+
+sub del {
+  my $self = shift;
+  my %args = @_;
+
+  # Options
+  # ----------------------------------------------------------------------------
+  my $password            = exists $args{PASSWORD} ? $args{PASSWORD} : undef;
+  my $non_interactive     = exists $args{NON_INTERACTIVE}
+                            ? $args{NON_INTERACTIVE} : 0;
+  my $svn_non_interactive = exists $args{SVN_NON_INTERACTIVE}
+                            ? $args{SVN_NON_INTERACTIVE} : 0;
+  $svn_non_interactive    = $non_interactive ? 1 : $svn_non_interactive;
+
+  # Ensure URL is a branch
+  # ----------------------------------------------------------------------------
+  e_report $self->url_peg, ': not a branch, abort.' if not $self->is_branch;
+
+  # Create a temporary file for the commit log message
+  my $temp_handle = $self->_commit_message(
+      sprintf("Deleted %s.\n", $self->branch_path()), 'D', $non_interactive,
+  );
+
+  # Check with the user to see if he/she wants to go ahead
+  # ----------------------------------------------------------------------------
+  if (not $non_interactive) {
+    my $mesg = '';
+    if (!$self->layout()->is_owned_by_user()) {
+      $mesg .= "\n";
+
+      if (exists $FCM1::CmUrl::owner_keywords{$self->branch_owner()}) {
+        my $type = $FCM1::CmUrl::owner_keywords{$self->branch_owner()};
+        $mesg .= '*** WARNING: YOU ARE DELETING A ' . uc ($type) .
+                 ' BRANCH.';
+
+      } else {
+        $mesg .= '*** WARNING: YOU ARE DELETING A BRANCH NOT OWNED BY YOU.';
+      }
+
+      $mesg .= "\n" .
+               '*** Please ensure that you have the owner\'s permission.' .
+               "\n\n";
+    }
+
+    $mesg   .= 'Would you like to go ahead and delete this branch?';
+
+    my $reply = FCM1::Interactive::get_input (
+      title   => 'fcm branch',
+      message => $mesg,
+      type    => 'yn',
+      default => 'n',
+    );
+
+    return unless $reply eq 'y';
+  }
+
+  # Delete branch if answer is "y" for "yes"
+  # ----------------------------------------------------------------------------
+  print 'Deleting branch ', $self->url, ' ...', "\n";
+  $SVN->call(
+    'delete',
+    '-F', $temp_handle->filename(),
+    (defined $password    ? ('--password', $password) : ()),
+    ($svn_non_interactive ? '--non-interactive'       : ()),
+    $self->url(),
+  );
+
+  return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $cm_branch->display_info (
+#     [SHOW_CHILDREN => 1],
+#     [SHOW_OTHER    => 1]
+#     [SHOW_SIBLINGS => 1]
+#   );
+#
+# DESCRIPTION
+#   This method displays information of the current branch. If SHOW_CHILDREN is
+#   set, it shows information of all current children branches of the current
+#   branch. If SHOW_SIBLINGS is set, it shows information of siblings that have
+#   been merged recently with the current branch. If SHOW_OTHER is set, it shows
+#   information of custom/reverse merges.
+# ------------------------------------------------------------------------------
+
+sub display_info {
+  my $self = shift;
+  my %args = @_;
+
+  # Arguments
+  # ----------------------------------------------------------------------------
+  my $show_children = exists $args{SHOW_CHILDREN} ? $args{SHOW_CHILDREN} : 0;
+  my $show_other    = exists $args{SHOW_OTHER   } ? $args{SHOW_OTHER}    : 0;
+  my $show_siblings = exists $args{SHOW_SIBLINGS} ? $args{SHOW_SIBLINGS} : 0;
+
+  # Useful variables
+  # ----------------------------------------------------------------------------
+  my $separator  = '-' x 80 . "\n";
+  my $separator2 = '  ' . '-' x 78 . "\n";
+
+  # Print "info" as returned by "svn info"
+  # ----------------------------------------------------------------------------
+  for (
+    ['URL',                 'url'            ],
+    ['Repository Root',     'repository:root'],
+    ['Revision',            'revision'       ],
+    ['Last Changed Author', 'commit:author'  ],
+    ['Last Changed Rev',    'commit:revision'],
+    ['Last Changed Date',   'commit:date'    ],
+  ) {
+    my ($key, $flag) = @{$_};
+    if ($self->svninfo(FLAG => $flag)) {
+      printf("%s: %s\n", $key, $self->svninfo(FLAG => $flag));
+    }
+  }
+
+  if ($self->config->verbose) {
+    # Verbose mode, print log message at last changed revision
+    my %log = $self->svnlog (REV => $self->svninfo(FLAG => 'commit:revision'));
+    my @log = split /\n/, $log{msg};
+    print 'Last Changed Log:', "\n\n", map ({'  ' . $_ . "\n"} @log), "\n";
+  }
+
+  if ($self->is_branch) {
+    # Print create information
+    # --------------------------------------------------------------------------
+    my %log = $self->svnlog (REV => $self->create_rev);
+
+    print $separator;
+    print 'Branch Create Author: ', $log{author}, "\n" if $log{author};
+    print 'Branch Create Rev: ', $self->create_rev, "\n";
+    print 'Branch Create Date: ', &svn_date ($log{date}), "\n";
+
+    if ($self->config->verbose) {
+      # Verbose mode, print log message at last create revision
+      my @log = split /\n/, $log{msg};
+      print 'Branch Create Log:', "\n\n", map ({'  ' . $_ . "\n"} @log), "\n";
+    }
+
+    # Print delete information if branch no longer exists
+    # --------------------------------------------------------------------------
+    print 'Branch Delete Rev: ', $self->delete_rev, "\n" if $self->delete_rev;
+
+    # Report merges into/from the parent
+    # --------------------------------------------------------------------------
+    # Print the URL at REV of the parent branch
+    print $separator, 'Branch Parent: ', $self->parent->url_peg, "\n";
+
+    # Set up a new object for the parent at the current revision
+    # --------------------------------------------------------------------------
+    my $p_url  = $self->parent->url;
+    $p_url    .= '@' . $self->pegrev if $self->pegrev;
+    my $parent = FCM1::CmBranch->new (URL => $p_url);
+
+    if (not $parent->url_exists) {
+      print 'Branch parent deleted.', "\n";
+      return;
+    }
+
+    # Report merges into/from the parent
+    # --------------------------------------------------------------------------
+    print $self->_report_merges ($parent, 'Parent');
+  }
+
+  # Report merges with siblings
+  # ----------------------------------------------------------------------------
+  if ($show_siblings) {
+    # Report number of sibling branches found
+    print $separator, 'Searching for siblings ... ';
+    my @siblings = $self->siblings;
+    print scalar (@siblings), ' ', (@siblings> 1 ? 'siblings' : 'sibling'),
+          ' found.', "\n";
+
+    # Report branch name and merge information only if there are recent merges
+    my $out = '';
+    for my $sibling (@siblings) {
+      my $string = $self->_report_merges ($sibling, 'Sibling');
+
+      $out .= $separator2 . '  ' . $sibling->url . "\n" . $string if $string;
+    }
+
+    if (@siblings) {
+      if ($out) {
+        print 'Merges with existing siblings:', "\n", $out;
+
+      } else {
+        print 'No merges with existing siblings.', "\n";
+      }
+    }
+  }
+
+  # Report children
+  # ----------------------------------------------------------------------------
+  if ($show_children) {
+    # Report number of child branches found
+    print $separator, 'Searching for children ... ';
+    my @children = $self->children;
+    print scalar (@children), ' ', (@children > 1 ? 'children' : 'child'),
+          ' found.', "\n";
+
+    # Report children if they exist
+    print 'Current children:', "\n" if @children;
+
+    for my $child (@children) {
+      print $separator2, '  ', $child->url, "\n";
+      print '  Child Create Rev: ', $child->create_rev, "\n";
+      print $self->_report_merges ($child, 'Child');
+    }
+  }
+
+  # Report custom/reverse merges into the branch
+  # ----------------------------------------------------------------------------
+  if ($show_other) {
+    my %log = $self->svnlog (STOP_ON_COPY => 1);
+    my @out;
+
+    # Go down the revision log, checking for merge template messages
+    REV: for my $rev (sort {$b <=> $a} keys %log) {
+      # Loop each line of the log message at each revision
+      my @msg = split /\n/, $log{$rev}{msg};
+
+      for (@msg) {
+        # Ignore unless log message matches a merge template
+        if (/^Reversed r\d+(:\d+)? of \S+$/ or
+            s/^(Custom merge) into \S+(:.+)$/$1$2/) {
+          push @out, ('r' . $rev . ': ' . $_) . "\n";
+        }
+      }
+    }
+
+    print $separator, 'Other merges:', "\n", @out if @out;
+  }
+
+  return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $self->_report_merges ($branch, $relation);
+#
+# DESCRIPTION
+#   This method returns a string for displaying merge information with a
+#   branch, the $relation of which can be a Parent, a Sibling or a Child.
+# ------------------------------------------------------------------------------
+
+sub _report_merges {
+  my ($self, $branch, $relation) = @_;
+
+  my $indent    = ($relation eq 'Parent') ? '' : '  ';
+  my $separator = ($relation eq 'Parent') ? ('-' x 80) : ('  ' . '-' x 78);
+  $separator   .= "\n";
+
+  my $return = '';
+
+  # Report last merges into/from the $branch
+  # ----------------------------------------------------------------------------
+  my %merge  = (
+    'Last Merge From ' . $relation . ':'
+    => [$self->last_merge_from ($branch, 1)],
+    'Last Merge Into ' . $relation . ':'
+    => [$branch->last_merge_from ($self, 1)],
+  );
+
+  if ($self->config->verbose) {
+    # Verbose mode, print the log of the merge
+    for my $key (keys %merge) {
+      next if not @{ $merge{$key} };
+
+      # From: target (0) is self, upper delta (1) is $branch
+      # Into: target (0) is $branch, upper delta (1) is self
+      my $t = ($key =~ /From/) ? $self : $branch;
+
+      $return .= $indent . $key . "\n";
+      $return .= $separator . $t->display_svnlog ($merge{$key}[0]->pegrev);
+    }
+
+  } else {
+    # Normal mode, print in simplified form (rREV Parent at REV)
+    for my $key (keys %merge) {
+      next if not @{ $merge{$key} };
+
+      # From: target (0) is self, upper delta (1) is $branch
+      # Into: target (0) is $branch, upper delta (1) is self
+      $return .= $indent . $key . ' r' . $merge{$key}[0]->pegrev . ' ' .
+                 $merge{$key}[1]->path_peg . ' cf. ' .
+                 $merge{$key}[2]->path_peg . "\n";
+    }
+  }
+
+  if ($relation eq 'Sibling') {
+    # For sibling, do not report further if there is no recent merge
+    my @values = values %merge;
+
+    return $return unless (@{ $values[0] } or @{ $values[1] });
+  }
+
+  # Report available merges into/from the $branch
+  # ----------------------------------------------------------------------------
+  my %avail = (
+    'Merges Avail From ' . $relation . ':'
+    => ($self->delete_rev ? [] : [$self->avail_merge_from ($branch, 1)]),
+    'Merges Avail Into ' . $relation . ':'
+    => [$branch->avail_merge_from ($self, 1)],
+  );
+
+  if ($self->config->verbose) {
+    # Verbose mode, print the log of each revision
+    for my $key (keys %avail) {
+      next unless @{ $avail{$key} };
+
+      $return .= $indent . $key . "\n";
+
+      my $s = ($key =~ /From/) ? $branch: $self;
+
+      for my $rev (@{ $avail{$key} }) {
+        $return .= $separator . $s->display_svnlog ($rev);
+      }
+    }
+
+  } else {
+    # Normal mode, print only the revisions
+    for my $key (keys %avail) {
+      next unless @{ $avail{$key} };
+
+      $return .= $indent . $key . ' ' . join (' ', @{ $avail{$key} }) . "\n";
+    }
+  }
+
+  return $return;
+}
+
+# Returns a File::Temp object containing the commit log for create/del.
+sub _commit_message {
+    my ($self, $message, $action, $non_interactive) = @_;
+    my $commit_message_ctx = $COMMIT_MESSAGE_UTIL->ctx();
+    $commit_message_ctx->set_auto_part($message);
+    $commit_message_ctx->set_info_part(
+        sprintf("%s    %s\n", $action, $self->url())
+    );
+    if (!$non_interactive) {
+        $COMMIT_MESSAGE_UTIL->edit($commit_message_ctx);
+    }
+    $COMMIT_MESSAGE_UTIL->notify($commit_message_ctx);
+    $COMMIT_MESSAGE_UTIL->temp($commit_message_ctx);
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/CmUrl.pm b/lib/FCM1/CmUrl.pm
new file mode 100644
index 0000000..67bddc0
--- /dev/null
+++ b/lib/FCM1/CmUrl.pm
@@ -0,0 +1,639 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::CmUrl
+#
+# DESCRIPTION
+#   This class contains methods for manipulating a Subversion URL in a standard
+#   FCM project.
+#
+# ------------------------------------------------------------------------------
+
+package FCM1::CmUrl;
+use base qw{FCM1::Base};
+
+use strict;
+use warnings;
+
+use FCM::System::Exception;
+use FCM1::Keyword;
+use FCM1::Util qw/svn_date/;
+
+# Special branches
+our %owner_keywords = (Share => 'shared', Config => 'config', Rel => 'release');
+
+# Revision pattern
+my $rev_pattern = '\d+|HEAD|BASE|COMMITTED|PREV|\{.+\}';
+
+my $E = 'FCM::System::Exception';
+
+# "svn log --xml" handlers.
+# -> element node start tag handlers
+my %SVN_LOG_ELEMENT_0_HANDLER_FOR = (
+#   tag        => handler
+    'logentry' => \&_svn_log_handle_element_0_logentry,
+    'path'     => \&_svn_log_handle_element_0_path,
+);
+# -> text node (after a start tag) handlers
+my %SVN_LOG_TEXT_HANDLER_FOR = (
+#   tag    => handler
+    'date' => \&_svn_log_handle_text_date,
+    'path' => \&_svn_log_handle_text_path,
+);
+
+# Set the SVN utility provided by FCM::System::CM.
+our $SVN;
+sub set_svn_util {
+    $SVN = shift();
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $cm_url = FCM1::CmUrl->new ([URL => $url,]);
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::CmUrl class.
+#
+# ARGUMENTS
+#   URL - URL of a branch
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  my $self = FCM1::Base->new (%args);
+
+  $self->{URL} = (exists $args{URL} ? $args{URL} : '');
+
+  for (qw/LAYOUT BRANCH_LIST INFO LIST LOG LOG_RANGE RLIST/) {
+    $self->{$_} = undef;
+  }
+
+  bless $self, $class;
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $url = $cm_url->url_peg;
+#   $cm_url->url_peg ($url);
+#
+# DESCRIPTION
+#   This method returns/sets the current URL at PEG.
+# ------------------------------------------------------------------------------
+
+sub url_peg {
+  my $self = shift;
+
+  if (@_) {
+    if (! $self->{URL} or $_[0] ne $self->{URL}) {
+      # Re-set URL
+      $self->{URL} = shift;
+
+      # Re-set essential variables
+      $self->{$_}  = undef for (qw/LAYOUT RLIST LIST INFO LOG LOG_RANGE/);
+    }
+  }
+
+  return $self->{URL};
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flag = $cm_url->is_url ();
+#
+# DESCRIPTION
+#   Returns true if current url is a valid Subversion URL.
+# ------------------------------------------------------------------------------
+
+sub is_url {
+  my $self = shift;
+
+  # This should handle URL beginning with svn://, http:// and svn+ssh://
+  return ($self->url_peg =~ qr{^[\w\+\-]+://}msx);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flag = $cm_url->url_exists ([$rev]);
+#
+# DESCRIPTION
+#   Returns true if current url exists (at operative revision $rev) in a
+#   Subversion repository.
+# ------------------------------------------------------------------------------
+
+sub url_exists {
+  my ($self, $rev) = @_;
+
+  my $url = eval {$self->svninfo(FLAG => 'url', REV => $rev)};
+  if ($@) {
+    $@ = undef;
+  }
+
+  defined($url);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $cm_url->svninfo([FLAG => $flag], [REV => $rev]);
+#
+# DESCRIPTION
+#   Returns the value of $flag, where $flag is a field returned by "svn info
+#   --xml". The original hierarchy below the entry element is delimited by a
+#   colon in the name. (If $flag is not set, default to "url".) If REV is
+#   specified, it will be used as the operative revision.
+# ------------------------------------------------------------------------------
+
+sub svninfo {
+  my ($self, %args) = @_;
+  if (!$self->is_url()) {
+    return;
+  }
+  my $flag = exists($args{FLAG}) ? $args{FLAG} : 'url';
+  my $rev  = exists($args{REV})  ? $args{REV}  : undef;
+  $rev ||= ($self->pegrev ? $self->pegrev : 'HEAD');
+  # Get "info" for the specified revision if necessary
+  if (!exists($self->{INFO}{$rev})) {
+    $self->{INFO}{$rev}
+      = $SVN->get_info({'revision' => $rev}, $self->url_peg())->[0];
+  }
+  exists($self->{INFO}{$rev}{$flag}) ? $self->{INFO}{$rev}{$flag} : undef;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   %logs = $cm_url->svnlog (
+#     [REV          => $rev,]
+#     [REV          => \@revs,] # reference to a 2-element array
+#     [STOP_ON_COPY => 1,]
+#   );
+#
+# DESCRIPTION
+#   Returns the logs for the current URL. If REV is a range of revisions or not
+#   specified, return a hash where the keys are revision numbers and the values
+#   are the entries (which are hash references). If a single REV is specified,
+#   return the entry (a hash reference) at the specified REV. Each entry in the
+#   returned list is a hash reference, with the following structure:
+#
+#   $entry = {
+#     author => $author,              # the commit author
+#     date   => $date,                # the commit date (in seconds since epoch)
+#     msg    => $msg,                 # the log message
+#     paths  => {                     # list of changed paths
+#       $path1  => {                  # a changed path
+#         copyfrom-path => $frompath, # copy-from-path
+#         copyfrom-rev  => $fromrev,  # copy-from-revision
+#         action        => $action,   # action status code
+#       },
+#       ...     => { ... },           # ... more changed paths ...
+#     },
+#   }
+# ------------------------------------------------------------------------------
+
+sub svnlog {
+  my $self = shift;
+  my %args = @_;
+
+  my $stop_on_copy  = exists $args{STOP_ON_COPY} ? $args{STOP_ON_COPY} : undef;
+  my $rev_arg       = exists $args{REV}          ? $args{REV}          : 0;
+
+  my @revs;
+
+  # Get revision options
+  # ----------------------------------------------------------------------------
+  if ($rev_arg) {
+    if (ref ($rev_arg)) {
+      # Revision option is an array, a range of revisions specified?
+      ($revs [0], $revs [1]) = @$rev_arg;
+
+    } else {
+      # A single revision specified
+      $revs [0] = $rev_arg;
+    }
+
+    # Expand 'HEAD' revision
+    for my $rev (@revs) {
+      next unless uc ($rev) eq 'HEAD';
+      $rev = $self->svninfo(FLAG => 'revision', REV => 'HEAD');
+    }
+
+  } else {
+    # No revision option specified, get log for all revisions
+    $revs [0] = $self->svninfo(FLAG => 'revision');
+    $revs [1] = 1;
+  }
+
+  $revs [1] = $revs [0] if not $revs [1];
+  @revs     = sort {$b <=> $a} @revs;
+
+  # Check whether a "svn log" run is necessary
+  # ----------------------------------------------------------------------------
+  my $need_update = ! ($revs [0] == $revs [1] and exists $self->{LOG}{$revs [0]});
+  my @ranges      = @revs;
+  if ($need_update and $self->{LOG_RANGE}) {
+    my %log_range = %{ $self->{LOG_RANGE} };
+
+    if ($stop_on_copy) {
+      $ranges [1] = $log_range{UPPER} if $ranges [1] >= $log_range{LOWER_SOC};
+
+    } else {
+      $ranges [1] = $log_range{UPPER} if $ranges [1] >= $log_range{LOWER};
+    }
+  }
+
+  $need_update = 0 if $ranges [0] < $ranges [1];
+
+  if ($need_update) {
+    my @entries = @{$SVN->get_log(
+      {'revision' => join(':', @ranges), 'stop-on-copy' => $stop_on_copy},
+      $self->url_peg(),
+    )};
+    for my $entry (@entries) {
+      $self->{LOG}{$entry->{revision}} = $entry;
+      $entry->{paths} = {map {($_->{path} => $_)} @{$entry->{paths}}};
+    }
+
+    # Update the range cache
+    # --------------------------------------------------------------------------
+    # Upper end of the range
+    $self->{LOG_RANGE}{UPPER} = $ranges [0]
+      if ! $self->{LOG_RANGE}{UPPER} or $ranges [0] > $self->{LOG_RANGE}{UPPER};
+
+    # Lower end of the range, need to take into account the stop-on-copy option
+    if ($stop_on_copy) {
+      # Lower end of the range with stop-on-copy option
+      $self->{LOG_RANGE}{LOWER_SOC} = $ranges [1]
+        if ! $self->{LOG_RANGE}{LOWER_SOC} or
+           $ranges [1] < $self->{LOG_RANGE}{LOWER_SOC};
+
+      my $low = (sort {$a <=> $b} keys %{ $self->{LOG} }) [0];
+      $self->{LOG_RANGE}{LOWER} = $low
+        if ! $self->{LOG_RANGE}{LOWER} or $low < $self->{LOG_RANGE}{LOWER};
+
+    } else {
+      # Lower end of the range without the stop-on-copy option
+      $self->{LOG_RANGE}{LOWER} = $ranges [1]
+        if ! $self->{LOG_RANGE}{LOWER} or
+           $ranges [1] < $self->{LOG_RANGE}{LOWER};
+
+      $self->{LOG_RANGE}{LOWER_SOC} = $ranges [1]
+        if ! $self->{LOG_RANGE}{LOWER_SOC} or
+           $ranges [1] < $self->{LOG_RANGE}{LOWER_SOC};
+    }
+  }
+
+  my %return = ();
+
+  if (! $rev_arg or ref ($rev_arg)) {
+    # REV is an array, return log entries if they are within range
+    for my $rev (sort {$b <=> $a} keys %{ $self->{LOG} }) {
+      next if $rev > $revs [0] or $revs [1] > $rev;
+
+      $return{$rev} = $self->{LOG}{$rev};
+
+      if ($stop_on_copy) {
+        last if exists $self->{LOG}{$rev}{paths}{$self->branch_path} and
+           $self->{LOG}{$rev}{paths}{$self->branch_path}{action} eq 'A';
+      }
+    }
+
+  } else {
+    # REV is a scalar, return log of the specified revision if it exists
+    %return = %{ $self->{LOG}{$revs [0]} } if exists $self->{LOG}{$revs [0]};
+  }
+
+  return %return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $cm_branch->display_svnlog ($rev, [$wiki]);
+#
+# DESCRIPTION
+#   This method returns a string for displaying the log of the current branch
+#   at a $rev. If $wiki is set, returns a string for displaying in a Trac wiki
+#   table.  The value of $wiki should be the Subversion URL of a FCM project
+#   associated with the intended Trac system.
+# ------------------------------------------------------------------------------
+
+sub display_svnlog {
+  my ($self, $rev, $wiki) = @_;
+  my $return = '';
+
+  my %log = $self->svnlog (REV => $rev);
+
+  if ($wiki) {
+    # Output in Trac wiki format
+    # --------------------------------------------------------------------------
+    $return .= '|| ' . &svn_date ($log{date}) . ' || ' . $log{author} . ' || ';
+
+    my $trac_url = FCM1::Keyword::get_browser_url($self->url);
+
+    # Get list of tickets from log
+    my @tickets;
+    while ($log{msg} =~ /(?:(\w+):)?(?:#|ticket:)(\d+)/g) {
+      push @tickets, [$1, $2];
+    }
+    @tickets = sort {
+      if ($a->[0] and $b->[0]) {
+        $a->[0] cmp $b->[0] or $a->[1] <=> $b->[1];
+
+      } elsif ($a->[0]) {
+        1;
+
+      } else {
+        $a->[1] <=> $b->[1];
+      }
+    } @tickets;
+
+    if ($trac_url =~ qr{^$wiki(?:/*|$)}msx) {
+      # URL is in the specified $wiki, use Trac link
+      $return .= '[' . $rev . '] ||';
+
+      for my $ticket (@tickets) {
+        $return .= ' ';
+        $return .= $ticket->[0] . ':' if $ticket->[0];
+        $return .= '#' . $ticket->[1];
+      }
+
+      $return .= ' ||';
+
+    } else {
+      # URL is not in the specified $wiki, use full URL
+      my $rev_url = $trac_url;
+      $rev_url    =~ s{/intertrac/source:.*\z}{/intertrac/changeset:$rev}xms;
+      $return    .= '[' . $rev_url . ' ' . $rev . '] ||';
+
+      my $ticket_url = $trac_url;
+      $ticket_url    =~ s{/intertrac/source:.*\z}{/intertrac/}xms;
+
+      for my $ticket (@tickets) {
+        $return .= ' [' . $ticket_url;
+        $return .= $ticket->[0] . ':' if $ticket->[0];
+        $return .= 'ticket:' . $ticket->[1] . ' ' . $ticket->[1] . ']';
+      }
+
+      $return .= ' ||';
+    }
+
+  } else {
+    # Output in plain text format
+    # --------------------------------------------------------------------------
+    my @msg  = split /\n/, $log{msg};
+    my $line = (@msg > 1 ? ' lines' : ' line');
+
+    $return .= join (
+      ' | ',
+      ('r' . $rev, $log{author}, &svn_date ($log{date}), scalar (@msg) . $line),
+    );
+    $return .= "\n\n";
+    $return .= $log{msg};
+  }
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @list = $cm_url->branch_list ($rev);
+#
+# DESCRIPTION
+#   The method returns a list of branches in the current project, assuming the
+#   FCM naming convention. If $rev if specified, it returns the list of
+#   branches at $rev.
+# ------------------------------------------------------------------------------
+
+sub branch_list {
+  my ($self, $rev) = @_;
+  if (!defined($self->project())) {
+    return;
+  }
+  $rev = $self->svninfo(FLAG => 'revision', REV => $rev);
+  if (!exists($self->{BRANCH_LIST}{$rev})) {
+    my %layout_config = %{$self->layout()->get_config()};
+    my $url0 = $self->project_url();
+    my @d1_filters = ();
+    if ($layout_config{'dir-branch'}) {
+      $url0 .= '/' . $layout_config{'dir-branch'};
+    }
+    else {
+      for my $key (qw{trunk tag}) {
+        if ($layout_config{"dir-$key"}) {
+          push(@d1_filters, $layout_config{"dir-$key"});
+        }
+      }
+    }
+    $self->{BRANCH_LIST}{$rev} = [$SVN->get_list(
+      $url0 . '@' . $self->pegrev(),
+      sub {
+        my ($this_url, $this_name, $is_dir, $depth) = @_;
+        if ($depth == 1 && @d1_filters && grep {$this_name eq $_} @d1_filters) {
+          return (0, 0);
+        }
+        my $can_return = $depth >= $layout_config{'depth-branch'};
+        ($can_return, ($is_dir && !$can_return));
+      },
+    )];
+  }
+  @{$self->{BRANCH_LIST}{$rev}};
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $layout = $self->layout();
+#
+# DESCRIPTION
+#   Wrap FCM::System::CM::SVN->get_layout($url).
+# ------------------------------------------------------------------------------
+
+sub layout {
+  my ($self) = @_;
+  if (defined($self->{LAYOUT})) {
+    return $self->{LAYOUT};
+  }
+  my $url = $self->url_peg();
+  my $layout = $SVN->get_layout($url);
+  $self->{URL} = $layout->get_url();
+  $self->{LAYOUT} = $layout;
+
+  $layout;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $url = $cm_url->url();
+#   $url = $cm_url->pegrev();
+#   $url = $cm_url->root();
+#   $url = $cm_url->path();
+#   $url = $cm_url->path_peg();
+#
+# DESCRIPTION
+#   Return the relevant part of the current URL. The url method returns the URL
+#   without the peg revision. The pegrev method returns the peg revision. The
+#   root method returns the repository root. The path method returns the path in
+#   URL under root. The path_peg method returns the path in URL with a peg
+#   revision.
+# ------------------------------------------------------------------------------
+
+sub url {
+  my $layout = $_[0]->layout();
+  $layout->get_root() . $layout->get_path();
+}
+
+sub pegrev {
+  $_[0]->layout()->get_peg_rev();
+}
+
+sub root {
+  $_[0]->layout()->get_root();
+}
+
+sub path {
+  $_[0]->layout()->get_path();
+}
+
+sub path_peg {
+  my $layout = $_[0]->layout();
+  $layout->get_path() . '@' . $layout->get_peg_rev();
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $url = $cm_url->project_url_peg();
+#   $url = $cm_url->project_url();
+#   $url = $cm_url->project_path();
+#   $url = $cm_url->project();
+#   $url = $cm_url->branch_url();
+#   $url = $cm_url->branch_url_peg();
+#   $url = $cm_url->branch_path();
+#   $url = $cm_url->branch();
+#   $url = $cm_url->subdir();
+#
+# DESCRIPTION
+#   Return the relevant part of the current URL. The "project_*" methods return
+#   the "project" part. The "branch_*" methods return the "branch" part.
+#   The "*_url_peg" methods return the URL at PEG, and the "*_url" methods return
+#   the URL without the peg revision. The "*_path" methods return the path in
+#   the URL under the root.
+# ------------------------------------------------------------------------------
+
+sub project_url_peg {
+  my $layout = $_[0]->layout();
+  if (!defined($layout->get_project())) {
+    return;
+  }
+  my $path = $layout->get_project() ? '/' . $layout->get_project() : q{};
+  $layout->get_root() . $path . '@' . $layout->get_peg_rev();
+}
+
+sub project_url {
+  my $layout = $_[0]->layout();
+  if (!defined($layout->get_project())) {
+    return;
+  }
+  my $path = $layout->get_project() ? '/' . $layout->get_project() : q{};
+  $layout->get_root() . $path;
+}
+
+sub project_path {
+  my $layout = $_[0]->layout();
+  if (!defined($layout->get_project())) {
+    return;
+  }
+  '/' . $layout->get_project();
+}
+
+sub project {
+  $_[0]->layout()->get_project();
+}
+
+sub branch_url_peg {
+  my $layout = $_[0]->layout();
+  if (!$layout->get_branch()) {
+    return;
+  }
+  $_[0]->project_url() . '/' . $layout->get_branch()
+    . '@' . $layout->get_peg_rev();
+}
+
+sub branch_url {
+  my $layout = $_[0]->layout();
+  if (!$layout->get_branch()) {
+    return;
+  }
+  $_[0]->project_url() . '/' . $layout->get_branch();
+}
+
+sub branch_path {
+  my $layout = $_[0]->layout();
+  if (!$layout->get_branch()) {
+    return;
+  }
+  ($_[0]->project() ? '/' . $_[0]->project() : q{}) . '/' . $layout->get_branch();
+}
+
+sub branch {
+  $_[0]->layout()->get_branch();
+}
+
+sub subdir {
+  $_[0]->layout()->get_sub_tree();
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $obj->branch_owner();
+#
+# DESCRIPTION
+#   This method returns the owner of the branch (based on the default layout).
+# ------------------------------------------------------------------------------
+
+sub branch_owner {
+  $_[0]->layout()->get_branch_owner();
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flag = $cm_url->is_trunk();
+#   $flag = $cm_url->is_branch();
+#   $flag = $cm_url->is_tag();
+#
+# DESCRIPTION
+#   Return true if the branch of current URL belongs to a given category (i.e.
+#   trunk, branch or tag).
+# ------------------------------------------------------------------------------
+
+sub is_trunk {
+  $_[0]->layout()->is_trunk();
+}
+
+sub is_branch {
+  $_[0]->layout()->is_branch();
+}
+
+sub is_tag {
+  $_[0]->layout()->is_tag();
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+__END__
diff --git a/lib/FCM1/Config.pm b/lib/FCM1/Config.pm
new file mode 100644
index 0000000..35dc3c3
--- /dev/null
+++ b/lib/FCM1/Config.pm
@@ -0,0 +1,898 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::Config
+#
+# DESCRIPTION
+#   This is a class for reading and processing central and user configuration
+#   settings for FCM.
+#
+# ------------------------------------------------------------------------------
+
+package FCM1::Config;
+
+# Standard pragma
+use warnings;
+use strict;
+
+# Standard modules
+use File::Basename;
+use File::Spec::Functions;
+use FindBin;
+use POSIX qw/setlocale LC_ALL/;
+
+# FCM component modules
+use FCM1::CfgFile;
+
+# Other declarations:
+sub _get_hash_value;
+
+# Delimiter for setting and for list
+our $DELIMITER         = '::';
+our $DELIMITER_PATTERN = qr{::|/};
+our $DELIMITER_LIST    = ',';
+
+my $INSTANCE;
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $config = FCM1::Config->instance();
+#
+# DESCRIPTION
+#   Returns an instance of this class.
+# ------------------------------------------------------------------------------
+
+sub instance {
+    my ($class) = @_;
+    if (!defined($INSTANCE)) {
+        $INSTANCE = $class->new();
+        $INSTANCE->get_config();
+        $INSTANCE->is_initialising(0);
+    }
+    return $INSTANCE;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::Config->new (VERBOSE => $verbose);
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::Config class.
+#
+# ARGUMENTS
+#   VERBOSE - Set the verbose level of diagnostic output
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  # Ensure that all subsequent Subversion output is in UK English
+  if (setlocale (LC_ALL, 'en_GB')) {
+    $ENV{LANG} = 'en_GB';
+  }
+
+  my $self = {
+    initialising   => 1,
+    central_config => undef,
+    user_config    => undef,
+    user_id        => undef,
+    verbose        => exists $args{VERBOSE} ? $args{VERBOSE} : undef,
+    variable       => {},
+
+    # Primary settings
+    setting => {
+      # Fortran BLOCKDATA dependencies
+      BLD_BLOCKDATA => {},
+
+      # Copy dummy target
+      BLD_CPDUMMY => '$(FCM_DONEDIR)/FCM_CP.dummy',
+
+      # No dependency check
+      BLD_DEP_N => {},
+
+      # Additional (PP) dependencies
+      BLD_DEP => {},
+      BLD_DEP_PP => {},
+
+      # Excluded dependency
+      BLD_DEP_EXCL => {
+        '' => [
+          # Fortran intrinsic modules
+          'USE' . $DELIMITER . 'ISO_C_BINDING',
+          'USE' . $DELIMITER . 'IEEE_EXCEPTIONS',
+          'USE' . $DELIMITER . 'IEEE_ARITHMETIC',
+          'USE' . $DELIMITER . 'IEEE_FEATURES',
+
+          # Fortran intrinsic subroutines
+          'OBJ' . $DELIMITER . 'CPU_TIME',
+          'OBJ' . $DELIMITER . 'GET_COMMAND',
+          'OBJ' . $DELIMITER . 'GET_COMMAND_ARGUMENT',
+          'OBJ' . $DELIMITER . 'GET_ENVIRONMENT_VARIABLE',
+          'OBJ' . $DELIMITER . 'MOVE_ALLOC',
+          'OBJ' . $DELIMITER . 'MVBITS',
+          'OBJ' . $DELIMITER . 'RANDOM_NUMBER',
+          'OBJ' . $DELIMITER . 'RANDOM_SEED',
+          'OBJ' . $DELIMITER . 'SYSTEM_CLOCK',
+
+          # Dummy statements
+          'OBJ' . $DELIMITER . 'NONE',
+          'EXE' . $DELIMITER . 'NONE',
+        ],
+      },
+
+      # Extra executable dependencies
+      BLD_DEP_EXE => {},
+
+      # Dependency pattern for each type
+      BLD_DEP_PATTERN => {
+        H         => q/^#\s*include\s*['"](\S+)['"]/,
+        USE       => q/^\s*use\s+(\w+)/,
+        INTERFACE => q/^#?\s*include\s+['"](\S+##OUTFILE_EXT/ . $DELIMITER .
+                     q/INTERFACE##)['"]/,
+        INC       => q/^\s*include\s+['"](\S+)['"]/,
+        OBJ       => q#^\s*(?:/\*|!)\s*depends\s*on\s*:\s*(\S+)#,
+        EXE       => q/^\s*(?:#|;)\s*(?:calls|list|if|interface)\s*:\s*(\S+)/,
+      },
+
+      # Rename main program targets
+      BLD_EXE_NAME => {},
+
+      # Rename library targets
+      BLD_LIB => {'' => 'fcm_default'},
+
+      # Name of Makefile and run environment shell script
+      BLD_MISC => {
+        'BLDMAKEFILE' => 'Makefile',
+        'BLDRUNENVSH' => 'fcm_env.sh',
+      },
+
+      # PP flags
+      BLD_PP => {},
+
+      # Custom source file type
+      BLD_TYPE => {},
+
+      # Types that always need to be built
+      BLD_TYPE_ALWAYS_BUILD =>                   'PVWAVE' .
+                               $DELIMITER_LIST . 'GENLIST' .
+                               $DELIMITER_LIST . 'SQL',
+
+      # Dependency scan types
+      BLD_TYPE_DEP => {
+        FORTRAN =>              'USE' .
+                   $DELIMITER . 'INTERFACE' .
+                   $DELIMITER . 'INC' .
+                   $DELIMITER . 'OBJ',
+        FPP     =>              'USE' .
+                   $DELIMITER . 'INTERFACE' .
+                   $DELIMITER . 'INC' .
+                   $DELIMITER . 'H' .
+                   $DELIMITER . 'OBJ',
+        CPP     =>              'H' .
+                   $DELIMITER . 'OBJ',
+        C       =>              'H' .
+                   $DELIMITER . 'OBJ',
+        SCRIPT  =>              'EXE',
+      },
+
+      # Dependency scan types for pre-processing
+      BLD_TYPE_DEP_PP => {
+        FPP => 'H',
+        CPP => 'H',
+        C   => 'H',
+      },
+
+      # Types that cannot have duplicated targets
+      BLD_TYPE_NO_DUPLICATED_TARGET => '',
+
+      # BLD_VPATH, each value must be a comma separate list
+      # ''     translates to %
+      # 'FLAG' translates to {OUTFILE_EXT}{FLAG}
+      BLD_VPATH   => {
+        BIN   => q{},
+        ETC   => 'ETC',
+        DONE  => join($DELIMITER_LIST, qw{DONE IDONE}),
+        FLAGS => 'FLAGS',
+        INC   => q{},
+        LIB   => 'LIB',
+        OBJ   => 'OBJ',
+      },
+
+      # Cache basename
+      CACHE          => '.config',
+      CACHE_DEP      => '.config_dep',
+      CACHE_DEP_PP   => '.config_dep_pp',
+      CACHE_FILE_SRC => '.config_file_src',
+
+      # Types of "inc" statements expandable CFG files
+      CFG_EXP_INC =>                   'BLD' .
+                     $DELIMITER_LIST . 'EXT' .
+                     $DELIMITER_LIST . 'FCM',
+
+      # Configuration file labels that can be declared more than once
+      CFG_KEYWORD =>                   'USE' .
+                     $DELIMITER_LIST . 'INC' .
+                     $DELIMITER_LIST . 'TARGET' .
+                     $DELIMITER_LIST . 'BLD_DEP_EXCL',
+
+      # Labels for all types of FCM configuration files
+      CFG_LABEL => {
+        CFGFILE => 'CFG', # config file information
+        INC     => 'INC', # "include" from an configuration file
+
+        # Labels for central/user internal config setting
+        SETTING => 'SET',
+
+        # Labels for systems that allow inheritance
+        DEST => 'DEST', # destination
+        USE  => 'USE',  # use (inherit) a previous configuration
+
+        # Labels for bld and pck cfg
+        TARGET => 'TARGET', # BLD: declare targets, PCK: target of source file
+
+        # Labels for bld cfg
+        BLD_BLOCKDATA => 'BLOCKDATA',   # declare Fortran BLOCKDATA dependencies
+        BLD_DEP       => 'DEP',         # additional dependencies
+        BLD_DEP_N     => 'NO_DEP',      # no dependency check
+        BLD_DEP_EXCL  => 'EXCL_DEP',    # exclude automatic dependencies
+        BLD_DEP_EXE   => 'EXE_DEP',     # declare dependencies for program
+        BLD_EXE_NAME  => 'EXE_NAME',    # rename a main program
+        BLD_LIB       => 'LIB',         # rename library
+        BLD_PP        => 'PP',          # sub-package needs pre-process?
+        BLD_TYPE      => 'SRC_TYPE',    # custom source file type
+        DIR           => 'DIR',         # DEPRECATED, same as DEST
+        INFILE_EXT    => 'INFILE_EXT',  # change input file name extension type
+        INHERIT       => 'INHERIT',     # inheritance flag
+        NAME          => 'NAME',        # name the build
+        OUTFILE_EXT   => 'OUTFILE_EXT', # change output file type extension
+        FILE          => 'SRC',         # declare a sub-package
+        SEARCH_SRC    => 'SEARCH_SRC',  # search src/ sub-directory?
+        TOOL          => 'TOOL',        # declare a tool
+
+        # Labels for ext cfg
+        BDECLARE   => 'BLD',      # build declaration
+        CONFLICT   => 'CONFLICT', # set conflict mode
+        DIRS       => 'SRC',      # declare source directory
+        EXPDIRS    => 'EXPSRC',   # declare expandable source directory
+        MIRROR     => 'MIRROR',   # DEPRECATED, same as RDEST::MIRROR_CMD
+        OVERRIDE   => 'OVERRIDE', # DEPRECATED, replaced by CONFLICT
+        RDEST      => 'RDEST',    # declare remote destionation
+        REVISION   => 'REVISION', # declare branch revision in a project
+        REVMATCH   => 'REVMATCH', # branch revision must match changed revision
+        REPOS      => 'REPOS',    # declare branch in a project
+        VERSION    => 'VERSION',  # DEPRECATED, same as REVISION
+      },
+
+      # Default names of known FCM configuration files
+      CFG_NAME => {
+        BLD        => 'bld.cfg',      # build configuration file
+        EXT        => 'ext.cfg',      # extract configuration file
+        PARSED     => 'parsed_',      # as-parsed configuration file prefix
+      },
+
+      # Latest version of known FCM configuration files
+      CFG_VERSION => {
+        BLD        => '1.0', # bld cfg
+        EXT        => '1.0', # ext cfg
+      },
+
+      # Standard sub-directories for extract/build
+      DIR => {
+        BIN    => 'bin',    # executable
+        BLD    => 'bld',    # build
+        CACHE  => '.cache', # cache
+        CFG    => 'cfg',    # configuration
+        DONE   => 'done',   # "done"
+        ETC    => 'etc',    # miscellaneous items
+        FLAGS  => 'flags',  # "flags"
+        INC    => 'inc',    # include
+        LIB    => 'lib',    # library
+        OBJ    => 'obj',    # object
+        PPSRC  => 'ppsrc',  # pre-processed source
+        SRC    => 'src',    # source
+        TMP    => 'tmp',    # temporary directory
+      },
+
+      # A flag to indicate whether the revision of a given branch for extract
+      # must match with the revision of a changed revision of the branch
+      EXT_REVMATCH => 0, # default is false (allow any revision)
+
+      # Input file name extension and type
+      # (may overlap with output (below) and vpath (above))
+      INFILE_EXT => {
+        # General extensions
+        'f'    =>              'FORTRAN' .
+                  $DELIMITER . 'SOURCE',
+        'for'  =>              'FORTRAN' .
+                  $DELIMITER . 'SOURCE',
+        'ftn'  =>              'FORTRAN' .
+                  $DELIMITER . 'SOURCE',
+        'f77'  =>              'FORTRAN' .
+                  $DELIMITER . 'SOURCE',
+        'f90'  =>              'FORTRAN' .
+                  $DELIMITER . 'FORTRAN9X' .
+                  $DELIMITER . 'SOURCE',
+        'f95'  =>              'FORTRAN' .
+                  $DELIMITER . 'FORTRAN9X' .
+                  $DELIMITER . 'SOURCE',
+        'F'    =>              'FPP' .
+                  $DELIMITER . 'SOURCE',
+        'FOR'  =>              'FPP' .
+                  $DELIMITER . 'SOURCE',
+        'FTN'  =>              'FPP' .
+                  $DELIMITER . 'SOURCE',
+        'F77'  =>              'FPP' .
+                  $DELIMITER . 'SOURCE',
+        'F90'  =>              'FPP' .
+                  $DELIMITER . 'FPP9X' .
+                  $DELIMITER . 'SOURCE',
+        'F95'  =>              'FPP' .
+                  $DELIMITER . 'FPP9X' .
+                  $DELIMITER . 'SOURCE',
+        'c'    =>              'C' .
+                  $DELIMITER . 'SOURCE',
+        'cpp'  =>              'C' .
+                  $DELIMITER . 'C++' .
+                  $DELIMITER . 'SOURCE',
+        'h'    =>              'CPP' .
+                  $DELIMITER . 'INCLUDE',
+        'o'    =>              'BINARY' .
+                  $DELIMITER . 'OBJ',
+        'obj'  =>              'BINARY' .
+                  $DELIMITER . 'OBJ',
+        'exe'  =>              'BINARY' .
+                  $DELIMITER . 'EXE',
+        'a'    =>              'BINARY' .
+                  $DELIMITER . 'LIB',
+        'sh'   =>              'SCRIPT' .
+                  $DELIMITER . 'SHELL',
+        'ksh'  =>              'SCRIPT' .
+                  $DELIMITER . 'SHELL',
+        'bash' =>              'SCRIPT' .
+                  $DELIMITER . 'SHELL',
+        'csh'  =>              'SCRIPT' .
+                  $DELIMITER . 'SHELL',
+        'pl'   =>              'SCRIPT' .
+                  $DELIMITER . 'PERL',
+        'pm'   =>              'SCRIPT' .
+                  $DELIMITER . 'PERL',
+        'py'   =>              'SCRIPT' .
+                  $DELIMITER . 'PYTHON',
+        'tcl'  =>              'SCRIPT' .
+                  $DELIMITER . 'TCL',
+        'pro'  =>              'SCRIPT' .
+                  $DELIMITER . 'PVWAVE',
+
+        # Local extensions
+        'cfg'       =>              'CFGFILE',
+        'h90'       =>              'CPP' .
+                       $DELIMITER . 'INCLUDE',
+        'inc'       =>              'FORTRAN' .
+                       $DELIMITER . 'FORTRAN9X' .
+                       $DELIMITER . 'INCLUDE',
+        'interface' =>              'FORTRAN' .
+                       $DELIMITER . 'FORTRAN9X' .
+                       $DELIMITER . 'INCLUDE' .
+                       $DELIMITER . 'INTERFACE',
+      },
+
+      # Ignore input files matching the following names (comma-separated list)
+      INFILE_IGNORE =>                   'fcm_env.ksh' .
+                       $DELIMITER_LIST . 'fcm_env.sh',
+
+      # Input file name pattern and type
+      INFILE_PAT => {
+        '\w+Scr_\w+'              =>              'SCRIPT' .
+                                     $DELIMITER . 'SHELL',
+        '\w+Comp_\w+'             =>              'SCRIPT' .
+                                     $DELIMITER . 'SHELL' .
+                                     $DELIMITER . 'GENTASK',
+        '\w+(?:IF|Interface)_\w+' =>              'SCRIPT' .
+                                     $DELIMITER . 'SHELL' .
+                                     $DELIMITER . 'GENIF',
+        '\w+Suite_\w+'            =>              'SCRIPT' .
+                                     $DELIMITER . 'SHELL' .
+                                     $DELIMITER . 'GENSUITE',
+        '\w+List_\w+'             =>              'SCRIPT' .
+                                     $DELIMITER . 'SHELL' .
+                                     $DELIMITER . 'GENLIST',
+        '\w+Sql_\w+'              =>              'SCRIPT' .
+                                     $DELIMITER . 'SQL',
+      },
+
+      # Input text file pattern and type
+      INFILE_TXT => {
+        '(?:[ck]|ba)?sh'  =>              'SCRIPT' .
+                             $DELIMITER . 'SHELL',
+        'perl'            =>              'SCRIPT' .
+                             $DELIMITER . 'PERL',
+        'python'          =>              'SCRIPT' .
+                             $DELIMITER . 'PYTHON',
+        'tcl(?:sh)?|wish' =>              'SCRIPT' .
+                             $DELIMITER . 'TCL',
+      },
+
+      # Lock file
+      LOCK => {
+        BLDLOCK => 'fcm.bld.lock', # build lock file
+        EXTLOCK => 'fcm.ext.lock', # extract lock file
+      },
+
+      # Output file type and extension
+      # (may overlap with input and vpath (above))
+      OUTFILE_EXT => {
+        CFG       => '.cfg',       # FCM configuration file
+        DONE      => '.done',      # "done" files for compiled source
+        ETC       => '.etc',       # "etc" dummy file
+        EXE       => '.exe',       # binary executables
+        FLAGS     => '.flags',     # "flags" files, compiler flags config
+        IDONE     => '.idone',     # "done" files for included source
+        INTERFACE => '.interface', # interface for F90 subroutines/functions
+        LIB       => '.a',         # archive object library
+        MOD       => '.mod',       # compiled Fortran module information files
+        OBJ       => '.o',         # compiled object files
+        PDONE     => '.pdone',     # "done" files for pre-processed files
+        TAR       => '.tar',       # TAR archive
+      },
+
+      # Build commands and options (i.e. tools)
+      TOOL => {
+        SHELL        => '/bin/sh',         # Default shell
+
+        CPP          => 'cpp',             # C pre-processor
+        CPPFLAGS     => '-C',              # CPP flags
+        CPP_INCLUDE  => '-I',              # CPP flag, specify "include" path
+        CPP_DEFINE   => '-D',              # CPP flag, define macro
+        CPPKEYS      => '',                # CPP keys (definition macro)
+
+        CC           => 'cc',              # C compiler
+        CFLAGS       => '',                # CC flags
+        CC_COMPILE   => '-c',              # CC flag, compile only
+        CC_OUTPUT    => '-o',              # CC flag, specify output file name
+        CC_INCLUDE   => '-I',              # CC flag, specify "include" path
+        CC_DEFINE    => '-D',              # CC flag, define macro
+
+        FPP          => 'cpp',             # Fortran pre-processor
+        FPPFLAGS     => '-P -traditional', # FPP flags
+        FPP_INCLUDE  => '-I',              # FPP flag, specify "include" path
+        FPP_DEFINE   => '-D',              # FPP flag, define macro
+        FPPKEYS      => '',                # FPP keys (definition macro)
+
+        FC           => 'f90',             # Fortran compiler
+        FFLAGS       => '',                # FC flags
+        FC_COMPILE   => '-c',              # FC flag, compile only
+        FC_OUTPUT    => '-o',              # FC flag, specify output file name
+        FC_INCLUDE   => '-I',              # FC flag, specify "include" path
+        FC_MODSEARCH => '',                # FC flag, specify "module" path
+        FC_DEFINE    => '-D',              # FC flag, define macro
+
+        LD           => '',                # linker
+        LDFLAGS      => '',                # LD flags
+        LD_OUTPUT    => '-o',              # LD flag, specify output file name
+        LD_LIBSEARCH => '-L',              # LD flag, specify "library" path
+        LD_LIBLINK   => '-l',              # LD flag, specify link library
+
+        AR           => 'ar',              # library archiver
+        ARFLAGS      => 'rs',              # AR flags
+
+        MAKE         => 'make',            # make command
+        MAKEFLAGS    => '',                # make flags
+        MAKE_FILE    => '-f',              # make flag, path to Makefile
+        MAKE_SILENT  => '-s',              # make flag, silent diagnostic
+        MAKE_JOB     => '-j',              # make flag, number of jobs
+
+        INTERFACE    => 'file',            # name interface after file/program
+        GENINTERFACE => '',                # Fortran 9x interface generator
+
+        DIFF3        => 'diff3',           # extract diff3 merge
+        DIFF3FLAGS   => '-E -m',           # DIFF3 flags
+        GRAPHIC_DIFF => 'xxdiff',          # graphical diff tool
+        GRAPHIC_MERGE=> 'xxdiff',          # graphical merge tool
+      },
+
+      # List of tools that are local to FCM, (will not be exported to a Makefile)
+      TOOL_LOCAL =>                   'CPP' .
+                    $DELIMITER_LIST . 'CPPFLAGS' .
+                    $DELIMITER_LIST . 'CPP_INCLUDE' .
+                    $DELIMITER_LIST . 'CPP_DEFINE' .
+                    $DELIMITER_LIST . 'DIFF3' .
+                    $DELIMITER_LIST . 'DIFF3_FLAGS' .
+                    $DELIMITER_LIST . 'FPP' .
+                    $DELIMITER_LIST . 'FPPFLAGS' .
+                    $DELIMITER_LIST . 'FPP_INCLUDE' .
+                    $DELIMITER_LIST . 'FPP_DEFINE' .
+                    $DELIMITER_LIST . 'GRAPHIC_DIFF' .
+                    $DELIMITER_LIST . 'GRAPHIC_MERGE' .
+                    $DELIMITER_LIST . 'MAKE' .
+                    $DELIMITER_LIST . 'MAKEFLAGS' .
+                    $DELIMITER_LIST . 'MAKE_FILE' .
+                    $DELIMITER_LIST . 'MAKE_SILENT' .
+                    $DELIMITER_LIST . 'MAKE_JOB' .
+                    $DELIMITER_LIST . 'INTERFACE' .
+                    $DELIMITER_LIST . 'GENINTERFACE' .
+                    $DELIMITER_LIST . 'MIRROR' .
+                    $DELIMITER_LIST . 'REMOTE_SHELL',
+
+      # List of tools that allow sub-package declarations
+      TOOL_PACKAGE =>                   'CPPFLAGS' .
+                      $DELIMITER_LIST . 'CPPKEYS' .
+                      $DELIMITER_LIST . 'CFLAGS' .
+                      $DELIMITER_LIST . 'FPPFLAGS' .
+                      $DELIMITER_LIST . 'FPPKEYS' .
+                      $DELIMITER_LIST . 'FFLAGS' .
+                      $DELIMITER_LIST . 'LD' .
+                      $DELIMITER_LIST . 'LDFLAGS' .
+                      $DELIMITER_LIST . 'INTERFACE' .
+                      $DELIMITER_LIST . 'GENINTERFACE',
+
+      # Supported tools for compilable source
+      TOOL_SRC_PP => {
+        FPP     => {
+          COMMAND => 'FPP',
+          FLAGS   => 'FPPFLAGS',
+          PPKEYS  => 'FPPKEYS',
+          INCLUDE => 'FPP_INCLUDE',
+          DEFINE  => 'FPP_DEFINE',
+        },
+
+        C       => {
+          COMMAND => 'CPP',
+          FLAGS   => 'CPPFLAGS',
+          PPKEYS  => 'CPPKEYS',
+          INCLUDE => 'CPP_INCLUDE',
+          DEFINE  => 'CPP_DEFINE',
+        },
+      },
+
+      # Supported tools for compilable source
+      TOOL_SRC => {
+        FORTRAN => {
+          COMMAND => 'FC',
+          FLAGS   => 'FFLAGS',
+          OUTPUT  => 'FC_OUTPUT',
+          INCLUDE => 'FC_INCLUDE',
+        },
+
+        FPP     => {
+          COMMAND => 'FC',
+          FLAGS   => 'FFLAGS',
+          PPKEYS  => 'FPPKEYS',
+          OUTPUT  => 'FC_OUTPUT',
+          INCLUDE => 'FC_INCLUDE',
+          DEFINE  => 'FC_DEFINE',
+        },
+
+        C       => {
+          COMMAND => 'CC',
+          FLAGS   => 'CFLAGS',
+          PPKEYS  => 'CPPKEYS',
+          OUTPUT  => 'CC_OUTPUT',
+          INCLUDE => 'CC_INCLUDE',
+          DEFINE  => 'CC_DEFINE',
+        },
+      },
+
+      # FCM URL keyword and prefix, FCM revision keyword, and FCM Trac URL
+      URL          => {},
+      URL_REVISION => {},
+
+      URL_BROWSER_MAPPING => {},
+      URL_BROWSER_MAPPING_DEFAULT => {
+        LOCATION_COMPONENT_PATTERN
+        => qr{\A // ([^/]+) /+ ([^/]+)_svn /+(.*) \z}xms,
+        BROWSER_URL_TEMPLATE
+        => 'http://{1}/projects/{2}/intertrac/source:{3}{4}',
+        BROWSER_REV_TEMPLATE => '@{1}',
+      },
+
+      # Default web browser
+      WEB_BROWSER   => 'firefox',
+    },
+  };
+
+  # Backward compatibility: the REPOS setting is equivalent to the URL setting
+  $self->{setting}{REPOS} = $self->{setting}{URL};
+
+  # Alias the REVISION and TRAC setting to URL_REVISION and URL_TRAC
+  $self->{setting}{REVISION} = $self->{setting}{URL_REVISION};
+
+  bless $self, $class;
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in the "new" method.
+# ------------------------------------------------------------------------------
+
+for my $name (qw/central_config user_config user_id verbose/) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    # Argument specified, set property to specified argument
+    if (@_) {
+      $self->{$name} = $_[0];
+    }
+
+    # Default value for property
+    if (not defined $self->{$name}) {
+      if ($name eq 'central_config') {
+        # Central configuration file
+        if (-f catfile (dirname ($FindBin::Bin), 'etc', 'fcm.cfg')) {
+          $self->{$name} = catfile (
+            dirname ($FindBin::Bin), 'etc', 'fcm.cfg'
+          );
+
+        } elsif (-f catfile ($FindBin::Bin, 'fcm.cfg')) {
+          $self->{$name} = catfile ($FindBin::Bin, 'fcm.cfg');
+        }
+
+      } elsif ($name eq 'user_config') {
+        # User configuration file
+        my $home = (getpwuid ($<))[7];
+        $home = $ENV{HOME} if not defined $home;
+        $self->{$name} = catfile ($home, '.fcm')
+          if defined ($home) and -f catfile ($home, '.fcm');
+
+      } elsif ($name eq 'user_id') {
+        # User ID of current process
+        my $user = (getpwuid ($<))[0];
+        $user = $ENV{LOGNAME} if not defined $user;
+        $user = $ENV{USER} if not defined $user;
+        $self->{$name} = $user;
+
+      } elsif ($name eq 'verbose') {
+        # Verbose mode
+        $self->{$name} = exists $ENV{FCM_VERBOSE} ? $ENV{FCM_VERBOSE} : 1;
+      }
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flag = $obj->is_initialising();
+#
+# DESCRIPTION
+#   Returns true if this object is initialising.
+# ------------------------------------------------------------------------------
+sub is_initialising {
+  my ($self, $value) = @_;
+  if (defined($value)) {
+    $self->{initialising} = $value;
+  }
+  return $self->{initialising};
+}
+
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   %hash = %{ $obj->X () };
+#   $obj->X (\%hash);
+#
+#   $value = $obj->X ($index);
+#   $obj->X ($index, $value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in the "new" method.
+#
+#   If no argument is set, this method returns a hash containing a list of
+#   objects. If an argument is set and it is a reference to a hash, the objects
+#   are replaced by the specified hash.
+#
+#   If a scalar argument is specified, this method returns a reference to an
+#   object, if the indexed object exists or undef if the indexed object does
+#   not exist. If a second argument is set, the $index element of the hash will
+#   be set to the value of the argument.
+# ------------------------------------------------------------------------------
+
+for my $name (qw/variable/) {
+  no strict 'refs';
+
+  *$name = sub {
+    my ($self, $arg1, $arg2) = @_;
+
+    # Ensure property is defined as a reference to a hash
+    $self->{$name} = {} if not defined ($self->{$name});
+
+    # Argument 1 can be a reference to a hash or a scalar index
+    my ($index, %hash);
+
+    if (defined $arg1) {
+      if (ref ($arg1) eq 'HASH') {
+        %hash = %$arg1;
+
+      } else {
+        $index = $arg1;
+      }
+    }
+
+    if (defined $index) {
+      # A scalar index is defined, set and/or return the value of an element
+      $self->{$name}{$index} = $arg2 if defined $arg2;
+
+      return (
+        exists $self->{$name}{$index} ? $self->{$name}{$index} : undef
+      );
+
+    } else {
+      # A scalar index is not defined, set and/or return the hash
+      $self->{$name} = \%hash if defined $arg1;
+      return $self->{$name};
+    }
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $setting = $obj->setting (@labels);
+#   $obj->setting (\@labels, $setting);
+#
+# DESCRIPTION
+#   This method returns/sets an item under the setting hash table. The depth
+#   within the hash table is given by the list of arguments @labels, which
+#   should match with the keys in the multi-dimension setting hash table.
+# ------------------------------------------------------------------------------
+
+sub setting {
+  my $self = shift;
+
+  if (@_) {
+    my $arg1 = shift;
+    my $s    = $self->{setting};
+
+    if (ref ($arg1) eq 'ARRAY') {
+      # Assign setting
+      # ------------------------------------------------------------------------
+      my $value = shift;
+
+      while (defined (my $label = shift @$arg1)) {
+        if (exists $s->{$label}) {
+          if (ref $s->{$label} eq 'HASH') {
+            $s = $s->{$label};
+
+          } else {
+            $s->{$label} = $value;
+            last;
+          }
+
+        } else {
+          if (@$arg1) {
+            $s->{$label} = {};
+            $s           = $s->{$label};
+
+          } else {
+            $s->{$label} = $value;
+          }
+        }
+      }
+
+    } else {
+      # Get setting
+      # ------------------------------------------------------------------------
+      return _get_hash_value ($s->{$arg1}, @_) if exists $s->{$arg1};
+    }
+  }
+
+  return undef;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj->get_config ();
+#
+# DESCRIPTION
+#   This method reads the configuration settings from the central and the user
+#   configuration files.
+# ------------------------------------------------------------------------------
+
+sub get_config {
+  my $self = shift;
+
+  $self->_read_config_file ($self->central_config);  
+  $self->_read_config_file ($self->user_config);
+
+  return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj->_read_config_file ();
+#
+# DESCRIPTION
+#   This internal method reads a configuration file and assign values to the
+#   attributes of the current instance.
+# ------------------------------------------------------------------------------
+
+sub _read_config_file {
+  my $self        = shift;
+  my $config_file = $_[0];
+
+  if (!$config_file || !-f $config_file) {
+    return;
+  }
+
+  my $cfgfile = FCM1::CfgFile->new (SRC => $config_file, TYPE => 'FCM');
+  $cfgfile->read_cfg ();
+
+  LINE: for my $line (@{ $cfgfile->lines }) {
+    next unless $line->label;
+
+    # "Environment variables" start with $
+    if ($line->label =~ /^\$([A-Za-z_]\w*)$/) {
+      $ENV{$1} = $line->value;
+      next LINE;
+    }
+
+    # "Settings variables" start with "set"
+    if ($line->label_starts_with_cfg ('SETTING')) {
+      my @tags = $line->label_fields;
+      shift @tags;
+      @tags = map {uc} @tags;
+      $self->setting (\@tags, $line->value);
+      next LINE;
+    }
+
+    # Not a standard setting variable, put in internal variable list
+    (my $label = $line->label) =~ s/^\%//;
+    $self->variable ($label, $line->value);
+  }
+
+  1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $ref = _get_hash_value (arg1, arg2, ...);
+#
+# DESCRIPTION
+#   This internal method recursively gets a value from a multi-dimensional
+#   hash.
+# ------------------------------------------------------------------------------
+
+sub _get_hash_value {
+  my $value = shift;
+
+  while (defined (my $arg = shift)) {
+    if (exists $value->{$arg}) {
+      $value = $value->{$arg};
+
+    } else {
+      return undef;
+    }
+  }
+
+  return $value;
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/ConfigSystem.pm b/lib/FCM1/ConfigSystem.pm
new file mode 100644
index 0000000..144681c
--- /dev/null
+++ b/lib/FCM1/ConfigSystem.pm
@@ -0,0 +1,752 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::ConfigSystem
+#
+# DESCRIPTION
+#   This is the base class for FCM systems that are based on inherited
+#   configuration files, e.g. the extract and the build systems.
+#
+# ------------------------------------------------------------------------------
+
+package FCM1::ConfigSystem;
+use base qw{FCM1::Base};
+
+use strict;
+use warnings;
+
+use FCM1::CfgFile;
+use FCM1::CfgLine;
+use FCM1::Dest;
+use FCM1::Util     qw{expand_tilde e_report w_report};
+use Sys::Hostname qw{hostname};
+
+# List of property methods for this class
+my @scalar_properties = (
+ 'cfg',         # configuration file
+ 'cfg_methods', # list of sub-methods for parse_cfg
+ 'cfg_prefix',  # optional prefix in configuration declaration
+ 'dest',        # destination for output
+ 'inherit',     # list of inherited configurations
+ 'inherited',   # list of inheritance hierarchy
+ 'type',        # system type
+);
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::ConfigSystem->new;
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::ConfigSystem class.
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  my $self = FCM1::Base->new (%args);
+
+  $self->{$_} = undef for (@scalar_properties);
+
+  bless $self, $class;
+
+  # List of sub-methods for parse_cfg
+  $self->cfg_methods ([qw/header inherit dest/]);
+
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @scalar_properties.
+# ------------------------------------------------------------------------------
+
+for my $name (@scalar_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    # Argument specified, set property to specified argument
+    if (@_) {
+      $self->{$name} = $_[0];
+    }
+
+    # Default value for property
+    if (not defined $self->{$name}) {
+      if ($name eq 'cfg') {
+        # New configuration file
+        $self->{$name} = FCM1::CfgFile->new (TYPE => $self->type);
+
+      } elsif ($name =~ /^(?:cfg_methods|inherit|inherited)$/) {
+        # Reference to an array
+        $self->{$name} = [];
+
+      } elsif ($name eq 'cfg_prefix' or $name eq 'type') {
+        # Reference to an array
+        $self->{$name} = '';
+
+      } elsif ($name eq 'dest') {
+        # New destination
+        $self->{$name} = FCM1::Dest->new (TYPE => $self->type);
+      }
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   ($rc, $out_of_date) = $obj->check_cache ();
+#
+# DESCRIPTION
+#   This method returns $rc = 1 on success or undef on failure. It returns
+#   $out_of_date = 1 if current cache file is out of date relative to those in
+#   inherited runs or 0 otherwise.
+# ------------------------------------------------------------------------------
+
+sub check_cache {
+  my $self = shift;
+
+  my $rc = 1;
+  my $out_of_date = 0;
+
+  if (@{ $self->inherit } and -f $self->dest->cache) {
+    # Get modification time of current cache file
+    my $cur_mtime = (stat ($self->dest->cache))[9];
+
+    # Compare with modification times of inherited cache files
+    for my $use (@{ $self->inherit }) {
+      next unless -f $use->dest->cache;
+      my $use_mtime = (stat ($use->dest->cache))[9];
+      $out_of_date = 1 if $use_mtime > $cur_mtime;
+    }
+  }
+
+  return ($rc, $out_of_date);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->check_lock ();
+#
+# DESCRIPTION
+#   This method returns true if no lock is found in the destination or if the
+#   locks found are allowed. 
+# ------------------------------------------------------------------------------
+
+sub check_lock {
+  my $self = shift;
+
+  # Check all types of locks
+  for my $method (@FCM1::Dest::lockfiles) {
+    my $lock = $self->dest->$method;
+
+    # Check whether lock exists
+    next unless -e $lock;
+
+    # Check whether this lock is allowed
+    next if $self->check_lock_is_allowed ($lock);
+
+    # Throw error if a lock exists
+    w_report 'ERROR: ', $lock, ': lock file exists,';
+    w_report '       ', $self->dest->rootdir, ': destination is busy.';
+    return;
+  }
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->check_lock_is_allowed ($lock);
+#
+# DESCRIPTION
+#   This method returns true if it is OK for $lock to exist in the destination.
+# ------------------------------------------------------------------------------
+
+sub check_lock_is_allowed {
+  my ($self, $lock) = @_;
+
+  # Disallow all types of locks by default
+  return 0;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->compare_setting (
+#     METHOD_LIST  => \@method_list,
+#     [METHOD_ARGS => \@method_args,]
+#     [CACHEBASE   => $cachebase,]
+#   );
+#
+# DESCRIPTION
+#   This method gets settings from the previous cache and updates the current.
+#
+# METHOD
+#   The method returns true on success. @method_list must be a list of method
+#   names for processing the cached lines in the previous run. If an existing
+#   cache exists, its content is read into $old_lines, which is a list of
+#   FCM1::CfgLine objects. Otherwise, $old_lines is set to undef. If $cachebase
+#   is set, it is used for as the cache basename. Otherwise, the default for
+#   the current system is used. It calls each method in the @method_list using
+#   $self->$method ($old_lines, @method_args), which should return a
+#   two-element list. The first element should be a return code (1 for out of
+#   date, 0 for up to date and undef for failure). The second element should be
+#   a reference to a list of FCM1::CfgLine objects for the output.
+# ------------------------------------------------------------------------------
+
+sub compare_setting {
+  my ($self, %args) = @_;
+
+  my @method_list = exists ($args{METHOD_LIST}) ? @{ $args{METHOD_LIST} } : ();
+  my @method_args = exists ($args{METHOD_ARGS}) ? @{ $args{METHOD_ARGS} } : ();
+  my $cachebase   = exists ($args{CACHEBASE}) ? $args{CACHEBASE} : undef;
+
+  my $rc = 1;
+
+  # Read cache if the file exists
+  # ----------------------------------------------------------------------------
+  my $cache = $cachebase
+              ? File::Spec->catfile ($self->dest->cachedir, $cachebase)
+              : $self->dest->cache;
+  my @in_caches = ();
+  if (-f $cache) {
+    push @in_caches, $cache;
+
+  } else {
+    for my $use (@{ $self->inherit }) {
+      my $use_cache = $cachebase
+                      ? File::Spec->catfile ($use->dest->cachedir, $cachebase)
+                      : $use->dest->cache;
+      push @in_caches, $use_cache if -f $use_cache;
+    }
+  }
+
+  my $old_lines = undef;
+  for my $in_cache (@in_caches) {
+    next unless -f $in_cache;
+    my $cfg = FCM1::CfgFile->new (SRC => $in_cache);
+
+    if ($cfg->read_cfg) {
+      $old_lines = [] if not defined $old_lines;
+      push @$old_lines, @{ $cfg->lines };
+    }
+  }
+
+  # Call methods in @method_list to see if cache is out of date
+  # ----------------------------------------------------------------------------
+  my @new_lines = ();
+  my $out_of_date = 0;
+  for my $method (@method_list) {
+    my ($return, $lines);
+    ($return, $lines) = $self->$method ($old_lines, @method_args) if $rc;
+
+    if (defined $return) {
+      # Method succeeded
+      push @new_lines, @$lines;
+      $out_of_date = 1 if $return;
+
+    } else {
+      # Method failed
+      $rc = $return;
+      last;
+    }
+  }
+
+  # Update the cache in the current run
+  # ----------------------------------------------------------------------------
+  if ($rc) {
+    if (@{ $self->inherited } and $out_of_date) {
+      # If this is an inherited configuration, the cache must not be changed
+      w_report 'ERROR: ', $self->cfg->src,
+               ': inherited configuration does not match with its cache.';
+      $rc = undef;
+
+    } elsif ((not -f $cache) or $out_of_date) {
+      my $cfg = FCM1::CfgFile->new;
+      $cfg->lines ([sort {$a->label cmp $b->label} @new_lines]);
+      $rc = $cfg->print_cfg ($cache, 1);
+    }
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   ($changed_hash_ref, $new_lines_array_ref) =
+#     $self->compare_setting_in_config($prefix, \@old_lines);
+#
+# DESCRIPTION
+#   This method compares old and current settings for a specified item.
+#
+# METHOD
+#   This method does two things.
+#
+#   It uses the current configuration for the $prefix item to generate a list of
+#   new FCM1::CfgLine objects (which is returned as a reference in the second
+#   element of the returned list).
+#
+#   The values of the old lines are then compared with those of the new lines.
+#   Any settings that are changed are stored in a hash, which is returned as a
+#   reference in the first element of the returned list. The key of the hash is
+#   the name of the changed setting, and the value is the value of the new
+#   setting or undef if the setting no longer exists.
+#
+# ARGUMENTS
+#   $prefix    - the name of an item in FCM1::Config to be compared
+#   @old_lines - a list of FCM1::CfgLine objects containing the old settings
+# ------------------------------------------------------------------------------
+
+sub compare_setting_in_config {
+  my ($self, $prefix, $old_lines_ref) = @_;
+  
+  my %changed = %{$self->setting($prefix)};
+  my (@new_lines, %new_val_of);
+  while (my ($key, $val) = each(%changed)) {
+    $new_val_of{$key} = (ref($val) eq 'ARRAY' ? join(q{ }, sort(@{$val})) : $val);
+    push(@new_lines, FCM1::CfgLine->new(
+      LABEL => $prefix . $FCM1::Config::DELIMITER . $key,
+      VALUE => $new_val_of{$key},
+    ));
+  }
+
+  if (defined($old_lines_ref)) {
+    my %old_val_of
+      = map {($_->label_from_field(1), $_->value())} # converts into a hash
+        grep {$_->label_starts_with($prefix)}        # gets relevant lines
+        @{$old_lines_ref};
+
+    while (my ($key, $val) = each(%old_val_of)) {
+      if (exists($changed{$key})) {
+        if ($val eq $new_val_of{$key}) { # no change from old to new
+          delete($changed{$key});
+        }
+      }
+      else { # exists in old but not in new
+        $changed{$key} = undef;
+      }
+    }
+  }
+
+  return (\%changed, \@new_lines);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->invoke ([CLEAN => 1, ]%args);
+#
+# DESCRIPTION
+#   This method invokes the system. If CLEAN is set to true, it will only parse
+#   the configuration and set up the destination, but will not invoke the
+#   system. See the invoke_setup_dest and the invoke_system methods for list of
+#   other arguments in %args.
+# ------------------------------------------------------------------------------
+
+sub invoke {
+  my $self = shift;
+  my %args = @_;
+
+  # Print diagnostic at beginning of run
+  # ----------------------------------------------------------------------------
+  # Name of the system
+  (my $name = ref ($self)) =~ s/^FCM1:://;
+
+  # Print start time on system run, if verbose is true
+  my $date = localtime;
+  print $name, ' command started on ', $date, '.', "\n"
+    if $self->verbose;
+
+  # Start time (seconds since epoch)
+  my $otime = time;
+
+  # Parse the configuration file
+  my $rc = $self->invoke_stage ('Parse configuration', 'parse_cfg');
+
+  # Set up the destination
+  $rc = $self->invoke_stage ('Setup destination', 'invoke_setup_dest', %args)
+    if $rc;
+
+  # Invoke the system
+  # ----------------------------------------------------------------------------
+  $rc = $self->invoke_system (%args) if $rc and not $args{CLEAN};
+
+  # Remove empty directories
+  $rc = $self->dest->clean (MODE => 'EMPTY') if $rc;
+
+  # Print diagnostic at end of run
+  # ----------------------------------------------------------------------------
+  # Print lapse time at the end, if verbose is true
+  if ($self->verbose) {
+    my $total = time - $otime;
+    my $s_str = $total > 1 ? 'seconds' : 'second';
+    print '->TOTAL: ', $total, ' ', $s_str, "\n";
+  }
+
+  # Report end of system run
+  $date = localtime;
+  if ($rc) {
+    # Success
+    print $name, ' command finished on ', $date, '.', "\n"
+      if $self->verbose;
+
+  } else {
+    # Failure
+    e_report $name, ' failed on ', $date, '.';
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->invoke_setup_dest ([CLEAN|FULL => 1], [IGNORE_LOCK => 1]);
+#
+# DESCRIPTION
+#   This method sets up the destination and returns true on success.
+#
+# ARGUMENTS
+#   CLEAN|FULL   - If set to "true", set up the system in "clean|full" mode.
+#                  Sub-directories and files in the root directory created by
+#                  the previous invocation of the system will be removed. If
+#                  not set, the default is to run in "incremental" mode.
+#   IGNORE_LOCK  - If set to "true", it ignores any lock files that may exist in
+#                  the destination root directory. 
+# ------------------------------------------------------------------------------
+
+sub invoke_setup_dest {
+  my $self = shift;
+  my %args = @_;
+
+  # Set up destination
+  # ----------------------------------------------------------------------------
+  # Print destination in verbose mode
+  if ($self->verbose()) {
+    printf(
+      "Destination: %s@%s:%s\n",
+      scalar(getpwuid($<)),
+      hostname(),
+      $self->dest()->rootdir(),
+    );
+  }
+
+  my $rc = 1;
+  my $out_of_date = 0;
+
+  # Check whether lock exists in the destination root
+  $rc = $self->check_lock if $rc and not $args{IGNORE_LOCK};
+
+  # Check whether current cache is out of date relative to the inherited ones
+  ($rc, $out_of_date) = $self->check_cache if $rc;
+
+  # Remove sub-directories and files in destination in "full" mode
+  $rc = $self->dest->clean (MODE => 'ALL')
+    if $rc and ($args{FULL} or $args{CLEAN} or $out_of_date);
+
+  # Create build root directory if necessary
+  $rc = $self->dest->create if $rc;
+
+  # Set a lock in the destination root
+  $rc = $self->dest->set_lock if $rc;
+
+  # Generate an as-parsed configuration file
+  $self->cfg->print_cfg ($self->dest->parsedcfg);
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->invoke_stage ($name, $method, @args);
+#
+# DESCRIPTION
+#   This method invokes a named stage of the system, where $name is the name of
+#   the stage, $method is the name of the method for invoking the stage and
+#   @args are the arguments to the &method.
+# ------------------------------------------------------------------------------
+
+sub invoke_stage {
+  my ($self, $name, $method, @args) = @_;
+
+  # Print diagnostic at beginning of a stage
+  print '->', $name, ': start', "\n" if $self->verbose;
+  my $stime = time;
+
+  # Invoke the stage
+  my $rc = $self->$method (@args);
+
+  # Print diagnostic at end of a stage
+  my $total = time - $stime;
+  my $s_str = $total > 1 ? 'seconds' : 'second';
+  print '->', $name, ': ', $total, ' ', $s_str, "\n";
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->invoke_system (%args);
+#
+# DESCRIPTION
+#   This is a prototype method for invoking the system.
+# ------------------------------------------------------------------------------
+
+sub invoke_system {
+  my $self = shift;
+  my %args = @_;
+
+  print "Dummy code.\n";
+
+  return 0;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->parse_cfg($is_for_inheritance);
+#
+# DESCRIPTION
+#   This method calls other methods to parse the configuration file.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg {
+  my ($self, $is_for_inheritance) = @_;
+
+  # Read config file
+  # ----------------------------------------------------------------------------
+  if (!$self->cfg()->src() || !$self->cfg()->read_cfg($is_for_inheritance)) {
+    return;
+  }
+
+  if ($self->cfg->type ne $self->type) {
+    w_report 'ERROR: ', $self->cfg->src, ': not a ', $self->type,
+             ' config file.';
+    return;
+  }
+
+  # Strip out optional prefix from all labels
+  # ----------------------------------------------------------------------------
+  if ($self->cfg_prefix) {
+    for my $line (@{ $self->cfg->lines }) {
+      $line->prefix ($self->cfg_prefix);
+    }
+  }
+
+  # Filter lines from the configuration file
+  # ----------------------------------------------------------------------------
+  my @cfg_lines = grep {
+    $_->slabel                   and       # ignore empty/comment lines
+    index ($_->slabel, '%') != 0 and       # ignore user variable
+    not $_->slabel_starts_with_cfg ('INC') # ignore INC line
+  } @{ $self->cfg->lines };
+
+  # Parse the lines to read in the various settings, by calling the methods:
+  # $self->parse_cfg_XXX, where XXX is: header, inherit, dest, and the values
+  # in the list @{ $self->cfg_methods }.
+  # ----------------------------------------------------------------------------
+  my $rc = 1;
+  for my $name (@{ $self->cfg_methods }) {
+    my $method = 'parse_cfg_' . $name;
+    $self->$method (\@cfg_lines) or $rc = 0;
+  }
+
+  # Report warnings/errors
+  # ----------------------------------------------------------------------------
+  for my $line (@cfg_lines) {
+    $rc = 0 if not $line->parsed;
+    my $mesg = $line->format_error;
+    w_report $mesg if $mesg;
+  }
+
+  return ($rc);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->parse_cfg_dest (\@cfg_lines);
+#
+# DESCRIPTION
+#   This method parses the destination settings in the @cfg_lines.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg_dest {
+  my ($self, $cfg_lines) = @_;
+
+  my $rc = 1;
+
+  # DEST/DIR declarations
+  # ----------------------------------------------------------------------------
+  my @lines  = grep {
+    $_->slabel_starts_with_cfg ('DEST') or $_->slabel_starts_with_cfg ('DIR')
+  } @$cfg_lines;
+
+  # Only ROOTDIR declarations are accepted
+  for my $line (@lines) {
+    my ($d, $method) = $line->slabel_fields;
+    $d = lc $d;
+    $method = lc $method;
+
+    # Backward compatibility
+    $d = 'dest' if $d eq 'dir';
+
+    # Default to "rootdir"
+    $method = 'rootdir' if (not $method) or $method eq 'root';
+
+    # Only "rootdir" can be set
+    next unless $method eq 'rootdir';
+
+    $self->$d->$method (&expand_tilde ($line->value));
+    $line->parsed (1);
+  }
+
+  # Make sure root directory is set
+  # ----------------------------------------------------------------------------
+  if (not $self->dest->rootdir) {
+    w_report 'ERROR: ', $self->cfg->actual_src,
+             ': destination root directory not set.';
+    $rc = 0;
+  }
+
+  # Inherit destinations
+  # ----------------------------------------------------------------------------
+  @{$self->dest()->inherit()} = ();
+  my @nodes = @{$self->inherit()};
+  while (my $node = pop(@nodes)) {
+      push(@nodes, @{$node->inherit()});
+      push(@{$self->dest()->inherit()}, $node->dest());
+  }
+  @{$self->dest()->inherit()} = reverse(@{$self->dest()->inherit()});
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->parse_cfg_header (\@cfg_lines);
+#
+# DESCRIPTION
+#   This method parses the header setting in the @cfg_lines.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg_header {
+  my ($self, $cfg_lines) = @_;
+
+  # Set header lines as "parsed"
+  map {$_->parsed (1)} grep {$_->slabel_starts_with_cfg ('CFGFILE')} @$cfg_lines;
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->parse_cfg_inherit (\@cfg_lines);
+#
+# DESCRIPTION
+#   This method parses the inherit setting in the @cfg_lines.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg_inherit {
+  my ($self, $cfg_lines) = @_;
+
+  # USE declaration
+  # ----------------------------------------------------------------------------
+  my @lines = grep {$_->slabel_starts_with_cfg ('USE')} @$cfg_lines;
+
+  # Check for cyclic dependency
+  if (@lines and grep {$_ eq $self->cfg->actual_src} @{ $self->inherited }) {
+    # Error if current configuration file is in its own inheritance hierarchy
+    w_report 'ERROR: ', $self->cfg->actual_src, ': attempt to inherit itself.';
+    $_->error ($_->label . ': ignored due to cyclic dependency.') for (@lines);
+    return 0;
+  }
+
+  my $rc = 1;
+
+  for my $line (@lines) {
+    # Invoke new instance of the current class
+    my $use = ref ($self)->new;
+
+    # Set configuration file, inheritance hierarchy
+    # and attempt to parse the configuration
+    $use->cfg->src  (&expand_tilde ($line->value));
+    $use->inherited ([$self->cfg->actual_src, @{ $self->inherited }]);
+    $use->parse_cfg(1); # 1 = is for inheritance
+
+    # Add to list of inherit configurations
+    push @{ $self->inherit }, $use;
+
+    $line->parsed (1);
+  }
+
+  # Check locks in inherited destination
+  # ----------------------------------------------------------------------------
+  for my $use (@{ $self->inherit }) {
+    $rc = 0 unless $use->check_lock;
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @cfglines = $obj->to_cfglines ();
+#
+# DESCRIPTION
+#   This method returns the configuration lines of this object.
+# ------------------------------------------------------------------------------
+
+sub to_cfglines {
+  my ($self) = @_;
+
+  my @inherited_dests = map {
+    FCM1::CfgLine->new (
+      label => $self->cfglabel ('USE'), value => $_->dest->rootdir
+    );
+  } @{ $self->inherit };
+
+  return (
+    FCM1::CfgLine::comment_block ('File header'),
+    FCM1::CfgLine->new (
+      label => $self->cfglabel ('CFGFILE') . $FCM1::Config::DELIMITER . 'TYPE',
+      value => $self->type,
+    ),
+    FCM1::CfgLine->new (
+      label => $self->cfglabel ('CFGFILE') . $FCM1::Config::DELIMITER . 'VERSION',
+      value => '1.0',
+    ),
+    FCM1::CfgLine->new (),
+
+    @inherited_dests,
+
+    FCM1::CfgLine::comment_block ('Destination'),
+    ($self->dest->to_cfglines()),
+  );
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/Dest.pm b/lib/FCM1/Dest.pm
new file mode 100644
index 0000000..af65084
--- /dev/null
+++ b/lib/FCM1/Dest.pm
@@ -0,0 +1,899 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::Dest
+#
+# DESCRIPTION
+#   This class contains methods to set up a destination location of an FCM
+#   extract/build.
+#
+# ------------------------------------------------------------------------------
+use warnings;
+use strict;
+
+package FCM1::Dest;
+use base qw{FCM1::Base};
+
+use Carp             qw{croak}                          ;
+use Cwd              qw{cwd}                            ;
+use FCM1::CfgLine                                       ;
+use FCM1::Timer      qw{timestamp_command}              ;
+use FCM1::Util       qw{run_command touch_file w_report};
+use File::Basename   qw{basename dirname}               ;
+use File::Find       qw{find}                           ;
+use File::Path       qw{mkpath rmtree}                  ;
+use File::Spec                                          ;
+use Sys::Hostname    qw{hostname}                       ;
+use Text::ParseWords qw{shellwords}                     ;
+
+# Useful variables
+# ------------------------------------------------------------------------------
+# List of configuration files
+our @cfgfiles = (
+  'bldcfg',     # default location of the build configuration file
+  'extcfg',     # default location of the extract configuration file
+);
+
+# List of cache and configuration files, according to the dest type
+our @cfgfiles_type = (
+  'cache',     # default location of the cache file
+  'cfg',       # default location of the configuration file
+  'parsedcfg', # default location of the as-parsed configuration file
+);
+
+# List of lock files
+our @lockfiles = (
+  'bldlock',    # the build lock file
+  'extlock',    # the extract lock file
+);
+
+# List of misc files
+our @miscfiles_bld = (
+  'bldrunenvsh', # the build run environment shell script
+  'bldmakefile', # the build Makefile
+);
+
+# List of sub-directories created by extract
+our @subdirs_ext = (
+  'cfgdir',     # sub-directory for configuration files
+  'srcdir',     # sub-directory for source tree
+);
+
+# List of sub-directories that can be archived by "tar" at end of build
+our @subdirs_tar = (
+  'donedir',    # sub-directory for "done" files
+  'flagsdir',   # sub-directory for "flags" files
+  'incdir',     # sub-directory for include files
+  'ppsrcdir',   # sub-directory for pre-process source tree
+  'objdir',     # sub-directory for object files
+);
+
+# List of sub-directories created by build
+our @subdirs_bld = (
+  'bindir',     # sub-directory for executables
+  'etcdir',     # sub-directory for miscellaneous files
+  'libdir',     # sub-directory for object libraries
+  'tmpdir',     # sub-directory for temporary build files
+  @subdirs_tar, # -see above-
+);
+
+# List of sub-directories under rootdir
+our @subdirs = (
+  'cachedir',   # sub-directory for caches
+  @subdirs_ext, # -see above-
+  @subdirs_bld, # -see above-
+);
+
+# List of inherited search paths
+# "rootdir" + all @subdirs, with "XXXdir" replaced with "XXXpath"
+our @paths = (
+    'rootpath',
+    (map {my $key = $_; $key =~ s{dir\z}{path}msx; $key} @subdirs),
+);
+
+# List of properties and their default values.
+my %PROP_OF = (
+  # the original destination (if current destination is a mirror)
+  'dest0'                => undef,
+  # list of inherited FCM1::Dest objects
+  'inherit'              => [],
+  # remote login name
+  'logname'              => scalar(getpwuid($<)),
+  # lock file
+  'lockfile'             => undef,
+  # remote machine
+  'machine'              => hostname(),
+  # mirror command to use
+  'mirror_cmd'           => 'rsync',
+  # (for rsync) remote mkdir, the remote shell command
+  'rsh_mkdir_rsh'        => 'ssh',
+  # (for rsync) remote mkdir, the remote shell command flags
+  'rsh_mkdir_rshflags'   => '-n -oBatchMode=yes',
+  # (for rsync) remote mkdir, the remote shell command
+  'rsh_mkdir_mkdir'      => 'mkdir',
+  # (for rsync) remote mkdir, the remote shell command flags
+  'rsh_mkdir_mkdirflags' => '-p',
+  # (for rsync) remote mkdir, the remote shell command
+  'rsync'                => 'rsync',
+  # (for rsync) remote mkdir, the remote shell command flags
+  'rsyncflags'           => q{-a --exclude='.*' --delete-excluded}
+                            . q{ --timeout=900 --rsh='ssh -oBatchMode=yes'},
+  # destination root directory
+  'rootdir'              => undef,
+  # destination type, "bld" (default) or "ext"
+  'type'                 => 'bld',
+);
+# Hook for property setter
+my %PROP_HOOK_OF = (
+  'inherit' => \&_reset_inherit,
+  'rootdir' => \&_reset_rootdir,
+);
+
+# Mirror implementations
+my %MIRROR_IMPL_OF = (
+  rdist => \&_mirror_with_rdist,
+  rsync => \&_mirror_with_rsync,
+);
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::Dest->new(%args);
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::Dest class. See above for
+#   allowed list of properties. (KEYS should be in uppercase.)
+# ------------------------------------------------------------------------------
+
+sub new {
+  my ($class, %args) = @_;
+  my $self = bless(FCM1::Base->new(%args), $class);
+  while (my ($key, $value) = each(%args)) {
+    $key = lc($key);
+    if (exists($PROP_OF{$key})) {
+        $self->{$key} = $value;
+    }
+  }
+  for my $key (@subdirs, @paths, @lockfiles, @cfgfiles) {
+    $self->{$key} = undef;
+  }
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $self->DESTROY;
+#
+# DESCRIPTION
+#   This method is called automatically when the FCM1::Dest object is
+#   destroyed.
+# ------------------------------------------------------------------------------
+
+sub DESTROY {
+  my $self = shift;
+
+  # Remove the lockfile if it is set
+  unlink $self->lockfile if $self->lockfile and -f $self->lockfile;
+
+  return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in %PROP_OF.
+# ------------------------------------------------------------------------------
+
+while (my ($key, $default) = each(%PROP_OF)) {
+  no strict 'refs';
+  *{$key} = sub {
+    my $self = shift();
+    # Set property to specified value
+    if (@_) {
+      $self->{$key} = $_[0];
+      if (exists($PROP_HOOK_OF{$key})) {
+        $PROP_HOOK_OF{$key}->($self, $key);
+      }
+    }
+    # Sets default where possible
+    if (!defined($self->{$key})) {
+      $self->{$key} = $default;
+    }
+    return $self->{$key};
+  };
+}
+
+# Remote shell property: deprecated.
+sub remote_shell {
+  my $self = shift();
+  $self->rsh_mkdir_rsh(@_);
+}
+
+# Resets properties associated with root directory.
+sub _reset_rootdir {
+  my $self = shift();
+  for my $key (@cfgfiles, @lockfiles, @miscfiles_bld, @subdirs) {
+    $self->{$key} = undef;
+  }
+}
+
+# Reset properties associated with inherited paths.
+sub _reset_inherit {
+  my $self = shift();
+  for my $key (@paths) {
+    $self->{$key} = undef;
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#
+# DESCRIPTION
+#   This method returns X, where X is a location derived from rootdir, and can
+#   be one of:
+#     bindir, bldcfg, blddir, bldlock, bldrunenv, cache, cachedir, cfg, cfgdir,
+#     donedir, etcdir, extcfg, extlock, flagsdir, incdir, libdir, parsedcfg,
+#     ppsrcdir, objdir, or tmpdir.
+#
+#   Details of these properties are explained earlier.
+# ------------------------------------------------------------------------------
+
+for my $name (@cfgfiles, @cfgfiles_type, @lockfiles, @miscfiles_bld, @subdirs) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    # If variable not set, derive it from rootdir
+    if ($self->rootdir and not defined $self->{$name}) {
+      if ($name eq 'cache') {
+        # Cache file under root/.cache
+        $self->{$name} =  File::Spec->catfile (
+          $self->cachedir, $self->setting ('CACHE'),
+        );
+
+      } elsif ($name eq 'cfg') {
+        # Configuration file of current type
+        my $method = $self->type . 'cfg';
+        $self->{$name} = $self->$method;
+
+      } elsif (grep {$name eq $_} @cfgfiles) {
+        # Configuration files under the root/cfg
+        (my $label = uc ($name)) =~ s/CFG//;
+        $self->{$name} = File::Spec->catfile (
+          $self->cfgdir, $self->setting ('CFG_NAME', $label),
+        );
+
+      } elsif (grep {$name eq $_} @lockfiles) {
+        # Lock file
+        $self->{$name} = File::Spec->catfile (
+          $self->rootdir, $self->setting ('LOCK', uc ($name)),
+        );
+
+      } elsif (grep {$name eq $_} @miscfiles_bld) {
+        # Misc file
+        $self->{$name} = File::Spec->catfile (
+          $self->rootdir, $self->setting ('BLD_MISC', uc ($name)),
+        );
+
+      } elsif ($name eq 'parsedcfg') {
+        # As-parsed configuration file of current type
+        $self->{$name} = File::Spec->catfile (
+          dirname ($self->cfg),
+          $self->setting (qw/CFG_NAME PARSED/) . basename ($self->cfg),
+        )
+
+      } elsif (grep {$name eq $_} @subdirs) {
+        # Sub-directories under the root
+        (my $label = uc ($name)) =~ s/DIR//;
+        $self->{$name} = File::Spec->catfile (
+          $self->rootdir,
+          $self->setting ('DIR', $label),
+          ($name eq 'cachedir' ? '.' . $self->type : ()),
+        );
+      }
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#
+# DESCRIPTION
+#   This method returns X, an array containing the search path of a destination
+#   directory, which can be one of:
+#     binpath, bldpath, cachepath, cfgpath, donepath, etcpath, flagspath,
+#     incpath, libpath, ppsrcpath, objpath, rootpath, srcpath, or tmppath,
+#
+#   Details of these properties are explained earlier.
+# ------------------------------------------------------------------------------
+
+for my $name (@paths) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    (my $dir = $name) =~ s/path/dir/;
+
+    if ($self->$dir and not defined $self->{$name}) {
+      my @path = ();
+
+      # Recursively inherit the search path
+      for my $d (@{ $self->inherit }) {
+        unshift @path, $d->$dir;
+      }
+
+      # Place the path of the current build in the front
+      unshift @path, $self->$dir;
+
+      $self->{$name} = \@path;
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->archive ();
+#
+# DESCRIPTION
+#   This method creates TAR archives for selected sub-directories.
+# ------------------------------------------------------------------------------
+
+sub archive {
+  my $self = shift;
+
+  # Save current directory
+  my $cwd = cwd ();
+
+  my $tar      = $self->setting (qw/OUTFILE_EXT TAR/);
+  my $verbose  = $self->verbose;
+
+  for my $name (@subdirs_tar) {
+    my $dir = $self->$name;
+
+    # Ignore unless sub-directory exists
+    next unless -d $dir;
+
+    # Change to container directory
+    my $base = basename ($dir);
+    print 'cd ', dirname ($dir), "\n" if $verbose > 2;
+    chdir dirname ($dir);
+
+    # Run "tar" command
+    my $rc = &run_command (
+      [qw/tar -czf/, $base . $tar, $base],
+      PRINT => $verbose > 1, ERROR => 'warn',
+    );
+
+    # Remove sub-directory
+    &run_command ([qw/rm -rf/, $base], PRINT => $verbose > 1) if not $rc;
+  }
+
+  # Change back to "current" directory
+  print 'cd ', $cwd, "\n" if $verbose > 2;
+  chdir $cwd;
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $authority = $obj->authority();
+#
+# DESCRIPTION
+#   Returns LOGNAME at MACHINE for this destination if LOGNAME is defined and not
+#   the same as the user ID of the current process. Returns MACHINE if LOGNAME
+#   is the same as the user ID of the current process, but MACHINE is not the
+#   same as the current hostname. Returns an empty string if LOGNAME and
+#   MACHINE are not defined or are the same as in the current process.
+# ------------------------------------------------------------------------------
+
+sub authority {
+  my $self = shift;
+  my $return = '';
+
+  if ($self->logname ne $self->config->user_id) {
+    $return = $self->logname . '@' . $self->machine;
+
+  } elsif ($self->machine ne &hostname()) {
+    $return = $self->machine;
+  }
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->clean([ITEM => <list>,] [MODE => 'ALL|CONTENT|EMPTY',]);
+#
+# DESCRIPTION
+#   This method removes files/directories from the destination. If ITEM is set,
+#   it must be a reference to a list of method names for files/directories to
+#   be removed. Otherwise, the list is determined by the destination type. If
+#   MODE is ALL, all directories/files created by the extract/build are
+#   removed. If MODE is CONTENT, only contents within sub-directories are
+#   removed. If MODE is EMPTY (default), only empty sub-directories are
+#   removed.
+# ------------------------------------------------------------------------------
+
+sub clean {
+  my ($self, %args) = @_;
+  my $mode = exists $args{MODE} ? $args{MODE} : 'EMPTY';
+  my $rc = 1;
+  my @names
+    = $args{ITEM}            ? @{$args{ITEM}}
+    : $self->type() eq 'ext' ? ('cachedir', @subdirs_ext)
+    :                          ('cachedir', @subdirs_bld, @miscfiles_bld)
+    ;
+  my @items;
+  if ($mode eq 'CONTENT') {
+    for my $name (@names) {
+      my $item = $self->$name();
+      push(@items, _directory_contents($item));
+    }
+  }
+  else {
+    for my $name (@names) {
+      my $item = $self->$name();
+      if ($mode eq 'ALL' || -d $item && !_directory_contents($item)) {
+        push(@items, $item);
+      }
+    }
+  }
+  for my $item (@items) {
+    if ($self->verbose() >= 2) {
+      printf("%s: remove\n", $item);
+    }
+    eval {rmtree($item)};
+    if ($@) {
+      w_report($@);
+      $rc = 0;
+    }
+  }
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->create ([DIR => <dir-list>,]);
+#
+# DESCRIPTION
+#   This method creates the directories of a destination. If DIR is set, it
+#   must be a reference to a list of sub-directories to be created.  Otherwise,
+#   the sub-directory list is determined by the destination type. It returns
+#   true if the destination is created or if it exists and is writable.
+# ------------------------------------------------------------------------------
+
+sub create {
+  my ($self, %args) = @_;
+
+  my $rc = 1;
+
+  my @dirs;
+  if (exists $args{DIR} and $args{DIR}) {
+    # Create only selected sub-directories
+    @dirs = @{ $args{DIR} };
+
+  } else {
+    # Create rootdir, cachedir and read-write sub-directories for extract/build
+    @dirs = (
+      qw/rootdir cachedir/,
+      ($self->type eq 'ext' ? @subdirs_ext : @subdirs_bld),
+    );
+  }
+
+  for my $name (@dirs) {
+    my $dir = $self->$name;
+
+    # Create directory if it does not already exist
+    if (not -d $dir) {
+      print 'Make directory: ', $dir, "\n" if $self->verbose > 1;
+      mkpath $dir;
+    }
+
+    # Check whether directory exists and is writable
+    if (!-d $dir) {
+      w_report 'ERROR: ', $dir, ': cannot create destination.';
+      $rc = 0;
+    }
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->create_bldrunenvsh ();
+#
+# DESCRIPTION
+#   This method creates the runtime environment script for the build.
+# ------------------------------------------------------------------------------
+
+sub create_bldrunenvsh {
+  my $self = shift;
+
+  # Path to executable files and directory for misc files
+  my @bin_paths = grep {_directory_contents($_)} @{$self->binpath()};
+  my $bin_dir = -d $self->bindir() ? $self->bindir() : undef;
+  my $etc_dir = _directory_contents($self->etcdir()) ? $self->etcdir() : undef;
+
+  # Create a runtime environment script if necessary
+  if (@bin_paths || $etc_dir) {
+    my $path = $self->bldrunenvsh();
+    open(my $handle, '>', $path) || croak("$path: cannot open ($!)\n");
+    printf($handle "#!%s\n", $self->setting(qw/TOOL SHELL/));
+    if (@bin_paths) {
+      printf($handle "PATH=%s:\$PATH\n", join(':', @bin_paths));
+      print($handle "export PATH\n");
+    }
+    if ($etc_dir) {
+      printf($handle "FCM_ETCDIR=%s\n", $etc_dir);
+      print($handle "export FCM_ETCDIR\n");
+    }
+    close($handle) || croak("$path: cannot close ($!)\n");
+
+    # Create symbolic links fcm_env.ksh and bin/fcm_env.ksh for backward
+    # compatibility
+    my $FCM_ENV_KSH = 'fcm_env.ksh';
+    for my $link (
+      File::Spec->catfile($self->rootdir, $FCM_ENV_KSH),
+      ($bin_dir ? File::Spec->catfile($bin_dir, $FCM_ENV_KSH) : ()),
+    ) {
+      if (-l $link && readlink($link) ne $path || -e $link) {
+        unlink($link);
+      }
+      if (!-l $link) {
+        symlink($path, $link) || croak("$link: cannot create symbolic link\n");
+      }
+    }
+  }
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->dearchive ();
+#
+# DESCRIPTION
+#   This method extracts from TAR archives for selected sub-directories.
+# ------------------------------------------------------------------------------
+
+sub dearchive {
+  my $self = shift;
+
+  my $tar     = $self->setting (qw/OUTFILE_EXT TAR/);
+  my $verbose = $self->verbose;
+
+  # Extract archives if necessary
+  for my $name (@subdirs_tar) {
+    my $tar_file = $self->$name . $tar;
+
+    # Check whether tar archive exists for the named sub-directory
+    next unless -f $tar_file;
+
+    # If so, extract the archive and remove it afterwards
+    &run_command ([qw/tar -xzf/, $tar_file], PRINT => $verbose > 1);
+    &run_command ([qw/rm -f/, $tar_file], PRINT => $verbose > 1);
+  }
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $name = $obj->get_pkgname_of_path ($path);
+#
+# DESCRIPTION
+#   This method returns the package name of $path if $path is in (a relative
+#   path of) $self->srcdir, or undef otherwise.
+# ------------------------------------------------------------------------------
+
+sub get_pkgname_of_path {
+  my ($self, $path) = @_;
+
+  my $relpath = File::Spec->abs2rel ($path, $self->srcdir);
+  my $name = $relpath ? [File::Spec->splitdir ($relpath)] : undef;
+
+  return $name;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   %src = $obj->get_source_files ();
+#
+# DESCRIPTION
+#   This method returns a hash (keys = package names, values = file names)
+#   under $self->srcdir.
+# ------------------------------------------------------------------------------
+
+sub get_source_files {
+  my $self = shift;
+
+  my %src;
+  if ($self->srcdir and -d $self->srcdir) {
+    &find (sub {
+      return if /^\./;                    # ignore system/hidden file
+      return if -d $File::Find::name;     # ignore directory
+
+      my $name = join (
+        '__', @{ $self->get_pkgname_of_path ($File::Find::name) },
+      );
+      $src{$name} = $File::Find::name;
+    }, $self->srcdir);
+  }
+
+  return \%src;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->mirror (\@items);
+#
+# DESCRIPTION
+#   This method mirrors @items (list of method names for directories or files)
+#   from $dest0 (which must be an instance of FCM1::Dest for a local
+#   destination) to this destination.
+# ------------------------------------------------------------------------------
+
+sub mirror {
+  my ($self, $items_ref) = @_;
+  if ($self->authority() || $self->dest0()->rootdir() ne $self->rootdir()) {
+    # Diagnostic
+    if ($self->verbose()) {
+      printf(
+        "Destination: %s\n",
+        ($self->authority() ? $self->authority() . q{:} : q{}) . $self->rootdir()
+      );
+    }
+    if ($MIRROR_IMPL_OF{$self->mirror_cmd()}) {
+      $MIRROR_IMPL_OF{$self->mirror_cmd()}->($self, $self->dest0(), $items_ref);
+    }
+    else {
+      # Unknown mirroring tool
+      w_report($self->mirror_cmd, ': unknown mirroring tool, abort.');
+      return 0;
+    }
+  }
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->_mirror_with_rdist ($dest0, \@items);
+#
+# DESCRIPTION
+#   This internal method implements $self->mirror with "rdist".
+# ------------------------------------------------------------------------------
+
+sub _mirror_with_rdist {
+  my ($self, $dest0, $items) = @_;
+
+  my $rhost = $self->authority ? $self->authority : &hostname();
+
+  # Print distfile content to temporary file
+  my @distfile = ();
+  for my $label (@$items) {
+    push @distfile, '( ' . $dest0->$label . ' ) -> ' . $rhost . "\n";
+    push @distfile, '  install ' . $self->$label . ';' . "\n";
+  }
+
+  # Set up mirroring command (use "rdist" at the moment)
+  my $command = 'rdist -R';
+  $command   .= ' -q' unless $self->verbose > 1;
+  $command   .= ' -f - 1>/dev/null';
+
+  # Diagnostic
+  my $croak = 'Cannot execute "' . $command . '"';
+  if ($self->verbose > 2) {
+    print timestamp_command ($command, 'Start');
+    print '  ', $_ for (@distfile);
+  }
+
+  # Execute the mirroring command
+  open COMMAND, '|-', $command or croak $croak, ' (', $!, '), abort';
+  for my $line (@distfile) {
+    print COMMAND $line;
+  }
+  close COMMAND or croak $croak, ' (', $?, '), abort';
+
+  # Diagnostic
+  print timestamp_command ($command, 'End  ') if $self->verbose > 2;
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->_mirror_with_rsync($dest0, \@items);
+#
+# DESCRIPTION
+#   This internal method implements $self->mirror() with "rsync".
+# ------------------------------------------------------------------------------
+
+sub _mirror_with_rsync {
+  my ($self, $dest0, $items_ref) = @_;
+  my @rsh_mkdir;
+  if ($self->authority()) {
+    @rsh_mkdir = (
+        $self->rsh_mkdir_rsh(),
+        shellwords($self->rsh_mkdir_rshflags()),
+        $self->authority(),
+        $self->rsh_mkdir_mkdir(),
+        shellwords($self->rsh_mkdir_mkdirflags()),
+    );
+  }
+  my @rsync = ($self->rsync(), shellwords($self->rsyncflags()));
+  my @rsync_verbose = ($self->verbose() > 2 ? '-v' : ());
+  my $auth = $self->authority() ? $self->authority() . q{:} : q{};
+  for my $item (@{$items_ref}) {
+    # Create container directory, as rsync does not do it automatically
+    my $dir = dirname($self->$item());
+    if (@rsh_mkdir) {
+      run_command([@rsh_mkdir, $dir], TIME => $self->verbose() > 2);
+    }
+    else {
+      mkpath($dir);
+    }
+    run_command(
+      [@rsync, @rsync_verbose, $dest0->$item(), $auth . $dir],
+      TIME => $self->verbose > 2,
+    );
+  }
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->set_lock ();
+#
+# DESCRIPTION
+#   This method sets a lock in the current destination.
+# ------------------------------------------------------------------------------
+
+sub set_lock {
+  my $self = shift;
+
+  $self->lockfile ();
+
+  if ($self->type eq 'ext' and not $self->dest0) {
+    # Only set an extract lock for the local destination
+    $self->lockfile ($self->extlock);
+
+  } elsif ($self->type eq 'bld') {
+    # Set a build lock
+    $self->lockfile ($self->bldlock);
+  }
+
+  return &touch_file ($self->lockfile) if $self->lockfile;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @cfglines = $obj->to_cfglines ([$index]);
+#
+# DESCRIPTION
+#   This method returns a list of configuration lines for the current
+#   destination. If it is set, $index is the index number of the current
+#   destination.
+# ------------------------------------------------------------------------------
+
+sub to_cfglines {
+  my ($self, $index) = @_;
+
+  my $PREFIX = $self->cfglabel($self->dest0() ? 'RDEST' : 'DEST');
+  my $SUFFIX = ($index ? $FCM1::Config::DELIMITER . $index : q{});
+
+  my @return = (
+    FCM1::CfgLine->new(label => $PREFIX . $SUFFIX, value => $self->rootdir()),
+  );
+  if ($self->dest0()) {
+    for my $name (qw{
+      logname
+      machine
+      mirror_cmd
+      rsh_mkdir_rsh
+      rsh_mkdir_rshflags
+      rsh_mkdir_mkdir
+      rsh_mkdir_mkdirflags
+      rsync
+      rsyncflags
+    }) {
+      if ($self->{$name} && $self->{$name} ne $PROP_OF{$name}) { # not default
+        push(
+          @return,
+          FCM1::CfgLine->new(
+            label => $PREFIX . $FCM1::Config::DELIMITER . uc($name) . $SUFFIX,
+            value => $self->{$name},
+          ),
+        );
+      }
+    }
+  }
+
+  return @return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = $obj->write_rules ();
+#
+# DESCRIPTION
+#   This method returns a string containing Makefile variable declarations for
+#   directories and search paths in this destination.
+# ------------------------------------------------------------------------------
+
+sub write_rules {
+  my $self   = shift;
+  my $return = '';
+
+  # FCM_*DIR*
+  for my $i (0 .. @{ $self->inherit }) {
+    for my $name (@paths) {
+      (my $label = $name) =~ s/path$/dir/;
+      my $dir = $name eq 'rootpath' ? $self->$name->[$i] : File::Spec->catfile (
+        '$(FCM_ROOTDIR' . ($i ? $i : '') . ')',
+        File::Spec->abs2rel ($self->$name->[$i], $self->rootpath->[$i]),
+      );
+
+      $return .= ($i ? '' : 'export ') . 'FCM_' . uc ($label) . ($i ? $i : '') .
+                 ' := ' . $dir . "\n";
+    }
+  }
+
+  # FCM_*PATH
+  for my $name (@paths) {
+    (my $label = $name) =~ s/path$/dir/;
+
+    $return .= 'export FCM_' . uc ($name) . ' := ';
+    for my $i (0 .. @{ $self->$name } - 1) {
+      $return .= ($i ? ':' : '') . '$(FCM_' . uc ($label) . ($i ? $i : '') . ')';
+    }
+    $return .= "\n";
+  }
+
+  $return .= "\n";
+
+  return $return;
+}
+
+# Returns contents in directory.
+sub _directory_contents {
+  my $path = shift();
+  if (!-d $path) {
+    return;
+  }
+  opendir(my $handle, $path) || croak("$path: cannot open directory ($!)\n");
+  my @items = grep {$_ ne q{.} && $_ ne q{..}} readdir($handle);
+  closedir($handle);
+  map {File::Spec->catfile($path . $_)} @items;
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/Exception.pm b/lib/FCM1/Exception.pm
new file mode 100644
index 0000000..0b0c192
--- /dev/null
+++ b/lib/FCM1/Exception.pm
@@ -0,0 +1,108 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package FCM1::Exception;
+use overload (q{""} => \&as_string);
+
+use Scalar::Util qw{blessed};
+
+# ------------------------------------------------------------------------------
+# Returns true if $e is a blessed instance of this class.
+sub caught {
+    my ($class, $e) = @_;
+    return (blessed($e) && $e->isa($class));
+}
+
+# ------------------------------------------------------------------------------
+# Constructor
+sub new {
+    my ($class, $args_ref) = @_;
+    return bless(
+        {message => q{unknown problem}, ($args_ref ? %{$args_ref} : ())},
+        $class,
+    );
+}
+
+# ------------------------------------------------------------------------------
+# Returns a string representation of this exception
+sub as_string {
+    my ($self) = @_;
+    return sprintf("%s: %s\n", blessed($self), $self->get_message());
+}
+
+# ------------------------------------------------------------------------------
+# Returns the message of this exception
+sub get_message {
+    my ($self) = @_;
+    return $self->{message};
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM1::Exception
+
+=head1 SYNOPSIS
+
+    use FCM1::Exception;
+    eval {
+        croak(FCM1::Exception->new({message => $message}));
+    };
+    if ($@) {
+        if (FCM1::Exception->caught($@)) {
+            print({STDERR} $@);
+        }
+    }
+
+=head1 DESCRIPTION
+
+This exception is raised when there is a generic problem in FCM.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->caught($e)
+
+Returns true if $e is a blessed instance of this class.
+
+=item $class->new({message=E<gt>$message})
+
+Returns a new instance of this exception. Its first argument must be a
+reference to a hash containing the detailed I<message> of the exception.
+
+=item $e->as_string()
+
+Returns a string representation of this exception.
+
+=item $e->get_message()
+
+Returns the detailed message of this exception.
+
+=back
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM1/Extract.pm b/lib/FCM1/Extract.pm
new file mode 100644
index 0000000..9dbda6f
--- /dev/null
+++ b/lib/FCM1/Extract.pm
@@ -0,0 +1,1132 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::Extract
+#
+# DESCRIPTION
+#   This is the top level class for the FCM extract system.
+#
+# ------------------------------------------------------------------------------
+
+package FCM1::Extract;
+ at ISA = qw(FCM1::ConfigSystem);
+
+# Standard pragma
+use warnings;
+use strict;
+
+# Standard modules
+use File::Path;
+use File::Spec;
+
+# FCM component modules
+use FCM1::CfgFile;
+use FCM1::CfgLine;
+use FCM1::Config;
+use FCM1::ConfigSystem;
+use FCM1::Dest;
+use FCM1::ExtractFile;
+use FCM1::ExtractSrc;
+use FCM1::Keyword;
+use FCM1::ReposBranch;
+use FCM1::SrcDirLayer;
+use FCM1::Util;
+
+# List of scalar property methods for this class
+my @scalar_properties = (
+ 'bdeclare', # list of build declarations
+ 'branches', # list of repository branches
+ 'conflict', # conflict mode
+ 'rdest'   , # remote destination information
+);
+
+# List of hash property methods for this class
+my @hash_properties = (
+ 'srcdirs' , # list of source directory extract info
+ 'files',    # list of files processed key=pkgname, value=FCM1::ExtractFile
+);
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::Extract->new;
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::Extract class.
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  my $self = FCM1::ConfigSystem->new (%args);
+
+  $self->{$_} = undef for (@scalar_properties);
+
+  $self->{$_} = {} for (@hash_properties);
+
+  bless $self, $class;
+
+  # List of sub-methods for parse_cfg
+  push @{ $self->cfg_methods }, (qw/rdest bld conflict project/);
+
+  # System type
+  $self->type ('ext');
+
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @scalar_properties.
+# ------------------------------------------------------------------------------
+
+for my $name (@scalar_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    # Argument specified, set property to specified argument
+    if (@_) {
+      $self->{$name} = $_[0];
+    }
+
+    # Default value for property
+    if (not defined $self->{$name}) {
+      if ($name eq 'bdeclare' or $name eq 'branches') {
+        # Reference to an array
+        $self->{$name} = [];
+
+      } elsif ($name eq 'rdest') {
+        # New extract destination local/remote
+        $self->{$name} = FCM1::Dest->new (DEST0 => $self->dest(), TYPE => 'ext');
+
+      } elsif ($name eq 'conflict') {
+        # Conflict mode, default to "merge"
+        $self->{$name} = 'merge';
+      }
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   %hash = %{ $obj->X () };
+#   $obj->X (\%hash);
+#
+#   $value = $obj->X ($index);
+#   $obj->X ($index, $value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @hash_properties.
+#
+#   If no argument is set, this method returns a hash containing a list of
+#   objects. If an argument is set and it is a reference to a hash, the objects
+#   are replaced by the specified hash.
+#
+#   If a scalar argument is specified, this method returns a reference to an
+#   object, if the indexed object exists or undef if the indexed object does
+#   not exist. If a second argument is set, the $index element of the hash will
+#   be set to the value of the argument.
+# ------------------------------------------------------------------------------
+
+for my $name (@hash_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my ($self, $arg1, $arg2) = @_;
+
+    # Ensure property is defined as a reference to a hash
+    $self->{$name} = {} if not defined ($self->{$name});
+
+    # Argument 1 can be a reference to a hash or a scalar index
+    my ($index, %hash);
+
+    if (defined $arg1) {
+      if (ref ($arg1) eq 'HASH') {
+        %hash = %$arg1;
+
+      } else {
+        $index = $arg1;
+      }
+    }
+
+    if (defined $index) {
+      # A scalar index is defined, set and/or return the value of an element
+      $self->{$name}{$index} = $arg2 if defined $arg2;
+
+      return (
+        exists $self->{$name}{$index} ? $self->{$name}{$index} : undef
+      );
+
+    } else {
+      # A scalar index is not defined, set and/or return the hash
+      $self->{$name} = \%hash if defined $arg1;
+      return $self->{$name};
+    }
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->check_lock_is_allowed ($lock);
+#
+# DESCRIPTION
+#   This method returns true if it is OK for $lock to exist in the destination.
+# ------------------------------------------------------------------------------
+
+sub check_lock_is_allowed {
+  my ($self, $lock) = @_;
+
+  # Allow existence of build lock in inherited extract
+  return ($lock eq $self->dest->bldlock and @{ $self->inherited });
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->invoke_extract ();
+#
+# DESCRIPTION
+#   This method invokes the extract stage of the extract system. It returns
+#   true on success.
+# ------------------------------------------------------------------------------
+
+sub invoke_extract {
+  my $self = shift;
+
+  my $rc = 1;
+
+  my @methods = (
+    'expand_cfg',       # expand URL, revision keywords, relative path, etc
+    'create_dir_stack', # analyse the branches to create an extract sequence
+    'extract_src',      # use the sequence to extract source to destination
+    'write_cfg',        # generate final configuration file
+    'write_cfg_bld',    # generate build configuration file
+  );
+
+  for my $method (@methods) {
+    $rc = $self->$method if $rc;
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->invoke_mirror ();
+#
+# DESCRIPTION
+#   This method invokes the mirror stage of the extract system. It returns
+#   true on success.
+# ------------------------------------------------------------------------------
+
+sub invoke_mirror {
+  my $self = shift;
+  return $self->rdest->mirror ([qw/bldcfg extcfg srcdir/]);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->invoke_system ();
+#
+# DESCRIPTION
+#   This method invokes the extract system. It returns true on success.
+# ------------------------------------------------------------------------------
+
+sub invoke_system {
+  my $self = shift;
+
+  my $rc = 1;
+  
+  $rc = $self->invoke_stage ('Extract', 'invoke_extract');
+  $rc = $self->invoke_stage ('Mirror', 'invoke_mirror')
+    if $rc and $self->rdest->rootdir;
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->parse_cfg_rdest(\@cfg_lines);
+#
+# DESCRIPTION
+#   This method parses the remote destination settings in the @cfg_lines.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg_rdest {
+  my ($self, $cfg_lines_ref) = @_;
+
+  # RDEST declarations
+  # ----------------------------------------------------------------------------
+  for my $line (grep {$_->slabel_starts_with_cfg('RDEST')} @{$cfg_lines_ref}) {
+    my ($d, $method) = map {lc($_)} $line->slabel_fields();
+    $method ||= 'rootdir';
+    if ($self->rdest()->can($method)) {
+      $self->rdest()->$method(expand_tilde($line->value()));
+      $line->parsed(1);
+    }
+  }
+
+  # MIRROR declaration, deprecated = RDEST::MIRROR_CMD
+  # ----------------------------------------------------------------------------
+  for my $line (grep {$_->slabel_starts_with_cfg('MIRROR')} @{$cfg_lines_ref}) {
+    $self->rdest()->mirror_cmd($line->value());
+    $line->parsed(1);
+  }
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->parse_cfg_bld (\@cfg_lines);
+#
+# DESCRIPTION
+#   This method parses the build configurations in the @cfg_lines.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg_bld {
+  my ($self, $cfg_lines) = @_;
+
+  # BLD declarations
+  # ----------------------------------------------------------------------------
+  for my $line (grep {$_->slabel_starts_with_cfg ('BDECLARE')} @$cfg_lines) {
+    # Remove BLD from label
+    my @words = $line->slabel_fields;
+
+    # Check that a declaration follows BLD
+    next if @words <= 1;
+
+    push @{ $self->bdeclare }, FCM1::CfgLine->new (
+      LABEL  => join ($FCM1::Config::DELIMITER, @words),
+      PREFIX => $self->cfglabel ('BDECLARE'),
+      VALUE  => $line->value,
+    );
+    $line->parsed (1);
+  }
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->parse_cfg_conflict (\@cfg_lines);
+#
+# DESCRIPTION
+#   This method parses the conflict settings in the @cfg_lines.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg_conflict {
+  my ($self, $cfg_lines) = @_;
+
+  # Deprecated: Override mode setting
+  # ----------------------------------------------------------------------------
+  for my $line (grep {$_->slabel_starts_with_cfg ('OVERRIDE')} @$cfg_lines) {
+    next if ($line->slabel_fields) > 1;
+    $self->conflict ($line->bvalue ? 'override' : 'fail');
+    $line->parsed (1);
+    $line->warning($line->slabel . ' is deprecated. Use ' .
+                   $line->cfglabel('CONFLICT') . ' override|merge|fail.');
+  }
+
+  # Conflict mode setting
+  # ----------------------------------------------------------------------------
+  my @conflict_modes = qw/fail merge override/;
+  my $conflict_modes_pattern = join ('|', @conflict_modes);
+  for my $line (grep {$_->slabel_starts_with_cfg ('CONFLICT')} @$cfg_lines) {
+    if ($line->value =~ /$conflict_modes_pattern/i) {
+      $self->conflict (lc ($line->value));
+      $line->parsed (1);
+
+    } elsif ($line->value =~ /^[012]$/) {
+      $self->conflict ($conflict_modes[$line->value]);
+      $line->parsed (1);
+
+    } else {
+      $line->error ($line->value, ': invalid value');
+    }
+  }
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->parse_cfg_project (\@cfg_lines);
+#
+# DESCRIPTION
+#   This method parses the project settings in the @cfg_lines.
+# ------------------------------------------------------------------------------
+
+sub parse_cfg_project {
+  my ($self, $cfg_lines) = @_;
+
+  # Flag to indicate that a declared branch revision must match with its changed
+  # revision
+  # ----------------------------------------------------------------------------
+  for my $line (grep {$_->slabel_starts_with_cfg ('REVMATCH')} @$cfg_lines) {
+    next if ($line->slabel_fields) > 1;
+    $self->setting ([qw/EXT_REVMATCH/], $line->bvalue);
+    $line->parsed (1);
+  }
+
+  # Repository, revision and source directories
+  # ----------------------------------------------------------------------------
+  for my $name (qw/repos revision dirs expdirs/) {
+    my @lines = grep {
+      $_->slabel_starts_with_cfg (uc ($name)) or
+      $name eq 'revision' and $_->slabel_starts_with_cfg ('VERSION');
+    } @$cfg_lines;
+    for my $line (@lines) {
+      my @names = $line->slabel_fields;
+      shift @names;
+
+      # Detemine package and tag
+      my $tag     = pop @names;
+      my $pckroot = $names[0];
+      my $pck     = join ($FCM1::Config::DELIMITER, @names);
+
+      # Check that $tag and $pckroot are defined
+      next unless $tag and $pckroot;
+
+      # Check if branch already exists.
+      # If so, set $branch to point to existing branch
+      my $branch = undef;
+      for (@{ $self->branches }) {
+        next unless $_->package eq $pckroot and $_->tag eq $tag;
+
+        $branch = $_;
+        last;
+      }
+
+      # Otherwise, create a new branch
+      if (not $branch) {
+        $branch = FCM1::ReposBranch->new (PACKAGE => $pckroot, TAG => $tag,);
+
+        push @{ $self->branches }, $branch;
+      }
+
+      if ($name eq 'repos' or $name eq 'revision') {
+        # Branch location or revision
+        $branch->$name ($line->value);
+
+      } else { # $name eq 'dirs' or $name eq 'expdirs'
+        # Source directory or expandable source directory
+        if ($pck eq $pckroot and $line->value !~ m#^/#) {
+          # Sub-package name not set and source directory quoted as a relative
+          # path, determine package name from path name
+          $pck = join (
+            $FCM1::Config::DELIMITER,
+            ($pckroot, File::Spec->splitdir ($line->value)),
+          );
+        }
+
+        # A "/" is equivalent to the top (empty) package
+        my $value = ($line->value =~ m#^/+$#) ? '' : $line->value;
+        $branch->$name ($pck, $value);
+      }
+
+      $line->parsed (1);
+    }
+  }
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->expand_cfg ();
+#
+# DESCRIPTION
+#   This method expands the settings of the extract configuration.
+# ------------------------------------------------------------------------------
+
+sub expand_cfg {
+  my $self = shift;
+
+  my $rc = 1;
+  for my $use (@{ $self->inherit }) {
+    $rc = $use->expand_cfg if $rc;
+  }
+
+  return $rc unless $rc;
+
+  # Establish a set of source directories from the "base repository"
+  my %base_branches = ();
+
+  # Inherit "base" set of source directories from re-used extracts
+  for my $use (@{ $self->inherit }) {
+    my @branches = @{ $use->branches };
+
+    for my $branch (@branches) {
+      my $package              = $branch->package;
+      $base_branches{$package} = $branch unless exists $base_branches{$package};
+    }
+  }
+
+  for my $branch (@{ $self->branches }) {
+    # Expand URL keywords if necessary
+    if ($branch->repos) {
+      my $repos = FCM1::Util::tidy_url(FCM1::Keyword::expand($branch->repos()));
+      $branch->repos ($repos) if $repos ne $branch->repos;
+    }
+
+    # Check that repository type and revision are set
+    if ($branch->repos and &is_url ($branch->repos)) {
+      $branch->type ('svn') unless $branch->type;
+      $branch->revision ('head') unless $branch->revision;
+
+    } else {
+      $branch->type ('user') unless $branch->type;
+      $branch->revision ('user') unless $branch->revision;
+    }
+
+    $rc = $branch->expand_revision if $rc; # Get revision number from keywords
+    $rc = $branch->expand_path     if $rc; # Expand relative path to full path
+    $rc = $branch->expand_all      if $rc; # Search sub-directories
+    last unless $rc;
+
+    my $package = $branch->package;
+
+    if (exists $base_branches{$package}) {
+      # A base branch for this package exists
+
+      # If current branch has no source directory, use the set provided by the
+      # base branch
+      my %dirs = %{ $branch->dirs };
+      $branch->add_base_dirs ($base_branches{$package}) unless keys %dirs;
+
+    } else {
+      # This package does not yet have a base branch, set this branch as base
+      $base_branches{$package} = $branch;
+    }
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->create_dir_stack ();
+#
+# DESCRIPTION
+#   This method creates a hash of source directories to be processed. If the
+#   flag INHERITED is set to true, the source directories are assumed processed
+#   and extracted.
+# ------------------------------------------------------------------------------
+
+sub create_dir_stack {
+  my $self = shift;
+  my %args = @_;
+
+  # Inherit from USE ext cfg
+  for my $use (@{ $self->inherit }) {
+    $use->create_dir_stack () or return 0;
+    my %use_srcdirs = %{ $use->srcdirs };
+
+    while (my ($key, $value) = each %use_srcdirs) {
+      $self->srcdirs ($key, $value);
+
+      # Re-set destination to current destination
+      my @path = split (/$FCM1::Config::DELIMITER/, $key);
+      $self->srcdirs ($key)->{DEST} = File::Spec->catfile (
+        $self->dest->srcdir, @path,
+      );
+    }
+  }
+
+  # Build stack from current ext cfg
+  for my $branch (@{ $self->branches }) {
+    my %branch_dirs = %{ $branch->dirs };
+
+    for my $dir (keys %branch_dirs) {
+      # Check whether source directory is already in the list
+      if (not $self->srcdirs ($dir)) { # if not, create it
+        $self->srcdirs ($dir, {
+          DEST  => File::Spec->catfile (
+            $self->dest->srcdir, split (/$FCM1::Config::DELIMITER/, $dir)
+          ),
+          STACK => [],
+          FILES => {},
+        });
+      }
+
+      my $stack = $self->srcdirs ($dir)->{STACK}; # copy reference
+
+      # Create a new layer in the input stack
+      my $layer = FCM1::SrcDirLayer->new (
+        NAME      => $dir,
+        PACKAGE   => $branch->package,
+        TAG       => $branch->tag,
+        LOCATION  => $branch->dirs ($dir),
+        REPOSROOT => $branch->repos,
+        REVISION  => $branch->revision,
+        TYPE      => $branch->type,
+        EXTRACTED => @{ $self->inherited }
+                     ? $self->srcdirs ($dir)->{DEST} : undef,
+      );
+
+      # Check whether layer is already in the stack
+      my $exist = grep {
+        $_->location eq $layer->location and $_->revision eq $layer->revision;
+      } @{ $stack };
+
+      if (not $exist) {
+        # If not already exist, put layer into stack
+
+        # Note: user stack always comes last
+        if (! $layer->user and exists $stack->[-1] and $stack->[-1]->user) {
+          my $lastlayer = pop @{ $stack };
+          push @{ $stack }, $layer;
+          $layer = $lastlayer;
+        }
+
+        push @{ $stack }, $layer;
+
+      } elsif ($layer->user) {
+
+        # User layer already exists, overwrite it
+        $stack->[-1] = $layer;
+
+      }
+    }
+  }
+
+  # Use the cache to sort the source directory layer hash
+  return $self->compare_setting (METHOD_LIST => ['sort_dir_stack']);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   ($rc, \@new_lines) = $self->sort_dir_stack ($old_lines);
+#
+# DESCRIPTION
+#   This method sorts thesource directories hash to be processed.
+# ------------------------------------------------------------------------------
+
+sub sort_dir_stack {
+  my ($self, $old_lines) = @_;
+
+  my $rc = 0;
+
+  my %old = ();
+  if ($old_lines) {
+    for my $line (@$old_lines) {
+      $old{$line->label} = $line->value;
+    }
+  }
+
+  my %new;
+
+  # Compare each layer to base layer, discard unnecessary layers
+  DIR: for my $srcdir (keys %{ $self->srcdirs }) {
+    my @stack = ();
+
+    while (my $layer = shift @{ $self->srcdirs ($srcdir)->{STACK} }) {
+      if ($layer->user) {
+        # Local file system branch, check that the declared location exists
+        if (-d $layer->location) {
+          # Local file system branch always takes precedence
+          push @stack, $layer;
+
+        } else {
+          w_report 'ERROR: ', $layer->location, ': declared source directory ',
+                   'does not exists ';
+          $rc = undef;
+          last DIR;
+        }
+
+      } else {
+        my $key = join ($FCM1::Config::DELIMITER, (
+          $srcdir, $layer->location, $layer->revision
+        ));
+
+        unless ($layer->extracted and $layer->commit) {
+          # See if commit revision information is cached
+          if (keys %old and exists $old{$key}) {
+            $layer->commit ($old{$key});
+
+          } else {
+            $layer->get_commit;
+            $rc = 1;
+          }
+
+          # Check source directory for commit revision, if necessary
+          if (not $layer->commit) {
+            w_report 'Error: cannot determine the last changed revision of ',
+                     $layer->location;
+            $rc = undef;
+            last DIR;
+          }
+
+          # Set cache directory for layer
+          my $tag_ver = $layer->tag . '__' . $layer->commit;
+          $layer->cachedir (File::Spec->catfile (
+            $self->dest->cachedir,
+            split (/$FCM1::Config::DELIMITER/, $srcdir),
+            $tag_ver,
+          ));
+        }
+
+        # New line in cache config file
+        $new{$key} = $layer->commit;
+
+        # Push this layer in the stack:
+        # 1. it has a different revision compared to the top layer
+        # 2. it is the top layer (base line code)
+        if (@stack > 0) {
+          push @stack, $layer if $layer->commit != $stack[0]->commit;
+
+        } else {
+          push @stack, $layer;
+        }
+
+      }
+    }
+
+    $self->srcdirs ($srcdir)->{STACK} = \@stack;
+  }
+
+  # Write "commit cache" file
+  my @new_lines;
+  if (defined ($rc)) {
+    for my $key (sort keys %new) {
+      push @new_lines, FCM1::CfgLine->new (label => $key, value => $new{$key});
+    }
+  }
+
+  return ($rc, \@new_lines);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->extract_src ();
+#
+# DESCRIPTION
+#   This internal method performs the extract of the source directories and
+#   files if necessary.
+# ------------------------------------------------------------------------------
+
+sub extract_src {
+  my $self = shift;
+  my $rc = 1;
+
+  # Ensure destinations exist and are directories
+  for my $srcdir (values %{ $self->srcdirs }) {
+    last if not $rc;
+    if (-f $srcdir->{DEST}) {
+      w_report $srcdir->{DEST},
+               ': destination exists and is not a directory, abort.';
+      $rc = 0;
+    }
+  }
+
+  # Retrieve previous/record current extract configuration for each file.
+  $rc = $self->compare_setting (
+    CACHEBASE => $self->setting ('CACHE_FILE_SRC'),
+    METHOD_LIST => ['compare_setting_srcfiles'],
+  ) if $rc;
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   ($rc, \@new_lines) = $self->compare_setting_srcfiles ($old_lines);
+#
+# DESCRIPTION
+#   For each file to be extracted, this method creates an instance of an
+#   FCM1::ExtractFile object. It then compares its file's sources to determine
+#   if they have changed. If so, it will allow the FCM1::ExtractFile to
+#   "re-extract" the file to the destination. Otherwise, it will set
+#   FCM1::ExtractFile->dest_status to a null string to denote an "unchanged"
+#   dest_status.
+#
+# SEE ALSO
+#   FCM1::ConfigSystem->compare_setting.
+# ------------------------------------------------------------------------------
+
+sub compare_setting_srcfiles {
+  my ($self, $old_lines) = @_;
+  my $rc = 1;
+
+  # Retrieve previous extract configuration for each file
+  # ----------------------------------------------------------------------------
+  my %old = ();
+  if ($old_lines) {
+    for my $line (@$old_lines) {
+      $old{$line->label} = $line->value;
+    }
+  }
+
+  # Build up a sequence using a FCM1::ExtractFile object for each file
+  # ----------------------------------------------------------------------------
+  for my $srcdir (values %{ $self->srcdirs }) {
+    my %pkgnames0; # (to be) list of package names in the base layer
+    for my $i (0 .. @{ $srcdir->{STACK} } - 1) {
+      my $layer = $srcdir->{STACK}->[$i];
+      # Update the cache for each layer of the stack if necessary
+      $layer->update_cache unless $layer->extracted or -d $layer->localdir;
+
+      # Get list of files in the cache or local directory
+      my %pkgnames;
+      for my $file (($layer->get_files)) {
+        my $pkgname = join (
+          '/', split (/$FCM1::Config::DELIMITER/, $layer->name), $file
+        );
+        $pkgnames0{$pkgname} = 1 if $i == 0; # store package name in base layer
+        $pkgnames{$pkgname} = 1; # store package name in the current layer
+        if (not $self->files ($pkgname)) {
+          $self->files ($pkgname, FCM1::ExtractFile->new (
+            conflict => $self->conflict,
+            dest     => $self->dest->srcpath,
+            pkgname  => $pkgname,
+          ));
+
+          # Base is empty
+          $self->files ($pkgname)->src->[0] = FCM1::ExtractSrc->new (
+            id      => $layer->tag,
+            pkgname => $pkgname,
+          ) if $i > 0;
+        }
+        my $cache = File::Spec->catfile ($layer->localdir, $file);
+        push @{ $self->files ($pkgname)->src }, FCM1::ExtractSrc->new (
+          cache   => $cache,
+          id      => $layer->tag,
+          pkgname => $pkgname,
+          rev     => ($layer->user ? (stat ($cache))[9] : $layer->commit),
+          uri     => join ('/', $layer->location, $file),
+        );
+      }
+
+      # List of removed files in this layer (relative to base layer)
+      if ($i > 0) {
+        for my $pkgname (keys %pkgnames0) {
+          push @{ $self->files ($pkgname)->src }, FCM1::ExtractSrc->new (
+            id      => $layer->tag,
+            pkgname => $pkgname,
+          ) if not exists $pkgnames{$pkgname}
+        }
+      }
+    }
+  }
+
+  # Compare with old settings
+  # ----------------------------------------------------------------------------
+  my %new = ();
+  for my $key (sort keys %{ $self->files }) {
+    # Set up value for cache
+    my @sources = ();
+    for my $src (@{ $self->files ($key)->src }) {
+      push @sources, (defined ($src->uri) ? ($src->uri . '@' . $src->rev) : '');
+    }
+
+    my $value = join ($FCM1::Config::DELIMITER, @sources);
+
+    # Set FCM1::ExtractFile->dest_status to "unchanged" if value is unchanged
+    if (exists($old{$key}) && $old{$key} eq $value && !grep {!$_} @sources) {
+      $self->files($key)->dest_status('');
+    }
+
+    # Write current settings
+    $new{$key} = $value;
+  }
+
+  # Delete those that exist in previous extract but not in current
+  # ----------------------------------------------------------------------------
+  for my $key (sort keys %old) {
+    next if exists $new{$key};
+    $self->files ($key, FCM1::ExtractFile->new (
+      dest    => $self->dest->srcpath,
+      pkgname => $key,
+    ));
+  }
+
+  # Extract each file, if necessary
+  # ----------------------------------------------------------------------------
+  for my $key (sort keys %{ $self->files }) {
+    $rc = $self->files ($key)->run if defined ($rc);
+    last if not defined ($rc);
+  }
+
+  # Report status
+  # ----------------------------------------------------------------------------
+  if (defined ($rc) and $self->verbose) {
+    my %src_status_count = ();
+    my %dest_status_count = ();
+    for my $key (sort keys %{ $self->files }) {
+      # Report changes in destination in verbose 1 or above
+      my $dest_status = $self->files ($key)->dest_status;
+      my $src_status = $self->files ($key)->src_status;
+      next unless $self->verbose and $dest_status;
+
+      if ($dest_status and $dest_status ne '?') {
+        if (exists $dest_status_count{$dest_status}) {
+          $dest_status_count{$dest_status}++;
+
+        } else {
+          $dest_status_count{$dest_status} = 1;
+        }
+      }
+
+      if ($src_status and $src_status ne '?') {
+        if (exists $src_status_count{$src_status}) {
+          $src_status_count{$src_status}++;
+
+        } else {
+          $src_status_count{$src_status} = 1;
+        }
+      }
+
+      # Destination status in column 1, source status in column 2
+      if ($self->verbose > 1) {
+        my @srcs = @{$self->files ($key)->src_actual};
+        print ($dest_status ? $dest_status : ' ');
+        print ($src_status ? $src_status : ' ');
+        print ' ' x 5, $key;
+        print ' (', join (', ', map {$_->id} @srcs), ')' if @srcs;
+        print "\n";
+      }
+    }
+
+    # Report number of files in each dest_status category
+    if (%dest_status_count) {
+      print 'Column 1: ' if $self->verbose > 1;
+      print 'Destination status summary:', "\n";
+      for my $key (sort keys %FCM1::ExtractFile::DEST_STATUS_CODE) {
+        next unless $key;
+        next if not exists ($dest_status_count{$key});
+        print '  No of files ';
+        print '[', $key, '] ' if $self->verbose > 1;
+        print $FCM1::ExtractFile::DEST_STATUS_CODE{$key}, ': ',
+              $dest_status_count{$key}, "\n";
+      }
+    }
+
+    # Report number of files in each dest_status category
+    if (%src_status_count) {
+      print 'Column 2: ' if $self->verbose > 1;
+      print 'Source status summary:', "\n";
+      for my $key (sort keys %FCM1::ExtractFile::SRC_STATUS_CODE) {
+        next unless $key;
+        next if not exists ($src_status_count{$key});
+        print '  No of files ';
+        print '[', $key, '] ' if $self->verbose > 1;
+        print $FCM1::ExtractFile::SRC_STATUS_CODE{$key}, ': ',
+              $src_status_count{$key}, "\n";
+      }
+    }
+  }
+
+  # Record configuration of current extract for each file
+  # ----------------------------------------------------------------------------
+  my @new_lines;
+  if (defined ($rc)) {
+    for my $key (sort keys %new) {
+      push @new_lines, FCM1::CfgLine->new (label => $key, value => $new{$key});
+    }
+  }
+
+  return ($rc, \@new_lines);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @array = $self->sort_bdeclare ();
+#
+# DESCRIPTION
+#   This method returns sorted build declarations, filtering out repeated
+#   entries, where possible.
+# ------------------------------------------------------------------------------
+
+sub sort_bdeclare {
+  my $self = shift;
+
+  # Get list of build configuration labels that can be declared multiple times
+  my %cfg_keyword = map {
+    ($self->cfglabel ($_), 1)
+  } split (/$FCM1::Config::DELIMITER_LIST/, $self->setting ('CFG_KEYWORD'));
+
+  my @bdeclares = ();
+  for my $d (reverse @{ $self->bdeclare }) {
+    # Reconstruct array from bottom up
+    # * always add declarations that can be declared multiple times
+    # * otherwise add only if it is declared below
+    unshift @bdeclares, $d
+      if exists $cfg_keyword{uc (($d->slabel_fields)[0])} or
+         not grep {$_->slabel eq $d->slabel} @bdeclares;
+  }
+
+  return (sort {$a->slabel cmp $b->slabel} @bdeclares);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @cfglines = $obj->to_cfglines ();
+#
+# DESCRIPTION
+#   See description of FCM1::ConfigSystem->to_cfglines for further information.
+# ------------------------------------------------------------------------------
+
+sub to_cfglines {
+  my ($self) = @_;
+
+  return (
+    FCM1::ConfigSystem::to_cfglines($self),
+
+    $self->rdest->to_cfglines (),
+    FCM1::CfgLine->new (),
+
+    @{ $self->bdeclare } ? (
+      FCM1::CfgLine::comment_block ('Build declarations'),
+      map {
+        FCM1::CfgLine->new (label => $_->label, value => $_->value)
+      } ($self->sort_bdeclare),
+      FCM1::CfgLine->new (),
+    ) : (),
+
+    FCM1::CfgLine::comment_block ('Project and branches'),
+    (map {($_->to_cfglines ())} @{ $self->branches }),
+
+    ($self->conflict ne 'merge') ? (
+      FCM1::CfgLine->new (
+        label => $self->cfglabel ('CONFLICT'), value => $self->conflict,
+      ),
+      FCM1::CfgLine->new (),
+    ) : (),
+  );
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @cfglines = $obj->to_cfglines_bld ();
+#
+# DESCRIPTION
+#   Returns a list of configuration lines of the current extract suitable for
+#   feeding into the build system.
+# ------------------------------------------------------------------------------
+
+sub to_cfglines_bld {
+  my ($self) = @_;
+
+  my $dest = $self->rdest->rootdir ? 'rdest' : 'dest';
+  my $root = File::Spec->catfile ('$HERE', '..');
+
+  my @inherits;
+  my @no_inherits;
+  if (@{ $self->inherit }) {
+    # List of inherited builds
+    for (@{ $self->inherit }) {
+      push @inherits, FCM1::CfgLine->new (
+        label => $self->cfglabel ('USE'), value => $_->$dest->rootdir
+      );
+    }
+
+    # List of files that should not be inherited
+    for my $key (sort keys %{ $self->files }) {
+      next unless $self->files ($key)->dest_status eq 'd';
+      my $label = join ('::', (
+        $self->cfglabel ('INHERIT'),
+        $self->cfglabel ('FILE'),
+        split (m#/#, $self->files ($key)->pkgname),
+      ));
+      push @no_inherits, FCM1::CfgLine->new (label => $label, value => 'false');
+    }
+  }
+
+  return (
+    FCM1::CfgLine::comment_block ('File header'),
+    (map
+      {my ($lbl, $val) = @{$_}; FCM1::CfgLine->new(label => $lbl, value => $val)}
+      (
+        [$self->cfglabel('CFGFILE') . $FCM1::Config::DELIMITER . 'TYPE'   , 'bld'],
+        [$self->cfglabel('CFGFILE') . $FCM1::Config::DELIMITER . 'VERSION', '1.0'],
+        [],
+      )
+    ),
+
+    @{ $self->inherit } ? (
+      @inherits,
+      @no_inherits,
+      FCM1::CfgLine->new (),
+    ) : (),
+
+    FCM1::CfgLine::comment_block ('Destination'),
+    FCM1::CfgLine->new (label => $self->cfglabel ('DEST'), value => $root),
+    FCM1::CfgLine->new (),
+
+    @{ $self->bdeclare } ? (
+      FCM1::CfgLine::comment_block ('Build declarations'),
+      map {
+        FCM1::CfgLine->new (label => $_->slabel, value => $_->value)
+      } ($self->sort_bdeclare),
+      FCM1::CfgLine->new (),
+    ) : (),
+  );
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->write_cfg ();
+#
+# DESCRIPTION
+#   This method writes the configuration file at the end of the run. It calls
+#   $self->write_cfg_system ($cfg) to write any system specific settings.
+# ------------------------------------------------------------------------------
+
+sub write_cfg {
+  my $self = shift;
+
+  my $cfg = FCM1::CfgFile->new (TYPE => $self->type);
+  $cfg->lines ([$self->to_cfglines()]);
+  $cfg->print_cfg ($self->dest->extcfg);
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $self->write_cfg_bld ();
+#
+# DESCRIPTION
+#   This internal method writes the build configuration file.
+# ------------------------------------------------------------------------------
+
+sub write_cfg_bld {
+  my $self = shift;
+
+  my $cfg = FCM1::CfgFile->new (TYPE => 'bld');
+  $cfg->lines ([$self->to_cfglines_bld()]);
+  $cfg->print_cfg ($self->dest->bldcfg);
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/ExtractConfigComparator.pm b/lib/FCM1/ExtractConfigComparator.pm
new file mode 100644
index 0000000..bd4d50c
--- /dev/null
+++ b/lib/FCM1/ExtractConfigComparator.pm
@@ -0,0 +1,371 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+################################################################################
+# A generic reporter of the comparator's result
+{
+    package Reporter;
+
+    ############################################################################
+    # Class method: Constructor
+    sub new {
+        my ($class) = @_;
+        return bless(\do{my $annon_scalar}, $class);
+    }
+
+    ############################################################################
+    # Class method: Factory for Reporter object
+    sub get_reporter {
+        my ($self, $comparator) = @_;
+        my $class = defined($comparator->get_wiki()) ? 'WikiReporter'
+                  :                                    'TextReporter'
+                  ;
+        return $class->new();
+    }
+
+    ############################################################################
+    # Reports the results
+    sub report {
+        my ($self, $comparator) = @_;
+        if (keys(%{$comparator->get_log_of()})) {
+            print("Revisions at which extract declarations are modified:\n\n");
+        }
+        $self->report_impl($comparator);
+    }
+
+    ############################################################################
+    # Does the actual reporting
+    sub report_impl {
+        my ($self, $comparator) = @_;
+    }
+}
+
+################################################################################
+# Reports the comparator's result in Trac wiki format
+{
+    package WikiReporter;
+    our @ISA = qw{Reporter};
+
+    use FCM1::CmUrl;
+    use FCM1::Keyword;
+    use FCM1::Util qw{tidy_url};
+
+    ############################################################################
+    # Reports the comparator's result
+    sub report_impl {
+        my ($self, $comparator) = @_;
+        # Output in wiki format
+        my $wiki_url = FCM1::CmUrl->new(
+            URL => tidy_url(FCM1::Keyword::expand($comparator->get_wiki()))
+        );
+        my $base_trac
+            = $comparator->get_wiki()
+            ? FCM1::Keyword::get_browser_url($wiki_url->project_url())
+            : $wiki_url;
+        if (!$base_trac) {
+            $base_trac = $wiki_url;
+        }
+
+        for my $key (sort keys(%{$comparator->get_log_of()})) {
+            my $branch_trac = FCM1::Keyword::get_browser_url($key);
+            $branch_trac =~ s{\A $base_trac (?:/*|\z)}{source:}xms;
+            print("[$branch_trac]:\n");
+            my %branch_of = %{$comparator->get_log_of()->{$key}};
+            for my $rev (sort {$b <=> $a} keys(%branch_of)) {
+                print(
+                    $branch_of{$rev}->display_svnlog($rev, $base_trac), "\n",
+                );
+            }
+            print("\n");
+        }
+    }
+}
+
+################################################################################
+# Reports the comparator's result in simple text format
+{
+    package TextReporter;
+    our @ISA = qw{Reporter};
+
+    use FCM1::Config;
+
+    my $SEPARATOR = q{-} x 80 . "\n";
+
+    ############################################################################
+    # Reports the comparator's result
+    sub report_impl {
+        my ($self, $comparator) = @_;
+        for my $key (sort keys(%{$comparator->get_log_of()})) {
+            # Output in plain text format
+            print $key, ':', "\n";
+            my %branch_of = %{$comparator->get_log_of()->{$key}};
+            if (FCM1::Config->instance()->verbose() > 1) {
+                for my $rev (sort {$b <=> $a} keys(%branch_of)) {
+                    print(
+                        $SEPARATOR, $branch_of{$rev}->display_svnlog($rev), "\n"
+                    );
+                }
+            }
+            else {
+                print(join(q{ }, sort {$b <=> $a} keys(%branch_of)), "\n");
+            }
+            print $SEPARATOR, "\n";
+        }
+    }
+}
+
+package FCM1::ExtractConfigComparator;
+
+use FCM1::CmUrl;
+use FCM1::Extract;
+
+################################################################################
+# Class method: Constructor
+sub new {
+    my ($class, $args_ref) = @_;
+    return bless({%{$args_ref}}, $class);
+}
+
+################################################################################
+# Returns an array containing the 2 configuration files to compare
+sub get_files {
+    my ($self) = @_;
+    return (wantarray() ? @{$self->{files}} : $self->{files});
+}
+
+################################################################################
+# Returns the wiki link on wiki mode
+sub get_wiki {
+    my ($self) = @_;
+    return $self->{wiki};
+}
+
+################################################################################
+# Returns the result log
+sub get_log_of {
+    my ($self) = @_;
+    return (wantarray() ? %{$self->{log_of}} : $self->{log_of});
+}
+
+################################################################################
+# Invokes the comparator
+sub invoke {
+    my ($self) = @_;
+
+    # Reads the extract configurations
+    my (@cfg, $rc);
+    for my $i (0 .. 1) {
+        $cfg[$i] = FCM1::Extract->new();
+        $cfg[$i]->cfg()->src($self->get_files()->[$i]);
+        $cfg[$i]->parse_cfg();
+        $rc = $cfg[$i]->expand_cfg();
+        if (!$rc) {
+            e_report();
+        }
+    }
+
+    # Get list of URLs
+    # --------------------------------------------------------------------------
+    my @urls = ();
+    for my $i (0 .. 1) {
+        # List of branches in each extract configuration file
+        my @branches = @{$cfg[$i]->branches()};
+        BRANCH:
+        for my $branch (@branches) {
+            # Ignore declarations of local directories
+            if ($branch->type() eq 'user') {
+                next BRANCH;
+            }
+
+            # List of SRC declarations in each branch
+            my %dirs = %{$branch->dirs()};
+
+            for my $dir (values(%dirs)) {
+                # Set up a new instance of FCM1::CmUrl object for each SRC
+                my $cm_url = FCM1::CmUrl->new (
+                    URL => $dir . (
+                        $branch->revision() ? '@' . $branch->revision() : q{}
+                    ),
+                );
+
+                $urls[$i]{$cm_url->branch_url()}{$dir} = $cm_url;
+            }
+        }
+    }
+
+    # Compare
+    # --------------------------------------------------------------------------
+    $self->{log_of} = {};
+    for my $i (0 .. 1) {
+        # Compare the first file with the second one and then vice versa
+        my $j = ($i == 0) ? 1 : 0;
+
+        for my $branch (sort keys(%{$urls[$i]})) {
+            if (exists($urls[$j]{$branch})) {
+                # Same REPOS declarations in both files
+                DIR:
+                for my $dir (sort keys(%{$urls[$i]{$branch}})) {
+                    if (exists($urls[$j]{$branch}{$dir})) {
+                        if ($i == 1) {
+                            next DIR;
+                        }
+
+                        my $this_url = $urls[$i]{$branch}{$dir};
+                        my $that_url = $urls[$j]{$branch}{$dir};
+
+                        # Compare their last changed revisions
+                        my $this_rev
+                            = $this_url->svninfo(FLAG => 'commit:revision');
+                        my $that_rev
+                            = $that_url->svninfo(FLAG => 'commit:revision');
+
+                        # Make sure last changed revisions differ
+                        if ($this_rev eq $that_rev) {
+                            next DIR;
+                        }
+
+                        # Not interested in the log before the minimum revision
+                        my $min_rev
+                            = $this_url->pegrev() > $that_url->pegrev()
+                              ? $that_url->pegrev() : $this_url->pegrev();
+
+                        $this_rev = $min_rev if $this_rev < $min_rev;
+                        $that_rev = $min_rev if $that_rev < $min_rev;
+
+                        # Get list of changed revisions using the commit log
+                        my $u = ($this_rev > $that_rev) ? $this_url : $that_url;
+                        my %revs = $u->svnlog(REV => [$this_rev, $that_rev]);
+
+                        REV:
+                        for my $rev (keys %revs) {
+                            # Check if revision is already in the list
+                            if (
+                                   exists($self->{log_of}{$branch}{$rev})
+                                || $rev == $min_rev
+                            ) {
+                                next REV;
+                            }
+
+                            # Get list of changed paths. Accept this revision
+                            # only if it contains changes in the current branch
+                            my %paths  = %{$revs{$rev}{paths}};
+
+                            PATH:
+                            for my $path (keys(%paths)) {
+                                my $change_url
+                                    = FCM1::CmUrl->new(URL => $u->root() . $path);
+
+                                if ($change_url->branch() eq $u->branch()) {
+                                    $self->{log_of}{$branch}{$rev} = $u;
+                                    last PATH;
+                                }
+                            }
+                        }
+                    }
+                    else {
+                        $self->_report_added(
+                            $urls[$i]{$branch}{$dir}->url_peg(), $i, $j);
+                    }
+                }
+            }
+            else {
+                $self->_report_added($branch, $i, $j);
+            }
+        }
+    }
+
+    my $reporter = Reporter->get_reporter($self);
+    $reporter->report($self);
+    return $rc;
+}
+
+################################################################################
+# Reports added/deleted declaration
+sub _report_added {
+    my ($self, $branch, $i, $j) = @_;
+    printf(
+        "%s:\n  in    : %s\n  not in: %s\n\n",
+        $branch, $self->get_files()->[$i], $self->get_files()->[$j],
+    );
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM1::ExtractConfigComparator
+
+=head1 SYNOPSIS
+
+    use FCM1::ExtractConfigComparator;
+    my $comparator = FCM1::ExtractConfigComparator->new({files => \@files});
+    $comparator->invoke();
+
+=head1 DESCRIPTION
+
+An object of this class represents a comparator of FCM extract configuration.
+It is used to compare the VC branch declarations in 2 FCM extract configuration
+files.
+
+=head1 METHODS
+
+=over 4
+
+=item C<new({files =E<gt> \@files, wiki =E<gt> $wiki})>
+
+Constructor.
+
+=item get_files()
+
+Returns an array containing the 2 configuration files to compare.
+
+=item get_wiki()
+
+Returns the wiki link on wiki mode.
+
+=item invoke()
+
+Invokes the comparator.
+
+=back
+
+=head1 TO DO
+
+More documentation.
+
+Improve the parser for extract configuration.
+
+Separate the comparator with the reporters.
+
+Add reporter to display HTML.
+
+More unit tests.
+
+=head1 SEE ALSO
+
+L<FCM1::Extract|FCM1::Extract>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM1/ExtractFile.pm b/lib/FCM1/ExtractFile.pm
new file mode 100644
index 0000000..309e662
--- /dev/null
+++ b/lib/FCM1/ExtractFile.pm
@@ -0,0 +1,423 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::ExtractFile
+#
+# DESCRIPTION
+#   Select/combine a file in different branches and extract it to destination.
+#
+# ------------------------------------------------------------------------------
+
+use warnings;
+use strict;
+
+package FCM1::ExtractFile;
+use base qw{FCM1::Base};
+
+use FCM1::Util      qw{run_command w_report};
+use File::Basename qw{dirname};
+use File::Compare  qw{compare};
+use File::Copy     qw{copy};
+use File::Path     qw{mkpath};
+use File::Spec;
+use File::Temp     qw(tempfile);
+
+# List of property methods for this class
+my @scalar_properties = (
+  'conflict',    # conflict mode
+  'dest',        # search path to destination file
+  'dest_status', # destination status, see below
+  'pkgname',     # package name of this file
+  'src',         # list of FCM1::ExtractSrc, specified for this file
+  'src_actual',  # list of FCM1::ExtractSrc, actually used by this file
+  'src_status',  # source status, see below
+);
+
+# Status code definition for $self->dest_status
+our %DEST_STATUS_CODE = (
+  ''  => 'unchanged',
+  'M' => 'modified',
+  'A' => 'added',
+  'a' => 'added, overridding inherited',
+  'D' => 'deleted',
+  'd' => 'deleted, overridding inherited',
+  '?' => 'irrelevant',
+);
+
+# Status code definition for $self->src_status
+our %SRC_STATUS_CODE = (
+  'A' => 'added by a branch',
+  'B' => 'from the base',
+  'D' => 'deleted by a branch',
+  'M' => 'modified by a branch',
+  'G' => 'merged from 2+ branches',
+  'O' => 'overridden by a branch',
+  '?' => 'irrelevant',
+);
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::ExtractFile->new ();
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::ExtractFile class.
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  my $self = FCM1::Base->new (%args);
+
+  for (@scalar_properties) {
+    $self->{$_} = exists $args{$_} ? $args{$_} : undef;
+  }
+
+  bless $self, $class;
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @scalar_properties.
+# ------------------------------------------------------------------------------
+
+for my $name (@scalar_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    # Argument specified, set property to specified argument
+    if (@_) {
+      $self->{$name} = $_[0];
+    }
+
+    # Default value for property
+    if (not defined $self->{$name}) {
+      if ($name eq 'conflict') {
+        $self->{$name} = 'merge'; # default to "merge" mode
+
+      } elsif ($name eq 'dest' or $name eq 'src' or $name eq 'src_actual') {
+        $self->{$name} = [];      # default to an empty list
+      }
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->run();
+#
+# DESCRIPTION
+#   This method runs only if $self->dest_status is not defined. It updates the
+#   destination according to the source in the list and the conflict mode
+#   setting. It updates the file in $self->dest as appropriate and sets
+#   $self->dest_status. (See above.) This method returns true on success.
+# ------------------------------------------------------------------------------
+
+sub run {
+  my ($self) = @_;
+  my $rc = 1;
+
+  if (not defined ($self->dest_status)) {
+    # Assume file unchanged
+    $self->dest_status ('');
+
+    if (@{ $self->src }) {
+      my $used;
+      # Determine or set up a file for comparing with the destination
+      ($rc, $used) = $self->run_get_used();
+
+      # Attempt to compare the destination with $used. Update on change.
+      if ($rc) {
+        $rc = defined ($used) ? $self->run_update($used) : $self->run_delete();
+      }
+
+    } else {
+      # No source, delete file in destination
+      $self->src_status ('?');
+      $rc = $self->run_delete();
+    }
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->run_delete();
+#
+# DESCRIPTION
+#   This method is part of run(). It detects this file in the destination path.
+#   If this file is in the current destination, it attempts to delete it and
+#   sets the dest_status to "D". If this file is in an inherited destination,
+#   it sets the dest_status to "d".
+# ------------------------------------------------------------------------------
+
+sub run_delete {
+  my ($self) = @_;
+
+  my $rc = 1;
+
+  $self->dest_status ('?');
+  for my $i (0 .. @{ $self->dest } - 1) {
+    my $dest = File::Spec->catfile ($self->dest->[$i], $self->pkgname);
+    next unless -f $dest;
+    if ($i == 0) {
+      $rc = unlink $dest;
+      $self->dest_status ('D');
+
+    } else {
+      $self->dest_status ('d');
+      last;
+    }
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   ($rc, $used) = $obj->run_get_used();
+#
+# DESCRIPTION
+#   This method is part of run(). It attempts to work out or set up the $used
+#   file. ($used is undef if it is not defined in a branch for this file.)
+# ------------------------------------------------------------------------------
+
+sub run_get_used {
+  my ($self) = @_;
+  my $rc = 1;
+  my $used;
+
+  my @sources = ($self->src->[0]);
+  my $src_status = 'B';
+  if (defined ($self->src->[0]->cache)) {
+    # File exists in base branch
+    for my $i (1 .. @{ $self->src } - 1) {
+      if (defined ($self->src->[$i]->cache)) {
+        # Detect changes in this file between base branch and branch $i
+        push @sources, $self->src->[$i]
+          if &compare ($self->src->[0]->cache, $self->src->[$i]->cache);
+
+      } else {
+        # File deleted in branch $i
+        @sources = ($self->src->[$i]);
+        last unless $self->conflict eq 'override';
+      }
+    }
+
+    if ($rc) {
+      if (@sources > 2) {
+        if ($self->conflict eq 'fail') {
+          # On conflict, fail in fail mode
+          w_report 'ERROR: ', $self->pkgname,
+                   ': modified in 2+ branches in fail conflict mode.';
+          $rc = undef;
+
+        } elsif ($self->conflict eq 'override') {
+          $used = $sources[-1]->cache;
+          $src_status = 'O';
+
+        } else {
+          # On conflict, attempt to merge in merge mode
+          ($rc, $used) = $self->run_get_used_by_merge (@sources);
+          $src_status = 'G' if $rc;
+        }
+
+      } else {
+        # 0 or 1 change, use last source
+        if (defined $sources[-1]->cache) {
+          $used = $sources[-1]->cache;
+          $src_status = 'M' if @sources > 1;
+
+        } else {
+          $src_status = 'D';
+        }
+      }
+    }
+
+  } else {
+    # File does not exist in base branch
+    @sources = ($self->src->[-1]);
+    $used = $self->src->[1]->cache;
+    $src_status = (defined ($used) ? 'A' : 'D');
+    if ($self->conflict ne 'override' and defined ($used)) {
+      for my $i (1 - @{ $self->src } .. -2) {
+        # Allow this only if files are the same in all branches
+        my $file = $self->src->[$i]->cache;
+        if ((not defined ($file)) or &compare ($used, $file)) {
+          w_report 'ERROR: ', $self->pkgname, ': cannot merge:',
+                   ' not found in base branch,',
+                   ' but differs in subsequent branches.';
+          $rc = undef;
+          last;
+
+        } else {
+          unshift @sources, $self->src->[$i];
+        }
+      }
+    }
+  }
+
+  $self->src_status ($src_status);
+  $self->src_actual (\@sources);
+
+  return ($rc, $used);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   ($rc, $used) = $obj->run_get_used_by_merge(@soruces);
+#
+# DESCRIPTION
+#   This method is part of run_get_used(). It attempts to merge the files in
+#   @sources and return a temporary file $used. @sources should be an array of
+#   FCM1::ExtractSrc objects. On success, $rc will be set to true.
+# ------------------------------------------------------------------------------
+
+sub run_get_used_by_merge {
+  my ($self, @sources) = @_;
+  my $rc = 1;
+
+  # Get temporary file
+  my ($fh, $used) = &tempfile ('fcm.ext.merge.XXXXXX', UNLINK => 1);
+  close $fh or die $used, ': cannot close';
+
+  for my $i (2 .. @sources - 1) {
+    # Invoke the diff3 command to merge
+    my $mine = ($i == 2 ? $sources[1]->cache : $used);
+    my $older = $sources[0]->cache;
+    my $yours = $sources[$i]->cache;
+    my @command = (
+      $self->setting (qw/TOOL DIFF3/),
+      split (/\s+/, $self->setting (qw/TOOL DIFF3FLAGS/)),
+      $mine, $older, $yours,
+    );
+    my $code;
+    my @out = &run_command (
+      \@command,
+      METHOD => 'qx',
+      ERROR  => 'ignore',
+      PRINT  => $self->verbose > 1,
+      RC     => \$code,
+      TIME   => $self->verbose > 2,
+    );
+
+    if ($code) {
+      # Failure, report and return
+      my $m = ($code == 1)
+              ? 'cannot resolve conflicts:'
+              : $self->setting (qw/TOOL DIFF3/) . 'command failed';
+      w_report 'ERROR: ', $self->pkgname, ': merge - ', $m;
+      if ($code == 1 and $self->verbose) {
+        for (0 .. $i) {
+          my $src = $sources[$_]->uri eq $sources[$_]->cache
+                    ? $sources[$_]->cache
+                    : ($sources[$_]->uri . '@' . $sources[$_]->rev);
+          w_report '  source[', $_, ']=', $src;
+        }
+
+        for (0 .. $i) {
+          w_report '  cache', $_, '=', $sources[$_]->cache;
+        }
+
+        w_report @out if $self->verbose > 2;
+      }
+      $rc = undef;
+      last;
+
+    } else {
+      # Success, write result to temporary file
+      open FILE, '>', $used or die $used, ': cannot open (', $!, ')';
+      print FILE @out;
+      close FILE or die $used, ': cannot close (', $!, ')';
+
+      # File permission, use most permissive combination of $mine and $yours
+      my $perm = ((stat($mine))[2] & 07777) | ((stat($yours))[2] & 07777);
+      chmod ($perm, $used);
+    }
+  }
+
+  return ($rc, $used);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->run_update($used_file);
+#
+# DESCRIPTION
+#   This method is part of run(). It compares the $used_file with the one in
+#   the destination. If the file does not exist in the destination or if its
+#   content is out of date, the destination is updated with the content in the
+#   $used_file. Returns true on success.
+# ------------------------------------------------------------------------------
+
+sub run_update {
+  my ($self, $used_file) = @_;
+  my ($is_diff, $is_diff_in_perms, $is_in_prev, $rc) = (1, 1, undef, 1);
+
+  # Compare with the previous version if it exists
+  DEST:
+  for my $i (0 .. @{$self->dest()} - 1) {
+    my $prev_file = File::Spec->catfile($self->dest()->[$i], $self->pkgname());
+    if (-f $prev_file) {
+      $is_in_prev = $i;
+      $is_diff = compare($used_file, $prev_file);
+      $is_diff_in_perms = (stat($used_file))[2] != (stat($prev_file))[2];
+      last DEST;
+    }
+  }
+  if (!$is_diff && !$is_diff_in_perms) {
+    return $rc;
+  }
+
+  # Update destination
+  my $dest_file = File::Spec->catfile($self->dest()->[0], $self->pkgname());
+  if ($is_diff) {
+    my $dir = dirname($dest_file);
+    if (!-d $dir) {
+      mkpath($dir);
+    }
+    $rc = copy($used_file, $dest_file);
+  }
+  $rc &&= chmod((stat($used_file))[2] & oct(7777), $dest_file);
+  if ($rc) {
+    $self->dest_status(
+        $is_in_prev          ? 'a'
+      : defined($is_in_prev) ? 'M'
+      :                        'A'
+    );
+  }
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/ExtractSrc.pm b/lib/FCM1/ExtractSrc.pm
new file mode 100644
index 0000000..0cba119
--- /dev/null
+++ b/lib/FCM1/ExtractSrc.pm
@@ -0,0 +1,100 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::ExtractSrc
+#
+# DESCRIPTION
+#   This class is used by the extract system to define the functionalities of a
+#   source file (or directory) in a branch.
+#
+# ------------------------------------------------------------------------------
+
+package FCM1::ExtractSrc;
+ at ISA = qw(FCM1::Base);
+
+# Standard pragma
+use warnings;
+use strict;
+
+# FCM component modules
+use FCM1::Base;
+
+# List of scalar property methods for this class
+my @scalar_properties = (
+  'cache',   # location of the cache of this file in the current extract
+  'id',      # short ID of the branch where this file is from
+  'ignore',  # if set to true, ignore this file from this source
+  'pkgname', # package name of this file
+  'rev',     # last changed revision/timestamp of this file
+  'uri',     # URL/source path of this file
+);
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::ExtractSrc->new (%args);
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::ExtractSrc class. See
+#   @scalar_properties above for allowed list of properties in the constructor.
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  my $self = FCM1::Base->new (%args);
+
+  for (@scalar_properties) {
+    $self->{$_} = exists $args{$_} ? $args{$_} : undef;
+  }
+
+  bless $self, $class;
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @scalar_properties.
+# ------------------------------------------------------------------------------
+
+for my $name (@scalar_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    # Argument specified, set property to specified argument
+    if (@_) {
+      $self->{$name} = $_[0];
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/Interactive.pm b/lib/FCM1/Interactive.pm
new file mode 100644
index 0000000..4339b76
--- /dev/null
+++ b/lib/FCM1/Interactive.pm
@@ -0,0 +1,144 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package FCM1::Interactive;
+use base qw{Exporter};
+
+our @EXPORT_OK = qw{get_input};
+
+use FCM1::Util::ClassLoader;
+
+my $DEFAULT_IMPL_CLASS = 'FCM1::Interactive::InputGetter::CLI';
+my %DEFAULT_IMPL_CLASS_OPTIONS = ();
+
+my $IMPL_CLASS = $DEFAULT_IMPL_CLASS;
+my %IMPL_CLASS_OPTIONS = %DEFAULT_IMPL_CLASS_OPTIONS;
+
+################################################################################
+# Returns the name of the current class/settings for getting input
+sub get_impl {
+    return (wantarray() ? ($IMPL_CLASS, \%IMPL_CLASS_OPTIONS) : $IMPL_CLASS);
+}
+
+################################################################################
+# Returns the name of the current class/settings for getting input
+sub get_default_impl {
+    return (
+        wantarray()
+        ? ($DEFAULT_IMPL_CLASS, \%DEFAULT_IMPL_CLASS_OPTIONS)
+        : $DEFAULT_IMPL_CLASS
+    );
+}
+
+################################################################################
+# Sets the name of the class/settings for getting input
+sub set_impl {
+    my ($impl_class, $impl_class_options_ref) = @_;
+    if ($impl_class) {
+        $IMPL_CLASS = $impl_class;
+        if ($impl_class_options_ref) {
+            %IMPL_CLASS_OPTIONS = (%{$impl_class_options_ref});
+        }
+        else {
+            %IMPL_CLASS_OPTIONS = ();
+        }
+    }
+}
+
+################################################################################
+# Gets an input from the user and returns it
+sub get_input {
+    my (%options) = @_;
+    my ($class_name, $class_options_ref) = get_impl();
+    FCM1::Util::ClassLoader::load($class_name);
+    %options = map {lc($_), $options{$_}} keys(%options);
+    return $class_name->new({%{$class_options_ref}, %options})->invoke();
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM1::Interactive
+
+=head1 SYNOPSIS
+
+    use FCM1::Interactive;
+    FCM1::Interactive::set_impl('My::InputGetter', {option1 => 'value1', ...});
+    $answer = FCM1::Interactive::get_input(
+        title   => 'My title',
+        message => 'Would you like to ...?',
+        type    => 'yn',
+        default => 'n',
+    );
+
+=head1 DESCRIPTION
+
+Common interface for getting an interactive user reply. The default is to use a
+L<FCM1::Interactive::InputGetter::CLI|FCM1::Interactive::InputGetter::CLI> object
+with no extra options.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item get_impl()
+
+Returns the class that implements the function for get_input(%options). In
+scalar context, returns the class name only. In list context, returns the class
+name and the extra hash options that would be passed to its constructor.
+
+=item get_default_impl()
+
+Returns the defaut values for get_impl().
+
+=item set_impl($impl_class,$impl_class_options_ref)
+
+Sets the class that implements the function for get_input(%options). The name
+of the class is given in $impl_class. Any extra options that should be given to
+the constructor should be set in the hash reference $impl_class_options_ref.
+
+=item get_input(%options)
+
+Calls the appropriate function to get an input string from the user, and
+returns it.
+
+Input options are: I<title>, for a short title of the prompt, I<message>, for
+the message prompt, I<type> for the prompt type, and I<default> for the default
+value of the return value.
+
+Prompt type can be YN (yes or no), YNA (yes, no or all) or input (for an input
+string).
+
+=back
+
+=head1 SEE ALSO
+
+L<FCM1::Interactive::InputGetter|FCM1::Interactive::InputGetter>,
+L<FCM1::Interactive::InputGetter::CLI|FCM1::Interactive::InputGetter::CLI>,
+L<FCM1::Interactive::InputGetter::GUI|FCM1::Interactive::InputGetter::GUI>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM1/Interactive/InputGetter.pm b/lib/FCM1/Interactive/InputGetter.pm
new file mode 100644
index 0000000..5749133
--- /dev/null
+++ b/lib/FCM1/Interactive/InputGetter.pm
@@ -0,0 +1,135 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package FCM1::Interactive::InputGetter;
+
+use Carp qw{croak};
+
+################################################################################
+# Constructor
+sub new {
+    my ($class, $args_ref) = @_;
+    return bless({%{$args_ref}}, $class);
+}
+
+################################################################################
+# Methods: get_*
+for my $key (
+    ############################################################################
+    # Returns the title of the prompt
+    'title',
+    ############################################################################
+    # Returns the message of the prompt
+    'message',
+    ############################################################################
+    # Returns the of the prompt
+    'type',
+    ############################################################################
+    # Returns the default return value
+    'default',
+) {
+    no strict qw{refs};
+    my $getter = "get_$key";
+    *$getter = sub {
+        my ($self) = @_;
+        return $self->{$key};
+    }
+}
+
+################################################################################
+# Invokes the getter
+sub invoke {
+    my ($self) = @_;
+    croak("FCM1::Interactive::InputGetter->invoke() not implemented.");
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM1::Interactive::TxtInputGetter
+
+=head1 SYNOPSIS
+
+    use FCM1::Interactive::TxtInputGetter;
+    $answer = FCM1::Interactive::get_input(
+        title   => 'My title',
+        message => 'Would you like to ...?',
+        type    => 'yn',
+        default => 'n',
+    );
+
+=head1 DESCRIPTION
+
+An object of this abstract class is used by
+L<FCM1::Interactive|FCM1::Interactive> to get a user reply.
+
+=head1 METHODS
+
+=over 4
+
+=item new($args_ref)
+
+Constructor, normally invoked via L<FCM1::Interactive|FCM1::Interactive>.
+
+Input options are: I<title>, for a short title of the prompt, I<message>, for
+the message prompt, I<type> for the prompt type, and I<default> for the default
+value of the return value.
+
+Prompt type can be YN (yes or no), YNA (yes, no or all) or input (for an input
+string).
+
+=item get_title()
+
+Returns the title of the prompt.
+
+=item get_message()
+
+Returns the message of the prompt.
+
+=item get_type()
+
+Returns the type of the prompt, can be YN (yes or no), YNA (yes, no or all) or
+input (for an input string).
+
+=item get_default()
+
+Returns the default return value of invoke().
+
+=item invoke()
+
+Gets an input string from the user, and returns it. Sub-classes must override
+this method.
+
+=back
+
+=head1 SEE ALSO
+
+L<FCM1::Interactive|FCM1::Interactive>,
+L<FCM1::Interactive::TxtInputGetter|FCM1::Interactive::TxtInputGetter>,
+L<FCM1::Interactive::GUIInputGetter|FCM1::Interactive::GUIInputGetter>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM1/Interactive/InputGetter/CLI.pm b/lib/FCM1/Interactive/InputGetter/CLI.pm
new file mode 100644
index 0000000..b28c3af
--- /dev/null
+++ b/lib/FCM1/Interactive/InputGetter/CLI.pm
@@ -0,0 +1,100 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package FCM1::Interactive::InputGetter::CLI;
+use base qw{FCM1::Interactive::InputGetter};
+
+my $DEF_MSG = q{ (or just press <return> for "%s")};
+my %EXTRA_MSG_FOR = (
+    yn  => qq{\nEnter "y" or "n"},
+    yna => qq{\nEnter "y", "n" or "a"},
+);
+my %CHECKER_FOR = (
+    yn  => sub {$_[0] eq 'y' || $_[0] eq 'n'},
+    yna => sub {$_[0] eq 'y' || $_[0] eq 'n' || $_[0] eq 'a'},
+);
+
+sub invoke {
+    my ($self) = @_;
+    my $type = $self->get_type() ? lc($self->get_type()) : q{};
+    my $message
+        = $self->get_message()
+        . (exists($EXTRA_MSG_FOR{$type}) ? $EXTRA_MSG_FOR{$type} : q{})
+        . ($self->get_default() ? sprintf($DEF_MSG, $self->get_default()) : q{})
+        . q{: }
+        ;
+    while (1) {
+        print($message);
+        my $answer = readline(STDIN);
+        chomp($answer);
+        if (!$answer && $self->get_default()) {
+            $answer = $self->get_default();
+        }
+        if (!exists($CHECKER_FOR{$type}) || $CHECKER_FOR{$type}->($answer)) {
+            return $answer;
+        }
+    }
+    return;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM1::Interactive::InputGetter::CLI
+
+=head1 SYNOPSIS
+
+    use FCM1::Interactive;
+    $answer = FCM1::Interactive::get_input(
+        title   => 'My title',
+        message => 'Would you like to ...?',
+        type    => 'yn',
+        default => 'n',
+    );
+
+=head1 DESCRIPTION
+
+This is a solid implementation of
+L<FCM1::Interactive::InputGetter|FCM1::Interactive::InputGetter>. It gets a user
+reply from STDIN using a prompt on STDOUT.
+
+=head1 METHODS
+
+See L<FCM1::Interactive::InputGetter|FCM1::Interactive::InputGetter> for a list of
+methods.
+
+=head1 TO DO
+
+Use IO::Prompt.
+
+=head1 SEE ALSO
+
+L<FCM1::Interactive|FCM1::Interactive>,
+L<FCM1::Interactive::InputGetter|FCM1::Interactive::InputGetter>,
+L<FCM1::Interactive::InputGetter::GUI|FCM1::Interactive::InputGetter::GUI>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM1/Interactive/InputGetter/GUI.pm b/lib/FCM1/Interactive/InputGetter/GUI.pm
new file mode 100644
index 0000000..f2dfcb9
--- /dev/null
+++ b/lib/FCM1/Interactive/InputGetter/GUI.pm
@@ -0,0 +1,261 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package FCM1::Interactive::InputGetter::GUI;
+use base qw{FCM1::Interactive::InputGetter};
+
+use Tk;
+
+################################################################################
+# Returns the geometry string for the pop up message box
+sub get_geometry {
+    my ($self) = @_;
+    return $self->{geometry};
+}
+
+################################################################################
+# Invokes the getter
+sub invoke {
+    my ($self) = @_;
+    my $answer;
+    local $| = 1;
+
+    # Create a main window
+    my $mw = MainWindow->new();
+    $mw->title($self->get_title());
+
+    # Define the default which applies if the dialog box is just closed or
+    # the user selects 'cancel'
+    $answer = $self->get_default() ? $self->get_default() : q{};
+
+    if (defined($self->get_type()) && $self->get_type() =~ qr{\A yn}ixms) {
+        # Create a yes-no(-all) dialog box
+
+        # If TYPE is YNA then add a third button: 'all'
+        my $buttons = $self->get_type() =~ qr{a \z}ixms ? 3 : 2;
+
+        # Message of the dialog box
+        $mw->Label('-text' => $self->get_message())->grid(
+            '-row'        => 0,
+            '-column'     => 0,
+            '-columnspan' => $buttons,
+            '-padx'       => 10,
+            '-pady'       => 10,
+        );
+
+        # The "yes" button
+        my $y_b = $mw->Button(
+            '-text'      => 'Yes',
+            '-underline' => 0,
+            '-command'   => sub {$answer = 'y'; $mw->destroy()},
+        )
+        ->grid('-row' => 1, '-column' => 0, '-padx' => 5, '-pady' => 5);
+
+        # The "no" button
+        my $n_b = $mw->Button (
+            '-text'      => 'No',
+            '-underline' => 0,
+            '-command'   => sub {$answer = 'n'; $mw->destroy()},
+        )
+        ->grid('-row' => 1, '-column' => 1, '-padx' => 5, '-pady' => 5);
+
+        # The "all" button
+        my $a_b;
+        if ($buttons == 3) {
+            $a_b = $mw->Button(
+                '-text'      => 'All',
+                '-underline' => 0,
+                '-command'   => sub {$answer = 'a'; $mw->destroy()},
+            )
+            ->grid('-row' => 1, '-column' => 2, '-padx' => 5, '-pady' => 5);
+        }
+
+        # Keyboard binding
+        if ($buttons == 3) {
+            $mw->bind('<Key>' => sub {
+                my $button
+                    = $Tk::event->K() eq 'Y' || $Tk::event->K() eq 'y' ? $y_b
+                    : $Tk::event->K() eq 'N' || $Tk::event->K() eq 'n' ? $n_b
+                    : $Tk::event->K() eq 'A' || $Tk::event->K() eq 'a' ? $a_b
+                    :                                                    undef
+                    ;
+                if (defined($button)) {
+                    $button->invoke();
+                }
+            });
+        }
+        else {
+            $mw->bind('<Key>' => sub {
+                my $button
+                    = $Tk::event->K() eq 'Y' || $Tk::event->K() eq 'y' ? $y_b
+                    : $Tk::event->K() eq 'N' || $Tk::event->K() eq 'n' ? $n_b
+                    :                                                    undef
+                    ;
+                if (defined($button)) {
+                    $button->invoke();
+                }
+            });
+        }
+
+        # Handle the situation when the user attempts to quit the window
+        $mw->protocol('WM_DELETE_WINDOW', sub {
+            if (self->get_default()) {
+                $answer = $self->get_default();
+            }
+            $mw->destroy();
+        });
+    }
+    else {
+        # Create a dialog box to obtain an input string
+        # Message of the dialog box
+        $mw->Label('-text' => $self->get_message())->grid(
+            '-row'    => 0,
+            '-column' => 0,
+            '-padx'   => 5,
+            '-pady'   => 5,
+        );
+
+        # Entry box for the user to type in the input string
+        my $entry   = $answer;
+        my $input_e = $mw->Entry(
+            '-textvariable' => \$entry,
+            '-width'        => 40,
+        )
+        ->grid(
+            '-row'    => 0,
+            '-column' => 1,
+            '-sticky' => 'ew',
+            '-padx'   => 5,
+            '-pady'   => 5,
+        );
+
+        my $b_f = $mw->Frame->grid(
+            '-row'        => 1,
+            '-column'     => 0,
+            '-columnspan' => 2,
+            '-sticky'     => 'e',
+        );
+
+        # An OK button to accept the input string
+        my $ok_b = $b_f->Button (
+            '-text' => 'OK',
+            '-command' => sub {$answer = $entry; $mw->destroy()},
+        )
+        ->grid('-row' => 0, '-column' => 0, '-padx' => 5, '-pady' => 5);
+
+        # A Cancel button to reject the input string
+        my $cancel_b = $b_f->Button(
+            '-text' => 'Cancel',
+            '-command' => sub {$answer = undef; $mw->destroy()},
+        )
+        ->grid('-row' => 0, '-column' => 1, '-padx' => 5, '-pady' => 5);
+
+        # Keyboard binding
+        $mw->bind ('<Key>' => sub {
+            if ($Tk::event->K eq 'Return' or $Tk::event->K eq 'KP_Enter') {
+                $ok_b->invoke();
+            }
+            elsif ($Tk::event->K eq 'Escape') {
+                $cancel_b->invoke();
+            }
+        });
+
+        # Allow the entry box to expand
+        $mw->gridColumnconfigure(1, '-weight' => 1);
+
+        # Set initial focus on the entry box
+        $input_e->focus();
+        $input_e->icursor('end');
+    }
+
+    $mw->geometry($self->get_geometry());
+
+    # Switch on "always on top" property for $mw
+    $mw->property(
+        qw/set _NET_WM_STATE ATOM/,
+        32,
+        ['_NET_WM_STATE_STAYS_ON_TOP'],
+        ($mw->toplevel()->wrapper())[0],
+    );
+
+    MainLoop();
+    return $answer;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM1::Interactive::InputGetter::GUI
+
+=head1 SYNOPSIS
+
+    use FCM1::Interactive;
+    $answer = FCM1::Interactive::get_input(
+        title   => 'My title',
+        message => 'Would you like to ...?',
+        type    => 'yn',
+        default => 'n',
+    );
+
+=head1 DESCRIPTION
+
+This is a solid implementation of
+L<FCM1::Interactive::InputGetter|FCM1::Interactive::InputGetter>. It gets a user
+reply from a TK pop up message box.
+
+=head1 METHODS
+
+See L<FCM1::Interactive::InputGetter|FCM1::Interactive::InputGetter> for a list of
+inherited methods.
+
+=over 4
+
+=item new($args_ref)
+
+As in L<FCM1::Interactive::InputGetter|FCM1::Interactive::InputGetter>, but also
+accept a I<geometry> element for setting the geometry string of the pop up
+message box.
+
+=item get_geometry()
+
+Returns the geometry string for the pop up message box.
+
+=back
+
+=head1 TO DO
+
+Tidy up the logic of invoke(). Separate the logic for YN/A box and string input
+box, probably using a strategy pattern. Factor out the logic for the display
+and the return value.
+
+=head1 SEE ALSO
+
+L<FCM1::Interactive|FCM1::Interactive>,
+L<FCM1::Interactive::InputGetter|FCM1::Interactive::InputGetter>,
+L<FCM1::Interactive::InputGetter::CLI|FCM1::Interactive::InputGetter::CLI>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM1/Keyword.pm b/lib/FCM1/Keyword.pm
new file mode 100644
index 0000000..6ed97fc
--- /dev/null
+++ b/lib/FCM1/Keyword.pm
@@ -0,0 +1,175 @@
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package FCM1::Keyword;
+
+use FCM::Context::Locator;
+
+# Returns/Sets the FCM 2 utility functional object.
+{   my $UTIL;
+    sub get_util {
+        $UTIL;
+    }
+    sub set_util {
+        $UTIL = $_[0];
+    }
+}
+
+# Expands (the keywords in) the specfied location (and REV), and returns them
+sub expand {
+    my ($in_loc, $in_rev) = @_;
+    my $target = $in_rev ? $in_loc . '@' . $in_rev : $in_loc;
+    my $locator = FCM::Context::Locator->new($target);
+    _unparse_loc(get_util()->loc_as_normalised($locator), $in_rev);
+}
+
+# Returns the corresponding browser URL for the input VC location
+sub get_browser_url {
+    my ($in_loc, $in_rev) = @_;
+    my $target = $in_rev ? $in_loc . '@' . $in_rev : $in_loc;
+    my $locator = FCM::Context::Locator->new($target);
+    get_util()->loc_browser_url($locator);
+}
+
+# Un-expands the specfied location (and REV) to keywords, and returns them
+sub unexpand {
+    my ($in_loc, $in_rev) = @_;
+    my $target = $in_rev ? $in_loc . '@' . $in_rev : $in_loc;
+    my $locator = FCM::Context::Locator->new($target);
+    _unparse_loc(get_util()->loc_as_keyword($locator), $in_rev);
+}
+
+# If $in_rev, returns (LOC, REV). Otherwise, returns LOC at REV
+sub _unparse_loc {
+    my ($loc, $in_rev) = @_;
+    if (!$loc) {
+        return;
+    }
+    if ($in_rev) {
+        my ($l, $r) = $loc =~ qr{\A (.*?) @([^@]+) \z}msx;
+        if ($l && $r) {
+            return ($l, $r);
+        }
+    }
+    return $loc;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM1::Keyword
+
+=head1 SYNOPSIS
+
+    use FCM1::Keyword;
+
+    $loc = FCM1::Keyword::expand('fcm:namespace/path at rev-keyword');
+    $loc = FCM1::Keyword::unexpand('svn://host/namespace/path@1234');
+
+    ($loc, $rev) = FCM1::Keyword::expand('fcm:namespace/path', 'rev-keyword');
+    ($loc, $rev) = FCM1::Keyword::unexpand('svn://host/namespace/path', 1234);
+
+    $loc = FCM1::Keyword::get_browser_url('fcm:namespace/path');
+    $loc = FCM1::Keyword::get_browser_url('svn://host/namespace/path');
+
+    $loc = FCM1::Keyword::get_browser_url('fcm:namespace/path at 1234');
+    $loc = FCM1::Keyword::get_browser_url('svn://host/namespace/path@1234');
+
+    $loc = FCM1::Keyword::get_browser_url('fcm:namespace/path', 1234);
+    $loc = FCM1::Keyword::get_browser_url('svn://host/namespace/path', 1234);
+
+=head1 DESCRIPTION
+
+Provides a compatibility layer for code in FCM1::* name space by wrapping the
+keyword related functions in L<FCM::Util|FCM::Util>. An instance of
+L<FCM::Util|FCM::Util> must be set via the set_util($value) function before
+using the other functions.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item expand($loc)
+
+Expands FCM keywords in $loc and returns the result.
+
+If $loc is a I<fcm> scheme URI, the leading part (before any "/" or "@"
+characters) of the URI opaque is the namespace of a FCM location keyword. This
+is expanded into the actual value. Optionally, $loc can be suffixed with a peg
+revision (an "@" followed by any characters). If a peg revision is a FCM
+revision keyword, it is expanded into the actual revision.
+
+=item expand($loc,$rev)
+
+Same as C<expand($loc)>, but $loc should not contain a peg revision. Returns a
+list containing the expanded version of $loc and $rev.
+
+=item get_browser_url($loc)
+
+Given a repository $loc in a known keyword namespace, returns the corresponding
+URL for the code browser.
+
+Optionally, $loc can be suffixed with a peg revision (an "@" followed by any
+characters).
+
+=item get_browser_url($loc,$rev)
+
+Same as get_browser_url($loc), but the revision should be specified using $rev
+but not pegged with $loc.
+
+=item get_util()
+
+Returns the L<FCM::Util|FCM::Util> instance (set by set_util($value)).
+
+=item set_util($value)
+
+Sets the L<FCM::Util|FCM::Util> instance.
+
+=item unexpand($loc)
+
+Does the opposite of expand($loc). Returns the FCM location keyword equivalence
+of $loc. If the $loc can be mapped using 2 or more namespaces, the namespace
+that results in the longest substitution is used. Optionally, $loc can be
+suffixed with a peg revision (an "@" followed by any characters). If a peg
+revision is a known revision, it is turned into its corresponding revision
+keyword.
+
+=item unexpand($loc,$rev)
+
+Same as unexpand($loc), but $loc should not contain a peg revision. Returns a
+list containing the unexpanded version of $loc and $rev 
+
+=item 
+
+=back
+
+=head1 SEE ALSO
+
+L<FCM::System|FCM::System>,
+L<FCM::Util|FCM::Util>
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/lib/FCM1/ReposBranch.pm b/lib/FCM1/ReposBranch.pm
new file mode 100644
index 0000000..1194d64
--- /dev/null
+++ b/lib/FCM1/ReposBranch.pm
@@ -0,0 +1,528 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::ReposBranch
+#
+# DESCRIPTION
+#   This class contains methods for gathering information for a repository
+#   branch. It currently supports Subversion repository and local user
+#   directory.
+#
+# ------------------------------------------------------------------------------
+
+use warnings;
+use strict;
+
+package FCM1::ReposBranch;
+use base qw{FCM1::Base};
+
+use FCM1::CfgLine;
+use FCM1::Keyword;
+use FCM1::Util      qw{expand_tilde is_url run_command w_report};
+use File::Basename qw{dirname};
+use File::Find     qw{find};
+use File::Spec;
+
+# List of scalar property methods for this class
+my @scalar_properties = (
+  'package',  # package name of which this repository belongs
+  'repos',    # repository branch root URL/path
+  'revision', # the revision of this branch
+  'tag',      # "tag" name of this branch of the repository
+  'type',     # repository type
+);
+
+# List of hash property methods for this class
+my @hash_properties = (
+  'dirs',    # list of non-recursive directories in this branch
+  'expdirs', # list of recursive directories in this branch
+);
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::ReposBranch->new (%args);
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::ReposBranch class. See
+#   @scalar_properties above for allowed list of properties in the constructor.
+#   (KEYS should be in uppercase.)
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  my $self = FCM1::Base->new (%args);
+
+  for (@scalar_properties) {
+    $self->{$_} = exists $args{uc ($_)} ? $args{uc ($_)} : undef;
+  }
+
+  $self->{$_} = {} for (@hash_properties);
+
+  bless $self, $class;
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @scalar_properties.
+# ------------------------------------------------------------------------------
+
+for my $name (@scalar_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    # Argument specified, set property to specified argument
+    if (@_) {
+      $self->{$name} = $_[0];
+    }
+
+    return $self->{$name};
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   %hash = %{ $obj->X () };
+#   $obj->X (\%hash);
+#
+#   $value = $obj->X ($index);
+#   $obj->X ($index, $value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @hash_properties.
+#
+#   If no argument is set, this method returns a hash containing a list of
+#   objects. If an argument is set and it is a reference to a hash, the objects
+#   are replaced by the specified hash.
+#
+#   If a scalar argument is specified, this method returns a reference to an
+#   object, if the indexed object exists or undef if the indexed object does
+#   not exist. If a second argument is set, the $index element of the hash will
+#   be set to the value of the argument.
+# ------------------------------------------------------------------------------
+
+for my $name (@hash_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my ($self, $arg1, $arg2) = @_;
+
+    # Ensure property is defined as a reference to a hash
+    $self->{$name} = {} if not defined ($self->{$name});
+
+    # Argument 1 can be a reference to a hash or a scalar index
+    my ($index, %hash);
+
+    if (defined $arg1) {
+      if (ref ($arg1) eq 'HASH') {
+        %hash = %$arg1;
+
+      } else {
+        $index = $arg1;
+      }
+    }
+
+    if (defined $index) {
+      # A scalar index is defined, set and/or return the value of an element
+      $self->{$name}{$index} = $arg2 if defined $arg2;
+
+      return (
+        exists $self->{$name}{$index} ? $self->{$name}{$index} : undef
+      );
+
+    } else {
+      # A scalar index is not defined, set and/or return the hash
+      $self->{$name} = \%hash if defined $arg1;
+      return $self->{$name};
+    }
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->expand_revision;
+#
+# DESCRIPTION
+#   This method expands the revision keywords of the current branch to a
+#   revision number. It returns true on success.
+# ------------------------------------------------------------------------------
+
+sub expand_revision {
+  my $self = shift;
+
+  my $rc = 1;
+  if ($self->type eq 'svn') {
+    # Expand revision keyword
+    my $rev = (FCM1::Keyword::expand($self->repos(), $self->revision()))[1];
+
+    # Get last changed revision of the specified revision
+    my $info_ref = $self->_svn_info($self->repos(), $rev);
+    if (!defined($info_ref->{'Revision'})) {
+      my $url = $self->repos() . ($rev ? '@' . $rev : q{});
+      w_report("ERROR: $url: not a valid URL\n");
+      return 0;
+    }
+    my $lc_rev = $info_ref->{'Last Changed Rev'};
+    $rev       = $info_ref->{'Revision'};
+
+    # Print info if specified revision is not the last commit revision
+    if (uc($self->revision()) ne 'HEAD' && $lc_rev != $rev) {
+      my $message = $self->repos . '@' . $rev . ': last changed at [' .
+                    $lc_rev . '].';
+      if ($self->setting ('EXT_REVMATCH') and uc ($self->revision) ne 'HEAD') {
+        w_report "ERROR: specified and last changed revisions differ:\n",
+                 '       ', $message, "\n";
+        $rc = 0;
+
+      } else {
+        print 'INFO: ', $message, "\n";
+      }
+    }
+
+    if ($self->verbose > 1 and uc ($self->revision) ne 'HEAD') {
+      # See if there is a later change of the branch at the HEAD
+      my $head_lc_rev = $self->_svn_info($self->repos())->{'Last Changed Rev'};
+
+      if (defined($head_lc_rev) && $head_lc_rev != $lc_rev) {
+        # Ensure that this is the same branch by checking its history
+        my @lines = &run_command (
+          [qw/svn log -q --incremental -r/, $lc_rev, $self->repos . '@HEAD'],
+          METHOD => 'qx', TIME => $self->verbose > 2, ERROR => 'ignore',
+        );
+
+        print 'INFO: ', $self->repos, '@', $rev,
+              ': newest commit at [', $head_lc_rev, '].', "\n"
+          if @lines;
+      }
+    }
+
+    $self->revision ($rev) if $rev ne $self->revision;
+
+  } elsif ($self->type eq 'user') {
+    1; # Do nothing
+
+  } else {
+    w_report 'ERROR: ', $self->repos, ': repository type "', $self->type,
+             '" not supported.';
+    $rc = 0;
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->expand_path;
+#
+# DESCRIPTION
+#   This method expands the relative path names of sub-directories to full
+#   path names. It returns true on success.
+# ------------------------------------------------------------------------------
+
+sub expand_path {
+  my $self = shift;
+
+  my $rc = 1;
+  if ($self->type eq 'svn') {
+    # SVN repository
+    # Do nothing unless there is a declared repository for this branch
+    return unless $self->repos;
+
+    # Remove trailing /
+    my $repos = $self->repos;
+    $self->repos ($repos) if $repos =~ s#/+$##;
+
+    # Consider all declared (expandable) sub-directories
+    for my $name (qw/dirs expdirs/) {
+      for my $dir (keys %{ $self->$name }) {
+        # Do nothing if declared sub-directory is quoted as a full URL
+        next if &is_url ($self->$name ($dir));
+
+        # Expand sub-directory to full URL
+        $self->$name ($dir, $self->repos . (
+          $self->$name ($dir) ? ('/' . $self->$name ($dir)) : ''
+        ));
+      }
+    }
+    # Note: "catfile" cannot be used in the above statement because it has
+    #       the tendency of removing a slash from double slashes.
+
+  } elsif ($self->type eq 'user') {
+    # Local user directories
+
+    # Expand leading ~ for all declared (expandable) sub-directories
+    for my $name (qw/dirs expdirs/) {
+      for my $dir (keys %{ $self->$name }) {
+        $self->$name ($dir, expand_tilde $self->$name ($dir));
+      }
+    }
+
+    # A top directory for the source is declared
+    if ($self->repos) {
+      # Expand leading ~ for the top directory
+      $self->repos (expand_tilde $self->repos);
+
+      # Get the root directory of the file system
+      my $rootdir = File::Spec->rootdir ();
+
+      # Expand top directory to absolute path, if necessary
+      $self->repos (File::Spec->rel2abs ($self->repos))
+        if $self->repos !~ m/^$rootdir/;
+
+      # Remove trailing /
+      my $repos = $self->repos;
+      $self->repos ($repos) if $repos =~ s#/+$##;
+
+      # Consider all declared (expandable) sub-directories
+      for my $name (qw/dirs expdirs/) {
+        for my $dir (keys %{ $self->$name }) {
+          # Do nothing if declared sub-directory is quoted as a full path
+          next if $self->$name ($dir) =~ m#^$rootdir#;
+
+          # Expand sub-directory to full path
+          $self->$name (
+            $dir, $self->$name ($dir)
+                  ? File::Spec->catfile ($self->repos, $self->$name ($dir))
+                  : $self->repos
+          );
+        }
+      }
+    }
+
+  } else {
+    w_report 'ERROR: ', $self->repos, ': repository type "', $self->type,
+             '" not supported.';
+    $rc = 0;
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->expand_all();
+#
+# DESCRIPTION
+#   This method searches the expandable source directories recursively for
+#   source directories containing regular files. The namespaces and the locators
+#   of these sub-directories are then added to the source directory hash table.
+#   Returns true on success.
+# ------------------------------------------------------------------------------
+
+sub expand_all {
+  my ($self) = @_;
+  my %finder_of = (
+    user => sub {
+      my ($root_locator) = @_;
+      my %ns_of;
+      my $wanted = sub {
+        my $base_name = $_;
+        my $path = $File::Find::name;
+        if (-f $path && !-l $path) {
+          my $dir_path      = dirname($path);
+          my $rel_dir_path  = File::Spec->abs2rel($dir_path, $root_locator);
+          if ($rel_dir_path eq q{.}) {
+             $rel_dir_path = q{};
+          }
+          if (!exists($ns_of{$dir_path})) {
+            $ns_of{$dir_path} = [File::Spec->splitdir($rel_dir_path)];
+          }
+        }
+      };
+      find($wanted, $root_locator);
+      return \%ns_of;
+    },
+    svn  => sub {
+      my ($root_locator) = @_;
+      my $runner = sub {
+        map {chomp($_); $_} run_command(
+          ['svn', @_,  '-R', join('@', $root_locator, $self->revision())],
+          METHOD => 'qx', TIME => $self->config()->verbose() > 2,
+        );
+      };
+      # FIXME: check for symlink switched off due to "svn pg" being very slow
+      #my %symlink_in
+      #  = map {($_ =~ qr{\A(.+)\s-\s(\*)\z}xms)} ($runner->(qw{pg svn:special}));
+      #my @locators
+      #  = grep {$_ !~ qr{/\z}xms && !$symlink_in{$_}} ($runner->('ls'));
+      my @locators = grep {$_ !~ qr{/\z}xms} ($runner->('ls'));
+      my %ns_of;
+      for my $locator (@locators) {
+        my ($rel_dir_locator) = $locator =~ qr{\A(.*)/[^/]+\z}xms; # dirname
+        $rel_dir_locator ||= q{};
+        my $dir_locator
+          = $rel_dir_locator ? join(q{/}, $root_locator, $rel_dir_locator)
+          :                    $root_locator
+          ;
+        if (!exists($ns_of{$dir_locator})) {
+          $ns_of{$dir_locator} = [split(q{/}, $rel_dir_locator)];
+        }
+      }
+      return \%ns_of;
+    },
+  );
+
+  if (!defined($finder_of{$self->type()})) {
+    w_report(sprintf(
+        qq{ERROR: %s: resource type "%s" not supported},
+        $self->repos(),
+        $self->type(),
+    ));
+    return;
+  }
+  while (my ($root_ns, $root_locator) = each(%{$self->expdirs()})) {
+    my @root_ns_list = split(qr{$FCM1::Config::DELIMITER}xms, $root_ns);
+    my $ns_hash_ref = $finder_of{$self->type()}->($root_locator);
+    while (my ($dir_path, $ns_list_ref) = each(%{$ns_hash_ref})) {
+      if (!grep {$_ =~ qr{\A\.}xms || $_ =~ qr{~\z}xms} @{$ns_list_ref}) {
+        my $ns = join($FCM1::Config::DELIMITER, @root_ns_list, @{$ns_list_ref});
+        $self->dirs($ns, $dir_path);
+      }
+    }
+  }
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $n = $obj->add_base_dirs ($base);
+#
+# DESCRIPTION
+#   Add a list of source directories to the current branch based on the set
+#   provided by $base, which must be a reference to a FCM1::ReposBranch
+#   instance. It returns the total number of used sub-directories in the
+#   current repositories.
+# ------------------------------------------------------------------------------
+
+sub add_base_dirs {
+  my $self = shift;
+  my $base = shift;
+
+  my %base_dirs = %{ $base->dirs };
+
+  for my $key (keys %base_dirs) {
+    # Remove repository root from base directories
+    if ($base_dirs{$key} eq $base->repos) {
+      $base_dirs{$key} = '';
+
+    } else {
+      $base_dirs{$key} = substr $base_dirs{$key}, length ($base->repos) + 1;
+    }
+
+    # Append base directories to current repository root
+    $self->dirs ($key, $base_dirs{$key});
+  }
+
+  # Expand relative path names of sub-directories
+  $self->expand_path;
+
+  return scalar keys %{ $self->dirs };
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @cfglines = $obj->to_cfglines ();
+#
+# DESCRIPTION
+#   This method returns a list of configuration lines for the current branch.
+# ------------------------------------------------------------------------------
+
+sub to_cfglines {
+  my ($self) = @_;
+  my @return = ();
+
+  my $suffix = $self->package . $FCM1::Config::DELIMITER . $self->tag;
+  push @return, FCM1::CfgLine->new (
+    label => $self->cfglabel ('REPOS') . $FCM1::Config::DELIMITER . $suffix,
+    value => $self->repos,
+  ) if $self->repos;
+
+  push @return, FCM1::CfgLine->new (
+    label => $self->cfglabel ('REVISION') . $FCM1::Config::DELIMITER . $suffix,
+    value => $self->revision,
+  ) if $self->revision;
+
+  for my $key (sort keys %{ $self->dirs }) {
+    my $value = $self->dirs ($key);
+
+    # Use relative path where possible
+    if ($self->repos) {
+      if ($value eq $self->repos) {
+        $value = '';
+
+      } elsif (index ($value, $self->repos) == 0) {
+        $value = substr ($value, length ($self->repos) + 1);
+      }
+    }
+
+    # Use top package name where possible
+    my $dsuffix = $key . $FCM1::Config::DELIMITER . $self->tag;
+    $dsuffix = $suffix if $value ne $self->dirs ($key) and $key eq join (
+      $FCM1::Config::DELIMITER, $self->package, File::Spec->splitdir ($value)
+    );
+
+    push @return, FCM1::CfgLine->new (
+      label => $self->cfglabel ('DIRS') . $FCM1::Config::DELIMITER . $dsuffix,
+      value => $value,
+    );
+  }
+
+  push @return, FCM1::CfgLine->new ();
+
+  return @return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   my $hash_ref = $self->_svn_info($url[, $rev]);
+#
+# DESCRIPTION
+#   Executes "svn info" and returns each field in a hash.
+# ------------------------------------------------------------------------------
+sub _svn_info {
+  my ($self, $url, $rev) = @_;
+  return {
+    map {
+      chomp();
+      my ($key, $value) = split(qr{\s*:\s*}xms, $_, 2);
+      $key ? ($key, $value) : ();
+    } run_command(
+      [qw{svn info}, ($rev ? ('-r', $rev, join('@', $url, $rev)) : $url)], 
+      DEVNULL => 1,
+      ERROR   => 'ignore',
+      METHOD  => 'qx',
+      TIME    => $self->verbose() > 2,
+    )
+  };
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/SrcDirLayer.pm b/lib/FCM1/SrcDirLayer.pm
new file mode 100644
index 0000000..b1dda79
--- /dev/null
+++ b/lib/FCM1/SrcDirLayer.pm
@@ -0,0 +1,277 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::SrcDirLayer
+#
+# DESCRIPTION
+#   This class contains methods to manipulate the extract of a source
+#   directory from a branch of a (Subversion) repository.
+#
+# ------------------------------------------------------------------------------
+use warnings;
+use strict;
+
+package FCM1::SrcDirLayer;
+use base qw{FCM1::Base};
+
+use FCM1::Util      qw{run_command e_report w_report};
+use File::Basename qw{dirname};
+use File::Path     qw{mkpath};
+use File::Spec;
+
+# List of property methods for this class
+my @scalar_properties = (
+  'cachedir',  # cache directory for this directory branch
+  'commit',    # revision at which the source directory was changed
+  'extracted', # is this branch already extracted?
+  'files',     # list of source files in this directory branch
+  'location',  # location of the source directory in the branch
+  'name',      # sub-package name of the source directory
+  'package',   # top level package name of which the current repository belongs
+  'reposroot', # repository root URL
+  'revision',  # revision of the repository branch
+  'tag',       # package/revision tag of the current repository branch
+  'type',      # type of the repository branch ("svn" or "user")
+);
+
+my %ERR_MESS_OF = (
+    CACHE_WRITE => '%s: cannot write to cache',
+    SYMLINK     => '%s/%s: ignore symbolic link',
+    VC_TYPE     => '%s: repository type not supported',
+);
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $obj = FCM1::SrcDirLayer->new (%args);
+#
+# DESCRIPTION
+#   This method constructs a new instance of the FCM1::SrcDirLayer class. See
+#   above for allowed list of properties. (KEYS should be in uppercase.)
+# ------------------------------------------------------------------------------
+
+sub new {
+  my $this  = shift;
+  my %args  = @_;
+  my $class = ref $this || $this;
+
+  my $self = FCM1::Base->new (%args);
+
+  for (@scalar_properties) {
+    $self->{$_} = exists $args{uc ($_)} ? $args{uc ($_)} : undef;
+  }
+
+  bless $self, $class;
+  return $self;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $value = $obj->X;
+#   $obj->X ($value);
+#
+# DESCRIPTION
+#   Details of these properties are explained in @scalar_properties.
+# ------------------------------------------------------------------------------
+
+for my $name (@scalar_properties) {
+  no strict 'refs';
+
+  *$name = sub {
+    my $self = shift;
+
+    # Argument specified, set property to specified argument
+    if (@_) {
+      $self->{$name} = $_[0];
+    }
+
+    # Default value for property
+    if (not defined $self->{$name}) {
+      if ($name eq 'files') {
+        # Reference to an array
+        $self->{$name} = [];
+      }
+    }
+
+    return $self->{$name};
+  }
+}
+
+# Handles error/warning events.
+sub _err {
+    my ($key, $args_ref, $warn_only) = @_;
+    my $reporter = $warn_only ? \&w_report : \&e_report;
+    $args_ref ||= [];
+    $reporter->(sprintf($ERR_MESS_OF{$key} . ".\n", @{$args_ref}));
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $dir = $obj->localdir;
+#
+# DESCRIPTION
+#   This method returns the user or cache directory for the current revision
+#   of the repository branch.
+# ------------------------------------------------------------------------------
+
+sub localdir {
+  my $self = shift;
+
+  return $self->user ? $self->location : $self->cachedir;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $user = $obj->user;
+#
+# DESCRIPTION
+#   This method returns the string "user" if the current source directory
+#   branch is a local directory. Otherwise, it returns "undef".
+# ------------------------------------------------------------------------------
+
+sub user {
+  my $self = shift;
+
+  return $self->type eq 'user' ? 'user' : undef;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rev = $obj->get_commit;
+#
+# DESCRIPTION
+#   If the current repository type is "svn", this method attempts to obtain
+#   the revision in which the branch is last committed. On a successful
+#   operation, it returns this revision number. Otherwise, it returns
+#   "undef".
+# ------------------------------------------------------------------------------
+
+sub get_commit {
+  my $self = shift;
+
+  if ($self->type eq 'svn') {
+    # Execute the "svn info" command
+    my @lines   = &run_command (
+      [qw/svn info -r/, $self->revision, $self->location . '@' . $self->revision],
+      METHOD => 'qx', TIME => $self->config->verbose > 2,
+    );
+
+    my $rev;
+    for (@lines) {
+      if (/^Last\s+Changed\s+Rev\s*:\s*(\d+)/i) {
+        $rev = $1;
+        last;
+      }
+    }
+
+    # Commit revision of this source directory
+    $self->commit ($rev);
+
+    return $self->commit;
+
+  } elsif ($self->type eq 'user') {
+    return;
+
+  } else {
+    _err('VC_TYPE', [$self->type()]);
+  }
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = $obj->update_cache;
+#
+# DESCRIPTION
+#   If the current repository type is "svn", this method attempts to extract
+#   the current revision source directory from the current branch from the
+#   repository, sending the output to the cache directory. It returns true on
+#   a successful operation, or false if the repository is not of type "svn".
+# ------------------------------------------------------------------------------
+
+sub update_cache {
+  my $self = shift;
+
+  return unless $self->cachedir;
+
+  # Create cache extract destination, if necessary
+  my $dirname = dirname $self->cachedir;
+  mkpath($dirname);
+
+  if (!-d $dirname) {
+    _err('CACHE_WRITE', [$dirname]);
+  }
+  
+  if ($self->type eq 'svn') {
+    # Set up the extract command, "svn export --force -q -N"
+    my @command = (
+      qw/svn export --force -q -N/,
+      $self->location . '@' . $self->revision,
+      $self->cachedir,
+    );
+
+    &run_command (\@command, TIME => $self->config->verbose > 2);
+
+  } elsif ($self->type eq 'user') {
+    return;
+
+  } else {
+    _err('VC_TYPE', [$self->type()]);
+  }
+
+  return 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @files = $obj->get_files();
+#
+# DESCRIPTION
+#   This method returns a list of file base names in the (cache of) this source
+#   directory in the current branch.
+# ------------------------------------------------------------------------------
+
+sub get_files {
+  my ($self) = @_;
+  opendir(my $dir, $self->localdir())
+    || die($self->localdir(), ': cannot read directory');
+  my @base_names = ();
+  BASE_NAME:
+  while (my $base_name = readdir($dir)) {
+    if ($base_name =~ qr{\A\.}xms || $base_name =~ qr{~\z}xms) {
+        next BASE_NAME;
+    }
+    my $path = File::Spec->catfile($self->localdir(), $base_name);
+    if (-d $path) {
+        next BASE_NAME;
+    }
+    if (-l $path) {
+        _err('SYMLINK', [$self->location(), $base_name], 1);
+        next BASE_NAME;
+    }
+    push(@base_names, $base_name);
+  }
+  closedir($dir);
+  $self->files(\@base_names);
+  return @base_names;
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/Timer.pm b/lib/FCM1/Timer.pm
new file mode 100644
index 0000000..26c080c
--- /dev/null
+++ b/lib/FCM1/Timer.pm
@@ -0,0 +1,85 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::Timer
+#
+# DESCRIPTION
+#   This is a package of timer utility used by the FCM command.
+#
+# ------------------------------------------------------------------------------
+
+package FCM1::Timer;
+
+# Standard pragma
+use warnings;
+use strict;
+
+# Exports
+our (@ISA, @EXPORT, @EXPORT_OK);
+
+sub timestamp_command;
+
+require Exporter;
+ at ISA = qw(Exporter);
+ at EXPORT = qw(timestamp_command);
+
+# ------------------------------------------------------------------------------
+
+# Module level variables
+my %cmd_start_time = (); # Command start time, (key = command, value = time)
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = &FCM1::Timer::timestamp_command ($command[, $status]);
+#
+# DESCRIPTION
+#   This function returns a string adding to $command a prefix according the
+#   value of $status. If $status is not specified or does not match the word
+#   "end", the status is assumed to be "start". At "start", the prefix will
+#   contain the current timestamp. If $status is the word "end", the prefix
+#   will contain the total time taken since this function was called with the
+#   same $command at the "start" status.
+# ------------------------------------------------------------------------------
+
+sub timestamp_command {
+  (my $command, my $status) = @_;
+
+  my $prefix;
+  if ($status and $status =~ /end/i) {
+    # Status is "end", insert time taken
+    my $lapse = time () - $cmd_start_time{$command};
+    $prefix = sprintf "# Time taken: %12d s=> ", $lapse;
+
+  } else {
+    # Status is "start", insert time stamp
+    $cmd_start_time{$command} = time;
+
+    (my $sec, my $min, my $hour, my $mday, my $mon, my $year) = localtime;
+    $prefix = sprintf "# Start: %04d-%02d-%02d %02d:%02d:%02d=> ",
+              $year + 1900, $mon + 1, $mday, $hour, $min, $sec;
+  }
+
+  return $prefix . $command . "\n";
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/Util.pm b/lib/FCM1/Util.pm
new file mode 100644
index 0000000..2752b11
--- /dev/null
+++ b/lib/FCM1/Util.pm
@@ -0,0 +1,564 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#   FCM1::Util
+#
+# DESCRIPTION
+#   This is a package of misc utilities used by the FCM command.
+#
+# ------------------------------------------------------------------------------
+
+use warnings;
+use strict;
+
+package FCM1::Util;
+require Exporter;
+our @ISA = qw{Exporter};
+
+sub expand_tilde;
+sub e_report;
+sub find_file_in_path;
+sub get_command_string;
+sub get_rev_of_wc;
+sub get_url_of_wc;
+sub get_url_peg_of_wc;
+sub get_wct;
+sub is_url;
+sub is_wc;
+sub print_command;
+sub run_command;
+sub svn_date;
+sub tidy_url;
+sub touch_file;
+sub w_report;
+
+our @EXPORT = qw{
+    expand_tilde
+    e_report
+    find_file_in_path
+    get_command_string
+    get_rev_of_wc
+    get_url_of_wc
+    get_url_peg_of_wc
+    get_wct
+    is_url
+    is_wc
+    print_command
+    run_command
+    svn_date
+    tidy_url
+    touch_file
+    w_report
+};
+
+# Standard modules
+use Carp;
+use Cwd;
+use File::Basename;
+use File::Find;
+use File::Path;
+use File::Spec;
+use POSIX qw{strftime SIGINT SIGKILL SIGTERM WEXITSTATUS WIFSIGNALED WTERMSIG};
+
+# FCM component modules
+use FCM1::Timer;
+
+# ------------------------------------------------------------------------------
+
+# Module level variables
+my %svn_info       = (); # "svn info" log, (key1 = path,
+                         # key2 = URL, Revision, Last Changed Rev)
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   %srcdir = &FCM1::Util::find_file_in_path ($file, \@path);
+#
+# DESCRIPTION
+#   Search $file in @path. Returns the full path of the $file if it is found
+#   in @path. Returns "undef" if $file is not found in @path.
+# ------------------------------------------------------------------------------
+
+sub find_file_in_path {
+  my ($file, $path) = @_;
+
+  for my $dir (@$path) {
+    my $full_file = File::Spec->catfile ($dir, $file);
+    return $full_file if -e $full_file;
+  }
+
+  return undef;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $expanded_path = &FCM1::Util::expand_tilde ($path);
+#
+# DESCRIPTION
+#   Returns an expanded path if $path is a path that begins with a tilde (~).
+# ------------------------------------------------------------------------------
+
+sub expand_tilde {
+  my $file = $_[0];
+
+  $file =~ s#^~([^/]*)#$1 ? (getpwnam $1)[7] : ($ENV{HOME} || $ENV{LOGDIR})#ex;
+
+  # Expand . and ..
+  while ($file =~ s#/+\.(?:/+|$)#/#g) {next}
+  while ($file =~ s#/+[^/]+/+\.\.(?:/+|$)#/#g) {next}
+
+  # Remove trailing /
+  $file =~ s#/*$##;
+
+  return $file;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $rc = &FCM1::Util::touch_file ($file);
+#
+# DESCRIPTION
+#   Touch $file if it exists. Create $file if it does not exist. Return 1 for
+#   success or 0 otherwise.
+# ------------------------------------------------------------------------------
+
+sub touch_file {
+  my $file = $_[0];
+  my $rc   = 1;
+
+  if (-e $file) {
+    my $now = time;
+    $rc = utime $now, $now, $file;
+
+  } else {
+    mkpath dirname ($file) unless -d dirname ($file);
+
+    $rc = open FILE, '>', $file;
+    $rc = close FILE if $rc;
+  }
+
+  return $rc;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flag = &is_wc ([$path]);
+#
+# DESCRIPTION
+#   Returns true if current working directory (or $path) is a Subversion
+#   working copy.
+# ------------------------------------------------------------------------------
+
+sub is_wc {
+  my $path = shift() || cwd();
+  my $path_of_dir = -f $path ? dirname($path) : $path;
+  if (-e File::Spec->catfile($path_of_dir, qw{.svn entries})) {
+      return 1;
+  }
+  my $inforc = &run_command (
+    [qw/svn info/, $path_of_dir],
+    METHOD => 'qx', DEVNULL => 1, ERROR => 'ignore'
+  );
+  return $inforc != 0;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $flag = &is_url ($url);
+#
+# DESCRIPTION
+#   Returns true if $url is a URL.
+# ------------------------------------------------------------------------------
+
+sub is_url {
+  # This should handle URL beginning with svn://, http:// and svn+ssh://
+  return ($_[0] =~ m#^[\+\w]+://#);
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $url = tidy_url($url);
+#
+# DESCRIPTION
+#   Returns a tidied version of $url by removing . and .. in the path.
+# ------------------------------------------------------------------------------
+
+sub tidy_url {
+    my ($url) = @_;
+    if (!is_url($url)) {
+        return $url;
+    }
+    my $DOT_PATTERN     = qr{/+ \. (?:/+|(@|\z))}xms;
+    my $DOT_DOT_PATTERN = qr{/+ [^/]+ /+ \.\. (?:/+|(@|\z))}xms;
+    my $TRAILING_SLASH_PATTERN = qr{([^/]+) /* (@|\z)}xms;
+    my $RIGHT_EVAL = q{'/' . ($1 ? $1 : '')};
+    DOT:
+    while ($url =~ s{$DOT_PATTERN}{$RIGHT_EVAL}eegxms) {
+        next DOT;
+    }
+    DOT_DOT:
+    while ($url =~ s{$DOT_DOT_PATTERN}{$RIGHT_EVAL}eegxms) {
+        next DOT_DOT;
+    }
+    $url =~ s{$TRAILING_SLASH_PATTERN}{$1$2}xms;
+    return $url;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = &get_wct ([$dir]);
+#
+# DESCRIPTION
+#   If current working directory (or $dir) is a Subversion working copy,
+#   returns the top directory of this working copy; otherwise returns an empty
+#   string.
+# ------------------------------------------------------------------------------
+
+sub get_wct {
+  my $dir = @_ ? $_[0] : cwd ();
+
+  return '' if not &is_wc ($dir);
+
+  my $updir = dirname $dir;
+  while (&is_wc ($updir)) {
+    $dir   = $updir;
+    $updir = dirname $dir;
+    last if $updir eq $dir;
+  }
+
+  return $dir;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = &get_url_of_wc ([$path[, $refresh]]);
+#
+# DESCRIPTION
+#   If current working directory (or $path) is a Subversion working copy,
+#   returns the URL of the associated Subversion repository; otherwise returns
+#   an empty string. If $refresh is specified, do not use the cached
+#   information.
+# ------------------------------------------------------------------------------
+
+sub get_url_of_wc {
+  my $path    = @_ ? $_[0] : cwd ();
+  my $refresh = exists $_[1] ? $_[1] : 0;
+  my $url  = '';
+
+  if (&is_wc ($path)) {
+    delete $svn_info{$path} if $refresh;
+    &_invoke_svn_info (PATH => $path) unless exists $svn_info{$path};
+    $url = $svn_info{$path}{URL};
+  }
+
+  return $url;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = &get_url_peg_of_wc ([$path[, $refresh]]);
+#
+# DESCRIPTION
+#   If current working directory (or $path) is a Subversion working copy,
+#   returns the URL at REV of the associated Subversion repository; otherwise
+#   returns an empty string. If $refresh is specified, do not use the cached
+#   information.
+# ------------------------------------------------------------------------------
+
+sub get_url_peg_of_wc {
+  my $path    = @_ ? $_[0] : cwd ();
+  my $refresh = exists $_[1] ? $_[1] : 0;
+  my $url  = '';
+
+  if (&is_wc ($path)) {
+    delete $svn_info{$path} if $refresh;
+    &_invoke_svn_info (PATH => $path) unless exists $svn_info{$path};
+    $url = $svn_info{$path}{URL} . '@' . $svn_info{$path}{Revision};
+  }
+
+  return $url;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   &_invoke_svn_info (PATH => $path);
+#
+# DESCRIPTION
+#   The function is internal to this module. It invokes "svn info" on $path to
+#   gather information on URL, Revision and Last Changed Rev. The information
+#   is stored in a hash table at the module level, so that the information can
+#   be re-used.
+# ------------------------------------------------------------------------------
+
+sub _invoke_svn_info {
+  my %args = @_;
+  my $path = $args{PATH};
+  my $cfg  = FCM1::Config->instance();
+
+  return if exists $svn_info{$path};
+
+  # Invoke "svn info" command
+  my @info = &run_command (
+    [qw/svn info/, $path],
+    PRINT => $cfg->verbose > 2, METHOD => 'qx', DEVNULL => 1, ERROR => 'ignore',
+  );
+  for (@info) {
+    chomp;
+
+    if (/^(URL|Revision|Last Changed Rev):\s*(.+)$/) {
+      $svn_info{$path}{$1} = $2;
+    }
+  }
+
+  return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $string = &get_command_string ($cmd);
+#   $string = &get_command_string (\@cmd);
+#
+# DESCRIPTION
+#   The function returns a string by converting the list in @cmd or the scalar
+#   $cmd to a form, where it can be executed as a shell command.
+# ------------------------------------------------------------------------------
+
+sub get_command_string {
+  my $cmd    = $_[0];
+  my $return = '';
+
+  if (ref ($cmd) and ref ($cmd) eq 'ARRAY') {
+    # $cmd is a reference to an array
+
+    # Print each argument
+    for my $i (0 .. @{ $cmd } - 1) {
+      my $arg = $cmd->[$i];
+
+      $arg =~ s/./*/g if $i > 0 and $cmd->[$i - 1] eq '--password';
+
+      if ($arg =~ /[\s'"*?]/) {
+        # Argument contains a space, quote it
+        if (index ($arg, "'") >= 0) {
+          # Argument contains an apostrophe, quote it with double quotes
+          $return .= ($i > 0 ? ' ' : '') . '"' . $arg . '"';
+
+        } else {
+          # Otherwise, quote argument with apostrophes
+          $return .= ($i > 0 ? ' ' : '') . "'" . $arg . "'";
+        }
+
+      } else {
+        # Argument does not contain a space, just print it
+        $return .= ($i > 0 ? ' ' : '') . ($arg eq '' ? "''" : $arg);
+      }
+    }
+
+  } else {
+    # $cmd is a scalar, just print it "as is"
+    $return = $cmd;
+  }
+
+  return $return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   &print_command ($cmd);
+#   &print_command (\@cmd);
+#
+# DESCRIPTION
+#   The function prints the list in @cmd or the scalar $cmd, as it would be
+#   executed by the shell.
+# ------------------------------------------------------------------------------
+
+sub print_command {
+  my $cmd = $_[0];
+
+  print '=> ', &get_command_string ($cmd) , "\n";
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   @return = &run_command (\@cmd, <OPTIONS>);
+#   @return = &run_command ($cmd , <OPTIONS>);
+#
+# DESCRIPTION
+#   This function executes the command in the list @cmd or in the scalar $cmd.
+#   The remaining are optional arguments in a hash table. Valid options are
+#   listed below. If the command is run using "qx", the function returns the
+#   standard output from the command. If the command is run using "system", the
+#   function returns true on success. By default, the function dies on failure.
+#
+# OPTIONS
+#   METHOD  => $method - this can be "system", "exec" or "qx". This determines
+#                        how the command will be executed. If not set, the
+#                        default is to run the command with "system".
+#   PRINT   => 1       - if set, print the command before executing it.
+#   ERROR   => $flag   - this should only be set if METHOD is set to "system"
+#                        or "qx". The $flag can be "die" (default), "warn" or
+#                        "ignore". If set to "die", the function dies on error.
+#                        If set to "warn", the function issues a warning on
+#                        error, and the function returns false. If set to
+#                        "ignore", the function returns false on error.
+#   RC      => 1       - if set, must be a reference to a scalar, which will be
+#                        set to the return code of the command.
+#   DEVNULL => 1       - if set, re-direct STDERR to /dev/null before running
+#                        the command.
+#   TIME    => 1       - if set, print the command with a timestamp before
+#                        executing it, and print the time taken when it
+#                        completes. This option supersedes the PRINT option.
+# ------------------------------------------------------------------------------
+
+sub run_command {
+  my ($cmd, %input_opt_of) = @_;
+  my %opt_of = (
+    DEVNULL => undef,
+    ERROR   => 'die',
+    METHOD  => 'system',
+    PRINT   => undef,
+    RC      => undef,
+    TIME    => undef,
+    %input_opt_of,
+  );
+  local($|) = 1; # Make sure STDOUT is flushed before running command
+
+  # Print the command before execution, if necessary
+  if ($opt_of{TIME}) {
+    print(timestamp_command(get_command_string($cmd)));
+  }
+  elsif ($opt_of{PRINT}) {
+    print_command($cmd);
+  }
+
+  # Re-direct STDERR to /dev/null if necessary
+  if ($opt_of{DEVNULL}) {
+    no warnings;
+    open(OLDERR, ">&STDERR") || croak("Cannot dup STDERR ($!), abort");
+    use warnings;
+    open(STDERR, '>', File::Spec->devnull())
+      || croak("Cannot redirect STDERR ($!), abort");
+    # Make sure the channels are unbuffered
+    my $select = select();
+    select(STDERR); local($|) = 1;
+    select($select);
+  }
+
+  my @return = ();
+  if (ref($cmd) && ref($cmd) eq 'ARRAY') {
+    # $cmd is an array
+    my @command = @{$cmd};
+    if ($opt_of{METHOD} eq 'qx') {
+      @return = qx(@command);
+    }
+    elsif ($opt_of{METHOD} eq 'exec') {
+      exec(@command);
+    }
+    else {
+      system(@command);
+      @return = $? ? () : (1);
+    }
+  }
+  else {
+    # $cmd is an scalar
+    if ($opt_of{METHOD} eq 'qx') {
+      @return = qx($cmd);
+    }
+    elsif ($opt_of{METHOD} eq 'exec') {
+      exec($cmd);
+    }
+    else {
+      system($cmd);
+      @return = $? ? () : (1);
+    }
+  }
+  my $rc = $?;
+
+  # Put STDERR back to normal, if redirected previously
+  if ($opt_of{DEVNULL}) {
+    close(STDERR);
+    open(STDERR, ">&OLDERR") || croak("Cannot dup STDERR ($!), abort");
+  }
+
+  # Print the time taken for command after execution, if necessary
+  if ($opt_of{TIME}) {
+    print(timestamp_command(get_command_string($cmd), 'end'));
+  }
+
+  # Signal and return code
+  my ($signal, $status) = (WTERMSIG($rc), WEXITSTATUS($rc));
+  if (exists($opt_of{RC})) {
+    ${$opt_of{RC}} = $status;
+  }
+  if (WIFSIGNALED($rc) && grep {$signal == $_} (SIGINT, SIGKILL, SIGTERM)) {
+    croak(sprintf('%s terminated (%d)', get_command_string($cmd), $signal));
+  }
+  if ($status && $opt_of{ERROR} ne 'ignore') {
+    my $func_ref = $opt_of{ERROR} eq 'warn' ? \&carp : \&croak;
+    $func_ref->(sprintf('%s failed (%d)', get_command_string($cmd), $status));
+  }
+  return @return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   &e_report (@message);
+#
+# DESCRIPTION
+#   The function prints @message to STDERR and aborts with a error.
+# ------------------------------------------------------------------------------
+
+sub e_report {
+  print STDERR @_, "\n" if @_;
+
+  exit 1;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   &w_report (@message);
+#
+# DESCRIPTION
+#   The function prints @message to STDERR and returns.
+# ------------------------------------------------------------------------------
+
+sub w_report {
+  print STDERR @_, "\n" if @_;
+
+  return;
+}
+
+# ------------------------------------------------------------------------------
+# SYNOPSIS
+#   $date = &svn_date ($time);
+#
+# DESCRIPTION
+#   The function returns a date, formatted as by Subversion. The argument $time
+#   is the number of seconds since epoch.
+# ------------------------------------------------------------------------------
+
+sub svn_date {
+  my $time = shift;
+
+  return strftime ('%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)', localtime ($time));
+}
+
+# ------------------------------------------------------------------------------
+
+1;
+
+__END__
diff --git a/lib/FCM1/Util/ClassLoader.pm b/lib/FCM1/Util/ClassLoader.pm
new file mode 100644
index 0000000..55a07eb
--- /dev/null
+++ b/lib/FCM1/Util/ClassLoader.pm
@@ -0,0 +1,93 @@
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package FCM1::Util::ClassLoader;
+use base qw{Exporter};
+
+our @EXPORT_OK = qw{load};
+
+use Carp qw{croak};
+use FCM1::Exception;
+
+sub load {
+    my ($class, $test_method) = @_;
+    if (!$test_method) {
+        $test_method = 'new';
+    }
+    if (!UNIVERSAL::can($class, $test_method)) {
+        eval('require ' . $class);
+        if ($@) {
+            croak(FCM1::Exception->new({message => sprintf(
+                "%s: class loading failed: %s", $class, $@,
+            )}));
+        }
+    }
+    return $class;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+FCM1::ClassLoader
+
+=head1 SYNOPSIS
+
+    use FCM1::Util::ClassLoader;
+    $load_ok = FCM1::Util::ClassLoader::load($class);
+
+=head1 DESCRIPTION
+
+A wrapper for loading a class dynamically.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item load($class,$test_method)
+
+If $class can call $test_method, returns $class. Otherwise, attempts to
+require() $class and returns it. If this fails, croak() with a
+L<FCM1::Exception|FCM1::Exception>.
+
+=item load($class)
+
+Shorthand for C<load($class, 'new')>.
+
+=back
+
+=head1 DIAGNOSTICS
+
+=over 4
+
+=item L<FCM1::Exception|FCM1::Exception>
+
+The load($class,$test_method) function croak() with this exception if it fails
+to load the specified class.
+
+=back
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/licences/Apache2 b/licences/Apache2
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/licences/Apache2
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/licences/GPL3 b/licences/GPL3
new file mode 120000
index 0000000..012065c
--- /dev/null
+++ b/licences/GPL3
@@ -0,0 +1 @@
+../COPYING
\ No newline at end of file
diff --git a/man/man1/fcm.1 b/man/man1/fcm.1
new file mode 100644
index 0000000..820e4c4
--- /dev/null
+++ b/man/man1/fcm.1
@@ -0,0 +1,42 @@
+.\" Process this file with
+.\" groff -man -Tascii fcm.1
+.\"
+.TH fcm 1 "" "" "User Commands"
+.SH NAME
+fcm - FCM, tools for managing and building source code.
+.SH SYNOPSIS
+.B fcm
+.I command
+[
+.I options
+] [
+.I args
+]
+.SH OVERVIEW
+.B fcm
+is a set of tools for managing and building source code.
+For full detail of the system, please refer to the FCM user guide, which you
+should receive with this distribution.
+.PP
+Run "fcm help" to access the built-in tool documentation.
+.SH AUTHOR
+FCM Team <fcm-team at metoffice.gov.uk>.
+Please feedback any bug reports or feature requests to us by e-mail.
+.SH COPYRIGHT
+\(co British Crown Copyright 2006-14 Met Office.
+.PP
+FCM is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+.PP
+FCM is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+.PP
+You should have received a copy of the GNU General Public License
+along with FCM. If not, see <http://www.gnu.org/licenses/>.
+.SH SEE ALSO
+.BR svn (1),
+.BR perl (1)
diff --git a/sbin/fcm-add-svn-repos b/sbin/fcm-add-svn-repos
new file mode 100755
index 0000000..c4a5c60
--- /dev/null
+++ b/sbin/fcm-add-svn-repos
@@ -0,0 +1,105 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{add_svn_repository};
+use FCM::Admin::Util qw{option2config};
+use Getopt::Long qw{GetOptions};
+use Pod::Usage qw{pod2usage};
+
+main();
+
+sub main {
+    my %option;
+    my $result = GetOptions(
+        \%option,
+        q{help|usage|h},
+        q{svn-live-dir=s},
+        q{svn-project-suffix=s},
+    );
+    if (!$result) {
+        pod2usage(1);
+    }
+    if (exists($option{help})) {
+        pod2usage(q{-verbose} => 1);
+    }
+    option2config(\%option);
+    if (@ARGV != 1) {
+        my $message = sprintf(
+            qq{Expected exactly 1 argument, %d given.}, scalar(@ARGV),
+        );
+        pod2usage({q{-exitval} => 1, q{-message} => $message});
+    }
+    my ($project_name) = @ARGV;
+    add_svn_repository($project_name);
+}
+
+__END__
+=head1 NAME
+
+fcm-add-trac-env
+
+=head1 SYNOPSIS
+
+    fcm-add-svn-repos [OPTIONS] PROJECT
+
+=head1 OPTIONS
+
+=over 4
+
+=item --help, -h, --usage
+
+Prints help and exits.
+
+=item --svn-live-dir=DIR
+
+Specifies the root location of the live directory of Subversion repositories.
+See L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --svn-project-suffix=NAME
+
+Specifies the suffix added to the project name for Subversion repositories. The
+default is "_svn".
+
+=back
+
+=head1 ARGUMENTS
+
+=over 4
+
+=item PROJECT
+
+Specifies the name of the project to add.
+
+=back
+
+=head1 DESCRIPTION
+
+This program adds a new Subversion repository.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/fcm-add-svn-repos-and-trac-env b/sbin/fcm-add-svn-repos-and-trac-env
new file mode 100755
index 0000000..8d354c2
--- /dev/null
+++ b/sbin/fcm-add-svn-repos-and-trac-env
@@ -0,0 +1,119 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{add_svn_repository add_trac_environment};
+use FCM::Admin::Util qw{option2config};
+use Getopt::Long qw{GetOptions};
+use Pod::Usage qw{pod2usage};
+
+main();
+
+sub main {
+    my %option;
+    my $result = GetOptions(
+        \%option,
+        q{help|usage|h},
+        q{svn-live-dir=s},
+        q{svn-project-suffix=s},
+        q{trac-host-id=s},
+        q{trac-live-dir=s},
+    );
+    if (!$result) {
+        pod2usage(1);
+    }
+    if (exists($option{help})) {
+        pod2usage(q{-verbose} => 1);
+    }
+    option2config(\%option);
+    if (@ARGV != 1) {
+        my $message = sprintf(
+            qq{Expected exactly 1 argument, %d given.}, scalar(@ARGV),
+        );
+        pod2usage({q{-exitval} => 1, q{-message} => $message});
+    }
+    my ($project_name) = @ARGV;
+    add_svn_repository($project_name);
+    add_trac_environment($project_name);
+}
+
+__END__
+=head1 NAME
+
+fcm-add-trac-env
+
+=head1 SYNOPSIS
+
+    fcm-add-svn-repos-and-trac-env [OPTIONS] PROJECT
+
+=head1 OPTIONS
+
+=over 4
+
+=item --help, -h, --usage
+
+Prints help and exits.
+
+=item --svn-live-dir=DIR
+
+Specifies the root location of the live directory of Subversion repositories.
+See L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --svn-project-suffix=NAME
+
+Specifies the suffix added to the project name for Subversion repositories. The
+default is "_svn".
+
+=item --trac-host-id=HOST
+
+Specifies the host ID of the Trac server for the new Trac environment. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --trac-live-dir=DIR
+
+Specifies the root location of the live directory of Trac environments. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=back
+
+=head1 ARGUMENTS
+
+=over 4
+
+=item PROJECT
+
+Specifies the name of the project to add.
+
+=back
+
+=head1 DESCRIPTION
+
+This program adds a new Subversion repository with an associated Trac
+environment to their live directories.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/fcm-add-trac-env b/sbin/fcm-add-trac-env
new file mode 100755
index 0000000..4b55b08
--- /dev/null
+++ b/sbin/fcm-add-trac-env
@@ -0,0 +1,117 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{add_trac_environment};
+use FCM::Admin::Util qw{option2config};
+use Getopt::Long qw{GetOptions};
+use Pod::Usage qw{pod2usage};
+
+main();
+
+sub main {
+    my %option;
+    my $result = GetOptions(
+        \%option,
+        q{help|usage|h},
+        q{svn-live-dir=s},
+        q{svn-project-suffix=s},
+        q{trac-host-id=s},
+        q{trac-live-dir=s},
+    );
+    if (!$result) {
+        pod2usage(1);
+    }
+    if (exists($option{help})) {
+        pod2usage(q{-verbose} => 1);
+    }
+    option2config(\%option);
+    if (@ARGV != 1) {
+        my $message = sprintf(
+            qq{Expected exactly 1 argument, %d given.}, scalar(@ARGV),
+        );
+        pod2usage({q{-exitval} => 1, q{-message} => $message});
+    }
+    my ($project_name) = @ARGV;
+    add_trac_environment($project_name);
+}
+
+__END__
+=head1 NAME
+
+fcm-add-trac-env
+
+=head1 SYNOPSIS
+
+    fcm-add-trac-env [OPTIONS] PROJECT
+
+=head1 OPTIONS
+
+=over 4
+
+=item --help, -h, --usage
+
+Prints help and exits.
+
+=item --svn-live-dir=DIR
+
+Specifies the root location of the live directory of Subversion repositories.
+See L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --svn-project-suffix=NAME
+
+Specifies the suffix added to the project name for Subversion repositories. The
+default is "_svn".
+
+=item --trac-host-id=HOST
+
+Specifies the host ID of the Trac server for the new Trac environment. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --trac-live-dir=DIR
+
+Specifies the root location of the live directory of Trac environments. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=back
+
+=head1 ARGUMENTS
+
+=over 4
+
+=item PROJECT
+
+Specifies the name of the project to add.
+
+=back
+
+=head1 DESCRIPTION
+
+This program adds a new Trac environment to the live directory.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/fcm-backup-svn-repos b/sbin/fcm-backup-svn-repos
new file mode 100755
index 0000000..5568c97
--- /dev/null
+++ b/sbin/fcm-backup-svn-repos
@@ -0,0 +1,137 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{
+    backup_svn_repository
+    filter_projects
+    get_projects_from_svn_live
+};
+use FCM::Admin::Util qw{option2config};
+use Getopt::Long qw{GetOptions};
+use Pod::Usage qw{pod2usage};
+
+main();
+
+sub main {
+    my %option;
+    my $result = GetOptions(
+        \%option,
+        q{help|usage|h},
+        q{no-housekeep-dumps},
+        q{no-pack},
+        q{no-verify-integrity},
+        q{svn-backup-dir=s},
+        q{svn-dump-dir=s},
+        q{svn-live-dir=s},
+        q{svn-project-suffix=s},
+    );
+    if (!$result) {
+        pod2usage(1);
+    }
+    if (exists($option{help})) {
+        pod2usage(q{-verbose} => 1);
+    }
+    option2config(\%option);
+    my @projects = filter_projects([get_projects_from_svn_live()], \@ARGV);
+    for my $project (@projects) {
+        backup_svn_repository(\%option, $project);
+    }
+}
+
+__END__
+
+=head1 NAME
+
+fcm-backup-svn-repos
+
+=head1 SYNOPSIS
+
+    fcm-backup-svn-repos [OPTIONS] [PROJECT ...]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --help, -h, --usage
+
+Prints help and exits.
+
+=item --no-housekeep-dumps
+
+If this option is specified, the program will not housekeep the backup revision
+dumps of each repository.
+
+=item --no-pack
+
+If this option is specified, the program will not pack each repository before
+running the backup.
+
+=item --no-verify-integrity
+
+If this option is specified, the program will not verify the integrity of a
+repository before running the backup.
+
+=item --svn-backup-dir=DIR
+
+Specifies the root location of the backup directory. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --svn-dump-dir=DIR
+
+Specifies the root location of the directory where revision dumps are kept. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --svn-live-dir=DIR
+
+Specifies the root location of the live directory. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --svn-project-suffix=NAME
+
+Specifies the suffix added to the project name. The default is "_svn".
+
+=back
+
+=head1 ARGUMENTS
+
+=over 4
+
+=item PROJECT
+
+Specifies one or more project to back up. If no project is specified, the
+program searches the live directory for projects to back up.
+
+=back
+
+=head1 DESCRIPTION
+
+This program archives Subversion repositories in the live directory to the
+backup directory.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/fcm-backup-trac-env b/sbin/fcm-backup-trac-env
new file mode 100755
index 0000000..ae1f906
--- /dev/null
+++ b/sbin/fcm-backup-trac-env
@@ -0,0 +1,118 @@
+#!/usr/bin/perl
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{
+    backup_trac_environment
+    backup_trac_files
+    filter_projects
+    get_projects_from_trac_live
+};
+use FCM::Admin::Util qw{option2config};
+use Getopt::Long qw{GetOptions};
+use Pod::Usage qw{pod2usage};
+
+main();
+
+sub main {
+    my %option;
+    my $result = GetOptions(
+        \%option,
+        q{help|usage|h},
+        q{no-verify-integrity},
+        q{trac-backup-dir=s},
+        q{trac-live-dir=s},
+    );
+    if (!$result) {
+        pod2usage(1);
+    }
+    if (exists($option{help})) {
+        pod2usage(q{-verbose} => 1);
+    }
+    option2config(\%option);
+    my @projects = filter_projects([get_projects_from_trac_live()], \@ARGV);
+    for my $project (@projects) {
+        backup_trac_environment(\%option, $project);
+    }
+    if (!@ARGV) {
+        backup_trac_files();
+    }
+}
+
+__END__
+
+=head1 NAME
+
+fcm-backup-trac-env
+
+=head1 SYNOPSIS
+
+    fcm-backup-trac-env [OPTIONS] [PROJECT ...]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --help, -h, --usage
+
+Prints help and exits.
+
+=item --no-verify-integrity
+
+If this option is specified, the program will not verify the integrity of the
+database before running the backup.
+
+=item --trac-backup-dir=DIR
+
+Specifies the root location of the backup directory. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --trac-live-dir=DIR
+
+Specifies the root location of the live directory. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=back
+
+=head1 ARGUMENTS
+
+=over 4
+
+=item PROJECT
+
+Specifies one or more project to back up. If no project is specified, the
+program searches the live directory for projects and other files to back up.
+
+=back
+
+=head1 DESCRIPTION
+
+This program archives Trac environments, and other files in the live directory
+to the backup directory.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/fcm-commit-update b/sbin/fcm-commit-update
new file mode 100755
index 0000000..2cd788c
--- /dev/null
+++ b/sbin/fcm-commit-update
@@ -0,0 +1,170 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use File::Basename qw{basename};
+use File::Spec;
+
+use FCM::Admin::Config;
+use FCM::Admin::Runner;
+use FCM::Admin::System qw{
+    distribute_wc
+    filter_projects
+    get_projects_from_svn_live
+    install_svn_hook
+};
+use FCM::Admin::Util qw{
+    run_mkpath
+    run_rmtree
+    run_svn_info
+    run_svn_update
+    write_file
+};
+use Getopt::Long qw{GetOptions};
+use Pod::Usage qw{pod2usage};
+use Text::ParseWords qw{shellwords};
+
+# ------------------------------------------------------------------------------
+my $CONFIG = FCM::Admin::Config->instance();
+my %PATTERN_OF = (
+    q{}      => qr{.*}xms,
+    SRC_HOOK => qr{svn-hooks/}xms,
+);
+
+if (!caller()) {
+    main(@ARGV);
+}
+
+# ------------------------------------------------------------------------------
+# The main logic.
+sub main {
+    local(@ARGV) = @_;
+    my %option;
+    my $result = GetOptions(
+        \%option,
+        q{help|usage|h},
+        q{force},
+    );
+    if (!$result) {
+        pod2usage(1);
+    }
+    if (exists($option{help})) {
+        pod2usage(q{-verbose} => 1);
+    }
+    create_lock() || return;
+    my $RUNNER = FCM::Admin::Runner->instance();
+    my $is_force = $option{'force'};
+    UPDATE:
+    while (1) {
+        my @updates;
+        for my $source_key (shellwords($CONFIG->get_mirror_keys())) {
+            my $method = "get_$source_key";
+            push(@updates, run_svn_update($CONFIG->$method()));
+        }
+        if (!$is_force && !@updates) {
+            last UPDATE;
+        }
+        if ($is_force || grep {$_ =~ $PATTERN_OF{'SRC_HOOK'}} @updates) {
+            $RUNNER->run(
+                '(re-)installing hook scripts',
+                sub {
+                    for my $project (get_projects_from_svn_live()) {
+                        install_svn_hook($project);
+                    }
+                    return 1;
+                }
+            );
+        }
+        if ($is_force || grep {$_ =~ $PATTERN_OF{q{}}} @updates) {
+            $RUNNER->run(
+                'distributing FCM to standard locations', \&distribute_wc);
+        }
+        $is_force = 0;
+    }
+}
+
+# ------------------------------------------------------------------------------
+# Creates a lock. Returns true on success. Removes lock when program finishes.
+our $LOCK;
+sub create_lock {
+    my $home = (getpwuid($<))[7];
+    $LOCK = File::Spec->catfile($home, sprintf(".%s.lock", basename($0)));
+    if (-e $LOCK) {
+        $LOCK = undef;
+        return;
+    }
+    return run_mkpath($LOCK);
+    END {
+        if ($LOCK) {
+            run_rmtree($LOCK);
+        }
+    }
+}
+
+__END__
+
+=head1 NAME
+
+fcm-commit-update
+
+=head1 SYNOPSIS
+
+    fcm-commit-update
+
+=head1 DESCRIPTION
+
+This program performs the post-commit update for the FCM system. It runs
+continuously until no more update is available. It prevent another copy from
+running by creating a lock. If another copy detects a lock, it exits without
+doing anything.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --force
+
+Force an update.
+
+=back
+
+=head1 ARGUMENTS
+
+=over 4
+
+=item REPOS-NAME
+
+The name of the repository invoking this program.
+
+=item LOG-DIR-PATH
+
+The path to the log directory.
+
+=back
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/fcm-daily-update b/sbin/fcm-daily-update
new file mode 100755
index 0000000..4b6552e
--- /dev/null
+++ b/sbin/fcm-daily-update
@@ -0,0 +1,190 @@
+#!/usr/bin/perl
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use File::Basename qw{basename};
+use File::Spec::Functions qw{catfile};
+use IO::File;
+use Mail::Mailer;
+use Time::Piece qw{gmtime};
+
+use FCM::Admin::Config;
+use FCM::Admin::Runner;
+use FCM::Admin::System qw{
+    backup_svn_repository
+    backup_trac_environment
+    backup_trac_files
+    get_projects_from_svn_live
+    get_projects_from_trac_live
+    get_users
+    housekeep_svn_hook_logs
+    manage_users_in_svn_passwd
+    manage_users_in_trac_passwd
+    manage_users_in_trac_db_of
+};
+
+my $THIS = basename($0);
+my $CONFIG = FCM::Admin::Config->instance();
+my $UTIL = $FCM::Admin::Config::UTIL;
+
+if (!caller()) {
+    main(@ARGV);
+}
+
+sub main {
+    local(@ARGV) = @_;
+
+    # Redirects STDOUT and STDERR to the $out_file
+    open(my $old_out, q{>&}, \*STDOUT)
+        || die("$THIS: cannot duplicate STDOUT ($!)\n");
+    open(my $old_err, q{>&}, \*STDERR)
+        || die("$THIS: cannot duplicate STDERR ($!)\n");
+    my $log_dir = $UTIL->file_tilde_expand($CONFIG->get_log_dir());
+    my $now = gmtime();
+    my $day_of_week = lc($now->day_of_week()); # 0=sun, 1=mon, etc
+    my $day = lc($now->day()); # lower case day of week, e.g. sun, mon
+    my $out_file = catfile($log_dir, "$THIS-$day_of_week$day.log");
+    open(STDOUT, q{>}, $out_file)
+        || die("$THIS: cannot redirect STDOUT ($!)\n");
+    open(STDERR, q{>&}, \*STDOUT)
+        || die("$THIS: cannot redirect STDERR ($!)\n");
+
+    do_tasks();
+
+    # Restores STDOUT and STDERR
+    open(STDERR, q{>&}, $old_err)
+        || die("$THIS: cannot reinstate STDERR ($!)\n");
+    open(STDOUT, q{>&}, $old_out)
+        || die("$THIS: cannot reinstate STDOUT ($!)\n");
+
+    notify($out_file);
+}
+
+# ------------------------------------------------------------------------------
+# Performs the daily update tasks.
+sub do_tasks {
+    # (no argument)
+    my $RUNNER = FCM::Admin::Runner->instance();
+    my @svn_projects = get_projects_from_svn_live();
+    my @trac_projects = get_projects_from_trac_live();
+    my $user_ref = undef;
+    $RUNNER->run_continue(
+        "retrieving user accounts",
+        sub {$user_ref = get_users(); 1;},
+    );
+    if (defined($user_ref)) {
+        if ($CONFIG->get_svn_passwd_file()) {
+            $RUNNER->run_continue(
+                "updating SVN user accounts",
+                sub {manage_users_in_svn_passwd($user_ref)},
+            );
+        }
+        if ($CONFIG->get_trac_passwd_file()) {
+            $RUNNER->run_continue(
+                "updating Trac user accounts",
+                sub {manage_users_in_trac_passwd($user_ref)},
+            );
+        }
+        for my $project (@trac_projects) {
+            $RUNNER->run_continue(
+                "updating Trac accounts in $project",
+                sub {manage_users_in_trac_db_of($project, $user_ref)},
+            );
+        }
+    }
+    for my $project (@svn_projects) {
+        $RUNNER->run_continue(
+            "housekeep SVN repository logs for $project",
+            sub {housekeep_svn_hook_logs($project)},
+        );
+        $RUNNER->run_continue(
+            "backing up SVN repository for $project",
+            sub {backup_svn_repository({}, $project)},
+        );
+    }
+    for my $project (@trac_projects) {
+        $RUNNER->run_continue(
+            "backing up Trac environment for $project",
+            sub {backup_trac_environment({}, $project)},
+        );
+    }
+    $RUNNER->run_continue("backing up Trac files", \&backup_trac_files);
+}
+
+# ------------------------------------------------------------------------------
+# Notifies on completion.
+sub notify {
+    my ($out_file) = @_;
+    # Reports number of arguments in subject.
+    my @exceptions = FCM::Admin::Runner->instance()->get_exceptions();
+    my $subject
+        = sprintf(qq{$THIS finished with %d error(s)}, scalar(@exceptions));
+
+    my $mailer = Mail::Mailer->new();
+    $mailer->open({
+        From    => $CONFIG->get_notification_from(),
+        To      => $CONFIG->get_admin_email(),
+        Subject => $subject,
+    });
+
+    # Summarises the exceptions at the beginning of the message
+    $mailer->print(qq{$subject:\n});
+    for my $exception (@exceptions) {
+        $mailer->print(qq{    $exception});
+    }
+    $mailer->print(qq{\n});
+
+    # Prints content of the output
+    $mailer->print(q{#} x 72 . qq{\n});
+    $mailer->print(qq{# Output and error output of $THIS:\n});
+    $mailer->print(q{#} x 72 . qq{\n});
+    my $out_file_handle = IO::File->new($out_file);
+    if (defined($out_file_handle)) {
+        while (my $line = $out_file_handle->getline()) {
+            $mailer->print($line);
+        }
+        $out_file_handle->close();
+    }
+    $mailer->close();
+}
+
+__END__
+
+=head1 NAME
+
+fcm-daily-update
+
+=head1 SYNOPSIS
+
+    fcm-daily-update
+
+=head1 DESCRIPTION
+
+This program performs the daily update for the FCM system.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/fcm-install-svn-hook b/sbin/fcm-install-svn-hook
new file mode 100755
index 0000000..bc4b07b
--- /dev/null
+++ b/sbin/fcm-install-svn-hook
@@ -0,0 +1,115 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{
+    filter_projects
+    get_projects_from_svn_live
+    housekeep_svn_hook_logs
+    install_svn_hook
+};
+use FCM::Admin::Util qw{option2config};
+use Getopt::Long qw{GetOptions};
+use Pod::Usage qw{pod2usage};
+
+main();
+
+sub main {
+    my %option;
+    my $result = GetOptions(
+        \%option,
+        q{clean},
+        q{help|usage|h},
+        q{svn-live-dir=s},
+        q{svn-project-suffix=s},
+    );
+    if (!$result) {
+        pod2usage(1);
+    }
+    if (exists($option{help})) {
+        pod2usage(q{-verbose} => 1);
+    }
+    option2config(\%option);
+    my @projects = filter_projects([get_projects_from_svn_live()], \@ARGV);
+    for my $project (sort {$a->get_name() cmp $b->get_name()} @projects) {
+        install_svn_hook($project, $option{clean});
+        housekeep_svn_hook_logs($project);
+    }
+}
+
+__END__
+
+=head1 NAME
+
+fcm-install-svn-hook
+
+=head1 SYNOPSIS
+
+    fcm-install-svn-hook [OPTIONS] [PROJECT ...]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --clean
+
+Removes items (except logs) that are not in the install sources.
+
+=item --help, -h, --usage
+
+Prints help and exits.
+
+=item --svn-live-dir=DIR
+
+Specifies the root location of the live directory. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --svn-project-suffix=NAME
+
+Specifies the suffix added to the project name. The default is "_svn".
+
+=back
+
+=head1 ARGUMENTS
+
+=over 4
+
+=item PROJECT
+
+Specifies one or more project requiring hooks scripts to be installed. If no
+project is specified, the program install the hook scripts to all projects in
+the live directory.
+
+=back
+
+=head1 DESCRIPTION
+
+This program install hook scripts for Subversion repositories in the live
+directory, and install/housekeep the log files for the hook scripts.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/fcm-manage-trac-env-session b/sbin/fcm-manage-trac-env-session
new file mode 100755
index 0000000..50207cd
--- /dev/null
+++ b/sbin/fcm-manage-trac-env-session
@@ -0,0 +1,96 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{
+    filter_projects
+    get_projects_from_trac_live
+    get_users
+    manage_users_in_trac_db_of
+};
+use FCM::Admin::Util qw{option2config};
+use Getopt::Long qw{GetOptions};
+use Pod::Usage qw{pod2usage};
+
+if (!caller()) {
+    main(@ARGV);
+}
+
+sub main {
+    local(@ARGV) = @_;
+    my %option;
+    my $result = GetOptions(
+        \%option,
+        q{help|usage|h},
+        q{trac-live-dir=s},
+    );
+    if (!$result) {
+        pod2usage(1);
+    }
+    if (exists($option{help})) {
+        pod2usage(q{-verbose} => 1);
+    }
+    option2config(\%option);
+    my $user_ref = get_users();
+    for my $project (filter_projects([get_projects_from_trac_live()], \@ARGV)) {
+        manage_users_in_trac_db_of($project, $user_ref),
+    }
+}
+
+__END__
+
+=head1 NAME
+
+fcm-manage-trac-env-session
+
+=head1 SYNOPSIS
+
+    fcm-manage-trac-env-session [OPTIONS] [PROJECT ...]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --help, -h, --usage
+
+Prints help and exits.
+
+=item --trac-live-dir=DIR
+
+Specifies the root location of the live directory of the Trac environments. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=back
+
+=head1 DESCRIPTION
+
+This program manages session and session attributes for authenticated users in
+Trac environments. If no PROJECT is specified, the program acts on all trac
+environments in the live directory.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/fcm-manage-users b/sbin/fcm-manage-users
new file mode 100755
index 0000000..d7be833
--- /dev/null
+++ b/sbin/fcm-manage-users
@@ -0,0 +1,119 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{
+    get_projects_from_trac_live
+    get_users
+    manage_users_in_svn_passwd
+    manage_users_in_trac_passwd
+    manage_users_in_trac_db_of
+};
+use FCM::Admin::Util qw{option2config};
+use Getopt::Long qw{GetOptions};
+use Pod::Usage qw{pod2usage};
+
+main();
+
+sub main {
+    my %option;
+    my $result = GetOptions(
+        \%option,
+        q{help|usage|h},
+        q{svn-live-dir=s},
+        q{svn-passwd-file=s},
+        q{trac-live-dir=s},
+        q{trac-passwd-file=s},
+    );
+    if (!$result) {
+        pod2usage(1);
+    }
+    if (exists($option{help})) {
+        pod2usage(q{-verbose} => 1);
+    }
+    if (@ARGV) {
+        my $message = sprintf("No argument expected, %d given", scalar(@ARGV));
+        pod2usage({q{-exitval} => 1, q{-message} => $message});
+    }
+    option2config(\%option);
+    my $user_ref = get_users();
+    manage_users_in_svn_passwd($user_ref);
+    manage_users_in_trac_passwd($user_ref);
+    my @projects = get_projects_from_trac_live();
+    for my $project (@projects) {
+        manage_users_in_trac_db_of($project, $user_ref),
+    }
+}
+
+__END__
+
+=head1 NAME
+
+fcm-manage-users
+
+=head1 SYNOPSIS
+
+    fcm-manage-users [OPTIONS]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --help, -h, --usage
+
+Prints help and exits.
+
+=item --svn-live-dir=DIR
+
+Specifies the root location of the live directory of the Subversion
+repositories. See L<FCM::Admin::Config|FCM::Admin::Config> for the current
+default.
+
+=item --svn-passwd-file=FILE
+
+Specifies the base name of the Subversion password file. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --trac-live-dir=DIR
+
+Specifies the root location of the live directory of the Trac environments. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --trac-passwd-file=FILE
+
+Specifies the base name of the Trac password file. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=back
+
+=head1 DESCRIPTION
+
+This program manages user (login) information for Subversion repositories and
+Trac environments hosted by FCM.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/fcm-recover-svn-repos b/sbin/fcm-recover-svn-repos
new file mode 100755
index 0000000..fa13910
--- /dev/null
+++ b/sbin/fcm-recover-svn-repos
@@ -0,0 +1,136 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{
+    filter_projects
+    get_projects_from_svn_backup
+    recover_svn_repository
+};
+use FCM::Admin::Util qw{option2config};
+use Getopt::Long qw{GetOptions};
+use Pod::Usage qw{pod2usage};
+
+main();
+
+sub main {
+    my %option;
+    my $result = GetOptions(
+        \%option,
+        q{help|usage|h},
+        q{no-recover-dumps},
+        q{no-recover-hooks},
+        q{svn-backup-dir=s},
+        q{svn-dump-dir=s},
+        q{svn-live-dir=s},
+        q{svn-project-suffix=s},
+    );
+    if (!$result) {
+        pod2usage(1);
+    }
+    if (exists($option{help})) {
+        pod2usage(q{-verbose} => 1);
+    }
+    option2config(\%option);
+    my @projects = filter_projects([get_projects_from_svn_backup()], \@ARGV);
+    for my $project (sort {$a->get_name() cmp $b->get_name()} @projects) {
+        recover_svn_repository(
+            $project,
+            !$option{q{no-recover-dumps}},
+            !$option{q{no-recover-hooks}},
+        );
+    }
+}
+
+__END__
+
+=head1 NAME
+
+fcm-recover-svn-repos
+
+=head1 SYNOPSIS
+
+    fcm-recover-svn-repos [OPTIONS] [PROJECT ...]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --help, -h, --usage
+
+Prints help and exits.
+
+=item --no-recover-dumps
+
+If this option is specified, the program will not attempt to load the revision
+dumps of each repository after it has been restored from the backup.
+
+=item --no-recover-hooks
+
+If this option is specified, the program will not attempt to reinstate the hook
+scripts for each repository after it has been restored from the backup.
+
+=item --svn-backup-dir=DIR
+
+Specifies the root location of the backup directory. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --svn-dump-dir=DIR
+
+Specifies the root location of the directory where revision dumps are kept. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --svn-live-dir=DIR
+
+Specifies the root location of the live directory. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --svn-project-suffix=NAME
+
+Specifies the suffix added to the project name. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=back
+
+=head1 ARGUMENTS
+
+=over 4
+
+=item PROJECT
+
+Specifies one or more project to recover. If no project is specified, the
+program searches the backup directory for projects to recover.
+
+=back
+
+=head1 DESCRIPTION
+
+This program archives Subversion repositories in the live directory to the
+backup directory.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/fcm-recover-trac-env b/sbin/fcm-recover-trac-env
new file mode 100755
index 0000000..4d4a7fb
--- /dev/null
+++ b/sbin/fcm-recover-trac-env
@@ -0,0 +1,112 @@
+#!/usr/bin/perl
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{
+    filter_projects
+    get_projects_from_trac_backup
+    recover_trac_environment
+    recover_trac_files
+};
+use FCM::Admin::Util qw{option2config};
+use Getopt::Long qw{GetOptions};
+use Pod::Usage qw{pod2usage};
+
+main();
+
+sub main {
+    my %option;
+    my $result = GetOptions(
+        \%option,
+        q{help|usage|h},
+        q{trac-backup-dir=s},
+        q{trac-live-dir=s},
+    );
+    if (!$result) {
+        pod2usage(1);
+    }
+    if (exists($option{help})) {
+        pod2usage(q{-verbose} => 1);
+    }
+    option2config(\%option);
+    my @projects = filter_projects([get_projects_from_trac_backup()], \@ARGV);
+    for my $project (@projects) {
+        recover_trac_environment($project);
+    }
+    if (!@ARGV) {
+        recover_trac_files();
+    }
+}
+
+__END__
+
+=head1 NAME
+
+fcm-recover-trac-env
+
+=head1 SYNOPSIS
+
+    fcm-recover-trac-env [OPTIONS] [PROJECT ...]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --help, -h, --usage
+
+Prints help and exits.
+
+=item --trac-backup-dir=DIR
+
+Specifies the root location of the backup directory. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=item --trac-live-dir=DIR
+
+Specifies the root location of the live directory. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=back
+
+=head1 ARGUMENTS
+
+=over 4
+
+=item PROJECT
+
+Specifies one or more project to recover. If no project is specified, the
+program searches the backup directory for projects and files to recover.
+
+=back
+
+=head1 DESCRIPTION
+
+This program recovers Trac environments and files from the backup directory to
+the live directory.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/fcm-rpmbuild b/sbin/fcm-rpmbuild
new file mode 100755
index 0000000..25c2a27
--- /dev/null
+++ b/sbin/fcm-rpmbuild
@@ -0,0 +1,112 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# NAME
+#     fcm-rpmbuild
+#
+# SYNOPSIS
+#     fcm-rpmbuild [--gui] [REV]
+#     E.g.:
+#     fcm rpmbuild 2014-03
+#
+# DESCRIPTION
+#     Build an RPM for distributing FCM.
+#     Assume that the current working directory is a local Git clone containing
+#     the FCM project.
+#
+# OPTIONS
+#     --gui - add GUI packages such as "perl-Tk" and "xxdiff".
+#
+# ARGUMENTS
+#     REV - Revision to build. Default to HEAD.
+#-------------------------------------------------------------------------------
+
+set -eu
+THIS=$(basename $0)
+NAME='fcm'
+
+rpmdev-setuptree
+
+# Build RPM without no dependency on subversion or xxdiff?
+REQUIRES_GUI=
+if (($# > 0)) && [[ $1 == '--gui' ]]; then
+    REQUIRES_GUI='perl-Tk xxdiff'
+    shift 1
+fi
+
+# Create the source tree
+REV=${1:-HEAD}
+REV_NAME=$(git describe $REV)
+REV_BASE=$(git describe --abbrev=0 $REV)
+RELEASE=1
+if [[ $REV_NAME != $REV_BASE ]]; then
+    COMMIT_DATE=$(date -u +%Y%m%dT%H%M "--date=$(git show -s --format=%ci $REV)")
+    RELEASE=${COMMIT_DATE}git${REV_NAME#$REV_BASE-*-g}
+fi
+REV_BASE_DOT=$(sed 's/-/./g' <<<$REV_BASE)
+git archive --format=tar --prefix=$NAME-$REV_BASE_DOT/ $REV \
+    | (cd ~/rpmbuild/SOURCES/ && tar -xf -)
+SOURCE=~/rpmbuild/SOURCES/$NAME-$REV_BASE_DOT
+echo "FCM.VERSION=\"$REV_NAME\";" >$SOURCE/doc/etc/$NAME-version.js
+rm -r $SOURCE/{test,t}
+if [[ -z $REQUIRES_GUI ]]; then
+    rm $SOURCE/{bin/fcm_gui,lib/FCM1/Interactive/InputGetter/GUI.pm}
+fi
+
+# Create the rpmbuild spec file
+{
+    cat <<__SPEC__
+Name: $NAME
+Version: $REV_BASE_DOT
+Release: $RELEASE
+Summary: A modern Fortran build system + wrappers to SVN
+Group: Development/Tools
+License: GPLv3
+URL: https://github.com/metomi/$NAME/
+Source0: https://github.com/metomi/$NAME/releases/
+BuildArch: noarch
+Requires: diffutils filesystem gzip make perl-core perl-Config-IniFiles perl-MailTools perl-XML-Parser subversion subversion-perl $REQUIRES_GUI
+
+%description
+FCM: A modern Fortran build system + wrappers to Subversion for scientific
+software development
+
+%prep
+
+%build
+
+%install
+rm -fr %{buildroot}
+mkdir -p %{buildroot}/opt/ %{buildroot}/usr/bin
+cp -pr %_sourcedir/$NAME-$REV_BASE_DOT %{buildroot}/opt/$NAME
+cp -p %_sourcedir/$NAME-$REV_BASE_DOT/usr/bin/fcm %{buildroot}/usr/bin
+
+%clean 
+rm -fr %{buildroot}
+
+%files
+/opt/$NAME
+/usr/bin/fcm
+__SPEC__
+} >~/rpmbuild/SPECS/$NAME.spec
+
+cd ~/rpmbuild/SPECS
+rpmbuild -ba $NAME.spec
+rm -fr ~/rpmbuild/SOURCES/$NAME-$REV_BASE_DOT
+exit
diff --git a/sbin/fcm-user-to-email b/sbin/fcm-user-to-email
new file mode 100755
index 0000000..333198e
--- /dev/null
+++ b/sbin/fcm-user-to-email
@@ -0,0 +1,66 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{get_users};
+use FCM::Util;
+
+my $UTIL = FCM::Util->new();
+
+if (!caller()) {
+    main(@ARGV);
+}
+
+sub main {
+    local(@ARGV) = @_;
+    if (!@ARGV) {
+        return;
+    }
+    my @names = @ARGV;
+    local($FCM::Admin::System::UTIL) = $UTIL;
+    my @emails
+        = sort grep {$_} map {$_->get_email()} values(%{get_users(@names)});
+    print(join(q{,}, @emails) . "\n");
+}
+
+1;
+__END__
+
+=head1 NAME
+
+fcm-users-to-emails
+
+=head1 SYNOPSIS
+
+    fcm-user-to-email USER ...
+
+=head1 DESCRIPTION
+
+Print email addresses (comma separated) of users in argument list.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/fcm-vacuum-trac-env-db b/sbin/fcm-vacuum-trac-env-db
new file mode 100755
index 0000000..eda1546
--- /dev/null
+++ b/sbin/fcm-vacuum-trac-env-db
@@ -0,0 +1,101 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{
+    filter_projects
+    get_projects_from_trac_live
+    vacuum_trac_env_db
+};
+use FCM::Admin::Util qw{option2config};
+use Getopt::Long qw{GetOptions};
+use Pod::Usage qw{pod2usage};
+
+main();
+
+sub main {
+    my %option;
+    my $result = GetOptions(
+        \%option,
+        q{help|usage|h},
+        q{trac-live-dir=s},
+    );
+    if (!$result) {
+        pod2usage(1);
+    }
+    if (exists($option{help})) {
+        pod2usage(q{-verbose} => 1);
+    }
+    option2config(\%option);
+    my @projects = filter_projects([get_projects_from_trac_live()], \@ARGV);
+    for my $project (@projects) {
+        vacuum_trac_env_db($project);
+    }
+}
+
+__END__
+=head1 NAME
+
+fcm-vacuum-trac-env-db
+
+=head1 SYNOPSIS
+
+    fcm-vacuum-trac-env-db [OPTIONS] [PROJECT ...]
+
+=head1 OPTIONS
+
+=over 4
+
+=item --help, -h, --usage
+
+Prints help and exits.
+
+=item --trac-live-dir=DIR
+
+Specifies the root location of the live directory of Trac environments. See
+L<FCM::Admin::Config|FCM::Admin::Config> for the current default.
+
+=back
+
+=head1 ARGUMENTS
+
+=over 4
+
+=item PROJECT
+
+Specifies one or more project to vacuum. If no project is specified, the
+program searches the live directory for projects to vacuum.
+
+=back
+
+=head1 DESCRIPTION
+
+This program issues the "VACUUM" SQL command in the databases of the Trac
+environments in the live directory.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/my-regular-update.example b/sbin/my-regular-update.example
new file mode 100755
index 0000000..3aa6c11
--- /dev/null
+++ b/sbin/my-regular-update.example
@@ -0,0 +1,43 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+set -eu
+LOCK=/path/to/lock
+WC_OF_PROJECT=/path/to/working/copy/of/project
+
+if [[ -e $LOCK ]]; then
+    exit
+fi
+
+mkdir -p $LOCK
+echo "$(whoami)@$(hostname):$$" >$LOCK/info # info on who created the lock
+
+while true; do
+    UPDATED=$(cd $WC_OF_PROJECT && svn update | sed '$d' | cut -c6-)
+    if [[ -z $UPDATED ]]; then
+        break
+    fi
+    if [[ -n $(echo "$UPDATED" | grep '^foo/bar/[^/]*\.baz$') ]]; then
+        : # Performs some update if foo/bar/*.baz has changed
+    fi
+done
+
+rm $LOCK/info
+rmdir $LOCK
+exit
diff --git a/sbin/post-commit-bg b/sbin/post-commit-bg
new file mode 100755
index 0000000..d71e0e2
--- /dev/null
+++ b/sbin/post-commit-bg
@@ -0,0 +1,160 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# NAME
+#   post-commit-bg
+#
+# SYNOPSIS
+#   post-commit-bg REPOS REV
+#
+# ARGUMENTS
+#   REPOS - the path to the Subversion repository
+#   REV - the revision of the commit
+#   TXN - the commit transaction that becomes the revision
+#
+# DESCRIPTION
+#   This script performs the post-commit tasks of a Subversion repository in
+#   the background.
+#
+#   The script does the following:
+#   1. Creates an incremental revision dump of the current revision.
+#   2. Update corresponding Trac environment, if relevant.
+#   3. Checks the size of the revision dump. Warns if it exceeds a threshold.
+#   4. If this changeset has a change to "^/svnperms.conf", install its HEAD
+#      revision at "$REPOS/hooks/", or remove it from "$REPOS/hooks/" if it is
+#      removed from the HEAD.
+#   5. Runs "$REPOS/hooks/post-commit-bg-custom" and/or
+#      "$REPOS/hooks/post-commit-background-custom", if available.
+#   6. E-mails the host user account on error.
+#
+# ENVIRONMENT VARIABLES
+#   FCM_SVN_HOOK_COMMIT_DUMP_DIR
+#     The path to dump commit deltas. Generate a commit delta if specified.
+#   FCM_SVN_HOOK_TRAC_ROOT_DIR
+#     The root directories of Trac environments. Update corresponding Trac
+#     environment if specified.
+#   FCM_SVN_HOOK_REPOS_SUFFIX
+#     A suffix that should be removed from the basename of REPOS to get the
+#     name of the Trac environment. (Default is "".)
+#
+# FILES
+#   $REPOS/hooks/post-commit-bg-custom
+#   $REPOS/hooks/post-commit-background-custom
+#-------------------------------------------------------------------------------
+set -eu
+. "$(dirname $0)/trac_hook"
+
+REPOS=$1
+REV=$2
+TXN=$3
+
+export PATH=${PATH:-'/usr/local/bin:/bin:/usr/bin'}:$(dirname $0)
+THIS=$(basename $0)
+USER=${USER:-$(whoami)}
+LOG_REV="$REPOS/log/$THIS-$REV.log"
+
+main() {
+    local RET_CODE=0
+    local NOW=$(date -u +%FT%H:%M:%SZ)
+    local AUTHOR=$(svnlook author -r "$REV" "$REPOS")
+    echo "$NOW+ $REV by $AUTHOR"
+
+    # Dump revision delta
+    if [[ -n ${FCM_SVN_HOOK_COMMIT_DUMP_DIR:-} ]]; then
+        if [[ ! -d "$FCM_SVN_HOOK_COMMIT_DUMP_DIR" ]]; then
+            mkdir -p "$FCM_SVN_HOOK_COMMIT_DUMP_DIR" || true
+        fi
+        local NAME=$(basename "$REPOS")
+        local DUMP="$FCM_SVN_HOOK_COMMIT_DUMP_DIR/$NAME-$REV.gz"
+        echo "svnadmin dump -r$REV --incremental --deltas $REPOS | gzip 1>$DUMP"
+        svnadmin dump "-r$REV" --incremental --deltas "$REPOS" \
+            | gzip 1>"$DUMP" || RET_CODE=$?
+    fi
+
+    # Resync Trac
+    trac_hook "$REPOS" "$REV" added || RET_CODE=$?
+
+    # Check size - send warning email if threshold exceeded
+    local REV_FILE=$REPOS/db/revs/$((REV / 1000))/$REV
+    local REV_FILE_SIZE_THRESHOLD=1048576 # 1MB
+    local REV_FILE_SIZE=$(du -b -s $REV_FILE | cut -f 1)
+    if (($REV_FILE_SIZE > $REV_FILE_SIZE_THRESHOLD)); then
+        echo "REV_FILE_SIZE=$REV_FILE_SIZE # EXCEED $REV_FILE_SIZE_THRESHOLD"
+        RET_CODE=1
+    else
+        echo "REV_FILE_SIZE=$REV_FILE_SIZE # within $REV_FILE_SIZE_THRESHOLD"
+    fi
+
+    # Install commit.conf and svnperms.conf, if necessary
+    local CHANGED=$(svnlook changed -r "${REV}" "${REPOS}")
+    local NAME=
+    for NAME in 'commit.conf' 'svnperms.conf'; do
+        if grep -q "^....${NAME}\$" <<<"${CHANGED}"; then
+            # Don't specify revision, so always look at latest.
+            if svnlook filesize "${REPOS}" "${NAME}" >/dev/null 2>&1; then
+                echo "svnlook cat ${REPOS} ${NAME} >${REPOS}/hooks/${NAME}"
+                svnlook cat "${REPOS}" "${NAME}" >"${REPOS}/hooks/${NAME}"
+            else
+                echo "rm -f ${REPOS}/hooks/${NAME}"
+                rm -f "${REPOS}/hooks/${NAME}"
+            fi
+        fi
+    done
+
+    # On commit to a branch, notify the branch owner if author is not him/her
+    local COMMIT_CONFIG="${REPOS}/hooks/commit.conf"
+    if ! grep -q 'no-notify-branch-owner' "$COMMIT_CONFIG" 2>/dev/null; then
+        local ADDRS=$(post-commit-bg-notify-who "$REPOS" "$REV" "$TXN")
+        if [[ -n $ADDRS ]]; then
+            SUBJECT="-s$(basename $REPOS)@$REV by $AUTHOR"
+            FROM=
+            if [[ -n ${FCM_SVN_HOOK_NOTIFICATION_FROM:-} ]]; then
+                FROM="-r${FCM_SVN_HOOK_NOTIFICATION_FROM:-}"
+            fi
+            echo -n "svn log -v -r \"$REV\" \"file://$REPOS\""
+            echo " | mail \"$FROM\" \"$SUBJECT\" \"$ADDRS\""
+            svn log -v -r "$REV" "file://$REPOS" | mail "$FROM" "$SUBJECT" "$ADDRS"
+        fi
+    fi
+
+    # Custom hook
+    local CUSTOM_HOOK=
+    for CUSTOM_HOOK in \
+        "$REPOS/hooks/$THIS-custom" \
+        "$REPOS/hooks/post-commit-background-custom"
+    do
+        if [[ -x "$CUSTOM_HOOK" ]]; then
+            echo "$CUSTOM_HOOK $REPOS $REV $TXN"
+            "$CUSTOM_HOOK" "$REPOS" "$REV" "$TXN" || RET_CODE=$?
+        fi
+    done
+
+    echo "RET_CODE=$RET_CODE"
+    return $RET_CODE
+}
+
+if ! main 1>$LOG_REV 2>&1; then
+    if [[ -n ${FCM_SVN_HOOK_ADMIN_EMAIL:-} ]]; then
+        mail -s "[$THIS] $REPOS@$REV" "$FCM_SVN_HOOK_ADMIN_EMAIL" <"$LOG_REV" \
+            || true
+    fi
+fi
+cat "$LOG_REV"
+rm -f "$LOG_REV"
+exit
diff --git a/sbin/post-commit-bg-notify-who b/sbin/post-commit-bg-notify-who
new file mode 100755
index 0000000..a557c5b
--- /dev/null
+++ b/sbin/post-commit-bg-notify-who
@@ -0,0 +1,103 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{get_users};
+use FCM::System::CM::SVN;
+use FCM::Util;
+
+our @IGNORES = qw{Config Rel Share};
+
+my $UTIL = FCM::Util->new();
+my $CM_SYS = FCM::System::CM::SVN->new({'util' => $UTIL});
+
+if (!caller()) {
+    main(@ARGV);
+}
+
+sub main {
+    local(@ARGV) = @_;
+    my ($repos, $rev, $txn) = @ARGV;
+
+    my %layout_config = $CM_SYS->load_layout_config('file://' . $repos);
+    if (!$layout_config{'level-owner-branch'}) {
+        return;
+    }
+
+    my ($author) = $CM_SYS->stdout(qw{svnlook author -r}, $rev, $repos);
+
+    # Get list of new paths
+    my %names = (); # {$name1 => 1, $name2 => 1, ...}
+    my @lines = $CM_SYS->stdout(qw{svnlook changed -r}, $rev, $repos);
+    LINE:
+    for my $line (@lines) {
+        my $status = substr($line, 0, 1);
+        my $path = substr($line, 4);
+        my $layout = $CM_SYS->get_layout_common(
+            $repos,
+            ($status eq 'D' ? $rev - 1 : $rev),
+            $path,
+            1, # $is_local=1
+        );
+        my $owner = $layout->get_branch_owner();
+        if ($owner && $owner ne $author && !grep {$_ eq $owner} @IGNORES) {
+            $names{$owner} = 1;
+        }
+    }
+
+    # Get emails, if necessary
+    if (%names) {
+        local($FCM::Admin::System::UTIL) = $UTIL;
+        my @names = ($author, keys(%names));
+        my @emails
+            = sort grep {$_} map {$_->get_email()} values(%{get_users(@names)});
+        print(join(q{,}, @emails) . "\n");
+    }
+}
+
+1;
+__END__
+
+=head1 NAME
+
+post-commit-bg-notify-who
+
+=head1 SYNOPSIS
+
+    post-commit-bg-notify-who $REPOS $REV $TXN
+
+=head1 ARGUMENTS
+
+Accept the same arguments as a Subversion post-commit hook.
+
+=head1 DESCRIPTION
+
+This program prints email addresses who should be notified of the change. E.g.
+If this commit is performed by an author on someone else's branch.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/post-revprop-change-bg b/sbin/post-revprop-change-bg
new file mode 100755
index 0000000..0bab5d4
--- /dev/null
+++ b/sbin/post-revprop-change-bg
@@ -0,0 +1,111 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# NAME
+#   post-revprop-change-bg
+#
+# SYNOPSIS
+#   post-revprop-change-bg REPOS REV PROP_AUTHOR PROP_NAME ACTION
+#
+# ARGUMENTS
+#   REPOS - the path to the Subversion repository
+#   REV - the revision relevant for the property
+#   PROP_AUTHOR - the author of this property change
+#   PROP_NAME - the name of the property, should only be "svn:log"
+#   ACTION - the action of the property change, should only be "M"
+#
+# DESCRIPTION
+#   This script performs the post-revprop-change tasks of a Subversion
+#   repository in the background.
+#
+#   The script does the following:
+#   1. Write diff between old and new property.
+#   2. Update corresponding Trac environment, if relevant.
+#   3. E-mails the host user account on error.
+#   4. E-mails the changeset author if property author is not changeset author.
+#
+# ENVIRONMENT VARIABLES
+#   FCM_SVN_HOOK_TRAC_ROOT_DIR
+#     The root directories of Trac environments. Update corresponding Trac
+#     environment if specified.
+#   FCM_SVN_HOOK_REPOS_SUFFIX
+#     A suffix that should be removed from the basename of REPOS to get the
+#     name of the Trac environment. (Default is "".)
+#-------------------------------------------------------------------------------
+set -eu
+. "$(dirname $0)/trac_hook"
+
+REPOS=$1
+REV=$2
+PROP_AUTHOR=$3
+PROP_NAME=$4
+ACTION=$5
+
+export PATH=${PATH:-'/usr/local/bin:/bin:/usr/bin'}:$(dirname $0)
+THIS=$(basename $0)
+USER=${USER:-$(whoami)}
+LOG_TMP=$(mktemp "$REPOS/log/$THIS.log.XXXXXXXXXX")
+NAME=$(basename "$REPOS")
+
+main() {
+    local RET_CODE=0
+    local NOW=$(date -u +%FT%H:%M:%SZ)
+    echo "$NOW+ $ACTION $PROP_NAME @$REV by $PROP_AUTHOR"
+
+    # Resync Trac
+    trac_hook "$REPOS" "$REV" modified || RET_CODE=$?
+
+    # Diff old/new in log
+    local OLD_FILE=$(mktemp "$REPOS/log/$THIS.$REV.XXXXXXXXXX.old")
+    cat >"$OLD_FILE"
+    local DIFF_FILE=$(mktemp "$REPOS/log/$THIS.$REV.XXXXXXXXXX.diff")
+    {
+        echo "$NOW+ $ACTION $PROP_NAME @$REV by $PROP_AUTHOR"
+        printf '=%.0s' {1..72}
+        echo
+    } >"$DIFF_FILE"
+    svnlook pg -r "$REV" --revprop "$REPOS" "$PROP_NAME" \
+        | diff -u --label="old-value" --label="new-value" "$OLD_FILE" - \
+        | tee -a "$DIFF_FILE"
+
+    # Email to changeset author if not the same as property change author
+    REV_AUTHOR=$(svnlook author -r "$REV" "$REPOS")
+    if [[ "$REV_AUTHOR" != "$PROP_AUTHOR" ]]; then
+        SUBJECT="-s$(basename $REPOS)@$REV [$ACTION $PROP_NAME] by $PROP_AUTHOR"
+        FROM=
+        if [[ -n ${FCM_SVN_HOOK_NOTIFICATION_FROM:-} ]]; then
+            FROM="-r${FCM_SVN_HOOK_NOTIFICATION_FROM:-}"
+        fi
+        ADDRS=$(fcm-user-to-email "$REV_AUTHOR" "$PROP_AUTHOR")
+        mail "$FROM" "$SUBJECT" "$ADDRS" <"$DIFF_FILE" || true
+    fi
+    rm -f "$OLD_FILE" "$DIFF_FILE"
+
+    echo "RET_CODE=$RET_CODE"
+    return $RET_CODE
+}
+
+if ! main 1>$LOG_TMP 2>&1 && [[ -n ${FCM_SVN_HOOK_ADMIN_EMAIL:-} ]]; then
+    mail -s "[ERROR $THIS] $NAME" "$FCM_SVN_HOOK_ADMIN_EMAIL" <"$LOG_TMP" \
+        || true
+fi
+
+cat "$LOG_TMP"
+rm -f "$LOG_TMP"
+exit
diff --git a/sbin/pre-commit b/sbin/pre-commit
new file mode 100755
index 0000000..00cf366
--- /dev/null
+++ b/sbin/pre-commit
@@ -0,0 +1,110 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# NAME
+#   pre-commit
+#
+# SYNOPSIS
+#   pre-commit REPOS TXN
+#
+# ARGUMENTS
+#   REPOS - the path to the Subversion repository
+#   TXN - the commit transaction
+#
+# DESCRIPTION
+#   This script performs pre-commit check, including:
+#   1. Path-based permission check using "svnperms.py", if
+#      "$REPOS/hooks/svnperms.conf" exists.
+#   2. Size check. Transaction should occupy less than 10MB, or the number of
+#      MB specified in "$REPOS/hooks/pre-commit-size-threshold.conf".
+#   3. Runs "$REPOS/hooks/pre-commit-custom" if it exists.
+#
+# ENVIRONMENT VARIABLES
+#   FCM_SVN_HOOK_ADMIN_EMAIL
+#     The name of the admin team. (Default is "Admin".)
+#-------------------------------------------------------------------------------
+set -eu
+
+REPOS=$1
+TXN=$2
+
+export PATH=${PATH:-'/usr/local/bin:/bin:/usr/bin'}:$(dirname $0)
+THIS=$(basename $0)
+USER=${USER:-$(whoami)}
+NAME=$(basename "$REPOS")
+LOG_TXN="$REPOS/$THIS-$TXN.log"
+
+main() {
+    local NOW=$(date -u +%FT%H:%M:%SZ)
+    local AUTHOR=$(svnlook author -t "$TXN" "$REPOS")
+    echo "$NOW+ $TXN by $AUTHOR"
+    svnlook changed -t "$TXN" "$REPOS"
+    local ADMIN_EMAIL=${FCM_SVN_HOOK_ADMIN_EMAIL:-Admin}
+
+    # Check size.
+    local MB=1048576
+    local THRESHOLD=10
+    local SIZE_THRESHOLD_FILE="$REPOS/hooks/$THIS-size-threshold.conf"
+    if [[ -f "$SIZE_THRESHOLD_FILE" && -r "$SIZE_THRESHOLD_FILE" ]]; then
+        THRESHOLD=$(<"$SIZE_THRESHOLD_FILE")
+    fi
+    local TXN_FILE="$REPOS/db/txn-protorevs/$TXN.rev"
+    local SIZE=$(du -b "$TXN_FILE" | cut -f 1)
+    if ((SIZE > THRESHOLD * MB)); then
+        SIZE=$(du -h "$TXN_FILE" | cut -f 1)
+        echo "$NAME@$TXN: changeset size ${SIZE}B exceeds ${THRESHOLD}MB." >&2
+        echo "Email $ADMIN_EMAIL if you need to bypass this restriction." >&2
+        return 1
+    fi
+
+    # Check permission.
+    local PERM_CONFIG="$REPOS/hooks/svnperms.conf"
+    if [[ -r "$PERM_CONFIG" && -s "$PERM_CONFIG" ]]; then
+        svnperms.py -r "$REPOS" -t "$TXN" -f "$PERM_CONFIG" || return $?
+    elif [[ -L "$PERM_CONFIG" ]]; then
+        echo "$NAME: permission configuration file not found." >&2
+        echo "$ADMIN_EMAIL has been notified." >&2
+        return 1
+    fi
+
+    # Verify owner of any new branches, if relevant
+    local COMMIT_CONFIG="${REPOS}/hooks/commit.conf"
+    if ! grep -q 'no-verify-branch-owner' "$COMMIT_CONFIG" 2>/dev/null; then
+        pre-commit-verify-branch-owner "$REPOS" "$TXN" || return $?
+    fi
+
+    # Custom checking, if required
+    local CUSTOM_HOOK="$REPOS/hooks/$THIS-custom"
+    if [[ -x "$CUSTOM_HOOK" ]]; then
+        "$CUSTOM_HOOK" "$REPOS" "$TXN" || return $?
+    fi
+}
+
+RET_CODE=0
+if ! main 1>"$LOG_TXN" 2>&1; then
+    if [[ -n ${FCM_SVN_HOOK_ADMIN_EMAIL:-} ]]; then
+        mail -s "[$THIS] $REPOS@$TXN" "$FCM_SVN_HOOK_ADMIN_EMAIL" <"$LOG_TXN" \
+            || true
+    fi
+    cat "$LOG_TXN"
+    cat "$LOG_TXN" >&2
+    RET_CODE=1
+fi
+rm -f "$LOG_TXN"
+exit $RET_CODE
diff --git a/sbin/pre-commit-verify-branch-owner b/sbin/pre-commit-verify-branch-owner
new file mode 100755
index 0000000..536ec46
--- /dev/null
+++ b/sbin/pre-commit-verify-branch-owner
@@ -0,0 +1,100 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use FCM::Admin::System qw{verify_users};
+use FCM::System::CM::SVN;
+use FCM::Util;
+
+our @IGNORES = qw{Config Rel Share};
+
+my $UTIL = $FCM::Admin::System::UTIL;
+my $CM_SYS = FCM::System::CM::SVN->new({'util' => $UTIL});
+
+if (!caller()) {
+    main(@ARGV);
+}
+
+sub main {
+    local(@ARGV) = @_;
+    my ($repos, $txn) = @ARGV;
+
+    my %layout_config = $CM_SYS->load_layout_config('file://' . $repos);
+    if (!$layout_config{'level-owner-branch'}) {
+        return;
+    }
+
+    # Get list of new paths
+    my %lines_of; # $lines_of{$owner} = [$path, ...]
+    LINE:
+    for my $line ($CM_SYS->stdout(qw{svnlook changed -t}, $txn, $repos)) {
+        my $status = substr($line, 0, 1);
+        if ($status eq 'A') {
+            my $path = substr($line, 4);
+            # $is_local=1
+            my $layout = $CM_SYS->get_layout_common($repos, $txn, $path, 1);
+            my $owner = $layout->get_branch_owner();
+            if ($owner && !grep {$_ eq $owner} @IGNORES) {
+                if (!exists($lines_of{$owner})) {
+                    $lines_of{$owner} = [];
+                }
+                push(@{$lines_of{$owner}}, $line);
+            }
+        }
+    }
+
+    # Verify branch owners, if necessary
+    my @bad_users = verify_users(keys(%lines_of));
+    for my $bad_user (@bad_users) {
+        for my $line (@{$lines_of{$bad_user}}) {
+            warn("[INVALID BRANCH OWNER] $line\n");
+        }
+    }
+    exit(scalar(@bad_users));
+}
+
+1;
+__END__
+
+=head1 NAME
+
+pre-commit-verify-branch-owner
+
+=head1 SYNOPSIS
+
+    pre-commit-verify-branch-owner REPOS TXN
+
+=head1 ARGUMENTS
+
+Accept the same arguments as a Subversion pre-commit hook.
+
+=head1 DESCRIPTION
+
+This program ensures that users create a branch with its owner correctly set.
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
diff --git a/sbin/pre-revprop-change b/sbin/pre-revprop-change
new file mode 100755
index 0000000..0c69412
--- /dev/null
+++ b/sbin/pre-revprop-change
@@ -0,0 +1,84 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# NAME
+#   pre-revprop-change
+#
+# SYNOPSIS
+#   pre-revprop-change REPOS REV PROP_AUTHOR PROP_NAME ACTION
+#
+# ARGUMENTS
+#   REPOS - the path to the Subversion repository
+#   REV - the revision relevant for the property
+#   PROP_AUTHOR - the author of this property change
+#   PROP_NAME - the name of the property, should only be "svn:log"
+#   ACTION - the action of the property change, should only be "M"
+#
+# DESCRIPTION
+#   This script enables users to change revision properties.
+#
+#   By default, only "M svn:log" is allowed. If
+#   "$REPOS/hooks/pre-revprop-change-ok.conf" exists, the contents should be a
+#   list of allowed changes to revision properties. E.g.:
+#
+#   M svn:author
+#   M svn:log
+#
+#   An empty file disables all changes.
+#
+#   It e-mails the host user account whenever an action is blocked.
+#-------------------------------------------------------------------------------
+set -eu
+
+REPOS=$1
+REV=$2
+USER=$3
+PROPNAME=$4
+ACTION=$5
+
+export PATH=${PATH:-'/usr/local/bin:/bin:/usr/bin'}:$(dirname $0)
+THIS=$(basename "$0")
+USER=${USER:-(whoami)}
+NAME=$(basename "$REPOS")
+
+OK_CHANGES=$(echo 'M svn:log')
+OK_CHANGES_FILE="$REPOS/hooks/$THIS-ok.conf"
+if [[ -f $OK_CHANGES_FILE ]]; then
+    OK_CHANGES=$(<$OK_CHANGES_FILE)
+fi
+
+NOW=$(date -u +%FT%H:%M:%SZ)
+if ! grep -q "$ACTION  *$PROPNAME" <<<"$OK_CHANGES"; then
+    if [[ -n "$OK_CHANGES" ]]; then
+        echo -n "[$ACTION $PROPNAME] permission denied. Can only do:" >&2
+        while read; do
+            echo -n " [$REPLY]" >&2
+        done <<<"$OK_CHANGES"
+        echo >&2
+    else
+        echo "[$ACTION $PROPNAME] permission denied." >&2
+    fi
+    if [[ -n ${FCM_SVN_HOOK_ADMIN_EMAIL:-} ]]; then
+        mail -s "$NAME:$THIS" "$FCM_SVN_HOOK_ADMIN_EMAIL" <<<"[! $NOW] $@" \
+            || true
+    fi
+    echo "[! $NOW] $@"
+    exit 1
+fi
+exit
diff --git a/sbin/svnperms.py b/sbin/svnperms.py
new file mode 100755
index 0000000..af0eb36
--- /dev/null
+++ b/sbin/svnperms.py
@@ -0,0 +1,368 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+
+# THIS DISTRIBUTION HAS BEEN MODIFIED.
+# Original source downloaded from r1295006 at:
+# https://svn.apache.org/viewvc/subversion/trunk/tools/hook-scripts/svnperms.py
+# This version is modified to allow custom permission message per repository.
+
+import sys, os
+import getopt
+import shlex
+
+try:
+  # Python >=3.0
+  from subprocess import getstatusoutput as subprocess_getstatusoutput
+except ImportError:
+  # Python <3.0
+  from commands import getstatusoutput as subprocess_getstatusoutput
+try:
+    my_getopt = getopt.gnu_getopt
+except AttributeError:
+    my_getopt = getopt.getopt
+import re
+
+__author__ = "Gustavo Niemeyer <gustavo at niemeyer.net>"
+
+class Error(Exception): pass
+
+SECTION = re.compile(r'\[([^]]+?)(?:\s+extends\s+([^]]+))?\]')
+OPTION = re.compile(r'(\S+)\s*=\s*(.*)$')
+
+class Config:
+    def __init__(self, filename):
+        # Options are stored in __sections_list like this:
+        # [(sectname, [(optname, optval), ...]), ...]
+        self._sections_list = []
+        self._sections_dict = {}
+        self._read(filename)
+
+    def _read(self, filename):
+        # Use the same logic as in ConfigParser.__read()
+        file = open(filename)
+        cursectdict = None
+        optname = None
+        lineno = 0
+        for line in file:
+            lineno = lineno + 1
+            if line.isspace() or line[0] == '#':
+                continue
+            if line[0].isspace() and cursectdict is not None and optname:
+                value = line.strip()
+                cursectdict[optname] = "%s %s" % (cursectdict[optname], value)
+                cursectlist[-1][1] = "%s %s" % (cursectlist[-1][1], value)
+            else:
+                m = SECTION.match(line)
+                if m:
+                    sectname = m.group(1)
+                    parentsectname = m.group(2)
+                    if parentsectname is None:
+                        # No parent section defined, so start a new section
+                        cursectdict = self._sections_dict.setdefault \
+                            (sectname, {})
+                        cursectlist = []
+                    else:
+                        # Copy the parent section into the new section
+                        parentsectdict = self._sections_dict.get \
+                            (parentsectname, {})
+                        cursectdict = self._sections_dict.setdefault \
+                            (sectname, parentsectdict.copy())
+                        cursectlist = self.walk(parentsectname)
+                    self._sections_list.append((sectname, cursectlist))
+                    optname = None
+                elif cursectdict is None:
+                    raise Error("%s:%d: no section header" % \
+                                 (filename, lineno))
+                else:
+                    m = OPTION.match(line)
+                    if m:
+                        optname, optval = m.groups()
+                        optval = optval.strip()
+                        cursectdict[optname] = optval
+                        cursectlist.append([optname, optval])
+                    else:
+                        raise Error("%s:%d: parsing error" % \
+                                     (filename, lineno))
+
+    def sections(self):
+        return list(self._sections_dict.keys())
+
+    def options(self, section):
+        return list(self._sections_dict.get(section, {}).keys())
+
+    def get(self, section, option, default=None):
+        return self._sections_dict.get(option, default)
+
+    def walk(self, section, option=None):
+        ret = []
+        for sectname, options in self._sections_list:
+            if sectname == section:
+                for optname, value in options:
+                    if not option or optname == option:
+                        ret.append((optname, value))
+        return ret
+
+
+class Permission:
+    def __init__(self):
+        self._group = {}
+        self._permlist = []
+
+    def parse_groups(self, groupsiter):
+        for option, value in groupsiter:
+            groupusers = []
+            for token in shlex.split(value):
+                # expand nested groups in place; no forward decls
+                if token[0] == "@":
+                    try:
+                        groupusers.extend(self._group[token[1:]])
+                    except KeyError:
+                        raise Error, "group '%s' not found" % token[1:]
+                else:
+                    groupusers.append(token)
+            self._group[option] = groupusers
+
+    def parse_perms(self, permsiter):
+        for option, value in permsiter:
+            # Paths never start with /, so remove it if provided
+            if option[0] == "/":
+                option = option[1:]
+            pattern = re.compile("^%s$" % option)
+            for entry in value.split():
+                openpar, closepar = entry.find("("), entry.find(")")
+                groupsusers = entry[:openpar].split(",")
+                perms = entry[openpar+1:closepar].split(",")
+                users = []
+                for groupuser in groupsusers:
+                    if groupuser[0] == "@":
+                        try:
+                            users.extend(self._group[groupuser[1:]])
+                        except KeyError:
+                            raise Error("group '%s' not found" % \
+                                         groupuser[1:])
+                    else:
+                        users.append(groupuser)
+                self._permlist.append((pattern, users, perms))
+
+    def get(self, user, path):
+        ret = []
+        for pattern, users, perms in self._permlist:
+            if pattern.match(path) and (user in users or "*" in users):
+                ret = perms
+        return ret
+
+class SVNLook:
+    def __init__(self, repospath, txn=None, rev=None):
+        self.repospath = repospath
+        self.txn = txn
+        self.rev = rev
+
+    def _execcmd(self, *cmd, **kwargs):
+        cmdstr = " ".join(cmd)
+        status, output = subprocess_getstatusoutput(cmdstr)
+        if status != 0:
+            sys.stderr.write(cmdstr)
+            sys.stderr.write("\n")
+            sys.stderr.write(output)
+            raise Error("command failed: %s\n%s" % (cmdstr, output))
+        return status, output
+
+    def _execsvnlook(self, cmd, *args, **kwargs):
+        execcmd_args = ["svnlook", cmd, self.repospath]
+        self._add_txnrev(execcmd_args, kwargs)
+        execcmd_args += args
+        execcmd_kwargs = {}
+        keywords = ["show", "noerror"]
+        for key in keywords:
+            if key in kwargs:
+                execcmd_kwargs[key] = kwargs[key]
+        return self._execcmd(*execcmd_args, **execcmd_kwargs)
+
+    def _add_txnrev(self, cmd_args, received_kwargs):
+        if "txn" in received_kwargs:
+            txn = received_kwargs.get("txn")
+            if txn is not None:
+                cmd_args += ["-t", txn]
+        elif self.txn is not None:
+            cmd_args += ["-t", self.txn]
+        if "rev" in received_kwargs:
+            rev = received_kwargs.get("rev")
+            if rev is not None:
+                cmd_args += ["-r", rev]
+        elif self.rev is not None:
+            cmd_args += ["-r", self.rev]
+
+    def changed(self, **kwargs):
+        status, output = self._execsvnlook("changed", **kwargs)
+        if status != 0:
+            return None
+        changes = []
+        for line in output.splitlines():
+            line = line.rstrip()
+            if not line: continue
+            entry = [None, None, None]
+            changedata, changeprop, path = None, None, None
+            if line[0] != "_":
+                changedata = line[0]
+            if line[1] != " ":
+                changeprop = line[1]
+            path = line[4:]
+            changes.append((changedata, changeprop, path))
+        return changes
+
+    def author(self, **kwargs):
+        status, output = self._execsvnlook("author", **kwargs)
+        if status != 0:
+            return None
+        return output.strip()
+
+
+def check_perms(filename, section, repos, txn=None, rev=None, author=None):
+    svnlook = SVNLook(repos, txn=txn, rev=rev)
+    if author is None:
+        author = svnlook.author()
+    changes = svnlook.changed()
+    try:
+        config = Config(filename)
+    except IOError:
+        raise Error("can't read config file "+filename)
+    if not section in config.sections():
+        raise Error("section '%s' not found in config file" % section)
+    perm = Permission()
+    perm.parse_groups(config.walk("groups"))
+    perm.parse_groups(config.walk(section+" groups"))
+    perm.parse_perms(config.walk(section))
+    permerrors = []
+    for changedata, changeprop, path in changes:
+        pathperms = perm.get(author, path)
+        if changedata == "A" and "add" not in pathperms:
+            permerrors.append("you can't add "+path)
+        elif changedata == "U" and "update" not in pathperms:
+            permerrors.append("you can't update "+path)
+        elif changedata == "D" and "remove" not in pathperms:
+            permerrors.append("you can't remove "+path)
+        elif changeprop == "U" and "update" not in pathperms:
+            permerrors.append("you can't update properties of "+path)
+        #else:
+        #    print "cdata=%s cprop=%s path=%s perms=%s" % \
+        #          (str(changedata), str(changeprop), path, str(pathperms))
+    if permerrors:
+        message = config.get("message", "permerrors_prefix")
+        if message is None:
+            message = config.get(" ".join((section, "message")),
+                                 "permerrors_prefix")
+        if message is None:
+            message = "you don't have enough permissions for this transaction:"
+        permerrors.insert(0, message)
+        raise Error("\n".join(permerrors))
+
+
+# Command:
+
+USAGE = """\
+Usage: svnperms.py OPTIONS
+
+Options:
+    -r PATH    Use repository at PATH to check transactions
+    -t TXN     Query transaction TXN for commit information
+    -f PATH    Use PATH as configuration file (default is repository
+               path + /conf/svnperms.conf)
+    -s NAME    Use section NAME as permission section (default is
+               repository name, extracted from repository path)
+    -R REV     Query revision REV for commit information (for tests)
+    -A AUTHOR  Check commit as if AUTHOR had committed it (for tests)
+    -h         Show this message
+"""
+
+class MissingArgumentsException(Exception):
+    "Thrown when required arguments are missing."
+    pass
+
+def parse_options():
+    try:
+        opts, args = my_getopt(sys.argv[1:], "f:s:r:t:R:A:h", ["help"])
+    except getopt.GetoptError, e:
+        raise Error(e.msg)
+    class Options: pass
+    obj = Options()
+    obj.filename = None
+    obj.section = None
+    obj.repository = None
+    obj.transaction = None
+    obj.revision = None
+    obj.author = None
+    for opt, val in opts:
+        if opt == "-f":
+            obj.filename = val
+        elif opt == "-s":
+            obj.section = val
+        elif opt == "-r":
+            obj.repository = val
+        elif opt == "-t":
+            obj.transaction = val
+        elif opt == "-R":
+            obj.revision = val
+        elif opt == "-A":
+            obj.author = val
+        elif opt in ["-h", "--help"]:
+            sys.stdout.write(USAGE)
+            sys.exit(0)
+    missingopts = []
+    if not obj.repository:
+        missingopts.append("repository")
+    if not (obj.transaction or obj.revision):
+        missingopts.append("either transaction or a revision")
+    if missingopts:
+        raise MissingArgumentsException("missing required option(s): " + ", ".join(missingopts))
+    obj.repository = os.path.abspath(obj.repository)
+    if obj.filename is None:
+        obj.filename = os.path.join(obj.repository, "conf", "svnperms.conf")
+    if obj.section is None:
+        obj.section = os.path.basename(obj.repository)
+    if not (os.path.isdir(obj.repository) and
+            os.path.isdir(os.path.join(obj.repository, "db")) and
+            os.path.isdir(os.path.join(obj.repository, "hooks")) and
+            os.path.isfile(os.path.join(obj.repository, "format"))):
+        raise Error("path '%s' doesn't look like a repository" % \
+                     obj.repository)
+
+    return obj
+
+def main():
+    try:
+        opts = parse_options()
+        check_perms(opts.filename, opts.section,
+                    opts.repository, opts.transaction, opts.revision,
+                    opts.author)
+    except MissingArgumentsException, e:
+        sys.stderr.write("%s\n" % str(e))
+        sys.stderr.write(USAGE)
+        sys.exit(1)
+    except Error, e:
+        sys.stderr.write("error: %s\n" % str(e))
+        sys.exit(1)
+
+if __name__ == "__main__":
+    main()
+
+# vim:et:ts=4:sw=4
diff --git a/sbin/trac_hook b/sbin/trac_hook
new file mode 100644
index 0000000..224a1db
--- /dev/null
+++ b/sbin/trac_hook
@@ -0,0 +1,65 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# NAME
+#     trac_hook
+#
+# SYNOPSIS
+#     . trac_hook
+#     trac_hook "$REPOS" "$REV" added|modified
+#
+# DESCRIPTION
+#   Provide a function called "trac_hook", which updates the corresponding Trac
+#   environment, for post-commit or post-revprop-change.
+#
+# ENVIRONMENT VARIABLES
+#   FCM_SVN_HOOK_TRAC_ROOT_DIR
+#     The root directories of Trac environments. Update corresponding Trac
+#     environment if specified.
+#   FCM_SVN_HOOK_REPOS_SUFFIX
+#     A suffix that should be removed from the basename of REPOS to get the
+#     name of the Trac environment. (Default is "".)
+#-------------------------------------------------------------------------------
+
+trac_hook() {
+    local REPOS=$1
+    local REV=$2
+    local TRAC_ACT=$3
+    if which trac-admin 1>/dev/null 2>&1 \
+        && [[ -n ${FCM_SVN_HOOK_TRAC_ROOT_DIR:-} ]]
+    then
+        local TRAC_NAME=$(basename "$REPOS")
+        if [[ -n ${FCM_SVN_HOOK_REPOS_SUFFIX:-} ]]; then
+            TRAC_NAME=${NAME%$FCM_SVN_HOOK_REPOS_SUFFIX}
+        fi
+        local TRAC_DIR="$FCM_SVN_HOOK_TRAC_ROOT_DIR/$TRAC_NAME"
+        if [[ -d "$TRAC_DIR" ]]; then
+            if [[ $(trac-admin --version) == trac-admin\ 0.11* ]]; then
+                # N.B. "added" was automatic on access
+                if [[ "$TRAC_ACT" == 'modified' ]]; then
+                    echo "trac-admin $TRAC_DIR resync $REV"
+                    trac-admin "$TRAC_DIR" resync "$REV" >/dev/null
+                fi
+            else
+                echo "trac-admin $TRAC_DIR changeset $TRAC_ACT $REPOS $REV"
+                trac-admin "$TRAC_DIR" changeset "$TRAC_ACT" "$REPOS" "$REV"
+            fi
+        fi
+    fi
+}
diff --git a/t/etc/repo_files/lib/python/info/__init__.py b/t/etc/repo_files/lib/python/info/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/t/etc/repo_files/lib/python/info/poems.py b/t/etc/repo_files/lib/python/info/poems.py
new file mode 100644
index 0000000..94b1e56
--- /dev/null
+++ b/t/etc/repo_files/lib/python/info/poems.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""The Python, by Hilaire Belloc
+
+A Python I should not advise,--
+It needs a doctor for its eyes,
+And has the measles yearly.
+However, if you feel inclined
+To get one (to improve your mind,
+And not from fashion merely),
+Allow no music near its cage;
+And when it flies into a rage
+Chastise it, most severely.
+I had an aunt in Yucatan
+Who bought a Python from a man
+And kept it for a pet.
+She died, because she never knew
+These simple little rules and few;--
+The Snake is living yet.
+"""
+
+import this
+
+print "\n",  __doc__
diff --git a/t/etc/repo_files/module/hello_constants.f90 b/t/etc/repo_files/module/hello_constants.f90
new file mode 100644
index 0000000..b8237b9
--- /dev/null
+++ b/t/etc/repo_files/module/hello_constants.f90
@@ -0,0 +1,5 @@
+MODULE Hello_Constants
+
+INCLUDE 'hello_constants_dummy.inc'
+
+END MODULE Hello_Constants
diff --git a/t/etc/repo_files/module/hello_constants.inc b/t/etc/repo_files/module/hello_constants.inc
new file mode 100644
index 0000000..ae26a9b
--- /dev/null
+++ b/t/etc/repo_files/module/hello_constants.inc
@@ -0,0 +1 @@
+CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
diff --git a/t/etc/repo_files/module/hello_constants_dummy.inc b/t/etc/repo_files/module/hello_constants_dummy.inc
new file mode 100644
index 0000000..06f117b
--- /dev/null
+++ b/t/etc/repo_files/module/hello_constants_dummy.inc
@@ -0,0 +1 @@
+INCLUDE 'hello_constants.inc'
diff --git a/t/etc/repo_files/pro/hello.pro b/t/etc/repo_files/pro/hello.pro
new file mode 100644
index 0000000..bc880e8
--- /dev/null
+++ b/t/etc/repo_files/pro/hello.pro
@@ -0,0 +1,2 @@
+PRO HELLO
+END
diff --git a/t/etc/repo_files/pro/plot.pro b/t/etc/repo_files/pro/plot.pro
new file mode 100644
index 0000000..5896f2b
--- /dev/null
+++ b/t/etc/repo_files/pro/plot.pro
@@ -0,0 +1,3 @@
+PRO PLOT
+;  Calls    : hello.pro
+END
diff --git a/t/etc/repo_files/program/hello.F90 b/t/etc/repo_files/program/hello.F90
new file mode 100644
index 0000000..64b2cc2
--- /dev/null
+++ b/t/etc/repo_files/program/hello.F90
@@ -0,0 +1,20 @@
+PROGRAM Hello
+
+USE Hello_Constants, ONLY: hello_string
+
+IMPLICIT NONE
+
+INTEGER :: integer_arg = 1234
+
+#if defined(CALL_HELLO_SUB)
+INCLUDE 'hello_sub.interface'
+#endif
+
+CHARACTER (LEN=*), PARAMETER :: this = 'Hello'
+
+WRITE (*, '(A)') this // ': ' // TRIM (hello_string)
+#if defined(CALL_HELLO_SUB)
+CALL Hello_Sub (integer_arg)
+#endif
+
+END PROGRAM Hello
diff --git a/t/etc/repo_files/subroutine/hello_c.c b/t/etc/repo_files/subroutine/hello_c.c
new file mode 100644
index 0000000..45ca182
--- /dev/null
+++ b/t/etc/repo_files/subroutine/hello_c.c
@@ -0,0 +1,5 @@
+#include <stdio.h>
+
+void hello_c_ () {
+  printf ("%s\n", "Hello_C: Hello Earth!");
+}
diff --git a/t/etc/repo_files/subroutine/hello_sub.F90 b/t/etc/repo_files/subroutine/hello_sub.F90
new file mode 100644
index 0000000..b7d7da6
--- /dev/null
+++ b/t/etc/repo_files/subroutine/hello_sub.F90
@@ -0,0 +1,24 @@
+#if defined(HELLO_SUB)
+SUBROUTINE Hello_Sub (integer_arg)
+
+USE Hello_Constants, ONLY: hello_string
+
+IMPLICIT NONE
+
+CHARACTER (LEN=*), PARAMETER :: this = 'Hello_Sub'
+INTEGER :: integer_arg
+INTEGER :: integer_common
+COMMON /general/integer_common
+
+! DEPENDS ON: hello_c.o
+EXTERNAL Hello_C
+
+#include "hello_sub_dummy.h"
+
+WRITE (*, '(A,I0)') this // ': integer (arg): ', integer_arg
+WRITE (*, '(A,I0)') this // ': integer (common): ', integer_common
+
+CALL Hello_C ()
+
+END SUBROUTINE Hello_Sub
+#endif
diff --git a/t/etc/repo_files/subroutine/hello_sub.h b/t/etc/repo_files/subroutine/hello_sub.h
new file mode 100644
index 0000000..36fd211
--- /dev/null
+++ b/t/etc/repo_files/subroutine/hello_sub.h
@@ -0,0 +1 @@
+WRITE (*, '(A)') this // ': ' // TRIM (hello_string)
diff --git a/t/etc/repo_files/subroutine/hello_sub_dummy.h b/t/etc/repo_files/subroutine/hello_sub_dummy.h
new file mode 100644
index 0000000..591744b
--- /dev/null
+++ b/t/etc/repo_files/subroutine/hello_sub_dummy.h
@@ -0,0 +1 @@
+#include "hello_sub.h"
diff --git a/t/fcm-add-trac-env/00-basic.t b/t/fcm-add-trac-env/00-basic.t
new file mode 100755
index 0000000..974f517
--- /dev/null
+++ b/t/fcm-add-trac-env/00-basic.t
@@ -0,0 +1,83 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Basic tests for "fcm-add-trac-env".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+if ! which trac-admin 1>/dev/null 2>/dev/null; then
+    skip_all 'trac-admin not available'
+fi
+tests 20
+#-------------------------------------------------------------------------------
+set -e
+mkdir -p etc srv/{svn,trac}
+# Configuration
+export FCM_CONF_PATH="$PWD/etc"
+ADMIN_USERS='holly ivy'
+cat >etc/admin.cfg <<__CONF__
+svn_live_dir=$PWD/srv/svn
+trac_admin_users=$ADMIN_USERS
+trac_live_dir=$PWD/srv/trac
+__CONF__
+# Create some Subversion repositories
+for NAME in bus lorry taxi; do
+    svnadmin create "srv/svn/$NAME"
+done
+set +e
+#-------------------------------------------------------------------------------
+for NAME in bus car lorry taxi; do
+    TEST_KEY="$TEST_KEY_BASE-$NAME"
+    # Command OK
+    run_pass "$TEST_KEY" "$FCM_HOME/sbin/fcm-add-trac-env" "$NAME"
+    # Trac environment directory exists
+    run_pass "$TEST_KEY-d" test -d "$PWD/srv/trac/$NAME"
+    # Admin users are set
+    for ADMIN_USER in $ADMIN_USERS; do
+        trac-admin "$PWD/srv/trac/$NAME" permission list "$ADMIN_USER" \
+            >"$TEST_KEY.perm.out"
+        file_grep "$TEST_KEY.perm.out" \
+            "$ADMIN_USER  *TRAC_ADMIN" "$TEST_KEY.perm.out"
+    done
+    # Subversion repository paths in place
+    if [[ -d "srv/svn/$NAME" ]]; then
+        file_grep "$TEST_KEY-repository_dir" \
+            "repository_dir=$PWD/srv/svn/$NAME" \
+            "$PWD/srv/trac/$NAME/conf/trac.ini"
+    fi
+done
+
+TEST_KEY="$TEST_KEY_BASE-intertrac"
+file_cmp "$TEST_KEY" "$PWD/srv/trac/intertrac.ini" <<'__CONF__'
+[intertrac]
+bus.title=bus
+bus.url=https://localhost/trac/bus
+bus.compat=false
+car.title=car
+car.url=https://localhost/trac/car
+car.compat=false
+lorry.title=lorry
+lorry.url=https://localhost/trac/lorry
+lorry.compat=false
+taxi.title=taxi
+taxi.url=https://localhost/trac/taxi
+taxi.compat=false
+__CONF__
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/fcm-add-trac-env/test_header b/t/fcm-add-trac-env/test_header
new file mode 120000
index 0000000..90bd5a3
--- /dev/null
+++ b/t/fcm-add-trac-env/test_header
@@ -0,0 +1 @@
+../lib/bash/test_header
\ No newline at end of file
diff --git a/t/fcm-add/00-simple.t b/t/fcm-add/00-simple.t
new file mode 100644
index 0000000..d133099
--- /dev/null
+++ b/t/fcm-add/00-simple.t
@@ -0,0 +1,139 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm add".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+if [[ $? -ne 0 ]]; then
+    exit 1
+fi
+#-------------------------------------------------------------------------------
+tests 24
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch_wc add $REPOS_URL
+mkdir $TEST_DIR/wc/added_directory1
+svn add -q $TEST_DIR/wc/added_directory1
+touch $TEST_DIR/wc/added_directory1/added_file1
+mkdir $TEST_DIR/wc/added_directory2
+touch $TEST_DIR/wc/added_directory2/added_file2
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm add unversioned file
+TEST_KEY=$TEST_KEY_BASE-fcm-add-file
+run_pass "$TEST_KEY" fcm add added_directory1/added_file1
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+A         added_directory1/added_file1
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm add unversioned directory
+TEST_KEY=$TEST_KEY_BASE-fcm-add-dir
+run_pass "$TEST_KEY" fcm add added_directory2
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+A         added_directory2
+A         added_directory2/added_file2
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+cd $TEST_DIR
+teardown
+#-------------------------------------------------------------------------------
+init_repos
+init_branch_wc add_c $REPOS_URL
+touch $TEST_DIR/wc/unversioned_file
+mkdir $TEST_DIR/wc/unversioned_directory
+touch $TEST_DIR/wc/unversioned_directory/unversioned_file_2
+mkdir $TEST_DIR/wc/versioned_directory
+svn add -q $TEST_DIR/wc/versioned_directory
+touch $TEST_DIR/wc/versioned_directory/unversioned_file_3
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm add -c unversioned file
+TEST_KEY=$TEST_KEY_BASE-fcm-add-c-file
+run_pass "$TEST_KEY" fcm add -c unversioned_file <<'__EOF__'
+y
+__EOF__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+?       unversioned_file
+Would you like to run "svn add unversioned_file"?
+Enter "y", "n" or "a" (or just press <return> for "n"): A         unversioned_file
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm add -c unversioned directory
+TEST_KEY=$TEST_KEY_BASE-fcm-add-c-dir
+run_pass "$TEST_KEY" fcm add -c unversioned_directory <<'__EOF__'
+y
+__EOF__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+?       unversioned_directory
+Would you like to run "svn add unversioned_directory"?
+Enter "y", "n" or "a" (or just press <return> for "n"): A         unversioned_directory
+A         unversioned_directory/unversioned_file_2
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm add -c versioned directory
+TEST_KEY=$TEST_KEY_BASE-fcm-add-c-versioned-dir
+run_pass "$TEST_KEY" fcm add -c versioned_directory <<'__EOF__'
+n
+__EOF__
+file_test "$TEST_KEY.out" "$TEST_KEY.out" -s
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm status after above tests
+TEST_KEY=$TEST_KEY_BASE-fcm-add-c-status
+run_pass "$TEST_KEY" fcm st
+sort $TEST_DIR/"$TEST_KEY.out" -o $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+?       versioned_directory/unversioned_file_3
+A       unversioned_directory
+A       unversioned_directory/unversioned_file_2
+A       unversioned_file
+A       versioned_directory
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm add -c with no arguments
+TEST_KEY=$TEST_KEY_BASE-fcm-add-c-no-args
+fcm revert -q -R $TEST_DIR/wc/
+run_pass "$TEST_KEY" fcm add -c <<'__EOF__'
+y
+y
+y
+y
+__EOF__
+file_test "$TEST_KEY.out" "$TEST_KEY.out" -s
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm status after above tests
+TEST_KEY=$TEST_KEY_BASE-fcm-add-c-no-args-status
+run_pass "$TEST_KEY" fcm status
+sort $TEST_DIR/"$TEST_KEY.out" -o $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+A       unversioned_directory
+A       unversioned_directory/unversioned_file_2
+A       unversioned_file
+A       versioned_directory
+A       versioned_directory/unversioned_file_3
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-add/test_header b/t/fcm-add/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/fcm-add/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/fcm-backup-svn-repos/00-basic.t b/t/fcm-backup-svn-repos/00-basic.t
new file mode 100755
index 0000000..bf0651b
--- /dev/null
+++ b/t/fcm-backup-svn-repos/00-basic.t
@@ -0,0 +1,103 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Basic tests for "fcm-backup-svn-repos".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+if ! which svnadmin 1>/dev/null 2>/dev/null; then
+    skip_all 'svnadmin not available'
+fi
+tests 32
+#-------------------------------------------------------------------------------
+set -e
+mkdir -p etc srv/svn var/svn/{backups,cache,dumps}
+# Configuration
+export FCM_CONF_PATH="$PWD/etc"
+cat >etc/admin.cfg <<__CONF__
+svn_backup_dir=$PWD/var/svn/backups
+svn_dump_dir=$PWD/var/svn/dumps
+svn_live_dir=$PWD/srv/svn
+__CONF__
+# Create some repositories and populate them
+# Repository 1
+svnadmin create srv/svn/bar
+svn co -q file://$PWD/srv/svn/bar
+echo 'A man walks into a bar.' >bar/walk
+echo 'Barley drink.' >bar/barley
+svn add -q bar/*
+svn ci -q -m'test 1' bar
+svnadmin dump srv/svn/bar -r 1 --incremental --deltas -q \
+        | gzip >var/svn/dumps/bar-1.gz
+# Repository 2
+svnadmin create srv/svn/foo
+svn co -q file://$PWD/srv/svn/foo
+echo 'Food is yummy.' >foo/food
+svn add -q foo/*
+svn ci -q -m'test 1' foo
+svnadmin dump srv/svn/foo -r 1 --incremental --deltas -q \
+    | gzip >var/svn/dumps/foo-1.gz
+rm -fr bar foo
+set +e
+
+run_tests() {
+    local TEST_KEY=$1
+    run_pass "$TEST_KEY" "$FCM_HOME/sbin/fcm-backup-svn-repos"
+    for NAME in bar foo; do
+        file_test "$TEST_KEY-$NAME" var/svn/backups/$NAME.tgz
+        tar -xzf var/svn/backups/$NAME.tgz
+        svnlook youngest srv/svn/$NAME >"$TEST_KEY-$NAME.youngest.orig"
+        svnlook youngest $NAME >"$TEST_KEY-$NAME.youngest"
+        file_cmp "$TEST_KEY-$NAME.youngest" \
+            "$TEST_KEY-$NAME.youngest" "$TEST_KEY-$NAME.youngest.orig"
+        rm -fr $NAME
+        for REV in $(seq 1 $(<"$TEST_KEY-$NAME.youngest")); do
+            run_fail "$TEST_KEY-dumps-$NAME-$REV" ls var/svn/dumps/$NAME-$REV.gz
+        done
+    done
+}
+#-------------------------------------------------------------------------------
+run_tests "$TEST_KEY_BASE-1-1"
+run_tests "$TEST_KEY_BASE-1-2" # Re-run test
+#-------------------------------------------------------------------------------
+# Add more stuffs to repository 1
+svn co -q file://$PWD/srv/svn/bar
+for REV in {2..9}; do
+    echo "$REV men walk into a bar." >bar/walk
+    svn ci -m"test: $REV" bar/walk
+    svnadmin dump srv/svn/bar -r $REV --incremental --deltas -q \
+            | gzip >var/svn/dumps/bar-$REV.gz
+done
+# Add more stuffs to a copy of repository 1, to generate some more dumps To
+# prove that command will not housekeep dumps that are newer than the backed up
+# youngest.
+svnadmin hotcopy srv/svn/bar var/svn/cache/bar
+svn relocate file://$PWD/srv/svn/bar file://$PWD/var/svn/cache/bar bar
+for REV in {10..12}; do
+    echo "$REV men walk into a bar." >bar/walk
+    svn ci -m"test: $REV" bar/walk
+    svnadmin dump var/svn/cache/bar -r $REV --incremental --deltas -q \
+            | gzip >var/svn/dumps/bar-$REV.gz
+done
+run_tests "$TEST_KEY_BASE-2-1"
+for REV in {10..12}; do
+    file_test "$TEST_KEY_BASE-2-1-dumps-bar-$REV" var/svn/dumps/bar-$REV.gz
+done
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/fcm-backup-svn-repos/test_header b/t/fcm-backup-svn-repos/test_header
new file mode 120000
index 0000000..90bd5a3
--- /dev/null
+++ b/t/fcm-backup-svn-repos/test_header
@@ -0,0 +1 @@
+../lib/bash/test_header
\ No newline at end of file
diff --git a/t/fcm-branch-create/00-simple.t b/t/fcm-branch-create/00-simple.t
new file mode 100644
index 0000000..a6789eb
--- /dev/null
+++ b/t/fcm-branch-create/00-simple.t
@@ -0,0 +1,96 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm branch-create".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 12
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch_wc branch_test $REPOS_URL
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm branch-create
+TEST_KEY=$TEST_KEY_BASE-fcm-bc
+run_pass "$TEST_KEY" fcm branch-create -t SHARE --rev-flag=NONE \
+                                        --non-interactive \
+                                        my_branch_test
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] Source: $ROOT_URL/trunk at 1 (4)
+Change summary:
+--------------------------------------------------------------------------------
+A    $ROOT_URL/branches/dev/Share/my_branch_test
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+Created /${PROJECT}branches/dev/Share/my_branch_test from /${PROJECT}trunk at 1.
+--------------------------------------------------------------------------------
+
+Committed revision 5.
+[info] Created: $ROOT_URL/branches/dev/Share/my_branch_test
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests existence of branch
+TEST_KEY=$TEST_KEY_BASE-fcm-bc-branch-exists-sw
+run_pass "$TEST_KEY" svn switch \
+               $ROOT_URL/branches/dev/Share/my_branch_test
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+At revision 5.
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
+init_repos
+init_branch_wc branch_test $REPOS_URL
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm branch-create --branch-of-branch
+TEST_KEY=$TEST_KEY_BASE-fcm-bc-branch-of-branch
+run_pass "$TEST_KEY" fcm branch-create -t SHARE --rev-flag=NONE \
+                                       --non-interactive \
+                                       --branch-of-branch my_branch_test
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] Source: $ROOT_URL/branches/dev/Share/branch_test at 4 (4)
+Change summary:
+--------------------------------------------------------------------------------
+A    $ROOT_URL/branches/dev/Share/my_branch_test
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+Created /${PROJECT}branches/dev/Share/my_branch_test from /${PROJECT}branches/dev/Share/branch_test at 4.
+--------------------------------------------------------------------------------
+
+Committed revision 5.
+[info] Created: $ROOT_URL/branches/dev/Share/my_branch_test
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests existence of branch
+TEST_KEY=$TEST_KEY_BASE-fcm-bc-branch-of-branch-exists-sw
+run_pass "$TEST_KEY" svn switch \
+               $ROOT_URL/branches/dev/Share/my_branch_test
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+At revision 5.
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-branch-create/test_header b/t/fcm-branch-create/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/fcm-branch-create/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/fcm-branch-delete/00-simple.t b/t/fcm-branch-delete/00-simple.t
new file mode 100644
index 0000000..e516af0
--- /dev/null
+++ b/t/fcm-branch-delete/00-simple.t
@@ -0,0 +1,65 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm branch-create".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 12
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch branch_test $REPOS_URL
+init_branch_wc my_branch_test $REPOS_URL
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm branch-delete
+TEST_KEY=$TEST_KEY_BASE-delete
+run_pass "$TEST_KEY" fcm branch-delete --non-interactive $ROOT_URL/branches/dev/Share/branch_test
+file_grep "$TEST_KEY.out" "Deleting branch $ROOT_URL/branches/dev/Share/branch_test ..." \
+          "$TEST_KEY.out"
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests existence of branch
+TEST_KEY=$TEST_KEY_BASE-delete-branch-exists
+run_fail "$TEST_KEY" svn info \
+               $ROOT_URL/branches/dev/Share/branch_test
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" </dev/null
+file_test "$TEST_KEY.err" "$TEST_KEY.err" -s
+teardown
+#-------------------------------------------------------------------------------
+init_repos
+init_branch_wc branch_test $REPOS_URL
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm brm
+TEST_KEY=$TEST_KEY_BASE-brm
+run_pass "$TEST_KEY" fcm brm --non-interactive $ROOT_URL/branches/dev/Share/branch_test
+file_grep "$TEST_KEY.out" "Deleting branch $ROOT_URL/branches/dev/Share/branch_test ..." \
+          "$TEST_KEY.out"
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm brm disappearance of branch
+TEST_KEY=$TEST_KEY_BASE-brm-branch-exists
+run_fail "$TEST_KEY" svn info \
+               $ROOT_URL/branches/dev/Share/branch_test
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" </dev/null
+file_test "$TEST_KEY.err" "$TEST_KEY.err" -s
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-branch-delete/test_header b/t/fcm-branch-delete/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/fcm-branch-delete/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/fcm-branch-diff/00-simple.t b/t/fcm-branch-diff/00-simple.t
new file mode 100644
index 0000000..59d2680
--- /dev/null
+++ b/t/fcm-branch-diff/00-simple.t
@@ -0,0 +1,664 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm branch-diff".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 18
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch_wc branch_test $REPOS_URL
+cd $TEST_DIR/wc
+FILE_LIST=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+for FILE in $FILE_LIST; do 
+    sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $FILE
+    sed -i "/#/d; /^ *!/d" $FILE
+    sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $FILE
+done
+FILE_DIR=$(dirname $FILE)
+svn copy -q $FILE added_file
+svn copy -q $FILE_DIR added_directory
+svn delete --force -q $FILE_DIR
+svn commit -q -m "make branch diff"
+svn switch -q $ROOT_URL/trunk
+TMPFILE=$(mktemp)
+for FILE in $FILE_LIST; do
+    if [[ -e $FILE ]]; then
+        tac $FILE > $TMPFILE && mv $TMPFILE $FILE
+    fi
+done
+rm -f $TMPFILE
+svn commit -q -m "make trunk diff"
+svn switch -q $ROOT_URL/branches/dev/Share/branch_test
+#-------------------------------------------------------------------------------
+# Tests fcm branch-diff
+TEST_KEY=$TEST_KEY_BASE-fcm-branch-diff
+run_pass "$TEST_KEY" fcm branch-diff
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: added_file
+===================================================================
+--- added_file	(.../$ROOT_URL/trunk)	(revision 0)
++++ added_file	(revision 6)
+@@ -0,0 +1 @@
++INCLUDE 'hello_constants.INc'
+Index: added_directory/hello_constants_dummy.inc
+===================================================================
+--- added_directory/hello_constants_dummy.inc	(.../$ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants_dummy.inc	(revision 6)
+@@ -0,0 +1 @@
++INCLUDE 'hello_constants.INc'
+Index: added_directory/hello_constants.inc
+===================================================================
+--- added_directory/hello_constants.inc	(.../$ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants.inc	(revision 6)
+@@ -0,0 +1,2 @@
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: added_directory/hello_constants.f90
+===================================================================
+--- added_directory/hello_constants.f90	(.../$ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants.f90	(revision 6)
+@@ -0,0 +1,5 @@
++MODULE Hello_Constants
++
++INCLUDE 'hello_constants_dummy.INc'
++
++END MODULE Hello_Constants
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	(.../$ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +0,0 @@
+-INCLUDE 'hello_constants.inc'
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	(.../$ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +0,0 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	(.../$ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants.f90	(working copy)
+@@ -1,5 +0,0 @@
+-MODULE Hello_Constants
+-
+-INCLUDE 'hello_constants_dummy.inc'
+-
+-END MODULE Hello_Constants
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	(.../$ROOT_URL/trunk)	(revision 1)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	($ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +0,0 @@
+-INCLUDE 'hello_constants.inc'
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	($ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +0,0 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	($ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants.f90	(working copy)
+@@ -1,5 +0,0 @@
+-MODULE Hello_Constants
+-
+-INCLUDE 'hello_constants_dummy.inc'
+-
+-END MODULE Hello_Constants
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	($ROOT_URL/trunk)	(revision 1)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+Index: added_directory/hello_constants.f90
+===================================================================
+--- added_directory/hello_constants.f90	($ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants.f90	(revision 6)
+@@ -0,0 +1,5 @@
++MODULE Hello_Constants
++
++INCLUDE 'hello_constants_dummy.INc'
++
++END MODULE Hello_Constants
+Index: added_directory/hello_constants.inc
+===================================================================
+--- added_directory/hello_constants.inc	($ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants.inc	(revision 6)
+@@ -0,0 +1,2 @@
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: added_directory/hello_constants_dummy.inc
+===================================================================
+--- added_directory/hello_constants_dummy.inc	($ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants_dummy.inc	(revision 6)
+@@ -0,0 +1 @@
++INCLUDE 'hello_constants.INc'
+Index: added_file
+===================================================================
+--- added_file	($ROOT_URL/trunk)	(revision 0)
++++ added_file	(revision 6)
+@@ -0,0 +1 @@
++INCLUDE 'hello_constants.INc'
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm bdi
+TEST_KEY=$TEST_KEY_BASE-bdi
+run_pass "$TEST_KEY" fcm bdi
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: added_file
+===================================================================
+--- added_file	(.../$ROOT_URL/trunk)	(revision 0)
++++ added_file	(revision 6)
+@@ -0,0 +1 @@
++INCLUDE 'hello_constants.INc'
+Index: added_directory/hello_constants_dummy.inc
+===================================================================
+--- added_directory/hello_constants_dummy.inc	(.../$ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants_dummy.inc	(revision 6)
+@@ -0,0 +1 @@
++INCLUDE 'hello_constants.INc'
+Index: added_directory/hello_constants.inc
+===================================================================
+--- added_directory/hello_constants.inc	(.../$ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants.inc	(revision 6)
+@@ -0,0 +1,2 @@
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: added_directory/hello_constants.f90
+===================================================================
+--- added_directory/hello_constants.f90	(.../$ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants.f90	(revision 6)
+@@ -0,0 +1,5 @@
++MODULE Hello_Constants
++
++INCLUDE 'hello_constants_dummy.INc'
++
++END MODULE Hello_Constants
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	(.../$ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +0,0 @@
+-INCLUDE 'hello_constants.inc'
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	(.../$ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +0,0 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	(.../$ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants.f90	(working copy)
+@@ -1,5 +0,0 @@
+-MODULE Hello_Constants
+-
+-INCLUDE 'hello_constants_dummy.inc'
+-
+-END MODULE Hello_Constants
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	(.../$ROOT_URL/trunk)	(revision 1)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	($ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +0,0 @@
+-INCLUDE 'hello_constants.inc'
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	($ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +0,0 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	($ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants.f90	(working copy)
+@@ -1,5 +0,0 @@
+-MODULE Hello_Constants
+-
+-INCLUDE 'hello_constants_dummy.inc'
+-
+-END MODULE Hello_Constants
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	($ROOT_URL/trunk)	(revision 1)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+Index: added_directory/hello_constants.f90
+===================================================================
+--- added_directory/hello_constants.f90	($ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants.f90	(revision 6)
+@@ -0,0 +1,5 @@
++MODULE Hello_Constants
++
++INCLUDE 'hello_constants_dummy.INc'
++
++END MODULE Hello_Constants
+Index: added_directory/hello_constants.inc
+===================================================================
+--- added_directory/hello_constants.inc	($ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants.inc	(revision 6)
+@@ -0,0 +1,2 @@
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: added_directory/hello_constants_dummy.inc
+===================================================================
+--- added_directory/hello_constants_dummy.inc	($ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants_dummy.inc	(revision 6)
+@@ -0,0 +1 @@
++INCLUDE 'hello_constants.INc'
+Index: added_file
+===================================================================
+--- added_file	($ROOT_URL/trunk)	(revision 0)
++++ added_file	(revision 6)
+@@ -0,0 +1 @@
++INCLUDE 'hello_constants.INc'
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm branch-diff --wiki
+TEST_KEY=$TEST_KEY_BASE-wiki
+run_pass "$TEST_KEY" fcm branch-diff --wiki
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+diff:/${PROJECT}trunk at 1///${PROJECT}branches/dev/Share/branch_test at 6
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm bdi --wiki
+TEST_KEY=$TEST_KEY_BASE-bdi-wiki
+run_pass "$TEST_KEY" fcm bdi --wiki
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+diff:/${PROJECT}trunk at 1///${PROJECT}branches/dev/Share/branch_test at 6
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm bdi on the trunk
+svn switch -q $ROOT_URL/trunk
+TEST_KEY=$TEST_KEY_BASE-bdi-trunk
+run_fail "$TEST_KEY" fcm bdi
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" </dev/null
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<__ERR__
+[FAIL] $ROOT_URL/trunk at 6: not a valid URL of a standard FCM branch.
+
+__ERR__
+#-------------------------------------------------------------------------------
+# Tests fcm bdi with working copy changes
+svn switch -q $ROOT_URL/branches/dev/Share/branch_test
+TEST_KEY=$TEST_KEY_BASE-bdi-wc-changes
+echo "foo" > added_directory/foo$TEST_KEY
+svn add -q added_directory/foo$TEST_KEY
+echo "bar" > added_directory/bar$TEST_KEY
+run_pass "$TEST_KEY" fcm bdi
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: added_file
+===================================================================
+--- added_file	(.../$ROOT_URL/trunk)	(revision 0)
++++ added_file	(revision 6)
+@@ -0,0 +1 @@
++INCLUDE 'hello_constants.INc'
+Index: added_directory/hello_constants_dummy.inc
+===================================================================
+--- added_directory/hello_constants_dummy.inc	(.../$ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants_dummy.inc	(revision 6)
+@@ -0,0 +1 @@
++INCLUDE 'hello_constants.INc'
+Index: added_directory/foo00-simple-bdi-wc-changes
+===================================================================
+--- added_directory/foo00-simple-bdi-wc-changes	(.../$ROOT_URL/trunk)	(revision 0)
++++ added_directory/foo00-simple-bdi-wc-changes	(revision 0)
+@@ -0,0 +1 @@
++foo
+Index: added_directory/hello_constants.inc
+===================================================================
+--- added_directory/hello_constants.inc	(.../$ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants.inc	(revision 6)
+@@ -0,0 +1,2 @@
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: added_directory/hello_constants.f90
+===================================================================
+--- added_directory/hello_constants.f90	(.../$ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants.f90	(revision 6)
+@@ -0,0 +1,5 @@
++MODULE Hello_Constants
++
++INCLUDE 'hello_constants_dummy.INc'
++
++END MODULE Hello_Constants
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	(.../$ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +0,0 @@
+-INCLUDE 'hello_constants.inc'
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	(.../$ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +0,0 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	(.../$ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants.f90	(working copy)
+@@ -1,5 +0,0 @@
+-MODULE Hello_Constants
+-
+-INCLUDE 'hello_constants_dummy.inc'
+-
+-END MODULE Hello_Constants
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	(.../$ROOT_URL/trunk)	(revision 1)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	($ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +0,0 @@
+-INCLUDE 'hello_constants.inc'
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	($ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +0,0 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	($ROOT_URL/trunk)	(revision 1)
++++ module/hello_constants.f90	(working copy)
+@@ -1,5 +0,0 @@
+-MODULE Hello_Constants
+-
+-INCLUDE 'hello_constants_dummy.inc'
+-
+-END MODULE Hello_Constants
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	($ROOT_URL/trunk)	(revision 1)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+Index: added_directory/foo00-simple-bdi-wc-changes
+===================================================================
+--- added_directory/foo00-simple-bdi-wc-changes	($ROOT_URL/trunk)	(revision 0)
++++ added_directory/foo00-simple-bdi-wc-changes	(working copy)
+@@ -0,0 +1 @@
++foo
+Index: added_directory/hello_constants.f90
+===================================================================
+--- added_directory/hello_constants.f90	($ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants.f90	(revision 6)
+@@ -0,0 +1,5 @@
++MODULE Hello_Constants
++
++INCLUDE 'hello_constants_dummy.INc'
++
++END MODULE Hello_Constants
+Index: added_directory/hello_constants.inc
+===================================================================
+--- added_directory/hello_constants.inc	($ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants.inc	(revision 6)
+@@ -0,0 +1,2 @@
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: added_directory/hello_constants_dummy.inc
+===================================================================
+--- added_directory/hello_constants_dummy.inc	($ROOT_URL/trunk)	(revision 0)
++++ added_directory/hello_constants_dummy.inc	(revision 6)
+@@ -0,0 +1 @@
++INCLUDE 'hello_constants.INc'
+Index: added_file
+===================================================================
+--- added_file	($ROOT_URL/trunk)	(revision 0)
++++ added_file	(revision 6)
+@@ -0,0 +1 @@
++INCLUDE 'hello_constants.INc'
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-branch-diff/test_header b/t/fcm-branch-diff/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/fcm-branch-diff/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/fcm-branch-info/00-simple.t b/t/fcm-branch-info/00-simple.t
new file mode 100644
index 0000000..338a002
--- /dev/null
+++ b/t/fcm-branch-info/00-simple.t
@@ -0,0 +1,154 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm branch-info".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 12
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch sibling_branch_test $REPOS_URL
+init_branch_wc branch_test $REPOS_URL
+cd $TEST_DIR/wc
+fcm branch-create -t SHARE --rev-flag=NONE \
+                           --non-interactive \
+                           --branch-of-branch my_branch_test >/dev/null
+svn switch -q $ROOT_URL/trunk
+FILE_LIST=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+for FILE in $FILE_LIST; do 
+    sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $FILE
+    sed -i "/#/d; /^ *!/d" $FILE
+    sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $FILE
+done
+svn commit -q -m "add trunk commit"
+svn switch -q $ROOT_URL/branches/dev/Share/branch_test
+#-------------------------------------------------------------------------------
+# Tests fcm branch-info
+TEST_KEY=$TEST_KEY_BASE-info
+run_pass "$TEST_KEY" fcm branch-info
+sed -i "/ Date/d;" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+URL: $ROOT_URL/branches/dev/Share/branch_test
+Repository Root: $REPOS_URL
+Revision: 7
+Last Changed Author: $LOGNAME
+Last Changed Rev: 5
+--------------------------------------------------------------------------------
+Branch Create Author: $LOGNAME
+Branch Create Rev: 5
+--------------------------------------------------------------------------------
+Branch Parent: $ROOT_URL/trunk at 1
+Merges Avail From Parent: 7
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm branch-info -a
+TEST_KEY=$TEST_KEY_BASE-a
+run_pass "$TEST_KEY" fcm branch-info -a
+sed -i "/ Date/d;" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+URL: $ROOT_URL/branches/dev/Share/branch_test
+Repository Root: $REPOS_URL
+Revision: 7
+Last Changed Author: $LOGNAME
+Last Changed Rev: 5
+--------------------------------------------------------------------------------
+Branch Create Author: $LOGNAME
+Branch Create Rev: 5
+--------------------------------------------------------------------------------
+Branch Parent: $ROOT_URL/trunk at 1
+Merges Avail From Parent: 7
+--------------------------------------------------------------------------------
+Searching for siblings ... 1 sibling found.
+No merges with existing siblings.
+--------------------------------------------------------------------------------
+Searching for children ... 1 child found.
+Current children:
+  ------------------------------------------------------------------------------
+  $ROOT_URL/branches/dev/Share/my_branch_test
+  Child Create Rev: 6
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm branch-info --show-children
+TEST_KEY=$TEST_KEY_BASE-show-children
+run_pass "$TEST_KEY" fcm branch-info --show-children
+sed -i "/ Date/d;" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+URL: $ROOT_URL/branches/dev/Share/branch_test
+Repository Root: $REPOS_URL
+Revision: 7
+Last Changed Author: $LOGNAME
+Last Changed Rev: 5
+--------------------------------------------------------------------------------
+Branch Create Author: $LOGNAME
+Branch Create Rev: 5
+--------------------------------------------------------------------------------
+Branch Parent: $ROOT_URL/trunk at 1
+Merges Avail From Parent: 7
+--------------------------------------------------------------------------------
+Searching for children ... 1 child found.
+Current children:
+  ------------------------------------------------------------------------------
+  $ROOT_URL/branches/dev/Share/my_branch_test
+  Child Create Rev: 6
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm branch-info --show-siblings
+TEST_KEY=$TEST_KEY_BASE-show-siblings
+svn switch -q $ROOT_URL/branches/dev/Share/sibling_branch_test
+svn merge -q $ROOT_URL/trunk
+svn commit -q -m "Merged trunk into sibling branch"
+svn switch -q $ROOT_URL/branches/dev/Share/branch_test
+svn merge -q $ROOT_URL/branches/dev/Share/sibling_branch_test
+svn commit -q -m "Merged sibling into test branch"
+svn switch -q $ROOT_URL/branches/dev/Share/sibling_branch_test
+FILE_LIST=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+TMPFILE=$(mktemp)
+for FILE in $FILE_LIST; do 
+    cut -f 1 $FILE > $TMPFILE
+    mv $TMPFILE $FILE
+done
+svn commit -q -m "Add sibling commit"
+svn switch -q $ROOT_URL/branches/dev/Share/branch_test
+run_pass "$TEST_KEY" fcm branch-info --show-siblings
+sed -i "/ Date/d;" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+URL: $ROOT_URL/branches/dev/Share/branch_test
+Repository Root: $REPOS_URL
+Revision: 9
+Last Changed Author: $LOGNAME
+Last Changed Rev: 9
+--------------------------------------------------------------------------------
+Branch Create Author: $LOGNAME
+Branch Create Rev: 5
+--------------------------------------------------------------------------------
+Branch Parent: $ROOT_URL/trunk at 1
+Merges Avail From Parent: 7
+Merges Avail Into Parent: 9
+--------------------------------------------------------------------------------
+Searching for siblings ... 1 sibling found.
+No merges with existing siblings.
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-branch-info/test_header b/t/fcm-branch-info/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/fcm-branch-info/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/fcm-branch-list/00-simple.t b/t/fcm-branch-list/00-simple.t
new file mode 100644
index 0000000..8c5c68e
--- /dev/null
+++ b/t/fcm-branch-list/00-simple.t
@@ -0,0 +1,133 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm branch-list".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 23
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch sibling_branch_test $REPOS_URL
+init_branch_wc branch_test $REPOS_URL
+cd $TEST_DIR/wc
+fcm branch-create --rev-flag=NONE \
+                  --non-interactive \
+                  --branch-of-branch my_branch_test >/dev/null
+ROOT_PATH=
+if [[ -n ${TEST_PROJECT:-} ]]; then
+    ROOT_PATH=/$TEST_PROJECT
+fi
+MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/fred/donuts from /trunk at 1.")
+# Please note: if $LOGNAME is drfooeybar or Share, some tests will fail.
+svn mkdir -q -m "Dr Fooeybar branch" $ROOT_URL/branches/dev/drfooeybar/
+svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/drfooeybar/donuts \
+            -m "Made a branch $MESSAGE" --non-interactive
+FILE_LIST=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+for FILE in $FILE_LIST; do 
+    sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $FILE
+    sed -i "/#/d; /^ *!/d" $FILE
+    sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $FILE
+done
+svn commit -q -m "add branch commit"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/branch_test
+#-------------------------------------------------------------------------------
+# Tests fcm branch-list
+TEST_KEY=$TEST_KEY_BASE-list
+run_pass "$TEST_KEY" fcm branch-list
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] $ROOT_URL at 9: 1 match(es)
+$ROOT_URL/branches/dev/$LOGNAME/my_branch_test at 9
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm branch-list -a
+TEST_KEY=$TEST_KEY_BASE-a
+run_pass "$TEST_KEY" fcm branch-list -a
+sed -i "/ Date/d;" $TEST_DIR/$TEST_KEY.out
+TMPFILE=$(mktemp)
+sort > $TMPFILE <<__OUT__
+[info] $ROOT_URL at 9: 4 match(es)
+$ROOT_URL/branches/dev/Share/branch_test at 9
+$ROOT_URL/branches/dev/Share/sibling_branch_test at 9
+$ROOT_URL/branches/dev/$LOGNAME/my_branch_test at 9
+$ROOT_URL/branches/dev/drfooeybar/donuts at 9
+__OUT__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <$TMPFILE
+rm -f $TMPFILE
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm branch-list --user
+TEST_KEY=$TEST_KEY_BASE-a
+run_pass "$TEST_KEY" fcm branch-list --user=drfooeybar
+sed -i "/ Date/d;" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] $ROOT_URL at 9: 1 match(es)
+$ROOT_URL/branches/dev/drfooeybar/donuts at 9
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm branch-list --only (1)
+TEST_KEY=$TEST_KEY_BASE-only-1
+run_pass "$TEST_KEY" fcm branch-list --only=3:donut
+sed -i "/ Date/d;" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] $ROOT_URL at 9: 1 match(es)
+$ROOT_URL/branches/dev/drfooeybar/donuts at 9
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm branch-list --only (2)
+TEST_KEY=$TEST_KEY_BASE-only-2
+run_pass "$TEST_KEY" fcm branch-list --only=2:Share
+sed -i "/ Date/d;" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] $ROOT_URL at 9: 2 match(es)
+$ROOT_URL/branches/dev/Share/branch_test at 9
+$ROOT_URL/branches/dev/Share/sibling_branch_test at 9
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm branch-list --only (3)
+TEST_KEY=$TEST_KEY_BASE-only-3
+run_pass "$TEST_KEY" fcm branch-list --only=2:Share --only=3:sibling
+sed -i "/ Date/d;" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] $ROOT_URL at 9: 1 match(es)
+$ROOT_URL/branches/dev/Share/sibling_branch_test at 9
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm branch-list --only (4)
+TEST_KEY=$TEST_KEY_BASE-only-4
+run_pass "$TEST_KEY" fcm branch-list --only=1:something-not-right
+sed -i "/ Date/d;" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] $ROOT_URL at 9: 0 match(es)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm branch-list --only (5)
+TEST_KEY=$TEST_KEY_BASE-only-5
+run_fail "$TEST_KEY" fcm branch-list --only=1:\)
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-branch-list/test_header b/t/fcm-branch-list/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/fcm-branch-list/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/fcm-commit/00-simple.t b/t/fcm-commit/00-simple.t
new file mode 100644
index 0000000..5033679
--- /dev/null
+++ b/t/fcm-commit/00-simple.t
@@ -0,0 +1,134 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm commit".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 3
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch sibling_branch_test $REPOS_URL
+init_branch_wc branch_test $REPOS_URL
+cd $TEST_DIR/wc
+FILE_LIST=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+for FILE in $FILE_LIST; do
+    sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $FILE
+    sed -i "/#/d; /^ *!/d" $FILE
+    sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $FILE
+done
+FILE_DIR=$(dirname $FILE)
+svn copy -q $FILE added_file
+svn copy -q $FILE_DIR added_directory
+svn delete --force -q $FILE_DIR
+#-------------------------------------------------------------------------------
+# Tests fcm commit
+TEST_KEY=$TEST_KEY_BASE
+export SVN_EDITOR="sed -i 1i\foo" 
+run_pass "$TEST_KEY" fcm commit --svn-non-interactive <<__IN__
+y
+__IN__
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] sed -i 1i\foo: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Root   : $REPOS_URL]
+[Project: ${TEST_PROJECT:-}]
+[Branch : branches/dev/Share/branch_test]
+[Sub-dir: ]
+
+A  +    added_file
+D       module
+D       module/hello_constants_dummy.inc
+D       module/hello_constants.inc
+D       module/hello_constants.f90
+A  +    added_directory
+M  +    added_directory/hello_constants_dummy.inc
+M  +    added_directory/hello_constants.inc
+M  +    added_directory/hello_constants.f90
+M       lib/python/info/poems.py
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+foo
+--------------------------------------------------------------------------------
+
+*** WARNING: YOU ARE COMMITTING TO A Share BRANCH.
+*** Please ensure that you have the owner's permission.
+
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): Adding         added_directory
+Sending        added_directory/hello_constants.f90
+Sending        added_directory/hello_constants.inc
+Sending        added_directory/hello_constants_dummy.inc
+Adding         added_file
+Sending        lib/python/info/poems.py
+Deleting       module
+Transmitting file data .....
+Committed revision 6.
+At revision 6.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] sed -i 1i\foo: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Root   : $REPOS_URL]
+[Project: ${TEST_PROJECT:-}]
+[Branch : branches/dev/Share/branch_test]
+[Sub-dir: ]
+
+A  +    added_directory
+M  +    added_directory/hello_constants.f90
+M  +    added_directory/hello_constants.inc
+M  +    added_directory/hello_constants_dummy.inc
+A  +    added_file
+M       lib/python/info/poems.py
+D       module
+D       module/hello_constants.f90
+D       module/hello_constants.inc
+D       module/hello_constants_dummy.inc
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+foo
+--------------------------------------------------------------------------------
+
+*** WARNING: YOU ARE COMMITTING TO A Share BRANCH.
+*** Please ensure that you have the owner's permission.
+
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): Adding         added_directory
+Sending        added_directory/hello_constants.f90
+Sending        added_directory/hello_constants.inc
+Sending        added_directory/hello_constants_dummy.inc
+Adding         added_file
+Sending        lib/python/info/poems.py
+Deleting       module
+Transmitting file data .....
+Committed revision 6.
+Updating '.':
+At revision 6.
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-commit/01-subtree.t b/t/fcm-commit/01-subtree.t
new file mode 100644
index 0000000..b651608
--- /dev/null
+++ b/t/fcm-commit/01-subtree.t
@@ -0,0 +1,137 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm commit".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 3
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch sibling_branch_test $REPOS_URL
+init_branch_wc branch_test $REPOS_URL
+cd $TEST_DIR/wc
+FILE_LIST=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+for FILE in $FILE_LIST; do
+    sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $FILE
+    sed -i "/#/d; /^ *!/d" $FILE
+    sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $FILE
+done
+FILE_DIR=$(dirname $FILE)
+svn copy -q $FILE added_file
+svn copy -q $FILE_DIR added_directory
+svn delete --force -q $FILE_DIR
+#-------------------------------------------------------------------------------
+# Tests fcm commit
+TEST_KEY=$TEST_KEY_BASE
+export SVN_EDITOR="sed -i 1i\foo"
+cd program
+run_pass "$TEST_KEY" fcm commit --svn-non-interactive <<__IN__
+y
+__IN__
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+$TEST_DIR/wc: working directory changed to top of working copy.
+[info] sed -i 1i\foo: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Root   : $REPOS_URL]
+[Project: ${TEST_PROJECT:-}]
+[Branch : branches/dev/Share/branch_test]
+[Sub-dir: ]
+
+A  +    added_file
+D       module
+D       module/hello_constants_dummy.inc
+D       module/hello_constants.inc
+D       module/hello_constants.f90
+A  +    added_directory
+M  +    added_directory/hello_constants_dummy.inc
+M  +    added_directory/hello_constants.inc
+M  +    added_directory/hello_constants.f90
+M       lib/python/info/poems.py
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+foo
+--------------------------------------------------------------------------------
+
+*** WARNING: YOU ARE COMMITTING TO A Share BRANCH.
+*** Please ensure that you have the owner's permission.
+
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): Adding         added_directory
+Sending        added_directory/hello_constants.f90
+Sending        added_directory/hello_constants.inc
+Sending        added_directory/hello_constants_dummy.inc
+Adding         added_file
+Sending        lib/python/info/poems.py
+Deleting       module
+Transmitting file data .....
+Committed revision 6.
+At revision 6.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+$TEST_DIR/wc: working directory changed to top of working copy.
+[info] sed -i 1i\foo: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Root   : $REPOS_URL]
+[Project: ${TEST_PROJECT:-}]
+[Branch : branches/dev/Share/branch_test]
+[Sub-dir: ]
+
+A  +    added_directory
+M  +    added_directory/hello_constants.f90
+M  +    added_directory/hello_constants.inc
+M  +    added_directory/hello_constants_dummy.inc
+A  +    added_file
+M       lib/python/info/poems.py
+D       module
+D       module/hello_constants.f90
+D       module/hello_constants.inc
+D       module/hello_constants_dummy.inc
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+foo
+--------------------------------------------------------------------------------
+
+*** WARNING: YOU ARE COMMITTING TO A Share BRANCH.
+*** Please ensure that you have the owner's permission.
+
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): Adding         added_directory
+Sending        added_directory/hello_constants.f90
+Sending        added_directory/hello_constants.inc
+Sending        added_directory/hello_constants_dummy.inc
+Adding         added_file
+Sending        lib/python/info/poems.py
+Deleting       module
+Transmitting file data .....
+Committed revision 6.
+Updating '.':
+At revision 6.
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-commit/02-bad.t b/t/fcm-commit/02-bad.t
new file mode 100644
index 0000000..51c85d7
--- /dev/null
+++ b/t/fcm-commit/02-bad.t
@@ -0,0 +1,48 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Bad-behaviour tests for "fcm commit".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 3
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch sibling_branch_test $REPOS_URL
+init_branch_wc branch_test $REPOS_URL
+cd $TEST_DIR/wc
+svn copy -q pro/hello.pro copied_file
+svn copy -q module copied_directory
+svn delete -q --force lib
+rm -rf program/hello.F90
+#-------------------------------------------------------------------------------
+# Tests fcm commit
+TEST_KEY=$TEST_KEY_BASE
+export SVN_EDITOR="sed -i 1i\foo" 
+run_fail "$TEST_KEY" fcm commit --svn-non-interactive
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" </dev/null
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<__ERR__
+[ERROR] File(s) missing:
+!                5   program/hello.F90
+[FAIL] FCM1::Cm::Abort: abort
+
+__ERR__
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-commit/03-message-file.t b/t/fcm-commit/03-message-file.t
new file mode 100644
index 0000000..12de033
--- /dev/null
+++ b/t/fcm-commit/03-message-file.t
@@ -0,0 +1,58 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Tests for "fcm commit", attempt to add commit message file.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 6
+#-------------------------------------------------------------------------------
+svnadmin create foo
+svn co -q file://$PWD/foo 'test-work'
+touch 'test-work/#commit_message#'
+svn add 'test-work/#commit_message#'
+export SVN_EDITOR='cat'
+#-------------------------------------------------------------------------------
+# Tests fcm commit, bad commit file 1
+TEST_KEY="$TEST_KEY_BASE-1"
+run_fail "$TEST_KEY" fcm commit --svn-non-interactive 'test-work'
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+$PWD/test-work: working directory changed to top of working copy.
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<'__ERR__'
+[ERROR] Attempt to add commit message file:
+A       #commit_message#
+[FAIL] FCM1::Cm::Abort: abort
+
+__ERR__
+#-------------------------------------------------------------------------------
+# Tests fcm commit, bad commit file 2
+TEST_KEY="$TEST_KEY_BASE-2"
+cd 'test-work'
+run_fail "$TEST_KEY" fcm commit --svn-non-interactive
+cd ..
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" </dev/null
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<'__ERR__'
+[ERROR] Attempt to add commit message file:
+A       #commit_message#
+[FAIL] FCM1::Cm::Abort: abort
+
+__ERR__
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/fcm-commit/test_header b/t/fcm-commit/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/fcm-commit/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/fcm-conflicts/00-tree-add-add.t b/t/fcm-conflicts/00-tree-add-add.t
new file mode 100644
index 0000000..ea1207f
--- /dev/null
+++ b/t/fcm-conflicts/00-tree-add-add.t
@@ -0,0 +1,154 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm conflicts" (tree conflict mode).
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 24
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch ctrl $REPOS_URL
+init_branch_wc add_add $REPOS_URL
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: add, add, discard local   
+TEST_KEY=$TEST_KEY_BASE-discard
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+echo "Local contents (1)" > new_file
+svn add -q new_file
+svn commit -q -m "Added duplicate-name copy of conflict file"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/add_add
+echo "Merge contents (1)" >new_file
+echo "Merge contents (2)" >>new_file
+svn add -q new_file
+svn commit -q -m "Added duplicated-name copy of conflict file"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/add_add >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+n
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] new_file: in tree conflict.
+Locally: added.
+Externally: added.
+Answer (y) to keep the local file filename.
+Answer (n) to keep the external file filename.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") Resolved conflicted state of 'new_file'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: add, add, discard local (status)
+TEST_KEY=$TEST_KEY_BASE-discard-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+M       new_file
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: add, add, discard local (info)
+TEST_KEY=$TEST_KEY_BASE-discard-info
+run_pass "$TEST_KEY" svn info new_file
+sed -i "/Date:\|Updated:\|UUID:\|Checksum\|Relative URL:\|Working Copy Root Path:/d" $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Path: new_file
+Name: new_file
+URL: $ROOT_URL/branches/dev/Share/ctrl/new_file
+Repository Root: $REPOS_URL
+Revision: 7
+Node Kind: file
+Schedule: normal
+Last Changed Author: $LOGNAME
+Last Changed Rev: 6
+
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: add, add, discard local (cat)
+TEST_KEY=$TEST_KEY_BASE-discard-cat
+run_pass "$TEST_KEY" cat new_file
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Merge contents (1)
+Merge contents (2)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/branches/dev/Share/ctrl $TEST_DIR/wc
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: add, add, keep local
+TEST_KEY=$TEST_KEY_BASE-keep
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/add_add >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+y
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] new_file: in tree conflict.
+Locally: added.
+Externally: added.
+Answer (y) to keep the local file filename.
+Answer (n) to keep the external file filename.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") Resolved conflicted state of 'new_file'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: add, add, keep local (status)
+TEST_KEY=$TEST_KEY_BASE-keep-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: add, add, keep local (info)
+TEST_KEY=$TEST_KEY_BASE-keep-info
+run_pass "$TEST_KEY" svn info new_file
+sed -i "/Date:\|Updated:\|UUID:\|Checksum\|Relative URL:\|Working Copy Root Path:/d" $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Path: new_file
+Name: new_file
+URL: $ROOT_URL/branches/dev/Share/ctrl/new_file
+Repository Root: $REPOS_URL
+Revision: 7
+Node Kind: file
+Schedule: normal
+Last Changed Author: $LOGNAME
+Last Changed Rev: 6
+
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: add, add, keep local (cat)
+TEST_KEY=$TEST_KEY_BASE-keep-cat
+run_pass "$TEST_KEY" cat new_file
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Local contents (1)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-conflicts/01-tree-delete-delete.t b/t/fcm-conflicts/01-tree-delete-delete.t
new file mode 100644
index 0000000..57140c6
--- /dev/null
+++ b/t/fcm-conflicts/01-tree-delete-delete.t
@@ -0,0 +1,97 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm conflicts" (tree conflict mode).
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 12
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch ctrl $REPOS_URL
+init_branch_wc del_del $REPOS_URL
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, delete, discard local
+TEST_KEY=$TEST_KEY_BASE-discard
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+svn delete -q pro/hello.pro
+svn commit -q -m "Deleted conflict file (local)"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/del_del
+svn delete -q pro/hello.pro
+svn commit -q -m "Deleted conflict file (merge)"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/del_del >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+n
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: deleted.
+Externally: deleted.
+Answer (y) to accept the local delete.
+Answer (n) to accept the external delete.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, delete, discard local (status)
+TEST_KEY=$TEST_KEY_BASE-discard-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/branches/dev/Share/ctrl $TEST_DIR/wc
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, delete, keep local
+TEST_KEY=$TEST_KEY_BASE-keep
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/del_del >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+y
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: deleted.
+Externally: deleted.
+Answer (y) to accept the local delete.
+Answer (n) to accept the external delete.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, delete, keep local (status)
+TEST_KEY=$TEST_KEY_BASE-keep-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-conflicts/02-tree-delete-edit.t b/t/fcm-conflicts/02-tree-delete-edit.t
new file mode 100644
index 0000000..02f71ce
--- /dev/null
+++ b/t/fcm-conflicts/02-tree-delete-edit.t
@@ -0,0 +1,132 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm conflicts" (tree conflict mode).
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 18
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch ctrl $REPOS_URL
+init_branch_wc del_ed $REPOS_URL
+export SVN_EDITOR="sed -i 1i\foo"
+# Set a special (null) fcm-graphic-merge diff editor.
+export FCM_GRAPHIC_MERGE=fcm-dummy-diff
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, edit, discard local
+TEST_KEY=$TEST_KEY_BASE-discard
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+svn delete -q pro/hello.pro
+svn commit -q -m "Deleted local copy of conflict file"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/del_ed
+echo "Merge contents (1)" >>pro/hello.pro
+svn commit -q -m "Modified merge copy of conflict file"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/del_ed >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+n
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: deleted.
+Externally: edited.
+Answer (y) to accept the local delete.
+Answer (n) to keep the file.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") A         pro/hello.pro
+Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, edit, discard local (status)
+TEST_KEY=$TEST_KEY_BASE-discard-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+A  +    pro/hello.pro
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, edit, discard local (info)
+TEST_KEY=$TEST_KEY_BASE-discard-info
+run_pass "$TEST_KEY" svn info pro/hello.pro
+sed -i "/Date:\|Updated:\|UUID:\|Checksum\|Relative URL:\|Working Copy Root Path:/d" $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Path: pro/hello.pro
+Name: hello.pro
+URL: $ROOT_URL/branches/dev/Share/ctrl/pro/hello.pro
+Repository Root: $REPOS_URL
+Revision: 7
+Node Kind: file
+Schedule: add
+Copied From URL: $ROOT_URL/branches/dev/Share/del_ed/pro/hello.pro
+Copied From Rev: 7
+Last Changed Author: $LOGNAME
+Last Changed Rev: 7
+
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, edit, discard local (cat)
+TEST_KEY=$TEST_KEY_BASE-discard-cat
+run_pass "$TEST_KEY" cat pro/hello.pro
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+PRO HELLO
+END
+Merge contents (1)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/branches/dev/Share/ctrl $TEST_DIR/wc
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, edit, keep local
+TEST_KEY=$TEST_KEY_BASE-keep
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/del_ed >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+y
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: deleted.
+Externally: edited.
+Answer (y) to accept the local delete.
+Answer (n) to keep the file.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, edit, keep local (status)
+TEST_KEY=$TEST_KEY_BASE-keep-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-conflicts/03-tree-delete-rename.t b/t/fcm-conflicts/03-tree-delete-rename.t
new file mode 100644
index 0000000..56b4bfd
--- /dev/null
+++ b/t/fcm-conflicts/03-tree-delete-rename.t
@@ -0,0 +1,111 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm conflicts" (tree conflict mode).
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 15
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch ctrl $REPOS_URL
+init_branch_wc del_ren $REPOS_URL
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, rename, discard local
+TEST_KEY=$TEST_KEY_BASE-discard
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+svn delete -q pro/hello.pro
+svn commit -q -m "Deleted conflict file (local)"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/del_ren
+svn rename -q pro/hello.pro pro/hello.pro.renamed
+svn commit -q -m "Renamed conflict file (merge)"
+svn update -q
+echo "Merge changes (1)" >>pro/hello.pro.renamed
+svn commit -q -m "Modified conflict file (merge)"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/del_ren >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+n
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: deleted.
+Externally: renamed to pro/hello.pro.renamed.
+Answer (y) to accept the local delete.
+Answer (n) to accept the external rename.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, rename, discard local (status)
+TEST_KEY=$TEST_KEY_BASE-discard-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+A  +    pro/hello.pro.renamed
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, rename, discard local (cat)
+TEST_KEY=$TEST_KEY_BASE-discard-cat
+run_pass "$TEST_KEY" cat pro/hello.pro.renamed
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+PRO HELLO
+END
+Merge changes (1)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/branches/dev/Share/ctrl $TEST_DIR/wc
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, rename, keep local
+TEST_KEY=$TEST_KEY_BASE-keep
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/del_ren >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+y
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: deleted.
+Externally: renamed to pro/hello.pro.renamed.
+Answer (y) to accept the local delete.
+Answer (n) to accept the external rename.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") Reverted 'pro/hello.pro.renamed'
+Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, rename, keep local (status)
+TEST_KEY=$TEST_KEY_BASE-keep-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-conflicts/04-tree-edit-delete.t b/t/fcm-conflicts/04-tree-edit-delete.t
new file mode 100644
index 0000000..a1ac908
--- /dev/null
+++ b/t/fcm-conflicts/04-tree-edit-delete.t
@@ -0,0 +1,130 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm conflicts" (tree conflict mode).
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 18
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch ctrl $REPOS_URL
+init_branch_wc ed_del $REPOS_URL
+export SVN_EDITOR="sed -i 1i\foo"
+# Set a special (null) fcm-graphic-merge diff editor.
+export FCM_GRAPHIC_MERGE=fcm-dummy-diff
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: edit, delete, discard local
+TEST_KEY=$TEST_KEY_BASE-discard
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+echo "Local contents (1)" >>pro/hello.pro
+svn commit -q -m "Modified local copy of conflict file"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ed_del
+svn delete -q pro/hello.pro
+svn commit -q -m "Deleted merge copy of conflict file"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ed_del >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+n
+__IN__
+sed -i "/^Resolved conflicted state of 'pro\/hello.pro'$/d" $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: edited.
+Externally: deleted.
+Answer (y) to keep the file.
+Answer (n) to accept the external delete.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") D         pro/hello.pro
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: edit, delete, discard local (status)
+TEST_KEY=$TEST_KEY_BASE-discard-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+D       pro/hello.pro
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: edit, delete, discard local (info)
+TEST_KEY=$TEST_KEY_BASE-discard-info
+run_pass "$TEST_KEY" svn info pro/hello.pro
+sed -i "/Date:\|Updated:\|UUID:\|Checksum\|Relative URL:\|Working Copy Root Path:/d" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Path: pro/hello.pro
+Name: hello.pro
+URL: $ROOT_URL/branches/dev/Share/ctrl/pro/hello.pro
+Repository Root: $REPOS_URL
+Revision: 7
+Node Kind: file
+Schedule: delete
+Last Changed Author: $LOGNAME
+Last Changed Rev: 6
+
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/branches/dev/Share/ctrl $TEST_DIR/wc
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: edit, delete, keep local
+TEST_KEY=$TEST_KEY_BASE-keep
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ed_del >/dev/null 
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+y
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: edited.
+Externally: deleted.
+Answer (y) to keep the file.
+Answer (n) to accept the external delete.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: edit, delete, keep local (status)
+TEST_KEY=$TEST_KEY_BASE-keep-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: edit, delete, keep local (cat)
+TEST_KEY=$TEST_KEY_BASE-keep-cat
+run_pass "$TEST_KEY" cat pro/hello.pro
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+PRO HELLO
+END
+Local contents (1)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-conflicts/05-tree-edit-rename.t b/t/fcm-conflicts/05-tree-edit-rename.t
new file mode 100644
index 0000000..7e2fc01
--- /dev/null
+++ b/t/fcm-conflicts/05-tree-edit-rename.t
@@ -0,0 +1,185 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm conflicts" (tree conflict mode).
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 24
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch ctrl $REPOS_URL
+init_branch_wc ed_ren $REPOS_URL
+export SVN_EDITOR="sed -i 1i\foo"
+# Set a special (null) fcm-graphic-merge diff editor.
+export FCM_GRAPHIC_MERGE=fcm-dummy-diff
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: edit, rename, discard local
+TEST_KEY=$TEST_KEY_BASE-discard
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+echo "Local contents (1)" >>pro/hello.pro
+svn commit -q -m "Modified local copy of conflict file"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ed_ren
+echo "Merge contents (1)" >>pro/hello.pro
+svn rename -q pro/hello.pro pro/hello.pro.renamed
+svn commit -q -m "Modified and renamed merge copy of conflict file"
+svn update -q
+echo "Merge contents (2)" >>pro/hello.pro.renamed
+svn commit -q -m "Modified the merge copy of renamed conflict file"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ed_ren >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+n
+__IN__
+sed -i "/^Resolved conflicted state of 'pro\/hello.pro'$/d" $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: edited.
+Externally: renamed to pro/hello.pro.renamed.
+Answer (y) to keep the file.
+Answer (n) to accept the external rename.
+You can then merge in changes.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") diff3 pro/hello.pro.renamed.working pro/hello.pro.renamed.merge-left.r1 pro/hello.pro.renamed.merge-right.r8
+====
+1:3c
+  Local contents (1)
+2:2a
+3:3,4c
+  Merge contents (1)
+  Merge contents (2)
+D         pro/hello.pro
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: edit, rename, discard local (status)
+TEST_KEY=$TEST_KEY_BASE-discard-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+D       pro/hello.pro
+A  +    pro/hello.pro.renamed
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: edit, rename, discard local (info)
+TEST_KEY=$TEST_KEY_BASE-discard-info
+run_pass "$TEST_KEY" svn info pro/hello.pro.renamed
+sed -i "/Date:\|Updated:\|UUID:\|Checksum\|Relative URL:\|Working Copy Root Path:/d" $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Path: pro/hello.pro.renamed
+Name: hello.pro.renamed
+URL: $ROOT_URL/branches/dev/Share/ctrl/pro/hello.pro.renamed
+Repository Root: $REPOS_URL
+Revision: 8
+Node Kind: file
+Schedule: add
+Copied From URL: $ROOT_URL/branches/dev/Share/ed_ren/pro/hello.pro.renamed
+Copied From Rev: 8
+Last Changed Author: $LOGNAME
+Last Changed Rev: 8
+
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, rename, discard local (cat)
+TEST_KEY=$TEST_KEY_BASE-discard-cat
+run_pass "$TEST_KEY" cat pro/hello.pro.renamed
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+PRO HELLO
+END
+Merge contents (1)
+Merge contents (2)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/branches/dev/Share/ctrl $TEST_DIR/wc
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: edit, rename, keep local
+TEST_KEY=$TEST_KEY_BASE-keep
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ed_ren >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+y
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: edited.
+Externally: renamed to pro/hello.pro.renamed.
+Answer (y) to keep the file.
+Answer (n) to accept the external rename.
+You can then merge in changes.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") diff3 pro/hello.pro.working pro/hello.pro.merge-left.r1 pro/hello.pro.merge-right.r8
+====
+1:3c
+  Local contents (1)
+2:2a
+3:3,4c
+  Merge contents (1)
+  Merge contents (2)
+Reverted 'pro/hello.pro.renamed'
+Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: edit, rename, keep local (status)
+TEST_KEY=$TEST_KEY_BASE-keep-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: edit, rename, keep local (info)
+TEST_KEY=$TEST_KEY_BASE-keep-info
+run_pass "$TEST_KEY" svn info pro/hello.pro
+sed -i "/Date:\|Updated:\|UUID:\|Checksum\|Relative URL:\|Working Copy Root Path:/d" $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Path: pro/hello.pro
+Name: hello.pro
+URL: $ROOT_URL/branches/dev/Share/ctrl/pro/hello.pro
+Repository Root: $REPOS_URL
+Revision: 8
+Node Kind: file
+Schedule: normal
+Last Changed Author: $LOGNAME
+Last Changed Rev: 6
+
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: delete, rename, keep local (cat)
+TEST_KEY=$TEST_KEY_BASE-keep-cat
+run_pass "$TEST_KEY" cat pro/hello.pro
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+PRO HELLO
+END
+Local contents (1)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-conflicts/06-tree-rename-delete.t b/t/fcm-conflicts/06-tree-rename-delete.t
new file mode 100644
index 0000000..be05fbb
--- /dev/null
+++ b/t/fcm-conflicts/06-tree-rename-delete.t
@@ -0,0 +1,111 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm conflicts" (tree conflict mode).
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 15
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch ctrl $REPOS_URL
+init_branch_wc ren_del $REPOS_URL
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, delete, discard local
+TEST_KEY=$TEST_KEY_BASE-discard
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+svn rename -q pro/hello.pro pro/hello.pro.renamed
+svn commit -q -m "Renamed conflict file (local)"
+svn update -q
+echo "Local contents (1)" >>pro/hello.pro.renamed
+svn commit -q -m "Modified conflict file (local)"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ren_del
+svn delete -q pro/hello.pro
+svn commit -q -m "Deleted conflict file  (merge)"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ren_del >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+n
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: renamed to pro/hello.pro.renamed.
+Externally: deleted.
+Answer (y) to accept the local rename.
+Answer (n) to accept the external delete.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") D         pro/hello.pro.renamed
+Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, delete, discard local (status)
+TEST_KEY=$TEST_KEY_BASE-discard-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+D       pro/hello.pro.renamed
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/branches/dev/Share/ctrl $TEST_DIR/wc
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, delete, keep local
+TEST_KEY=$TEST_KEY_BASE-keep
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ren_del >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+y
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: renamed to pro/hello.pro.renamed.
+Externally: deleted.
+Answer (y) to accept the local rename.
+Answer (n) to accept the external delete.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, delete, keep local (status)
+TEST_KEY=$TEST_KEY_BASE-keep-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, delete, keep local (cat)
+TEST_KEY=$TEST_KEY_BASE-keep-cat
+run_pass "$TEST_KEY" cat pro/hello.pro.renamed
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+PRO HELLO
+END
+Local contents (1)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
diff --git a/t/fcm-conflicts/07-tree-rename-edit.t b/t/fcm-conflicts/07-tree-rename-edit.t
new file mode 100644
index 0000000..28d74ea
--- /dev/null
+++ b/t/fcm-conflicts/07-tree-rename-edit.t
@@ -0,0 +1,142 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm conflicts" (tree conflict mode).
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 18
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch ctrl $REPOS_URL
+init_branch_wc ren_ed $REPOS_URL
+# Set a special (null) fcm-graphic-merge diff editor.
+export FCM_GRAPHIC_MERGE=fcm-dummy-diff
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, edit, discard local
+TEST_KEY=$TEST_KEY_BASE-discard
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+svn rename -q pro/hello.pro pro/hello.pro.renamed
+svn commit -q -m "Renamed conflict file (local)"
+svn update -q
+echo "Local contents (1)" >>pro/hello.pro.renamed
+svn commit -q -m "Modified conflict file (local)"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ren_ed
+echo "Merge contents (1)" >>pro/hello.pro
+svn commit -q -m "Modified conflict file  (merge)"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ren_ed >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+n
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: renamed to pro/hello.pro.renamed.
+Externally: edited.
+Answer (y) to accept the local rename.
+Answer (n) to keep the file.
+You can then merge in changes.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") diff3 pro/hello.pro.renamed.working pro/hello.pro.renamed.merge-left.r1 pro/hello.pro.renamed.merge-right.r8
+====
+1:3c
+  Local contents (1)
+2:2a
+3:3c
+  Merge contents (1)
+A         pro/hello.pro
+D         pro/hello.pro.renamed
+Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, edit, discard local (status)
+TEST_KEY=$TEST_KEY_BASE-discard-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+sed -i "/^ \{8\}> moved /d" $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+A  +    pro/hello.pro
+D       pro/hello.pro.renamed
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, edit, discard local (cat)
+TEST_KEY=$TEST_KEY_BASE-discard-cat
+run_pass "$TEST_KEY" cat pro/hello.pro
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+PRO HELLO
+END
+Local contents (1)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/branches/dev/Share/ctrl $TEST_DIR/wc
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, edit, keep local
+TEST_KEY=$TEST_KEY_BASE-keep
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ren_ed >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+y
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: renamed to pro/hello.pro.renamed.
+Externally: edited.
+Answer (y) to accept the local rename.
+Answer (n) to keep the file.
+You can then merge in changes.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") diff3 pro/hello.pro.renamed.working pro/hello.pro.renamed.merge-left.r1 pro/hello.pro.renamed.merge-right.r8
+====
+1:3c
+  Local contents (1)
+2:2a
+3:3c
+  Merge contents (1)
+Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, edit, keep local (status)
+TEST_KEY=$TEST_KEY_BASE-keep-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, edit, keep local (cat)
+TEST_KEY=$TEST_KEY_BASE-keep-cat
+run_pass "$TEST_KEY" cat pro/hello.pro.renamed
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+PRO HELLO
+END
+Local contents (1)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
diff --git a/t/fcm-conflicts/08-tree-rename-rename-diff.t b/t/fcm-conflicts/08-tree-rename-rename-diff.t
new file mode 100644
index 0000000..fd44a7a
--- /dev/null
+++ b/t/fcm-conflicts/08-tree-rename-rename-diff.t
@@ -0,0 +1,144 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm conflicts" (tree conflict mode).
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 18
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch ctrl $REPOS_URL
+init_branch_wc ren_ren $REPOS_URL
+# Set a special (null) fcm-graphic-merge diff editor.
+export FCM_GRAPHIC_MERGE=fcm-dummy-diff
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, discard local
+TEST_KEY=$TEST_KEY_BASE-discard
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+svn rename -q pro/hello.pro pro/hello.pro.renamed-local
+svn commit -q -m "Renamed conflict file (local)"
+svn update -q
+echo "Local contents (1)" >>pro/hello.pro.renamed-local
+svn commit -q -m "Modified conflict file (local)"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ren_ren
+echo "Merge contents (1)" >>pro/hello.pro
+svn commit -q -m "Modified conflict file  (merge)"
+svn update -q
+svn rename -q pro/hello.pro pro/hello.pro.renamed-merge
+svn commit -q -m "Renamed conflict file (merge)"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ren_ren >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+n
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: renamed to pro/hello.pro.renamed-local.
+Externally: renamed to pro/hello.pro.renamed-merge.
+Answer (y) to accept the local rename.
+Answer (n) to accept the external rename.
+You can then merge in changes.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") diff3 pro/hello.pro.renamed-merge.working pro/hello.pro.renamed-merge.merge-left.r1 pro/hello.pro.renamed-merge.merge-right.r9
+====
+1:3c
+  Local contents (1)
+2:2a
+3:3c
+  Merge contents (1)
+D         pro/hello.pro.renamed-local
+Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, discard local (status)
+TEST_KEY=$TEST_KEY_BASE-discard-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+D       pro/hello.pro.renamed-local
+A  +    pro/hello.pro.renamed-merge
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, discard local (cat)
+TEST_KEY=$TEST_KEY_BASE-discard-cat
+run_pass "$TEST_KEY" cat pro/hello.pro.renamed-merge
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+PRO HELLO
+END
+Merge contents (1)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/branches/dev/Share/ctrl $TEST_DIR/wc
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, keep local
+TEST_KEY=$TEST_KEY_BASE-keep
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ren_ren >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+y
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: renamed to pro/hello.pro.renamed-local.
+Externally: renamed to pro/hello.pro.renamed-merge.
+Answer (y) to accept the local rename.
+Answer (n) to accept the external rename.
+You can then merge in changes.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") diff3 pro/hello.pro.renamed-local.working pro/hello.pro.renamed-local.merge-left.r1 pro/hello.pro.renamed-local.merge-right.r9
+====
+1:3c
+  Local contents (1)
+2:2a
+3:3c
+  Merge contents (1)
+Reverted 'pro/hello.pro.renamed-merge'
+Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, keep local (status)
+TEST_KEY=$TEST_KEY_BASE-keep-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, keep local (cat)
+TEST_KEY=$TEST_KEY_BASE-keep-cat
+run_pass "$TEST_KEY" cat pro/hello.pro.renamed-local
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+PRO HELLO
+END
+Local contents (1)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
diff --git a/t/fcm-conflicts/09-tree-rename-rename-same.t b/t/fcm-conflicts/09-tree-rename-rename-same.t
new file mode 100644
index 0000000..4fb2d91
--- /dev/null
+++ b/t/fcm-conflicts/09-tree-rename-rename-same.t
@@ -0,0 +1,220 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm conflicts" (tree conflict mode).
+# TODO: The behaviour exhibited by fcm conflicts in this file is wrong.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 33
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch ctrl $REPOS_URL
+init_branch_wc ren_ren $REPOS_URL
+# Set a special (null) fcm-graphic-merge diff editor.
+export FCM_GRAPHIC_MERGE=fcm-dummy-diff
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, discard local, discard local
+TEST_KEY=$TEST_KEY_BASE-discard-discard
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+svn rename -q pro/hello.pro pro/hello.pro.renamed
+svn commit -q -m "Renamed conflict file (local)"
+svn update -q
+echo "Local contents (1)" >>pro/hello.pro.renamed
+svn commit -q -m "Modified conflict file (local)"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ren_ren
+echo "Merge contents (1)" >>pro/hello.pro
+svn commit -q -m "Modified conflict file  (merge)"
+svn update -q
+svn rename -q pro/hello.pro pro/hello.pro.renamed
+svn commit -q -m "Renamed conflict file (merge)"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/ctrl
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ren_ren >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+n
+n
+__IN__
+sed -i -n "1,8p" $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: renamed to pro/hello.pro.renamed.
+Externally: deleted.
+Answer (y) to accept the local rename.
+Answer (n) to accept the external delete.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") D         pro/hello.pro.renamed
+Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, discard local, discard local (status)
+TEST_KEY=$TEST_KEY_BASE-discard-discard-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+D       pro/hello.pro.renamed
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/branches/dev/Share/ctrl $TEST_DIR/wc
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, discard local, keep local
+TEST_KEY=$TEST_KEY_BASE-discard-keep
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ren_ren >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+n
+y
+__IN__
+sed -i -n "1,8p" $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: renamed to pro/hello.pro.renamed.
+Externally: deleted.
+Answer (y) to accept the local rename.
+Answer (n) to accept the external delete.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") D         pro/hello.pro.renamed
+Resolved conflicted state of 'pro/hello.pro'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, discard local, keep local (status)
+TEST_KEY=$TEST_KEY_BASE-discard-keep-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+D       pro/hello.pro.renamed
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, discard local, keep local (cat)
+TEST_KEY=$TEST_KEY_BASE-discard-keep-cat
+run_fail "$TEST_KEY" cat pro/hello.pro.renamed
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" </dev/null
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<__ERR__
+cat: pro/hello.pro.renamed: No such file or directory
+__ERR__
+#-------------------------------------------------------------------------------
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/branches/dev/Share/ctrl $TEST_DIR/wc
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, keep local, discard local
+TEST_KEY=$TEST_KEY_BASE-keep-discard
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ren_ren >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+y
+n
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: renamed to pro/hello.pro.renamed.
+Externally: deleted.
+Answer (y) to accept the local rename.
+Answer (n) to accept the external delete.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") Resolved conflicted state of 'pro/hello.pro'
+[info] pro/hello.pro.renamed: in tree conflict.
+Locally: added.
+Externally: added.
+Answer (y) to keep the local file filename.
+Answer (n) to keep the external file filename.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") Resolved conflicted state of 'pro/hello.pro.renamed'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, keep local (status)
+TEST_KEY=$TEST_KEY_BASE-keep-discard-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+M       pro/hello.pro.renamed
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, keep local (cat)
+TEST_KEY=$TEST_KEY_BASE-keep-discard-cat
+run_pass "$TEST_KEY" cat pro/hello.pro.renamed
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+PRO HELLO
+END
+Merge contents (1)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/branches/dev/Share/ctrl $TEST_DIR/wc
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, keep local, keep local
+TEST_KEY=$TEST_KEY_BASE-keep-keep
+fcm merge --non-interactive $ROOT_URL/branches/dev/Share/ren_ren >/dev/null
+run_pass "$TEST_KEY" fcm conflicts <<__IN__
+y
+y
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] pro/hello.pro: in tree conflict.
+Locally: renamed to pro/hello.pro.renamed.
+Externally: deleted.
+Answer (y) to accept the local rename.
+Answer (n) to accept the external delete.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") Resolved conflicted state of 'pro/hello.pro'
+[info] pro/hello.pro.renamed: in tree conflict.
+Locally: added.
+Externally: added.
+Answer (y) to keep the local file filename.
+Answer (n) to keep the external file filename.
+Keep the local version?
+Enter "y" or "n" (or just press <return> for "n") Resolved conflicted state of 'pro/hello.pro.renamed'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, keep local, keep local (status)
+TEST_KEY=$TEST_KEY_BASE-keep-keep-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm conflicts: rename, rename, diff rename, keep local, keep local (cat)
+TEST_KEY=$TEST_KEY_BASE-keep-keep-cat
+run_pass "$TEST_KEY" cat pro/hello.pro.renamed
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+PRO HELLO
+END
+Local contents (1)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
diff --git a/t/fcm-conflicts/10-text.t b/t/fcm-conflicts/10-text.t
new file mode 100644
index 0000000..de7c53f
--- /dev/null
+++ b/t/fcm-conflicts/10-text.t
@@ -0,0 +1,180 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm conflicts" (text conflict following merge).
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 11
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_merge_branches merge1 merge2 $REPOS_URL
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-merge
+export SVN_EDITOR="sed -i 1i\foo"
+echo "The End" >> lib/python/info/poems.py
+svn commit -m "Finish off the poem" -q
+svn update -q
+run_pass "$TEST_KEY" fcm merge --non-interactive $ROOT_URL/branches/dev/Share/merge1
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-merge-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+ M      .
+?       unversioned_file
+M       subroutine/hello_sub_dummy.h
+A  +    added_file
+A  +    module/tree_conflict_file
+M       module/hello_constants_dummy.inc
+M       module/hello_constants.inc
+M       module/hello_constants.f90
+A  +    added_directory
+A  +    added_directory/hello_constants_dummy.inc
+A  +    added_directory/hello_constants.inc
+A  +    added_directory/hello_constants.f90
+?       lib/python/info/poems.py.merge-left.r1
+?       lib/python/info/poems.py.merge-right.r5
+?       lib/python/info/poems.py.working
+C       lib/python/info/poems.py
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+ M      .
+A  +    added_directory
+A  +    added_file
+C       lib/python/info/poems.py
+?       lib/python/info/poems.py.merge-left.r1
+?       lib/python/info/poems.py.merge-right.r5
+?       lib/python/info/poems.py.working
+M       module/hello_constants.f90
+M       module/hello_constants.inc
+M       module/hello_constants_dummy.inc
+A  +    module/tree_conflict_file
+M       subroutine/hello_sub_dummy.h
+?       unversioned_file
+Summary of conflicts:
+  Text conflicts: 1
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-merge-conflicts
+export FCM_GRAPHIC_MERGE=fcm-dummy-diff
+run_pass "$TEST_KEY" fcm conflicts <<'__IN__'
+y
+y
+__IN__
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] lib/python/info/poems.py: in text conflict.
+diff3 $PWD/lib/python/info/poems.py.working $PWD/lib/python/info/poems.py.merge-left.r1 $PWD/lib/python/info/poems.py.merge-right.r5
+====3
+1:1,2c
+2:1,2c
+  #!/usr/bin/env python
+  # -*- coding: utf-8 -*-
+3:0a
+====3
+1:6c
+2:6c
+  It needs a doctor for its eyes,
+3:4c
+  It needs a doctor FOR its eyes,
+====3
+1:8,9c
+2:8,9c
+  However, if you feel inclined
+  To get one (to improve your mind,
+3:6,8c
+  However, if you feel INclINed
+  To get one (
+  to improve your mINd,
+====3
+1:12c
+2:12c
+  And when it flies into a rage
+3:11c
+  And when it flies INto a rage
+====3
+1:14c
+2:14c
+  I had an aunt in Yucatan
+3:13c
+  I had an aunt IN Yucatan
+====3
+1:16c
+2:16c
+  And kept it for a pet.
+3:15c
+  And kept it FOR a pet.
+====3
+1:19c
+2:19c
+  The Snake is living yet.
+3:18c
+  The Snake is livINg yet.
+====
+1:24,25c
+  print "\n",  __doc__
+  The End
+2:24c
+  print "\n",  __doc__
+3:23c
+  prINt "\n",  __doc__
+Run "svn resolve --accept working lib/python/info/poems.py"?
+Enter "y" or "n" (or just press <return> for "n") Resolved conflicted state of 'lib/python/info/poems.py'
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-merge-conflicts-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+ M      .
+?       unversioned_file
+M       subroutine/hello_sub_dummy.h
+A  +    added_file
+A  +    module/tree_conflict_file
+M       module/hello_constants_dummy.inc
+M       module/hello_constants.inc
+M       module/hello_constants.f90
+A  +    added_directory
+A  +    added_directory/hello_constants_dummy.inc
+A  +    added_directory/hello_constants.inc
+A  +    added_directory/hello_constants.f90
+M       lib/python/info/poems.py
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+ M      .
+A  +    added_directory
+A  +    added_file
+M       lib/python/info/poems.py
+M       module/hello_constants.f90
+M       module/hello_constants.inc
+M       module/hello_constants_dummy.inc
+A  +    module/tree_conflict_file
+M       subroutine/hello_sub_dummy.h
+?       unversioned_file
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
diff --git a/t/fcm-conflicts/test_header b/t/fcm-conflicts/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/fcm-conflicts/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/fcm-diff/00-simple.t b/t/fcm-diff/00-simple.t
new file mode 100644
index 0000000..b0cdf42
--- /dev/null
+++ b/t/fcm-diff/00-simple.t
@@ -0,0 +1,239 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm diff".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 3
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_branch_wc branch_test $REPOS_URL
+cd $TEST_DIR/wc
+FILE_LIST=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+for FILE in $FILE_LIST; do 
+    sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $FILE
+    sed -i "/#/d; /^ *!/d" $FILE
+    sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $FILE
+done
+FILE_DIR=$(dirname $FILE)
+svn copy -q $FILE added_file
+svn copy -q $FILE_DIR added_directory
+svn delete --force -q $FILE_DIR
+#-------------------------------------------------------------------------------
+# Tests fcm branch-diff
+TEST_KEY=$TEST_KEY_BASE-fcm-diff
+run_pass "$TEST_KEY" fcm diff
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: added_file
+===================================================================
+--- added_file	(revision 4)
++++ added_file	(working copy)
+@@ -1 +1 @@
+-INCLUDE 'hello_constants.inc'
++INCLUDE 'hello_constants.INc'
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	(revision 4)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +0,0 @@
+-INCLUDE 'hello_constants.inc'
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	(revision 4)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +0,0 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	(revision 4)
++++ module/hello_constants.f90	(working copy)
+@@ -1,5 +0,0 @@
+-MODULE Hello_Constants
+-
+-INCLUDE 'hello_constants_dummy.inc'
+-
+-END MODULE Hello_Constants
+Index: added_directory/hello_constants_dummy.inc
+===================================================================
+--- added_directory/hello_constants_dummy.inc	(revision 4)
++++ added_directory/hello_constants_dummy.inc	(working copy)
+@@ -1 +1 @@
+-INCLUDE 'hello_constants.inc'
++INCLUDE 'hello_constants.INc'
+Index: added_directory/hello_constants.inc
+===================================================================
+--- added_directory/hello_constants.inc	(revision 4)
++++ added_directory/hello_constants.inc	(working copy)
+@@ -1 +1,2 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: added_directory/hello_constants.f90
+===================================================================
+--- added_directory/hello_constants.f90	(revision 4)
++++ added_directory/hello_constants.f90	(working copy)
+@@ -1,5 +1,5 @@
+ MODULE Hello_Constants
+ 
+-INCLUDE 'hello_constants_dummy.inc'
++INCLUDE 'hello_constants_dummy.INc'
+ 
+ END MODULE Hello_Constants
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	(revision 4)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: added_directory/hello_constants.f90
+===================================================================
+--- added_directory/hello_constants.f90	(revision 4)
++++ added_directory/hello_constants.f90	(working copy)
+@@ -1,5 +1,5 @@
+ MODULE Hello_Constants
+ 
+-INCLUDE 'hello_constants_dummy.inc'
++INCLUDE 'hello_constants_dummy.INc'
+ 
+ END MODULE Hello_Constants
+Index: added_directory/hello_constants.inc
+===================================================================
+--- added_directory/hello_constants.inc	(revision 4)
++++ added_directory/hello_constants.inc	(working copy)
+@@ -1 +1,2 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: added_directory/hello_constants_dummy.inc
+===================================================================
+--- added_directory/hello_constants_dummy.inc	(revision 4)
++++ added_directory/hello_constants_dummy.inc	(working copy)
+@@ -1 +1 @@
+-INCLUDE 'hello_constants.inc'
++INCLUDE 'hello_constants.INc'
+Index: added_file
+===================================================================
+--- added_file	(revision 4)
++++ added_file	(working copy)
+@@ -1 +1 @@
+-INCLUDE 'hello_constants.inc'
++INCLUDE 'hello_constants.INc'
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	(revision 4)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	(revision 4)
++++ module/hello_constants.f90	(working copy)
+@@ -1,5 +0,0 @@
+-MODULE Hello_Constants
+-
+-INCLUDE 'hello_constants_dummy.inc'
+-
+-END MODULE Hello_Constants
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	(revision 4)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +0,0 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	(revision 4)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +0,0 @@
+-INCLUDE 'hello_constants.inc'
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-diff/test_header b/t/fcm-diff/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/fcm-diff/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/fcm-install-svn-hook/00-basic.t b/t/fcm-install-svn-hook/00-basic.t
new file mode 100755
index 0000000..d3947a2
--- /dev/null
+++ b/t/fcm-install-svn-hook/00-basic.t
@@ -0,0 +1,144 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Basic tests for "fcm-install-svn-hook".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+. $TEST_SOURCE_DIR/test_header_more
+#-------------------------------------------------------------------------------
+if ! which svnadmin 1>/dev/null 2>/dev/null; then
+    skip_all 'svnadmin not available'
+fi
+tests 149
+#-------------------------------------------------------------------------------
+FCM_REAL_HOME=$(readlink -f "$FCM_HOME")
+TODAY=$(date -u +%Y%m%d)
+mkdir -p conf/
+export FCM_CONF_PATH="$PWD/conf"
+cat >conf/admin.cfg <<__CONF__
+svn_group=
+svn_live_dir=$PWD/svn-repos
+svn_project_suffix=
+__CONF__
+cat >hooks-env <<__CONF__
+[default]
+FCM_HOME=$FCM_REAL_HOME
+FCM_SVN_HOOK_ADMIN_EMAIL=$USER
+FCM_SVN_HOOK_COMMIT_DUMP_DIR=/var/svn/dumps
+FCM_SVN_HOOK_TRAC_ROOT_DIR=/srv/trac
+TZ=UTC
+__CONF__
+#-------------------------------------------------------------------------------
+# Live directory does not exist
+TEST_KEY="$TEST_KEY_BASE-no-live-dir"
+run_pass "$TEST_KEY" "$FCM_HOME/sbin/fcm-install-svn-hook"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" /dev/null
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" /dev/null
+#-------------------------------------------------------------------------------
+# Project does not exist
+TEST_KEY="$TEST_KEY_BASE-no-project"
+run_fail "$TEST_KEY" "$FCM_HOME/sbin/fcm-install-svn-hook" foo
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" /dev/null
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<'__ERR__'
+foo: not found
+__ERR__
+#-------------------------------------------------------------------------------
+# Live directory is empty
+TEST_KEY="$TEST_KEY_BASE-empty"
+mkdir svn-repos
+run_pass "$TEST_KEY" "$FCM_HOME/sbin/fcm-install-svn-hook"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" /dev/null
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" /dev/null
+#-------------------------------------------------------------------------------
+run_tests() {
+    # Create repository and add content if necessary
+    rm -fr svn-repos/foo
+    svnadmin create svn-repos/foo
+    if [[ -d svn-import ]]; then
+        svn import -q -m't' svn-import file://$PWD/svn-repos/foo
+    fi
+    # Hooks before
+    local HOOK_TMPLS=$(ls svn-repos/foo/hooks/*)
+    # Install
+    run_pass "$TEST_KEY" "$FCM_HOME/sbin/fcm-install-svn-hook" "$@"
+    # Hooks env
+    file_cmp "$TEST_KEY-hooks-env" svn-repos/foo/conf/hooks-env hooks-env
+    # Make sure all hooks are installed
+    local FILE=
+    for FILE in $(cd "$FCM_HOME/etc/svn-hooks" && ls); do
+        file_cmp "$TEST_KEY-$FILE" \
+            "$FCM_HOME/etc/svn-hooks/$FILE" "svn-repos/foo/hooks/$FILE"
+        file_test "$TEST_KEY-$FILE-chmod" "svn-repos/foo/hooks/$FILE" -x
+        file_test "$TEST_KEY-$FILE.log.$TODAY" \
+            "svn-repos/foo/log/$FILE.log.$TODAY"
+        readlink "svn-repos/foo/log/$FILE.log" >"$TEST_KEY-$FILE.log.link"
+        file_cmp "$TEST_KEY-$FILE.log" \
+            "$TEST_KEY-$FILE.log.link" <<<"$FILE.log.$TODAY"
+    done
+    # Hooks after
+    if [[ "$@" == *--clean* ]]; then
+        run_fail "$TEST_KEY-ls-tmpl" ls $HOOK_TMPLS
+    else
+        run_pass "$TEST_KEY-ls-tmpl" ls $HOOK_TMPLS
+    fi
+    # STDOUT and STDERR
+    date2datefmt "$TEST_KEY.out" >"$TEST_KEY.out.parsed"
+    m4 -DFCM_REAL_HOME=$FCM_REAL_HOME -DPWD=$PWD -DTODAY=$TODAY \
+        "$TEST_SOURCE_DIR/$TEST_KEY_BASE/$NAME.out" >"$TEST_KEY.out.exp"
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out.parsed" "$TEST_KEY.out.exp"
+    file_cmp "$TEST_KEY.err" "$TEST_KEY.err" /dev/null
+    # Run command a second time, should no longer install logs
+    run_pass "$TEST_KEY-2" "$FCM_HOME/sbin/fcm-install-svn-hook" "$@"
+    date2datefmt "$TEST_KEY-2.out" >"$TEST_KEY-2.out.parsed"
+    m4 -DFCM_REAL_HOME=$FCM_REAL_HOME -DPWD=$PWD \
+        "$TEST_SOURCE_DIR/$TEST_KEY_BASE/$NAME-2.out" >"$TEST_KEY-2.out.exp"
+    file_cmp "$TEST_KEY-2.out" "$TEST_KEY-2.out.parsed" "$TEST_KEY-2.out.exp"
+}
+
+# New install, single repository
+TEST_KEY="$TEST_KEY_BASE-new"
+NAME=new run_tests
+TEST_KEY="$TEST_KEY_BASE-new-foo"
+NAME=new run_tests foo
+
+# Clean install, single repository
+TEST_KEY="$TEST_KEY_BASE-clean"
+NAME=clean run_tests --clean
+TEST_KEY="$TEST_KEY_BASE-clean-foo"
+NAME=clean run_tests --clean foo
+
+# New install, single repository, with svnperms.conf
+TEST_KEY="$TEST_KEY_BASE-svnperms.conf"
+mkdir -p 'svn-import'
+echo '[foo]' >'svn-import/svnperms.conf'
+NAME='svnperms-conf' run_tests
+file_cmp "$TEST_KEY-ls-svnperms.conf" \
+    'svn-repos/foo/hooks/svnperms.conf' 'svn-import/svnperms.conf'
+
+# New install, single repository, with commit.conf
+TEST_KEY="$TEST_KEY_BASE-commit.conf"
+{
+    echo 'no-notify-branch-owner'
+    echo 'no-verify-branch-owner'
+} >'svn-import/commit.conf'
+NAME='commit-conf' run_tests
+file_cmp "$TEST_KEY-ls-svnperms.conf" \
+    'svn-repos/foo/hooks/commit.conf' 'svn-import/commit.conf'
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/fcm-install-svn-hook/00-basic/clean-2.out b/t/fcm-install-svn-hook/00-basic/clean-2.out
new file mode 120000
index 0000000..f0138ab
--- /dev/null
+++ b/t/fcm-install-svn-hook/00-basic/clean-2.out
@@ -0,0 +1 @@
+new-2.out
\ No newline at end of file
diff --git a/t/fcm-install-svn-hook/00-basic/clean.out b/t/fcm-install-svn-hook/00-basic/clean.out
new file mode 100644
index 0000000..6222b8a
--- /dev/null
+++ b/t/fcm-install-svn-hook/00-basic/clean.out
@@ -0,0 +1,17 @@
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/foo/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/foo/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/foo/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/foo/hooks/pre-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/hooks/post-commit.tmpl
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/hooks/post-lock.tmpl
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/hooks/post-revprop-change.tmpl
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/hooks/post-unlock.tmpl
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/hooks/pre-commit.tmpl
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/hooks/pre-lock.tmpl
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/hooks/pre-revprop-change.tmpl
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/hooks/pre-unlock.tmpl
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/hooks/start-commit.tmpl
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-commit.log.TODAY -> PWD/svn-repos/foo/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-commit.log.TODAY -> PWD/svn-repos/foo/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/pre-revprop-change.log
diff --git a/t/fcm-install-svn-hook/00-basic/commit-conf-2.out b/t/fcm-install-svn-hook/00-basic/commit-conf-2.out
new file mode 100644
index 0000000..351fbf7
--- /dev/null
+++ b/t/fcm-install-svn-hook/00-basic/commit-conf-2.out
@@ -0,0 +1,6 @@
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/foo/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/foo/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/foo/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/foo/hooks/pre-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: install PWD/svn-repos/foo/hooks/commit.conf <- ^/commit.conf
+YYYY-mm-ddTHH:MM:SSZ: install PWD/svn-repos/foo/hooks/svnperms.conf <- ^/svnperms.conf
diff --git a/t/fcm-install-svn-hook/00-basic/commit-conf.out b/t/fcm-install-svn-hook/00-basic/commit-conf.out
new file mode 100644
index 0000000..04c55a1
--- /dev/null
+++ b/t/fcm-install-svn-hook/00-basic/commit-conf.out
@@ -0,0 +1,10 @@
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/foo/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/foo/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/foo/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/foo/hooks/pre-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: install PWD/svn-repos/foo/hooks/commit.conf <- ^/commit.conf
+YYYY-mm-ddTHH:MM:SSZ: install PWD/svn-repos/foo/hooks/svnperms.conf <- ^/svnperms.conf
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-commit.log.TODAY -> PWD/svn-repos/foo/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-commit.log.TODAY -> PWD/svn-repos/foo/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/pre-revprop-change.log
diff --git a/t/fcm-install-svn-hook/00-basic/new-2.out b/t/fcm-install-svn-hook/00-basic/new-2.out
new file mode 100644
index 0000000..2f91761
--- /dev/null
+++ b/t/fcm-install-svn-hook/00-basic/new-2.out
@@ -0,0 +1,4 @@
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/foo/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/foo/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/foo/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/foo/hooks/pre-revprop-change
diff --git a/t/fcm-install-svn-hook/00-basic/new.out b/t/fcm-install-svn-hook/00-basic/new.out
new file mode 100644
index 0000000..d631893
--- /dev/null
+++ b/t/fcm-install-svn-hook/00-basic/new.out
@@ -0,0 +1,8 @@
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/foo/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/foo/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/foo/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/foo/hooks/pre-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-commit.log.TODAY -> PWD/svn-repos/foo/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-commit.log.TODAY -> PWD/svn-repos/foo/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/pre-revprop-change.log
diff --git a/t/fcm-install-svn-hook/00-basic/svnperms-conf-2.out b/t/fcm-install-svn-hook/00-basic/svnperms-conf-2.out
new file mode 100644
index 0000000..9671ede
--- /dev/null
+++ b/t/fcm-install-svn-hook/00-basic/svnperms-conf-2.out
@@ -0,0 +1,5 @@
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/foo/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/foo/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/foo/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/foo/hooks/pre-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: install PWD/svn-repos/foo/hooks/svnperms.conf <- ^/svnperms.conf
diff --git a/t/fcm-install-svn-hook/00-basic/svnperms-conf.out b/t/fcm-install-svn-hook/00-basic/svnperms-conf.out
new file mode 100644
index 0000000..4c4d89a
--- /dev/null
+++ b/t/fcm-install-svn-hook/00-basic/svnperms-conf.out
@@ -0,0 +1,9 @@
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/foo/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/foo/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/foo/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_REAL_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/foo/hooks/pre-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: install PWD/svn-repos/foo/hooks/svnperms.conf <- ^/svnperms.conf
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-commit.log.TODAY -> PWD/svn-repos/foo/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-commit.log.TODAY -> PWD/svn-repos/foo/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/pre-revprop-change.log
diff --git a/t/fcm-install-svn-hook/01-housekeep-log.t b/t/fcm-install-svn-hook/01-housekeep-log.t
new file mode 100755
index 0000000..f377876
--- /dev/null
+++ b/t/fcm-install-svn-hook/01-housekeep-log.t
@@ -0,0 +1,192 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests housekeep hook logs functionalities provided by "fcm-install-svn-hook".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+. $TEST_SOURCE_DIR/test_header_more
+#-------------------------------------------------------------------------------
+if ! which svnadmin 1>/dev/null 2>/dev/null; then
+    skip_all 'svnadmin not available'
+fi
+tests 14
+#-------------------------------------------------------------------------------
+FCM_REAL_HOME=$(readlink -f "$FCM_HOME")
+TODAY=$(date -u +%Y%m%d)
+mkdir -p conf/ svn-repos/
+export FCM_CONF_PATH="$PWD/conf"
+cat >conf/admin.cfg <<__CONF__
+svn_group=
+svn_live_dir=$PWD/svn-repos
+svn_project_suffix=
+__CONF__
+#-------------------------------------------------------------------------------
+# Newly created logs
+svnadmin create svn-repos/bar
+svnadmin create svn-repos/foo
+
+# 1st run, create logs
+KEY="0-cmd0"
+TEST_KEY="$TEST_KEY_BASE-$KEY"
+run_pass "$TEST_KEY" "$FCM_HOME/sbin/fcm-install-svn-hook"
+date2datefmt "$TEST_KEY.out" >"$TEST_KEY.out.parsed"
+m4 -DFCM_HOME="$FCM_REAL_HOME" -DPWD="$PWD" -DTODAY="$TODAY" \
+    "$TEST_SOURCE_DIR/$TEST_KEY_BASE/$KEY.out" >"$TEST_KEY.out.exp"
+diff -u "$TEST_KEY.out.parsed" "$TEST_KEY.out.exp" >&2
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out.parsed" "$TEST_KEY.out.exp"
+
+# Add something to logs between runs
+for FILE in svn-repos/{foo,bar}/log/*.log; do
+    echo "$FILE: time passes, and contents were written to me." >"$FILE"
+done
+sha1sum svn-repos/{foo,bar}/log/*.log >logs.shalsum
+#-------------------------------------------------------------------------------
+# 2nd run on same day, should leave logs alone
+KEY="0-cmd1"
+TEST_KEY="$TEST_KEY_BASE-$KEY"
+run_pass "$TEST_KEY" "$FCM_HOME/sbin/fcm-install-svn-hook"
+date2datefmt "$TEST_KEY.out" >"$TEST_KEY.out.parsed"
+m4 -DFCM_HOME="$FCM_REAL_HOME" -DPWD="$PWD" -DTODAY="$TODAY" \
+    "$TEST_SOURCE_DIR/$TEST_KEY_BASE/$KEY.out" >"$TEST_KEY.out.exp"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out.parsed" "$TEST_KEY.out.exp"
+# Logs should not be modified
+run_pass "$TEST_KEY.sha1sum" sha1sum -c logs.shalsum
+#-------------------------------------------------------------------------------
+# Pretend that logs were created 3 days ago
+TEST_KEY="$TEST_KEY_BASE-3"
+DATE_P3D=$(date --date='3 days ago' +%Y%m%d)
+
+# Add something to logs
+# Pretend that they were created 3 days ago
+for FILE in svn-repos/{foo,bar}/log/*.log; do
+    echo "$FILE: time flies in the world of testing." >>"$FILE"
+    NAME=$(readlink "$FILE")
+    mv "$(dirname $FILE)/$NAME" "$FILE.$DATE_P3D"
+    ln -f -s "$(basename $FILE).$DATE_P3D" "$FILE"
+done
+sha1sum svn-repos/{foo,bar}/log/*.log >logs.shalsum
+
+# Run with logs created 3 days ago
+# STDOUT should be identical to "0-cmd1".
+run_pass "$TEST_KEY" "$FCM_HOME/sbin/fcm-install-svn-hook"
+date2datefmt "$TEST_KEY.out" >"$TEST_KEY.out.parsed"
+m4 -DFCM_HOME="$FCM_REAL_HOME" -DPWD="$PWD" -DTODAY="$TODAY" \
+    "$TEST_SOURCE_DIR/$TEST_KEY_BASE/0-cmd1.out" >"$TEST_KEY.out.exp"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out.parsed" "$TEST_KEY.out.exp"
+
+# Logs should not be modified
+run_pass "$TEST_KEY.sha1sum" sha1sum -c logs.shalsum
+#-------------------------------------------------------------------------------
+# Pretend that logs were created 7 days ago
+TEST_KEY="$TEST_KEY_BASE-7"
+DATE_P7D=$(date --date='7 days ago' +%Y%m%d)
+
+# Add something to logs
+# Pretend that they were created 7 days ago
+for FILE in svn-repos/{foo,bar}/log/*.log; do
+    echo "$FILE: time continues to fly in the world of testing." >>"$FILE"
+    NAME=$(readlink "$FILE")
+    mv "$(dirname $FILE)/$NAME" "$FILE.$DATE_P7D"
+    ln -f -s "$(basename $FILE).$DATE_P7D" "$FILE"
+done
+sha1sum svn-repos/{foo,bar}/log/*.log >logs.shalsum
+
+# Run with logs created 7 days ago, should gzip old logs and create new ones
+run_pass "$TEST_KEY" "$FCM_HOME/sbin/fcm-install-svn-hook"
+date2datefmt "$TEST_KEY.out" >"$TEST_KEY.out.parsed"
+m4 -DFCM_HOME="$FCM_REAL_HOME" -DPWD="$PWD" -DTODAY="$TODAY" \
+    -DDATE_P7D="$DATE_P7D" \
+    "$TEST_SOURCE_DIR/$TEST_KEY_BASE/7-cmd.out" >"$TEST_KEY.out.exp"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out.parsed" "$TEST_KEY.out.exp"
+
+# Unzip old logs and check that their contents are unchanged
+mkdir -p old-logs/svn-repos/{foo,bar}/log
+for FILE in svn-repos/{foo,bar}/log/*.log.*.gz; do
+    gunzip -c "$FILE" >old-logs/${FILE%.$DATE_P7D.gz}
+done
+cd old-logs
+run_pass "$TEST_KEY.sha1sum" sha1sum -c ../logs.shalsum
+cd "$OLDPWD"
+#-------------------------------------------------------------------------------
+# Pretend that logs were created between 7 to 28 days ago
+TEST_KEY="$TEST_KEY_BASE-28"
+DATE_P14D=$(date --date='14 days ago' +%Y%m%d)
+DATE_P21D=$(date --date='21 days ago' +%Y%m%d)
+DATE_P28D=$(date --date='28 days ago' +%Y%m%d)
+
+# Create fake logs
+rm -f svn-repos/{foo,bar}/log/*.log*
+for FILE in svn-repos/{foo,bar}/log/{pre,post}-commit.log; do
+    for DATE in $DATE_P14D $DATE_P21D $DATE_P28D; do
+        echo "$FILE $DATE whatever" >"$FILE.$DATE"
+        gzip "$FILE.$DATE"
+    done
+    echo "$FILE $DATE_P7D whatever" >"$FILE.$DATE_P7D"
+    ln -s $(basename "$FILE.$DATE_P7D") "$FILE"
+done
+for FILE in svn-repos/{foo,bar}/log/{pre,post}-revprop-change.log; do
+    echo "$FILE $DATE_P7D whatever" >"$FILE.$DATE_P7D"
+    ln -s $(basename "$FILE.$DATE_P7D") "$FILE"
+done
+
+# Run with logs created 7 to 28 days ago.
+# Should remove oldest and empty ones, gzip old ones and create new ones
+run_pass "$TEST_KEY" "$FCM_HOME/sbin/fcm-install-svn-hook"
+date2datefmt "$TEST_KEY.out" >"$TEST_KEY.out.parsed"
+m4 -DFCM_HOME="$FCM_REAL_HOME" -DPWD="$PWD" -DTODAY="$TODAY" \
+    -DDATE_P7D="$DATE_P7D" -DDATE_P28D="$DATE_P28D" \
+    "$TEST_SOURCE_DIR/$TEST_KEY_BASE/28-cmd.out" >"$TEST_KEY.out.exp"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out.parsed" "$TEST_KEY.out.exp"
+ls svn-repos/{foo,bar}/log/*.log* | sort >"$TEST_KEY.ls.out"
+file_cmp "$TEST_KEY.ls" "$TEST_KEY.ls.out" <<__LIST__
+svn-repos/bar/log/post-commit.log
+svn-repos/bar/log/post-commit.log.$DATE_P21D.gz
+svn-repos/bar/log/post-commit.log.$DATE_P14D.gz
+svn-repos/bar/log/post-commit.log.$DATE_P7D.gz
+svn-repos/bar/log/post-commit.log.$TODAY
+svn-repos/bar/log/post-revprop-change.log
+svn-repos/bar/log/post-revprop-change.log.$DATE_P7D.gz
+svn-repos/bar/log/post-revprop-change.log.$TODAY
+svn-repos/bar/log/pre-commit.log
+svn-repos/bar/log/pre-commit.log.$DATE_P21D.gz
+svn-repos/bar/log/pre-commit.log.$DATE_P14D.gz
+svn-repos/bar/log/pre-commit.log.$DATE_P7D.gz
+svn-repos/bar/log/pre-commit.log.$TODAY
+svn-repos/bar/log/pre-revprop-change.log
+svn-repos/bar/log/pre-revprop-change.log.$DATE_P7D.gz
+svn-repos/bar/log/pre-revprop-change.log.$TODAY
+svn-repos/foo/log/post-commit.log
+svn-repos/foo/log/post-commit.log.$DATE_P21D.gz
+svn-repos/foo/log/post-commit.log.$DATE_P14D.gz
+svn-repos/foo/log/post-commit.log.$DATE_P7D.gz
+svn-repos/foo/log/post-commit.log.$TODAY
+svn-repos/foo/log/post-revprop-change.log
+svn-repos/foo/log/post-revprop-change.log.$DATE_P7D.gz
+svn-repos/foo/log/post-revprop-change.log.$TODAY
+svn-repos/foo/log/pre-commit.log
+svn-repos/foo/log/pre-commit.log.$DATE_P21D.gz
+svn-repos/foo/log/pre-commit.log.$DATE_P14D.gz
+svn-repos/foo/log/pre-commit.log.$DATE_P7D.gz
+svn-repos/foo/log/pre-commit.log.$TODAY
+svn-repos/foo/log/pre-revprop-change.log
+svn-repos/foo/log/pre-revprop-change.log.$DATE_P7D.gz
+svn-repos/foo/log/pre-revprop-change.log.$TODAY
+__LIST__
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/fcm-install-svn-hook/01-housekeep-log/0-cmd0.out b/t/fcm-install-svn-hook/01-housekeep-log/0-cmd0.out
new file mode 100644
index 0000000..3dd1556
--- /dev/null
+++ b/t/fcm-install-svn-hook/01-housekeep-log/0-cmd0.out
@@ -0,0 +1,16 @@
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/bar/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/bar/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/bar/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/bar/hooks/pre-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-commit.log.TODAY -> PWD/svn-repos/bar/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-revprop-change.log.TODAY -> PWD/svn-repos/bar/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-commit.log.TODAY -> PWD/svn-repos/bar/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-revprop-change.log.TODAY -> PWD/svn-repos/bar/log/pre-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/foo/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/foo/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/foo/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/foo/hooks/pre-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-commit.log.TODAY -> PWD/svn-repos/foo/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-commit.log.TODAY -> PWD/svn-repos/foo/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/pre-revprop-change.log
diff --git a/t/fcm-install-svn-hook/01-housekeep-log/0-cmd1.out b/t/fcm-install-svn-hook/01-housekeep-log/0-cmd1.out
new file mode 100644
index 0000000..5979d54
--- /dev/null
+++ b/t/fcm-install-svn-hook/01-housekeep-log/0-cmd1.out
@@ -0,0 +1,8 @@
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/bar/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/bar/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/bar/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/bar/hooks/pre-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/foo/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/foo/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/foo/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/foo/hooks/pre-revprop-change
diff --git a/t/fcm-install-svn-hook/01-housekeep-log/28-cmd.out b/t/fcm-install-svn-hook/01-housekeep-log/28-cmd.out
new file mode 100644
index 0000000..4395d49
--- /dev/null
+++ b/t/fcm-install-svn-hook/01-housekeep-log/28-cmd.out
@@ -0,0 +1,36 @@
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/bar/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/bar/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/bar/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/bar/hooks/pre-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/bar/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-commit.log.TODAY -> PWD/svn-repos/bar/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/bar/log/post-commit.log.DATE_P28D.gz
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/bar/log/post-commit.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/bar/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-revprop-change.log.TODAY -> PWD/svn-repos/bar/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/bar/log/post-revprop-change.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/bar/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-commit.log.TODAY -> PWD/svn-repos/bar/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/bar/log/pre-commit.log.DATE_P28D.gz
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/bar/log/pre-commit.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/bar/log/pre-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-revprop-change.log.TODAY -> PWD/svn-repos/bar/log/pre-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/bar/log/pre-revprop-change.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/foo/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/foo/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/foo/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/foo/hooks/pre-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-commit.log.TODAY -> PWD/svn-repos/foo/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/log/post-commit.log.DATE_P28D.gz
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/foo/log/post-commit.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/foo/log/post-revprop-change.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-commit.log.TODAY -> PWD/svn-repos/foo/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/log/pre-commit.log.DATE_P28D.gz
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/foo/log/pre-commit.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/log/pre-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/pre-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/foo/log/pre-revprop-change.log.DATE_P7D
diff --git a/t/fcm-install-svn-hook/01-housekeep-log/7-cmd.out b/t/fcm-install-svn-hook/01-housekeep-log/7-cmd.out
new file mode 100644
index 0000000..6152c5e
--- /dev/null
+++ b/t/fcm-install-svn-hook/01-housekeep-log/7-cmd.out
@@ -0,0 +1,32 @@
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/bar/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/bar/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/bar/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/bar/hooks/pre-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/bar/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-commit.log.TODAY -> PWD/svn-repos/bar/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/bar/log/post-commit.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/bar/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-revprop-change.log.TODAY -> PWD/svn-repos/bar/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/bar/log/post-revprop-change.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/bar/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-commit.log.TODAY -> PWD/svn-repos/bar/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/bar/log/pre-commit.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/bar/log/pre-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-revprop-change.log.TODAY -> PWD/svn-repos/bar/log/pre-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/bar/log/pre-revprop-change.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-commit to PWD/svn-repos/foo/hooks/post-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/post-revprop-change to PWD/svn-repos/foo/hooks/post-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-commit to PWD/svn-repos/foo/hooks/pre-commit
+YYYY-mm-ddTHH:MM:SSZ: copy FCM_HOME/etc/svn-hooks/pre-revprop-change to PWD/svn-repos/foo/hooks/pre-revprop-change
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-commit.log.TODAY -> PWD/svn-repos/foo/log/post-commit.log
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/foo/log/post-commit.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: post-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/post-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/foo/log/post-revprop-change.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-commit.log.TODAY -> PWD/svn-repos/foo/log/pre-commit.log
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/foo/log/pre-commit.log.DATE_P7D
+YYYY-mm-ddTHH:MM:SSZ: removing PWD/svn-repos/foo/log/pre-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: creating symlink: pre-revprop-change.log.TODAY -> PWD/svn-repos/foo/log/pre-revprop-change.log
+YYYY-mm-ddTHH:MM:SSZ: gzip PWD/svn-repos/foo/log/pre-revprop-change.log.DATE_P7D
diff --git a/t/fcm-install-svn-hook/02-env.t b/t/fcm-install-svn-hook/02-env.t
new file mode 100644
index 0000000..b3f26a4
--- /dev/null
+++ b/t/fcm-install-svn-hook/02-env.t
@@ -0,0 +1,59 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Test "fcm-install-svn-hook", "hooks-env" installation.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+. $TEST_SOURCE_DIR/test_header_more
+#-------------------------------------------------------------------------------
+if ! which svnadmin 1>/dev/null 2>/dev/null; then
+    skip_all 'svnadmin not available'
+fi
+tests 2
+FCM_REAL_HOME=$(readlink -f "$FCM_HOME")
+mkdir conf svn-repos
+export FCM_CONF_PATH="$PWD/conf"
+cat >conf/admin.cfg <<__CONF__
+admin_email=robert.fitzroy at metoffice.gov.uk
+notification_from=notifications at localhost
+svn_dump_dir=$PWD/svn/dumps
+svn_group=
+svn_hook_path_env=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+svn_live_dir=$PWD/svn-repos
+svn_project_suffix=.svn
+trac_live_dir=$PWD/trac
+__CONF__
+svnadmin create svn-repos/foo.svn
+cat >hooks-env <<__CONF__
+[default]
+FCM_HOME=$FCM_REAL_HOME
+FCM_SVN_HOOK_ADMIN_EMAIL=robert.fitzroy at metoffice.gov.uk
+FCM_SVN_HOOK_COMMIT_DUMP_DIR=$PWD/svn/dumps
+FCM_SVN_HOOK_NOTIFICATION_FROM=notifications at localhost
+FCM_SVN_HOOK_REPOS_SUFFIX=.svn
+FCM_SVN_HOOK_TRAC_ROOT_DIR=$PWD/trac
+PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+TZ=UTC
+__CONF__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_pass "$TEST_KEY" "$FCM_HOME/sbin/fcm-install-svn-hook"
+file_cmp "$TEST_KEY.foo.hooks-env" svn-repos/foo.svn/conf/hooks-env hooks-env
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/fcm-install-svn-hook/test_header b/t/fcm-install-svn-hook/test_header
new file mode 120000
index 0000000..90bd5a3
--- /dev/null
+++ b/t/fcm-install-svn-hook/test_header
@@ -0,0 +1 @@
+../lib/bash/test_header
\ No newline at end of file
diff --git a/t/fcm-install-svn-hook/test_header_more b/t/fcm-install-svn-hook/test_header_more
new file mode 100644
index 0000000..2418b64
--- /dev/null
+++ b/t/fcm-install-svn-hook/test_header_more
@@ -0,0 +1,25 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+
+# Convert a string like "2014-05-01T09:30:45Z" to "YYYY-mm-ddTHH:MM:SSZ".
+# This allows output files to compare.
+date2datefmt() {
+    perl -p -e 's/\d+-\d\d-\d\dT\d\d:\d\d:\d\dZ/YYYY-mm-ddTHH:MM:SSZ/' "$@"
+}
diff --git a/t/fcm-keyword-print/00-simple.t b/t/fcm-keyword-print/00-simple.t
new file mode 100755
index 0000000..f22fd83
--- /dev/null
+++ b/t/fcm-keyword-print/00-simple.t
@@ -0,0 +1,67 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm keyword-print".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+svnadmin create plants
+URL="file://$PWD/plants"
+svn mkdir --parents -q -m 'test' $URL/{daisy,ivy,holly}/trunk
+mkdir -p conf
+cat >conf/keyword.cfg <<__CFG__
+location{primary}[daisy]=$URL/daisy
+location{primary}[ivy]=$URL/ivy
+location{primary}[holly]=$URL/holly
+__CFG__
+export FCM_CONF_PATH=$PWD/conf
+#-------------------------------------------------------------------------------
+tests 21
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE" # no argument
+run_pass "$TEST_KEY" fcm kp
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+location{primary}[daisy] = $URL/daisy
+location{primary}[holly] = $URL/holly
+location{primary}[ivy] = $URL/ivy
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+for NS in daisy ivy holly; do
+    TEST_KEY="$TEST_KEY_BASE-$NS" # normal mode
+    run_pass "$TEST_KEY" fcm kp fcm:$NS
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+location{primary}[$NS] = $URL/$NS
+__OUT__
+    file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+
+    TEST_KEY="$TEST_KEY_BASE-v-$NS" # verbose mode
+    run_pass "$TEST_KEY" fcm kp -v fcm:$NS
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+location{primary}[$NS] = $URL/$NS
+location[${NS}-br] = $URL/$NS/branches
+location[${NS}-tg] = $URL/$NS/tags
+location[${NS}-tr] = $URL/$NS/trunk
+location[${NS}_br] = $URL/$NS/branches
+location[${NS}_tg] = $URL/$NS/tags
+location[${NS}_tr] = $URL/$NS/trunk
+__OUT__
+    file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+done
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/fcm-keyword-print/test_header b/t/fcm-keyword-print/test_header
new file mode 120000
index 0000000..90bd5a3
--- /dev/null
+++ b/t/fcm-keyword-print/test_header
@@ -0,0 +1 @@
+../lib/bash/test_header
\ No newline at end of file
diff --git a/t/fcm-loc-layout/00-simple.t b/t/fcm-loc-layout/00-simple.t
new file mode 100644
index 0000000..55e902c
--- /dev/null
+++ b/t/fcm-loc-layout/00-simple.t
@@ -0,0 +1,163 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm status".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 24
+#-------------------------------------------------------------------------------
+setup
+unset TEST_PROJECT
+init_repos
+init_merge_branches merge1 merge2 $REPOS_URL
+export SVN_EDITOR="sed -i 1i\foo"
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm loc-layout, no project, default setup
+TEST_KEY=$TEST_KEY_BASE-no-project-default
+run_pass "$TEST_KEY" fcm loc-layout
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+target: .
+url: $ROOT_URL/trunk at 9
+root: $REPOS_URL
+path: /trunk
+peg_rev: 9
+project: 
+branch: trunk
+branch_category: trunk
+sub_tree: 
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm loc-layout, no project, default setup, cd to subdirectory
+TEST_KEY=$TEST_KEY_BASE-no-project-subtree
+cd module
+run_pass "$TEST_KEY" fcm loc-layout
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+target: .
+url: $ROOT_URL/trunk/module at 9
+root: $REPOS_URL
+path: /trunk/module
+peg_rev: 9
+project: 
+branch: trunk
+branch_category: trunk
+sub_tree: module
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+cd ..
+#-------------------------------------------------------------------------------
+# Tests fcm loc-layout, no project, default setup, target subdirectory
+TEST_KEY=$TEST_KEY_BASE-no-project-target-subtree
+run_pass "$TEST_KEY" fcm loc-layout module
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+target: module
+url: $ROOT_URL/trunk/module at 9
+root: $REPOS_URL
+path: /trunk/module
+peg_rev: 9
+project: 
+branch: trunk
+branch_category: trunk
+sub_tree: module
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm loc-layout, no project, default setup, target subdirectory
+TEST_KEY=$TEST_KEY_BASE-no-project-target-repos
+run_pass "$TEST_KEY" fcm loc-layout $REPOS_URL
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+target: $REPOS_URL
+url: $ROOT_URL at 9
+root: $REPOS_URL
+path: 
+peg_rev: 9
+project: 
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
+setup
+init_repos_layout_roses
+svn checkout -q $ROOT_URL/a/a/0/0/0/trunk $TEST_DIR/wc
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm loc-layout, 'roses' 5-level project
+TEST_KEY=$TEST_KEY_BASE-roses-default
+run_pass "$TEST_KEY" fcm loc-layout
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+target: .
+url: $ROOT_URL/a/a/0/0/0/trunk at 3
+root: $REPOS_URL
+path: /a/a/0/0/0/trunk
+peg_rev: 3
+project: a/a/0/0/0
+branch: trunk
+branch_category: trunk
+sub_tree: 
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm loc-layout, 'roses' 5-level project
+TEST_KEY=$TEST_KEY_BASE-roses-subtree
+cd module
+run_pass "$TEST_KEY" fcm loc-layout
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+target: .
+url: $ROOT_URL/a/a/0/0/0/trunk/module at 3
+root: $REPOS_URL
+path: /a/a/0/0/0/trunk/module
+peg_rev: 3
+project: a/a/0/0/0
+branch: trunk
+branch_category: trunk
+sub_tree: module
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+cd ..
+#-------------------------------------------------------------------------------
+# Tests fcm loc-layout, 'roses' 5-level project
+TEST_KEY=$TEST_KEY_BASE-roses-target-subtree
+run_pass "$TEST_KEY" fcm loc-layout module
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+target: module
+url: $ROOT_URL/a/a/0/0/0/trunk/module at 3
+root: $REPOS_URL
+path: /a/a/0/0/0/trunk/module
+peg_rev: 3
+project: a/a/0/0/0
+branch: trunk
+branch_category: trunk
+sub_tree: module
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm loc-layout, no project, default setup, target subdirectory
+TEST_KEY=$TEST_KEY_BASE-roses-target-repos
+run_pass "$TEST_KEY" fcm loc-layout $REPOS_URL
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+target: $REPOS_URL
+url: $ROOT_URL at 3
+root: $REPOS_URL
+path: 
+peg_rev: 3
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
diff --git a/t/fcm-loc-layout/test_header b/t/fcm-loc-layout/test_header
new file mode 100644
index 0000000..6c38759
--- /dev/null
+++ b/t/fcm-loc-layout/test_header
@@ -0,0 +1,231 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/fcm-make/00-build-basic.t b/t/fcm-make/00-build-basic.t
new file mode 100755
index 0000000..6b0c9ba
--- /dev/null
+++ b/t/fcm-make/00-build-basic.t
@@ -0,0 +1,85 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Basic tests for "fcm make".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 18
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_pass "$TEST_KEY" fcm make
+find .fcm-make build -type f | sed 's/^\(\.fcm-make\/log\).*$/\1/' \
+    | sort >"$TEST_KEY.find"
+file_cmp "$TEST_KEY.find" "$TEST_KEY.find" <<'__OUT__'
+.fcm-make/config-as-parsed.cfg
+.fcm-make/config-on-success.cfg
+.fcm-make/ctx.gz
+.fcm-make/log
+build/bin/hello.exe
+build/include/world.mod
+build/o/hello.o
+build/o/world.o
+__OUT__
+file_test "$TEST_KEY.log" fcm-make.log
+file_test "$TEST_KEY.fcm-make-as-parsed.cfg" fcm-make-as-parsed.cfg
+file_test "$TEST_KEY.fcm-make-on-success.cfg" fcm-make-on-success.cfg
+readlink fcm-make.log >"$TEST_KEY.log.readlink"
+file_cmp "$TEST_KEY.log.readlink" "$TEST_KEY.log.readlink" <<<'.fcm-make/log'
+sed '/^\[info\] \(source->target\|target\|required-target\) /!d' \
+    .fcm-make/log >"$TEST_KEY.log.sed"
+file_cmp "$TEST_KEY.log.sed" "$TEST_KEY.log.sed" <<'__LOG__'
+[info] source->target / -> (archive) lib/ libo.a
+[info] source->target hello.f90 -> (link) bin/ hello.exe
+[info] source->target hello.f90 -> (install) include/ hello.f90
+[info] source->target hello.f90 -> (compile) o/ hello.o
+[info] source->target world.f90 -> (install) include/ world.f90
+[info] source->target world.f90 -> (compile+) include/ world.mod
+[info] source->target world.f90 -> (compile) o/ world.o
+[info] target hello.exe
+[info] target  - hello.o
+[info] target  -  - world.mod
+[info] target  -  -  - world.o
+[info] target  - world.o
+__LOG__
+file_test "$TEST_KEY-as-parsed.cfg" fcm-make-as-parsed.cfg
+readlink fcm-make-as-parsed.cfg >"$TEST_KEY-as-parsed.cfg.out"
+file_cmp "$TEST_KEY-as-parsed.cfg.out" "$TEST_KEY-as-parsed.cfg.out" \
+    <<<'.fcm-make/config-as-parsed.cfg'
+file_test "$TEST_KEY-on-success.cfg" fcm-make-on-success.cfg 
+readlink fcm-make-on-success.cfg >"$TEST_KEY-on-success.cfg.out"
+file_cmp "$TEST_KEY-on-success.cfg.out" "$TEST_KEY-on-success.cfg.out" \
+    <<<'.fcm-make/config-on-success.cfg'
+run_pass "$TEST_KEY.exe" $PWD/build/bin/hello.exe
+file_cmp "$TEST_KEY.exe.out" "$TEST_KEY.exe.out" <<<'Hello Earth'
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr"
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime.old"
+run_pass "$TEST_KEY" fcm make
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime"
+file_cmp "$TEST_KEY.mtime" "$TEST_KEY.mtime.old" "$TEST_KEY.mtime"
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr-fail"
+echo 'build.target=foo' >>fcm-make.cfg
+run_fail "$TEST_KEY" fcm make
+run_fail "$TEST_KEY.config-on-success" test -e .fcm-make/config-on-success
+run_fail "$TEST_KEY.fcm-make-on-success.cfg" test -e fcm-make-on-success.cfg
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/00-build-basic/bin/my-ld b/t/fcm-make/00-build-basic/bin/my-ld
new file mode 100755
index 0000000..aff7d0e
--- /dev/null
+++ b/t/fcm-make/00-build-basic/bin/my-ld
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+echo "$@" >$(basename $0).out
+exec ${FCM_TEST_FC:-gfortran} "$@"
diff --git a/t/fcm-make/00-build-basic/fcm-make.cfg b/t/fcm-make/00-build-basic/fcm-make.cfg
new file mode 100644
index 0000000..81f4f9b
--- /dev/null
+++ b/t/fcm-make/00-build-basic/fcm-make.cfg
@@ -0,0 +1,5 @@
+steps = build
+build.source = $HERE/src
+build.target{task} = link
+#build.prop{keep-lib-o} = true
+#build.prop{ld} = ld
diff --git a/t/fcm-make/00-build-basic/src/hello.f90 b/t/fcm-make/00-build-basic/src/hello.f90
new file mode 100644
index 0000000..ab9aaea
--- /dev/null
+++ b/t/fcm-make/00-build-basic/src/hello.f90
@@ -0,0 +1,4 @@
+program hello
+use world, only: get_world
+WRITE(*, '(A,A)') 'Hello ', trim(get_world())
+end program hello
diff --git a/t/fcm-make/00-build-basic/src/world.f90 b/t/fcm-make/00-build-basic/src/world.f90
new file mode 100644
index 0000000..7c08240
--- /dev/null
+++ b/t/fcm-make/00-build-basic/src/world.f90
@@ -0,0 +1,8 @@
+module world
+character(*), parameter :: world1 = 'Earth'
+contains
+elemental function get_world() result(w)
+character(len=len(world1)) :: w
+w = world1
+end function get_world
+end module world
diff --git a/t/fcm-make/01-build-link-opts b/t/fcm-make/01-build-link-opts
new file mode 120000
index 0000000..8bddc38
--- /dev/null
+++ b/t/fcm-make/01-build-link-opts
@@ -0,0 +1 @@
+00-build-basic
\ No newline at end of file
diff --git a/t/fcm-make/01-build-link-opts.t b/t/fcm-make/01-build-link-opts.t
new file mode 100755
index 0000000..beb7396
--- /dev/null
+++ b/t/fcm-make/01-build-link-opts.t
@@ -0,0 +1,80 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests some linker options for "fcm make".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 11
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+PATH=$PWD/bin:$PATH
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-keep-lib-o-incr"
+fcm make -q
+echo 'build.prop{keep-lib-o} = true' >>fcm-make.cfg
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime.old"
+run_pass "$TEST_KEY" fcm make
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime"
+if cmp -s "$TEST_KEY.mtime.old" "$TEST_KEY.mtime"; then
+    fail "$TEST_KEY.mtime"
+else
+    pass "$TEST_KEY.mtime"
+fi
+file_grep "$TEST_KEY.mtime.grep" 'lib/libhello[.]a' "$TEST_KEY.mtime"
+sed -i '/hello[.]exe/d' "$TEST_KEY.mtime.old"
+sed -i '/libhello[.]a/d; /hello[.]exe/d' "$TEST_KEY.mtime"
+file_cmp "$TEST_KEY.mtime.old" "$TEST_KEY.mtime.old" "$TEST_KEY.mtime"
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-keep-lib-o-new"
+# echo 'build.prop{keep-lib-o} = true' >>fcm-make.cfg # already done above
+run_pass "$TEST_KEY" fcm make --new
+find build -type f | sort >"$TEST_KEY.find"
+file_cmp "$TEST_KEY.find" "$TEST_KEY.find" <<'__OUT__'
+build/bin/hello.exe
+build/include/world.mod
+build/lib/libhello.a
+build/o/hello.o
+build/o/world.o
+__OUT__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-ld-incr"
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/fcm-make.cfg .
+fcm make -q --new
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime.old"
+echo 'build.prop{ld} = my-ld' >>fcm-make.cfg
+run_pass "$TEST_KEY" fcm make
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime"
+file_grep "$TEST_KEY.mtime.grep" 'build/my-ld[.]out' "$TEST_KEY.mtime"
+sed -i '/hello[.]exe/d' "$TEST_KEY.mtime.old"
+sed -i '/hello[.]exe/d; /my-ld[.]out/d' "$TEST_KEY.mtime"
+file_cmp "$TEST_KEY.mtime.old" "$TEST_KEY.mtime.old" "$TEST_KEY.mtime"
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-ld-new"
+# echo 'build.prop{ld} = my-ld' >>fcm-make.cfg # already done above
+run_pass "$TEST_KEY" fcm make --new
+find build -type f | sort >"$TEST_KEY.find"
+file_cmp "$TEST_KEY.find" "$TEST_KEY.find" <<'__OUT__'
+build/bin/hello.exe
+build/include/world.mod
+build/my-ld.out
+build/o/hello.o
+build/o/world.o
+__OUT__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/02-build-ext-iface.t b/t/fcm-make/02-build-ext-iface.t
new file mode 100755
index 0000000..4c06975
--- /dev/null
+++ b/t/fcm-make/02-build-ext-iface.t
@@ -0,0 +1,46 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests build ext-iface for "fcm make".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 4
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+#-------------------------------------------------------------------------------
+# Normal operation. Lots of examples in a single source file.
+TEST_KEY="$TEST_KEY_BASE-t1"
+TARGETS=t1.interface run_pass "$TEST_KEY" fcm make --new
+file_cmp "$TEST_KEY.interface" build/include/t1.interface expected/t1.interface
+#-------------------------------------------------------------------------------
+# Bad syntax 1: missing close bracket ) in a local declaration statement.
+# Hang at FCM-2-3-1.
+# We can ignore this problem, as it does not add to the interface
+TEST_KEY="$TEST_KEY_BASE-t2"
+TARGETS=t2.interface run_fail "$TEST_KEY" fcm make --new
+# Time may not be 0.0 on a very very slow computer
+sed -i '2s/ [0-9][0-9]*\.[0-9][0-9]* / ?.? /' "$TEST_KEY.err"
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<__ERR__
+[FAIL] $PWD/src/t2.f90(2): syntax error
+[FAIL] ext-iface  ?.? ! t2.interface         <- t2.f90
+[FAIL] ! t2.interface        : update task failed
+
+__ERR__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/02-build-ext-iface/expected/t1.interface b/t/fcm-make/02-build-ext-iface/expected/t1.interface
new file mode 100644
index 0000000..0ca0006
--- /dev/null
+++ b/t/fcm-make/02-build-ext-iface/expected/t1.interface
@@ -0,0 +1,70 @@
+interface
+logical function func_simple()
+end function func_simple
+logical function func_simple_1()
+end function
+logical function func_simple_2()
+end
+pure logical function func_simple_pure()
+end function func_simple_pure
+recursive pure integer function func_simple_recursive_pure(i)
+integer, intent(in) :: i
+end function func_simple_recursive_pure
+elemental logical function func_simple_elemental()
+end function func_simple_elemental
+integer(selected_int_kind(0)) function func_with_use_and_args(egg, ham)
+use foo
+use bar, only:&
+ & i_am_dim
+integer, intent(in) :: egg(i_am_dim)
+integer, intent(in) :: ham(i_am_dim, 2)
+end function func_with_use_and_args
+character(20) function func_with_parameters(egg, ham)
+character*(*), parameter :: x_param = '01234567890'
+character(*), parameter :: &
+ y_param &
+ = '!&!&!&!&!&!'
+character(len(x_param)), intent(in) :: egg
+character(len(y_param)), intent(in) :: ham
+end function func_with_parameters
+function func_with_parameters_1(egg, ham) result(r)
+integer, parameter :: x_param = 10
+integer z_param
+parameter(z_param = 2)
+real, intent(in), dimension(x_param) :: egg
+integer, intent(in) :: ham
+logical :: r(z_param)
+end function func_with_parameters_1
+character(10) function func_with_contains(mushroom, tomoato)
+character(5) mushroom
+character(5) tomoato
+end function func_with_contains
+Function func_mix_local_and_result(egg, ham, bacon) Result(Breakfast)
+Integer, Intent(in) :: egg, ham
+Real, Intent(in) :: bacon
+Real :: tomato, breakfast
+End Function func_mix_local_and_result
+subroutine sub_simple()
+end subroutine sub_simple
+subroutine sub_simple_1()
+end subroutine
+subroutine sub_simple_2()
+end
+subroutine sub_simple_3()
+end sub&
+&routine&
+& sub_simple_3
+subroutine sub_with_contains(foo)
+character*(len('!"&''&"!')) &
+ foo
+end subroutine sub_with_contains
+subroutine sub_with_renamed_import(i_am_dim)
+integer, parameter :: d = 2
+complex :: i_am_dim(d)
+end subroutine sub_with_renamed_import
+subroutine sub_with_external(proc)
+external proc
+end subroutine sub_with_external
+subroutine sub_with_end()
+end subroutine sub_with_end
+end interface
diff --git a/t/fcm-make/02-build-ext-iface/expected/t2.interface b/t/fcm-make/02-build-ext-iface/expected/t2.interface
new file mode 100644
index 0000000..97a772e
--- /dev/null
+++ b/t/fcm-make/02-build-ext-iface/expected/t2.interface
@@ -0,0 +1,4 @@
+interface
+subroutine t2()
+end subroutine t2
+end interface
diff --git a/t/fcm-make/02-build-ext-iface/fcm-make.cfg b/t/fcm-make/02-build-ext-iface/fcm-make.cfg
new file mode 100644
index 0000000..5e71b6b
--- /dev/null
+++ b/t/fcm-make/02-build-ext-iface/fcm-make.cfg
@@ -0,0 +1,4 @@
+steps=build
+build.source=$HERE/src
+$TARGETS{?}=t1.interface
+build.target=$TARGETS
diff --git a/t/fcm-make/02-build-ext-iface/src/m1.f90 b/t/fcm-make/02-build-ext-iface/src/m1.f90
new file mode 100644
index 0000000..f5c5e63
--- /dev/null
+++ b/t/fcm-make/02-build-ext-iface/src/m1.f90
@@ -0,0 +1,26 @@
+! A module with nonsense
+module bar
+type food
+integer :: cooking_method
+end type food
+type organic
+integer :: growing_method
+end type organic
+integer, parameter :: i_am_dim = 10
+end module bar
+
+! A module with more nonsense
+module foo
+use bar, only: FOOD
+integer :: foo_int
+contains
+subroutine foo_sub(egg)
+integer, parameter :: egg_dim = 10
+type(Food), intent(in) :: egg
+write(*, *) egg
+end subroutine foo_sub
+elemental function foo_func() result(f)
+integer :: f
+f = 0
+end function
+end module foo
diff --git a/t/fcm-make/02-build-ext-iface/src/t1.f90 b/t/fcm-make/02-build-ext-iface/src/t1.f90
new file mode 100644
index 0000000..c7ce2f2
--- /dev/null
+++ b/t/fcm-make/02-build-ext-iface/src/t1.f90
@@ -0,0 +1,163 @@
+! A simple function
+logical function func_simple()
+func_simple = .true.
+end function func_simple
+
+! A simple function, but with less friendly end
+logical function func_simple_1()
+func_simple_1 = .true.
+end function
+
+! A simple function, but with even less friendly end
+logical function func_simple_2()
+func_simple_2 = .true.
+end
+
+! A pure simple function
+pure logical function func_simple_pure()
+func_simple_pure = .true.
+end function func_simple_pure
+
+! A pure recursive function
+recursive pure integer function func_simple_recursive_pure(i)
+integer, intent(in) :: i
+if (i <= 0) then
+    func_simple_recursive_pure = i
+else
+    func_simple_recursive_pure = i + func_simple_recursive_pure(i - 1)
+end if
+end function func_simple_recursive_pure
+
+! An elemental simple function
+elemental logical function func_simple_elemental()
+func_simple_elemental = .true.
+end function func_simple_elemental
+
+! An function with arguments and module imports
+integer(selected_int_kind(0)) function func_with_use_and_args(egg, ham)
+use foo
+! Deliberate trailing spaces in next line
+use bar, only : organic,     i_am_dim   
+implicit none
+integer, intent(in) :: egg(i_am_dim)
+integer, intent(in) :: ham(i_am_dim, 2)
+real bacon
+! Deliberate trailing spaces in next line
+type(   organic   ) :: tomato   
+func_with_use_and_args = egg(1) + ham(1, 1)
+end function func_with_use_and_args
+
+! A function with some parameters
+character(20) function func_with_parameters(egg, ham)
+implicit none
+character*(*), parameter :: x_param = '01234567890'
+character(*), parameter :: & ! throw in some comments
+    y_param                &
+    = '!&!&!&!&!&!'          ! how to make life interesting
+integer, parameter :: z = 20
+character(len(x_param)), intent(in) :: egg
+character(len(y_param)), intent(in) :: ham
+func_with_parameters = egg // ham
+end function func_with_parameters
+
+! A function with some parameters, with a result
+function func_with_parameters_1(egg, ham) result(r)
+implicit none
+integer, parameter :: x_param = 10
+integer z_param
+parameter(z_param = 2)
+real, intent(in), dimension(x_param) :: egg
+integer, intent(in) :: ham
+logical :: r(z_param)
+r(1) = int(egg(1)) + ham > 0
+r(2) = .false.
+end function func_with_parameters_1
+
+! A function with a contains
+character(10) function func_with_contains(mushroom, tomoato)
+character(5) mushroom
+character(5) tomoato
+func_with_contains = func_with_contains_1()
+contains
+character(10) function func_with_contains_1()
+func_with_contains_1 = mushroom // tomoato
+end function func_with_contains_1
+end function func_with_contains
+
+! A function with its result declared after a local in the same statement
+Function func_mix_local_and_result(egg, ham, bacon) Result(Breakfast)
+Integer, Intent(in) :: egg, ham
+Real, Intent(in) :: bacon
+Real :: tomato, breakfast
+Breakfast = real(egg) + real(ham) + bacon
+End Function func_mix_local_and_result
+
+! A simple subroutine
+subroutine sub_simple()
+end subroutine sub_simple
+
+! A simple subroutine, with not so friendly end
+subroutine sub_simple_1()
+end subroutine
+
+! A simple subroutine, with even less friendly end
+subroutine sub_simple_2()
+end
+
+! A simple subroutine, with funny continuation
+subroutine sub_simple_3()
+end sub&
+&routine&
+& sub_simple_3
+
+! A subroutine with a few contains
+subroutine sub_with_contains(foo) ! " &
+! Deliberate trailing spaces in next line
+use Bar, only: i_am_dim    
+character*(len('!"&''&"!')) & ! what a mess!
+    foo
+call sub_with_contains_first()
+call sub_with_contains_second()
+call sub_with_contains_third()
+print*, foo
+contains
+subroutine sub_with_contains_first()
+interface
+integer function x()
+end function x
+end interface
+end subroutine sub_with_contains_first
+subroutine sub_with_contains_second()
+end subroutine
+subroutine sub_with_contains_third()
+end subroutine
+end subroutine sub_with_contains
+
+! A subroutine with a renamed module import
+subroutine sub_with_renamed_import(i_am_dim)
+use bar, only: i_am_not_dim => i_am_dim
+integer, parameter :: d = 2
+complex :: i_am_dim(d)
+print*, i_am_dim
+end subroutine sub_with_renamed_import
+
+! A subroutine with an external argument
+subroutine sub_with_external(proc)
+external proc
+call proc()
+end subroutine sub_with_external
+
+! A subroutine with a variable named "end"
+subroutine sub_with_end()
+integer :: end
+end = 0
+end subroutine sub_with_end
+
+! A module with nonsense
+module stuff
+character(*), parameter :: stuffing = 'yummy'
+contains
+subroutine matter()
+print*, 'Matter is not dark'
+end subroutine matter
+end module stuff
diff --git a/t/fcm-make/02-build-ext-iface/src/t2.f90 b/t/fcm-make/02-build-ext-iface/src/t2.f90
new file mode 100644
index 0000000..66ce982
--- /dev/null
+++ b/t/fcm-make/02-build-ext-iface/src/t2.f90
@@ -0,0 +1,3 @@
+subroutine t2()
+integer :: c( ! bad syntax
+end subroutine t2
diff --git a/t/fcm-make/03-build-include-paths.t b/t/fcm-make/03-build-include-paths.t
new file mode 100755
index 0000000..5d865f9
--- /dev/null
+++ b/t/fcm-make/03-build-include-paths.t
@@ -0,0 +1,69 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests for "fcm make", "build.prop{fc.include-paths}".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+
+function get_compiler_log() {
+    sed '/^\[info\] shell(0.*) gfortran/!d;
+         /hello\.exe/d;
+         s/^\[info\] shell(0.*) //' .fcm-make/log
+}
+#-------------------------------------------------------------------------------
+tests 11
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-control"
+run_fail "$TEST_KEY" fcm make
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+FCM_TEST_FC_INCLUDE_PATHS="$PWD/include/world1 $PWD/include/world2" \
+    run_pass "$TEST_KEY" fcm make
+$PWD/build/bin/hello.exe >"$TEST_KEY.command.out"
+file_cmp "$TEST_KEY.command.out" "$TEST_KEY.command.out" <<<'Hello Earth'
+get_compiler_log >"$TEST_KEY.gfortran.log"
+file_cmp "$TEST_KEY.gfortran.log" "$TEST_KEY.gfortran.log" <<__LOG__
+gfortran -oo/world.o -c -I./include -I$PWD/include/world1 -I$PWD/include/world2 $PWD/src/world.f90
+gfortran -oo/hello.o -c -I./include -I$PWD/include/world1 -I$PWD/include/world2 $PWD/src/hello.f90
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr0"
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime.old"
+FCM_TEST_FC_INCLUDE_PATHS="$PWD/include/world1 $PWD/include/world2" \
+    run_pass "$TEST_KEY" fcm make
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime"
+file_cmp "$TEST_KEY.mtime" "$TEST_KEY.mtime.old" "$TEST_KEY.mtime"
+$PWD/build/bin/hello.exe >"$TEST_KEY.command.out"
+file_cmp "$TEST_KEY.command.out" "$TEST_KEY.command.out" <<<'Hello Earth'
+get_compiler_log >"$TEST_KEY.gfortran.log"
+file_cmp "$TEST_KEY.gfortran.log" "$TEST_KEY.gfortran.log" </dev/null
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr1"
+FCM_TEST_FC_INCLUDE_PATHS="$PWD/include/world2 $PWD/include/world1" \
+    run_pass "$TEST_KEY" fcm make
+$PWD/build/bin/hello.exe >"$TEST_KEY.command.out"
+file_cmp "$TEST_KEY.command.out" "$TEST_KEY.command.out" <<<'Hello Moon'
+get_compiler_log >"$TEST_KEY.gfortran.log"
+file_cmp "$TEST_KEY.gfortran.log" "$TEST_KEY.gfortran.log" <<__LOG__
+gfortran -oo/world.o -c -I./include -I$PWD/include/world2 -I$PWD/include/world1 $PWD/src/world.f90
+gfortran -oo/hello.o -c -I./include -I$PWD/include/world2 -I$PWD/include/world1 $PWD/src/hello.f90
+__LOG__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/03-build-include-paths/fcm-make.cfg b/t/fcm-make/03-build-include-paths/fcm-make.cfg
new file mode 100644
index 0000000..6ae690f
--- /dev/null
+++ b/t/fcm-make/03-build-include-paths/fcm-make.cfg
@@ -0,0 +1,6 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=link
+build.prop{no-dep.include}=worldx.f90
+$FCM_TEST_FC_INCLUDE_PATHS{?}=
+build.prop{fc.include-paths}=$FCM_TEST_FC_INCLUDE_PATHS
diff --git a/t/fcm-make/03-build-include-paths/include/world1/worldx.f90 b/t/fcm-make/03-build-include-paths/include/world1/worldx.f90
new file mode 100644
index 0000000..9daa8f8
--- /dev/null
+++ b/t/fcm-make/03-build-include-paths/include/world1/worldx.f90
@@ -0,0 +1 @@
+character(*), parameter :: world1 = 'Earth'
diff --git a/t/fcm-make/03-build-include-paths/include/world2/worldx.f90 b/t/fcm-make/03-build-include-paths/include/world2/worldx.f90
new file mode 100644
index 0000000..9c54a71
--- /dev/null
+++ b/t/fcm-make/03-build-include-paths/include/world2/worldx.f90
@@ -0,0 +1 @@
+character(*), parameter :: world1 = 'Moon'
diff --git a/t/fcm-make/03-build-include-paths/src/hello.f90 b/t/fcm-make/03-build-include-paths/src/hello.f90
new file mode 100644
index 0000000..ab9aaea
--- /dev/null
+++ b/t/fcm-make/03-build-include-paths/src/hello.f90
@@ -0,0 +1,4 @@
+program hello
+use world, only: get_world
+WRITE(*, '(A,A)') 'Hello ', trim(get_world())
+end program hello
diff --git a/t/fcm-make/03-build-include-paths/src/world.f90 b/t/fcm-make/03-build-include-paths/src/world.f90
new file mode 100644
index 0000000..8ba26cb
--- /dev/null
+++ b/t/fcm-make/03-build-include-paths/src/world.f90
@@ -0,0 +1,8 @@
+module world
+include 'worldx.f90'
+contains
+elemental function get_world() result(w)
+character(len=len(world1)) :: w
+w = world1
+end function get_world
+end module world
diff --git a/t/fcm-make/04-build-libs.t b/t/fcm-make/04-build-libs.t
new file mode 100755
index 0000000..fce71cb
--- /dev/null
+++ b/t/fcm-make/04-build-libs.t
@@ -0,0 +1,71 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests for "fcm make", "build.prop{fc.lib-paths}" and "build.prop{fc.libs}".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+
+function get_linker_log() {
+    sed '/^\[info\] shell(0.*) gfortran/!d;
+         s/^\[info\] shell(0.*) //' .fcm-make/log
+}
+#-------------------------------------------------------------------------------
+tests 11
+set -e
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+gfortran -c src-lib/*
+mkdir -p greet/lib
+ar rs greet/lib/libgreet.a greet.o 2>/dev/null
+ar rs greet/lib/libearth.a earth.o 2>/dev/null
+ar rs greet/lib/libmoon.a moon.o 2>/dev/null
+rm *.o
+set +e
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-control"
+run_fail "$TEST_KEY" fcm make
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+FCM_TEST_FC_LIBS='greet earth' run_pass "$TEST_KEY" fcm make
+$PWD/build/bin/hello.exe >"$TEST_KEY.command.out"
+file_cmp "$TEST_KEY.command.out" "$TEST_KEY.command.out" <<<'Hello Earth'
+get_linker_log >"$TEST_KEY.gfortran.log"
+file_cmp "$TEST_KEY.gfortran.log" "$TEST_KEY.gfortran.log" <<__LOG__
+gfortran -obin/hello.exe o/hello.o -L$PWD/greet/lib -lgreet -learth
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr0"
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime.old"
+FCM_TEST_FC_LIBS='greet earth' run_pass "$TEST_KEY" fcm make
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime"
+file_cmp "$TEST_KEY.mtime" "$TEST_KEY.mtime.old" "$TEST_KEY.mtime"
+$PWD/build/bin/hello.exe >"$TEST_KEY.command.out"
+file_cmp "$TEST_KEY.command.out" "$TEST_KEY.command.out" <<<'Hello Earth'
+get_linker_log >"$TEST_KEY.gfortran.log"
+file_cmp "$TEST_KEY.gfortran.log" "$TEST_KEY.gfortran.log" </dev/null
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr1"
+FCM_TEST_FC_LIBS='greet moon' run_pass "$TEST_KEY" fcm make
+$PWD/build/bin/hello.exe >"$TEST_KEY.command.out"
+file_cmp "$TEST_KEY.command.out" "$TEST_KEY.command.out" <<<'Hello Moon'
+get_linker_log >"$TEST_KEY.gfortran.log"
+file_cmp "$TEST_KEY.gfortran.log" "$TEST_KEY.gfortran.log" <<__LOG__
+gfortran -obin/hello.exe o/hello.o -L$PWD/greet/lib -lgreet -lmoon
+__LOG__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/04-build-libs/fcm-make.cfg b/t/fcm-make/04-build-libs/fcm-make.cfg
new file mode 100644
index 0000000..c41a586
--- /dev/null
+++ b/t/fcm-make/04-build-libs/fcm-make.cfg
@@ -0,0 +1,6 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=link
+$FCM_TEST_FC_LIBS{?}=
+build.prop{fc.lib-paths}=$HERE/greet/lib
+build.prop{fc.libs}=$FCM_TEST_FC_LIBS
diff --git a/t/fcm-make/04-build-libs/src-lib/earth.f90 b/t/fcm-make/04-build-libs/src-lib/earth.f90
new file mode 100644
index 0000000..c0c050d
--- /dev/null
+++ b/t/fcm-make/04-build-libs/src-lib/earth.f90
@@ -0,0 +1,4 @@
+subroutine world(w)
+character(*), intent(out) :: w
+w = 'Earth'
+end subroutine world
diff --git a/t/fcm-make/04-build-libs/src-lib/greet.f90 b/t/fcm-make/04-build-libs/src-lib/greet.f90
new file mode 100644
index 0000000..1a52957
--- /dev/null
+++ b/t/fcm-make/04-build-libs/src-lib/greet.f90
@@ -0,0 +1,4 @@
+subroutine greet(hello, world)
+character(*), intent(in) :: hello, world
+write(*, '(A,1X,A)') trim(hello), trim(world)
+end subroutine greet
diff --git a/t/fcm-make/04-build-libs/src-lib/moon.f90 b/t/fcm-make/04-build-libs/src-lib/moon.f90
new file mode 100644
index 0000000..e5bacb9
--- /dev/null
+++ b/t/fcm-make/04-build-libs/src-lib/moon.f90
@@ -0,0 +1,4 @@
+subroutine world(w)
+character(*), intent(out) :: w
+w = 'Moon'
+end subroutine world
diff --git a/t/fcm-make/04-build-libs/src/hello.f90 b/t/fcm-make/04-build-libs/src/hello.f90
new file mode 100644
index 0000000..f6d31c7
--- /dev/null
+++ b/t/fcm-make/04-build-libs/src/hello.f90
@@ -0,0 +1,13 @@
+program hello
+character(5) :: w
+interface
+subroutine greet(hi, world)
+character(*), intent(in) :: hi, world
+end subroutine greet
+subroutine world(w)
+character(*), intent(out) :: w
+end subroutine world
+end interface
+call world(w)
+call greet('Hello', w)
+end program hello
diff --git a/t/fcm-make/05-build-c-cxx-basic.t b/t/fcm-make/05-build-c-cxx-basic.t
new file mode 100755
index 0000000..c3ec951
--- /dev/null
+++ b/t/fcm-make/05-build-c-cxx-basic.t
@@ -0,0 +1,84 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Basic tests for "fcm make" C and C++ source.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+
+function get_compiler_log() {
+    sed '/^\[info\] shell(0.*) gcc\|g++/!d;
+         s/^\[info\] shell(0.*) //' .fcm-make/log
+}
+#-------------------------------------------------------------------------------
+tests 14
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_pass "$TEST_KEY" fcm make
+find .fcm-make build -type f | sed 's/^\(\.fcm-make\/log\).*$/\1/' \
+    | sort >"$TEST_KEY.find"
+file_cmp "$TEST_KEY.find" "$TEST_KEY.find" <<'__OUT__'
+.fcm-make/config-as-parsed.cfg
+.fcm-make/config-on-success.cfg
+.fcm-make/ctx.gz
+.fcm-make/log
+build/bin/chello
+build/bin/cxxhello
+build/o/chello.o
+build/o/cxxhello.o
+__OUT__
+run_pass "$TEST_KEY.chello" $PWD/build/bin/chello
+file_cmp "$TEST_KEY.chello.out" "$TEST_KEY.chello.out" <<<'Hello C'
+run_pass "$TEST_KEY.cxxhello" $PWD/build/bin/cxxhello
+file_cmp "$TEST_KEY.cxxhello.out" "$TEST_KEY.cxxhello.out" <<<'Hello C++'
+get_compiler_log >"$TEST_KEY.compiler.log"
+file_cmp "$TEST_KEY.compiler.log" "$TEST_KEY.compiler.log" <<__LOG__
+gcc -oo/chello.o -c -I./include $PWD/src/chello.c
+gcc -obin/chello o/chello.o
+g++ -oo/cxxhello.o -c -I./include $PWD/src/cxxhello.cxx
+g++ -obin/cxxhello o/cxxhello.o
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr-0"
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime.old"
+run_pass "$TEST_KEY" fcm make
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime"
+file_cmp "$TEST_KEY.mtime" "$TEST_KEY.mtime.old" "$TEST_KEY.mtime"
+get_compiler_log >"$TEST_KEY.compiler.log"
+file_cmp "$TEST_KEY.compiler.log" "$TEST_KEY.compiler.log" </dev/null
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr-1"
+export CCFLAGS=-O2
+run_pass "$TEST_KEY" fcm make
+get_compiler_log >"$TEST_KEY.compiler.log"
+file_cmp "$TEST_KEY.compiler.log" "$TEST_KEY.compiler.log" <<__LOG__
+gcc -oo/chello.o -c -I./include -O2 $PWD/src/chello.c
+gcc -obin/chello o/chello.o
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr-2"
+export CXXFLAGS=-O3
+run_pass "$TEST_KEY" fcm make
+get_compiler_log >"$TEST_KEY.compiler.log"
+file_cmp "$TEST_KEY.compiler.log" "$TEST_KEY.compiler.log" <<__LOG__
+g++ -oo/cxxhello.o -c -I./include -O3 $PWD/src/cxxhello.cxx
+g++ -obin/cxxhello o/cxxhello.o
+__LOG__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/05-build-c-cxx-basic/fcm-make.cfg b/t/fcm-make/05-build-c-cxx-basic/fcm-make.cfg
new file mode 100644
index 0000000..88e5d80
--- /dev/null
+++ b/t/fcm-make/05-build-c-cxx-basic/fcm-make.cfg
@@ -0,0 +1,8 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=link
+build.prop{file-ext.bin}=
+$CCFLAGS{?}=
+build.prop{cc.flags}=$CCFLAGS
+$CXXFLAGS{?}=
+build.prop{cxx.flags}=$CXXFLAGS
diff --git a/t/fcm-make/05-build-c-cxx-basic/src/chello.c b/t/fcm-make/05-build-c-cxx-basic/src/chello.c
new file mode 100644
index 0000000..4809c80
--- /dev/null
+++ b/t/fcm-make/05-build-c-cxx-basic/src/chello.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main() {
+    printf("Hello C\n");
+    return 0;
+}
diff --git a/t/fcm-make/05-build-c-cxx-basic/src/cxxhello.cxx b/t/fcm-make/05-build-c-cxx-basic/src/cxxhello.cxx
new file mode 100644
index 0000000..04aa598
--- /dev/null
+++ b/t/fcm-make/05-build-c-cxx-basic/src/cxxhello.cxx
@@ -0,0 +1,6 @@
+#include <iostream>
+
+int main() {
+    std::cout << "Hello C++\n";
+    return 0;
+}
diff --git a/t/fcm-make/06-extract-ssh.t b/t/fcm-make/06-extract-ssh.t
new file mode 100755
index 0000000..994aef1
--- /dev/null
+++ b/t/fcm-make/06-extract-ssh.t
@@ -0,0 +1,108 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests for "fcm make", "extract" from SSH location.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+N_TESTS=6
+tests $N_TESTS
+#-------------------------------------------------------------------------------
+# Get a remote host for testing
+T_HOST=
+for FILE in $HOME/.metomi/fcm/t.cfg $FCM_HOME/etc/fcm/t.cfg; do
+    if [[ ! -f $FILE || ! -r $FILE ]]; then
+        continue
+    fi
+    T_HOST=$(fcm cfg $FILE | sed '/^ *host *=/!d; s/^ *host *= *//' | tail -1)
+    if [[ -n $T_HOST ]]; then
+        break
+    fi
+done
+if [[ -z $T_HOST ]]; then
+    skip $N_TESTS 'fcm/t.cfg: "host" not defined'
+    exit 0
+fi
+#-------------------------------------------------------------------------------
+# Create a source tree on the remote host
+mkdir -p hello/{greet,hello,hi,.secret}
+for NAME in mercury venus earth mars; do
+    echo "Greet $NAME" >hello/greet/greet_${NAME}.txt
+    echo "Hello $NAME" >hello/hello/hello_${NAME}.txt
+    echo "[Alien-speak] $NAME" >hello/.secret/hello_${NAME}.txt
+    echo "Hi $NAME" >hello/hi/hi_${NAME}.txt
+done
+T_HOST_WORK_DIR=$(ssh -oBatchMode=yes $T_HOST mktemp -d)
+rsync -a hello $T_HOST:$T_HOST_WORK_DIR
+rm -r hello
+#-------------------------------------------------------------------------------
+# Create a fcm-make.cfg
+cat >fcm-make.cfg <<__FCM_MAKE_CFG__
+steps=extract
+extract.ns=hello
+extract.location[hello]=$T_HOST:$T_HOST_WORK_DIR/hello
+__FCM_MAKE_CFG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_pass "$TEST_KEY" fcm make
+grep -e '\[info\] location hello: 0' -e '\[info\] AU hello:0' fcm-make.log \
+    >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+[info] location hello: 0: $T_HOST:$T_HOST_WORK_DIR/hello
+[info] AU hello:0      hi/hi_mars.txt
+[info] AU hello:0      greet/greet_venus.txt
+[info] AU hello:0      hello/hello_mercury.txt
+[info] AU hello:0      hello/hello_venus.txt
+[info] AU hello:0      greet/greet_mars.txt
+[info] AU hello:0      hi/hi_mercury.txt
+[info] AU hello:0      greet/greet_earth.txt
+[info] AU hello:0      greet/greet_mercury.txt
+[info] AU hello:0      hi/hi_earth.txt
+[info] AU hello:0      hello/hello_earth.txt
+[info] AU hello:0      hello/hello_mars.txt
+[info] AU hello:0      hi/hi_venus.txt
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr0"
+run_pass "$TEST_KEY" fcm make
+grep -e '\[info\]   dest:' -e '\[info\] source:' fcm-make.log \
+    >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+[info]   dest:   12 [U unchanged]
+[info] source:   12 [U from base]
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr1"
+echo 'Hello Martians' \
+    | ssh -oBatchMode=yes $T_HOST "cat >$T_HOST_WORK_DIR/hello/hello/hello_mars.txt"
+run_pass "$TEST_KEY" fcm make
+grep \
+    -e '\[info\]   dest:' \
+    -e '\[info\] source:' \
+    -e '\[info\] MU hello:0' \
+    fcm-make.log >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+[info] MU hello:0      hello/hello_mars.txt
+[info]   dest:    1 [M modified]
+[info]   dest:   11 [U unchanged]
+[info] source:   12 [U from base]
+__LOG__
+#-------------------------------------------------------------------------------
+ssh -oBatchMode=yes $T_HOST rm -r $T_HOST_WORK_DIR
+exit 0
diff --git a/t/fcm-make/07-build-ns-dep.t b/t/fcm-make/07-build-ns-dep.t
new file mode 100755
index 0000000..b938aa8
--- /dev/null
+++ b/t/fcm-make/07-build-ns-dep.t
@@ -0,0 +1,68 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests for "fcm make", "build.prop{ns-dep.o}".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 6
+set -e
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+set +e
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-bad-1"
+run_fail "$TEST_KEY" fcm make
+tail -2 .fcm-make/log >"$TEST_KEY.log" 2>/dev/null
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<'__LOG__'
+[FAIL] foo: bad or missing dependency (type=ns-dep.o)
+[FAIL]     required by: hello.exe
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-bad-2"
+mkdir src/foo # An empty directory
+run_fail "$TEST_KEY" fcm make
+tail -2 .fcm-make/log >"$TEST_KEY.log" 2>/dev/null
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<'__LOG__'
+[FAIL] foo: bad or missing dependency (type=ns-dep.o)
+[FAIL]     required by: hello.exe
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+FCM_TEST_NS_DEP_O=lib run_pass "$TEST_KEY" fcm make
+sed '/^\[info\] \(source->target\|target\) /!d' .fcm-make/log >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<'__LOG__'
+[info] source->target / -> (archive) lib/ libo.a
+[info] source->target lib -> (archive) lib/ lib/libo.a
+[info] source->target lib/earth.f90 -> (install) include/ earth.f90
+[info] source->target lib/earth.f90 -> (ext-iface) include/ earth.interface
+[info] source->target lib/earth.f90 -> (compile) o/ world.o
+[info] source->target lib/greet.f90 -> (install) include/ greet.f90
+[info] source->target lib/greet.f90 -> (ext-iface) include/ greet.interface
+[info] source->target lib/greet.f90 -> (compile) o/ greet.o
+[info] source->target main -> (archive) lib/ main/libo.a
+[info] source->target main/hello.f90 -> (link) bin/ hello.exe
+[info] source->target main/hello.f90 -> (install) include/ hello.f90
+[info] source->target main/hello.f90 -> (compile) o/ hello.o
+[info] target hello.exe
+[info] target  - hello.o
+[info] target  - world.o
+[info] target  - greet.o
+__LOG__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/07-build-ns-dep/fcm-make.cfg b/t/fcm-make/07-build-ns-dep/fcm-make.cfg
new file mode 100644
index 0000000..f631302
--- /dev/null
+++ b/t/fcm-make/07-build-ns-dep/fcm-make.cfg
@@ -0,0 +1,5 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=link
+$FCM_TEST_NS_DEP_O{?}=foo
+build.prop{ns-dep.o}=$FCM_TEST_NS_DEP_O
diff --git a/t/fcm-make/07-build-ns-dep/src/lib/earth.f90 b/t/fcm-make/07-build-ns-dep/src/lib/earth.f90
new file mode 100644
index 0000000..c0c050d
--- /dev/null
+++ b/t/fcm-make/07-build-ns-dep/src/lib/earth.f90
@@ -0,0 +1,4 @@
+subroutine world(w)
+character(*), intent(out) :: w
+w = 'Earth'
+end subroutine world
diff --git a/t/fcm-make/07-build-ns-dep/src/lib/greet.f90 b/t/fcm-make/07-build-ns-dep/src/lib/greet.f90
new file mode 100644
index 0000000..1a52957
--- /dev/null
+++ b/t/fcm-make/07-build-ns-dep/src/lib/greet.f90
@@ -0,0 +1,4 @@
+subroutine greet(hello, world)
+character(*), intent(in) :: hello, world
+write(*, '(A,1X,A)') trim(hello), trim(world)
+end subroutine greet
diff --git a/t/fcm-make/07-build-ns-dep/src/main/hello.f90 b/t/fcm-make/07-build-ns-dep/src/main/hello.f90
new file mode 100644
index 0000000..c11c8c6
--- /dev/null
+++ b/t/fcm-make/07-build-ns-dep/src/main/hello.f90
@@ -0,0 +1,13 @@
+program hello
+character(5) :: w
+interface
+subroutine greet(hello, world)
+character(*), intent(in) :: hello, world
+end subroutine greet
+subroutine world(w)
+character(*), intent(out) :: w
+end subroutine world
+end interface
+call world(w)
+call greet('Hello', w)
+end program hello
diff --git a/t/fcm-make/08-build-dup-dep.t b/t/fcm-make/08-build-dup-dep.t
new file mode 100755
index 0000000..8c51581
--- /dev/null
+++ b/t/fcm-make/08-build-dup-dep.t
@@ -0,0 +1,60 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests for "fcm make", "build.prop{ns-dep.o}".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 4
+set -e
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+set +e
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-bad"
+run_fail "$TEST_KEY" fcm make
+tail -2 .fcm-make/log >"$TEST_KEY.log" 2>/dev/null
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<'__LOG__'
+[FAIL] world.o: same target from [lib/earth.f90, lib/moon.f90]
+[FAIL]     required by: hello.exe
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+rm src/lib/moon.f90
+run_pass "$TEST_KEY" fcm make
+sed '/^\[info\] \(source->target\|target\) /!d' .fcm-make/log >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<'__LOG__'
+[info] source->target / -> (archive) lib/ libo.a
+[info] source->target lib -> (archive) lib/ lib/libo.a
+[info] source->target lib/earth.f90 -> (install) include/ earth.f90
+[info] source->target lib/earth.f90 -> (ext-iface) include/ earth.interface
+[info] source->target lib/earth.f90 -> (compile) o/ world.o
+[info] source->target lib/greet.f90 -> (install) include/ greet.f90
+[info] source->target lib/greet.f90 -> (ext-iface) include/ greet.interface
+[info] source->target lib/greet.f90 -> (compile) o/ greet.o
+[info] source->target main -> (archive) lib/ main/libo.a
+[info] source->target main/hello.f90 -> (link) bin/ hello.exe
+[info] source->target main/hello.f90 -> (install) include/ hello.f90
+[info] source->target main/hello.f90 -> (compile) o/ hello.o
+[info] target hello.exe
+[info] target  - hello.o
+[info] target  - world.o
+[info] target  - greet.o
+__LOG__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/08-build-dup-dep/fcm-make.cfg b/t/fcm-make/08-build-dup-dep/fcm-make.cfg
new file mode 100644
index 0000000..1a9229e
--- /dev/null
+++ b/t/fcm-make/08-build-dup-dep/fcm-make.cfg
@@ -0,0 +1,4 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=link
+build.prop{ns-dep.o}=lib
diff --git a/t/fcm-make/08-build-dup-dep/src/lib/earth.f90 b/t/fcm-make/08-build-dup-dep/src/lib/earth.f90
new file mode 100644
index 0000000..c0c050d
--- /dev/null
+++ b/t/fcm-make/08-build-dup-dep/src/lib/earth.f90
@@ -0,0 +1,4 @@
+subroutine world(w)
+character(*), intent(out) :: w
+w = 'Earth'
+end subroutine world
diff --git a/t/fcm-make/08-build-dup-dep/src/lib/greet.f90 b/t/fcm-make/08-build-dup-dep/src/lib/greet.f90
new file mode 100644
index 0000000..1a52957
--- /dev/null
+++ b/t/fcm-make/08-build-dup-dep/src/lib/greet.f90
@@ -0,0 +1,4 @@
+subroutine greet(hello, world)
+character(*), intent(in) :: hello, world
+write(*, '(A,1X,A)') trim(hello), trim(world)
+end subroutine greet
diff --git a/t/fcm-make/08-build-dup-dep/src/lib/moon.f90 b/t/fcm-make/08-build-dup-dep/src/lib/moon.f90
new file mode 100644
index 0000000..e5bacb9
--- /dev/null
+++ b/t/fcm-make/08-build-dup-dep/src/lib/moon.f90
@@ -0,0 +1,4 @@
+subroutine world(w)
+character(*), intent(out) :: w
+w = 'Moon'
+end subroutine world
diff --git a/t/fcm-make/08-build-dup-dep/src/main/hello.f90 b/t/fcm-make/08-build-dup-dep/src/main/hello.f90
new file mode 100644
index 0000000..c11c8c6
--- /dev/null
+++ b/t/fcm-make/08-build-dup-dep/src/main/hello.f90
@@ -0,0 +1,13 @@
+program hello
+character(5) :: w
+interface
+subroutine greet(hello, world)
+character(*), intent(in) :: hello, world
+end subroutine greet
+subroutine world(w)
+character(*), intent(out) :: w
+end subroutine world
+end interface
+call world(w)
+call greet('Hello', w)
+end program hello
diff --git a/t/fcm-make/09-build-dep-o.t b/t/fcm-make/09-build-dep-o.t
new file mode 100755
index 0000000..39b505d
--- /dev/null
+++ b/t/fcm-make/09-build-dep-o.t
@@ -0,0 +1,64 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests for "fcm make", "build.prop{dep.o}" top namespace.
+# (Cyclic dependency bug in 2013-09.)
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 2
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_pass "$TEST_KEY" fcm make
+sed '/^\[info\] \(source->target\|target\) /!d' .fcm-make/log >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<'__LOG__'
+[info] source->target / -> (archive) lib/ libo.a
+[info] source->target lib -> (archive) lib/ lib/libo.a
+[info] source->target lib/earth.f90 -> (install) include/ earth.f90
+[info] source->target lib/earth.f90 -> (ext-iface) include/ earth.interface
+[info] source->target lib/earth.f90 -> (compile) o/ world.o
+[info] source->target lib/greet.f90 -> (install) include/ greet.f90
+[info] source->target lib/greet.f90 -> (ext-iface) include/ greet.interface
+[info] source->target lib/greet.f90 -> (compile) o/ greet.o
+[info] source->target lib/greet_fmt_mod.f90 -> (install) include/ greet_fmt_mod.f90
+[info] source->target lib/greet_fmt_mod.f90 -> (compile+) include/ greet_fmt_mod.mod
+[info] source->target lib/greet_fmt_mod.f90 -> (compile) o/ greet_fmt_mod.o
+[info] source->target main -> (archive) lib/ main/libo.a
+[info] source->target main/hello.f90 -> (link) bin/ hello.exe
+[info] source->target main/hello.f90 -> (install) include/ hello.f90
+[info] source->target main/hello.f90 -> (compile) o/ hello.o
+[info] source->target main/hi.f90 -> (link) bin/ hi.exe
+[info] source->target main/hi.f90 -> (install) include/ hi.f90
+[info] source->target main/hi.f90 -> (compile) o/ hi.o
+[info] target hi.exe
+[info] target  - hi.o
+[info] target  - world.o
+[info] target  - greet.o
+[info] target  -  - greet_fmt_mod.mod
+[info] target  -  -  - greet_fmt_mod.o
+[info] target  - greet_fmt_mod.o
+[info] target hello.exe
+[info] target  - hello.o
+[info] target  - world.o
+[info] target  - greet.o (n-deps=1)
+[info] target  - greet_fmt_mod.o
+__LOG__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/09-build-dep-o/fcm-make.cfg b/t/fcm-make/09-build-dep-o/fcm-make.cfg
new file mode 100644
index 0000000..63daa09
--- /dev/null
+++ b/t/fcm-make/09-build-dep-o/fcm-make.cfg
@@ -0,0 +1,4 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=link
+build.prop{dep.o}=world.o greet.o
diff --git a/t/fcm-make/09-build-dep-o/src/lib/earth.f90 b/t/fcm-make/09-build-dep-o/src/lib/earth.f90
new file mode 100644
index 0000000..c0c050d
--- /dev/null
+++ b/t/fcm-make/09-build-dep-o/src/lib/earth.f90
@@ -0,0 +1,4 @@
+subroutine world(w)
+character(*), intent(out) :: w
+w = 'Earth'
+end subroutine world
diff --git a/t/fcm-make/09-build-dep-o/src/lib/greet.f90 b/t/fcm-make/09-build-dep-o/src/lib/greet.f90
new file mode 100644
index 0000000..ddeea6f
--- /dev/null
+++ b/t/fcm-make/09-build-dep-o/src/lib/greet.f90
@@ -0,0 +1,5 @@
+subroutine greet(hello, world)
+use greet_fmt_mod, only: greet_fmt
+character(*), intent(in) :: hello, world
+write(*, greet_fmt) trim(hello), trim(world)
+end subroutine greet
diff --git a/t/fcm-make/09-build-dep-o/src/lib/greet_fmt_mod.f90 b/t/fcm-make/09-build-dep-o/src/lib/greet_fmt_mod.f90
new file mode 100644
index 0000000..16f11cc
--- /dev/null
+++ b/t/fcm-make/09-build-dep-o/src/lib/greet_fmt_mod.f90
@@ -0,0 +1,3 @@
+MODULE greet_fmt_mod
+CHARACTER(*), PARAMETER :: greet_fmt='(A,1X,A)'
+END MODULE greet_fmt_mod
diff --git a/t/fcm-make/09-build-dep-o/src/main/hello.f90 b/t/fcm-make/09-build-dep-o/src/main/hello.f90
new file mode 100644
index 0000000..f6d31c7
--- /dev/null
+++ b/t/fcm-make/09-build-dep-o/src/main/hello.f90
@@ -0,0 +1,13 @@
+program hello
+character(5) :: w
+interface
+subroutine greet(hi, world)
+character(*), intent(in) :: hi, world
+end subroutine greet
+subroutine world(w)
+character(*), intent(out) :: w
+end subroutine world
+end interface
+call world(w)
+call greet('Hello', w)
+end program hello
diff --git a/t/fcm-make/09-build-dep-o/src/main/hi.f90 b/t/fcm-make/09-build-dep-o/src/main/hi.f90
new file mode 100644
index 0000000..0e30aa4
--- /dev/null
+++ b/t/fcm-make/09-build-dep-o/src/main/hi.f90
@@ -0,0 +1,13 @@
+program hi
+character(5) :: w
+interface
+subroutine greet(hello, world)
+character(*), intent(in) :: hello, world
+end subroutine greet
+subroutine world(w)
+character(*), intent(out) :: w
+end subroutine world
+end interface
+call world(w)
+call greet('Hello', w)
+end program hi
diff --git a/t/fcm-make/10-log b/t/fcm-make/10-log
new file mode 120000
index 0000000..8bddc38
--- /dev/null
+++ b/t/fcm-make/10-log
@@ -0,0 +1 @@
+00-build-basic
\ No newline at end of file
diff --git a/t/fcm-make/10-log.t b/t/fcm-make/10-log.t
new file mode 100755
index 0000000..cc03bda
--- /dev/null
+++ b/t/fcm-make/10-log.t
@@ -0,0 +1,54 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Basic tests for "fcm make".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 7
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_pass "$TEST_KEY" fcm make
+if [[ -d $FCM_HOME/.git ]]; then
+    VERSION="FCM $(git --git-dir=$FCM_HOME/.git describe)"
+else
+    VERSION=$(sed '/FCM\.VERSION/!d; s/^.*="\(.*\)";$/\1/' \
+        $FCM_HOME/doc/etc/fcm-version.js)
+    VERSION="FCM $VERSION"
+fi
+file_grep "$TEST_KEY.log.version" "\\[info\\] $VERSION" .fcm-make/log
+file_grep "$TEST_KEY.log.mode" '\[info\] mode=new' .fcm-make/log
+if [[ $(ls .fcm-make/log-* | wc -l) == 1 ]]; then
+    pass "$TEST_KEY-n-logs"
+else
+    fail "$TEST_KEY-n-logs"
+fi
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr"
+sleep 1
+run_pass "$TEST_KEY" fcm make
+file_grep "$TEST_KEY.log.mode" '\[info\] mode=incremental' .fcm-make/log
+if [[ $(ls .fcm-make/log-* | wc -l) == 2 ]]; then
+    pass "$TEST_KEY-n-logs"
+else
+    fail "$TEST_KEY-n-logs"
+fi
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/11-preprocess-include-path.t b/t/fcm-make/11-preprocess-include-path.t
new file mode 100755
index 0000000..b3679f0
--- /dev/null
+++ b/t/fcm-make/11-preprocess-include-path.t
@@ -0,0 +1,59 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests for "fcm make", "preprocess.prop{fpp.include-paths}".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+
+function get_cpp_log() {
+    sed '/^\[info\] shell(0.*) cpp/!d; s/^\[info\] shell(0.*) //' .fcm-make/log
+}
+#-------------------------------------------------------------------------------
+tests 11
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-control"
+run_fail "$TEST_KEY" fcm make
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+FCM_TEST_FPP_INCLUDE_PATHS="$PWD/include/world1" run_pass "$TEST_KEY" fcm make
+file_grep "$TEST_KEY.world.F90" "world1 = 'Earth'" $PWD/preprocess/src/world.F90
+get_cpp_log >"$TEST_KEY.cpp.log"
+file_cmp "$TEST_KEY.cpp.log" "$TEST_KEY.cpp.log" <<__LOG__
+cpp -P -traditional -I./include -I$PWD/include/world1 $PWD/src/world.F90
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr0"
+find preprocess -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime.old"
+FCM_TEST_FPP_INCLUDE_PATHS="$PWD/include/world1" run_pass "$TEST_KEY" fcm make
+find preprocess -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime"
+file_cmp "$TEST_KEY.mtime" "$TEST_KEY.mtime.old" "$TEST_KEY.mtime"
+file_grep "$TEST_KEY.world.F90" "world1 = 'Earth'" $PWD/preprocess/src/world.F90
+get_cpp_log >"$TEST_KEY.cpp.log"
+file_cmp "$TEST_KEY.cpp.log" "$TEST_KEY.cpp.log" </dev/null
+##-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr1"
+FCM_TEST_FPP_INCLUDE_PATHS="$PWD/include/world2" run_pass "$TEST_KEY" fcm make
+file_grep "$TEST_KEY.world.F90" "world1 = 'Moon'" $PWD/preprocess/src/world.F90
+get_cpp_log >"$TEST_KEY.cpp.log"
+file_cmp "$TEST_KEY.cpp.log" "$TEST_KEY.cpp.log" <<__LOG__
+cpp -P -traditional -I./include -I$PWD/include/world2 $PWD/src/world.F90
+__LOG__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/11-preprocess-include-path/fcm-make.cfg b/t/fcm-make/11-preprocess-include-path/fcm-make.cfg
new file mode 100644
index 0000000..ed71513
--- /dev/null
+++ b/t/fcm-make/11-preprocess-include-path/fcm-make.cfg
@@ -0,0 +1,4 @@
+steps=preprocess
+preprocess.source=$HERE/src
+$FCM_TEST_FPP_INCLUDE_PATHS{?}=
+preprocess.prop{fpp.include-paths}=$FCM_TEST_FPP_INCLUDE_PATHS
diff --git a/t/fcm-make/11-preprocess-include-path/include/world1/worldx.h b/t/fcm-make/11-preprocess-include-path/include/world1/worldx.h
new file mode 100644
index 0000000..9daa8f8
--- /dev/null
+++ b/t/fcm-make/11-preprocess-include-path/include/world1/worldx.h
@@ -0,0 +1 @@
+character(*), parameter :: world1 = 'Earth'
diff --git a/t/fcm-make/11-preprocess-include-path/include/world2/worldx.h b/t/fcm-make/11-preprocess-include-path/include/world2/worldx.h
new file mode 100644
index 0000000..9c54a71
--- /dev/null
+++ b/t/fcm-make/11-preprocess-include-path/include/world2/worldx.h
@@ -0,0 +1 @@
+character(*), parameter :: world1 = 'Moon'
diff --git a/t/fcm-make/11-preprocess-include-path/src/world.F90 b/t/fcm-make/11-preprocess-include-path/src/world.F90
new file mode 100644
index 0000000..b13078e
--- /dev/null
+++ b/t/fcm-make/11-preprocess-include-path/src/world.F90
@@ -0,0 +1,8 @@
+module world
+#include <worldx.h>
+contains
+elemental function get_world() result(w)
+character(len=len(world1)) :: w
+w = world1
+end function get_world
+end module world
diff --git a/t/fcm-make/12-build-class-prop.t b/t/fcm-make/12-build-class-prop.t
new file mode 100755
index 0000000..0436c69
--- /dev/null
+++ b/t/fcm-make/12-build-class-prop.t
@@ -0,0 +1,68 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests for "fcm make", *.prop{class,*}.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 6
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+PATH=$PWD/bin:$PATH
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_pass "$TEST_KEY" fcm make
+find build* -type f | sort >"$TEST_KEY.find"
+file_cmp "$TEST_KEY.find" "$TEST_KEY.find" <<'__FIND__'
+build/bin/hello.bin
+build/o/hello.o
+build_house/bin/hello_house
+build_house/o/hello_house.o
+build_office/bin/hello_office
+build_office/o/hello_office.o
+build_road/bin/hello_road
+build_road/o/hello_road.o
+__FIND__
+sed '/^\[info\] shell(0.*) \(my-fc\|gfortran\)/!d; s/^\[info\] shell(0.*) //' \
+    .fcm-make/log >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+my-fc -oo/hello.o -c -I./include $PWD/src/hello.f90
+my-fc -obin/hello.bin o/hello.o
+my-fc -oo/hello_house.o -c -I./include $PWD/src/hello_house.f90
+my-fc -obin/hello_house o/hello_house.o
+my-fc -oo/hello_office.o -c -I./include $PWD/src/hello_office.f90
+my-fc -obin/hello_office o/hello_office.o
+gfortran -oo/hello_road.o -c -I./include $PWD/src/hello_road.f90
+gfortran -obin/hello_road o/hello_road.o
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr"
+find build* -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime.old"
+run_pass "$TEST_KEY" fcm make
+find build* -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime"
+file_cmp "$TEST_KEY.mtime" "$TEST_KEY.mtime.old" "$TEST_KEY.mtime"
+sed '/^\[info\] \(compile\|link\)   targets:/!d; s/total-time=.*$//' \
+    .fcm-make/log >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<'__LOG__'
+[info] compile   targets: modified=0, unchanged=1, failed=0, 
+[info] compile   targets: modified=0, unchanged=1, failed=0, 
+[info] compile   targets: modified=0, unchanged=1, failed=0, 
+[info] compile   targets: modified=0, unchanged=1, failed=0, 
+__LOG__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/12-build-class-prop/bin/my-fc b/t/fcm-make/12-build-class-prop/bin/my-fc
new file mode 100755
index 0000000..d3762bc
--- /dev/null
+++ b/t/fcm-make/12-build-class-prop/bin/my-fc
@@ -0,0 +1,2 @@
+#!/bin/bash
+exec gfortran "$@"
diff --git a/t/fcm-make/12-build-class-prop/fcm-make.cfg b/t/fcm-make/12-build-class-prop/fcm-make.cfg
new file mode 100644
index 0000000..ab8fd1b
--- /dev/null
+++ b/t/fcm-make/12-build-class-prop/fcm-make.cfg
@@ -0,0 +1,14 @@
+step.class[build_house build_office build_road]=build
+steps=build build_house build_office build_road
+build.prop{class,fc}=my-fc
+build.prop{class,file-ext.bin}=
+build.source=$HERE/src
+build.target=hello.bin
+build.prop{file-ext.bin}=.bin
+build_house.source=$HERE/src
+build_house.target=hello_house
+build_office.source=$HERE/src
+build_office.target=hello_office
+build_road.source=$HERE/src
+build_road.target=hello_road
+build_road.prop{fc}=gfortran
diff --git a/t/fcm-make/12-build-class-prop/src/hello.f90 b/t/fcm-make/12-build-class-prop/src/hello.f90
new file mode 100644
index 0000000..f66011a
--- /dev/null
+++ b/t/fcm-make/12-build-class-prop/src/hello.f90
@@ -0,0 +1,3 @@
+program hello
+WRITE(*, '(A,1X,A)') 'Hello', 'World'
+end program hello
diff --git a/t/fcm-make/12-build-class-prop/src/hello_house.f90 b/t/fcm-make/12-build-class-prop/src/hello_house.f90
new file mode 100644
index 0000000..41b2365
--- /dev/null
+++ b/t/fcm-make/12-build-class-prop/src/hello_house.f90
@@ -0,0 +1,3 @@
+program hello_house
+WRITE(*, '(A,1X,A)') 'Hello', 'House'
+end program hello_house
diff --git a/t/fcm-make/12-build-class-prop/src/hello_office.f90 b/t/fcm-make/12-build-class-prop/src/hello_office.f90
new file mode 100644
index 0000000..abc33eb
--- /dev/null
+++ b/t/fcm-make/12-build-class-prop/src/hello_office.f90
@@ -0,0 +1,3 @@
+program hello_office
+WRITE(*, '(A,1X,A)') 'Hello', 'Office'
+end program hello_office
diff --git a/t/fcm-make/12-build-class-prop/src/hello_road.f90 b/t/fcm-make/12-build-class-prop/src/hello_road.f90
new file mode 100644
index 0000000..73bbff0
--- /dev/null
+++ b/t/fcm-make/12-build-class-prop/src/hello_road.f90
@@ -0,0 +1,3 @@
+program hello_road
+WRITE(*, '(A,1X,A)') 'Hello', 'Road'
+end program hello_road
diff --git a/t/fcm-make/13-build-target-prop.t b/t/fcm-make/13-build-target-prop.t
new file mode 100755
index 0000000..3af64be
--- /dev/null
+++ b/t/fcm-make/13-build-target-prop.t
@@ -0,0 +1,53 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Test "fcm make", build.prop{*}[target].
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 5
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+PATH=$PWD/bin:$PATH
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_pass "$TEST_KEY" fcm make
+find build -type f | sort >"$TEST_KEY.find"
+file_cmp "$TEST_KEY.find" "$TEST_KEY.find" <<'__OUT__'
+build/bin/hello.exe
+build/include/world.mod
+build/o/hello.o
+build/o/world.o
+__OUT__
+sed '
+    /\[info\] shell(0.*) \(gfortran\|my-fc\)/!d;
+    /hello\.exe/d;
+    s/^\[info\] shell(0.*) //
+' .fcm-make/log >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+my-fc -oo/world.o -c -I./include $PWD/src/world.f90
+gfortran -oo/hello.o -c -I./include $PWD/src/hello.f90
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr"
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime.old"
+run_pass "$TEST_KEY" fcm make
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime"
+file_cmp "$TEST_KEY.mtime" "$TEST_KEY.mtime.old" "$TEST_KEY.mtime"
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/13-build-target-prop/bin/my-fc b/t/fcm-make/13-build-target-prop/bin/my-fc
new file mode 100755
index 0000000..a5afb0e
--- /dev/null
+++ b/t/fcm-make/13-build-target-prop/bin/my-fc
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+exec gfortran "$@"
diff --git a/t/fcm-make/13-build-target-prop/fcm-make.cfg b/t/fcm-make/13-build-target-prop/fcm-make.cfg
new file mode 100644
index 0000000..3c7731c
--- /dev/null
+++ b/t/fcm-make/13-build-target-prop/fcm-make.cfg
@@ -0,0 +1,4 @@
+steps = build
+build.source = $HERE/src
+build.target{task} = link
+build.prop{fc}[world.o] = my-fc
diff --git a/t/fcm-make/13-build-target-prop/src/hello.f90 b/t/fcm-make/13-build-target-prop/src/hello.f90
new file mode 100644
index 0000000..ab9aaea
--- /dev/null
+++ b/t/fcm-make/13-build-target-prop/src/hello.f90
@@ -0,0 +1,4 @@
+program hello
+use world, only: get_world
+WRITE(*, '(A,A)') 'Hello ', trim(get_world())
+end program hello
diff --git a/t/fcm-make/13-build-target-prop/src/world.f90 b/t/fcm-make/13-build-target-prop/src/world.f90
new file mode 100644
index 0000000..7c08240
--- /dev/null
+++ b/t/fcm-make/13-build-target-prop/src/world.f90
@@ -0,0 +1,8 @@
+module world
+character(*), parameter :: world1 = 'Earth'
+contains
+elemental function get_world() result(w)
+character(len=len(world1)) :: w
+w = world1
+end function get_world
+end module world
diff --git a/t/fcm-make/14-build-etc.t b/t/fcm-make/14-build-etc.t
new file mode 100755
index 0000000..a1bfd07
--- /dev/null
+++ b/t/fcm-make/14-build-etc.t
@@ -0,0 +1,60 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Test "fcm make", build etc files, broken at 2013-11 due to:
+# build.prop{class,file-she.script} = #!
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 5
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+PATH=$PWD/bin:$PATH
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_pass "$TEST_KEY" fcm make
+find build -type f | sort >"$TEST_KEY.find"
+file_cmp "$TEST_KEY.find" "$TEST_KEY.find" <<'__OUT__'
+build/bin/foo
+build/etc/.etc
+build/etc/hello.txt
+build/etc/hi/.etc
+build/etc/hi/hi-earth.txt
+build/etc/hi/hi-mars.txt
+__OUT__
+sed '
+    /\[info\] install/!d;
+    /\[info\] install  *targets:/d;
+    s/^\[info\] install *[^ ]* M //
+' .fcm-make/log >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<'__LOG__'
+foo                  <- foo
+hello.txt            <- hello.txt
+hi/hi-earth.txt      <- hi/hi-earth.txt
+hi/hi-mars.txt       <- hi/hi-mars.txt
+.etc                 <- 
+hi/.etc              <- hi
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-incr"
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime.old"
+run_pass "$TEST_KEY" fcm make
+find build -type f -exec stat -c'%Y %n' {} \; | sort >"$TEST_KEY.mtime"
+file_cmp "$TEST_KEY.mtime" "$TEST_KEY.mtime.old" "$TEST_KEY.mtime"
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/14-build-etc/fcm-make.cfg b/t/fcm-make/14-build-etc/fcm-make.cfg
new file mode 100644
index 0000000..5b5828a
--- /dev/null
+++ b/t/fcm-make/14-build-etc/fcm-make.cfg
@@ -0,0 +1,4 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=install
+build.prop{class,file-she.script} = #!
diff --git a/t/fcm-make/14-build-etc/src/foo b/t/fcm-make/14-build-etc/src/foo
new file mode 100755
index 0000000..dd58ffb
--- /dev/null
+++ b/t/fcm-make/14-build-etc/src/foo
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+echo FOO
diff --git a/t/fcm-make/14-build-etc/src/hello.txt b/t/fcm-make/14-build-etc/src/hello.txt
new file mode 100644
index 0000000..557db03
--- /dev/null
+++ b/t/fcm-make/14-build-etc/src/hello.txt
@@ -0,0 +1 @@
+Hello World
diff --git a/t/fcm-make/14-build-etc/src/hi/hi-earth.txt b/t/fcm-make/14-build-etc/src/hi/hi-earth.txt
new file mode 100644
index 0000000..e6e0932
--- /dev/null
+++ b/t/fcm-make/14-build-etc/src/hi/hi-earth.txt
@@ -0,0 +1 @@
+Hi Earth
diff --git a/t/fcm-make/14-build-etc/src/hi/hi-mars.txt b/t/fcm-make/14-build-etc/src/hi/hi-mars.txt
new file mode 100644
index 0000000..ec2891b
--- /dev/null
+++ b/t/fcm-make/14-build-etc/src/hi/hi-mars.txt
@@ -0,0 +1 @@
+Hi Mars
diff --git a/t/fcm-make/15-extract-loc-reset.t b/t/fcm-make/15-extract-loc-reset.t
new file mode 100755
index 0000000..f124dc2
--- /dev/null
+++ b/t/fcm-make/15-extract-loc-reset.t
@@ -0,0 +1,182 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests for "fcm make", "extract", location reset and base eq diff cases.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+N_TESTS=34
+tests $N_TESTS
+#-------------------------------------------------------------------------------
+svnadmin create repos
+T_REPOS=file://$PWD/repos
+mkdir t
+echo "Hello World" >t/hello.txt
+echo "Hi World" >t/hi.txt
+svn import -q -m'Test' t/hello.txt $T_REPOS/trunk/hello.txt
+svn import -q -m'Test' t $T_REPOS/branch
+rm t/hello.txt t/hi.txt
+rmdir t
+#-------------------------------------------------------------------------------
+cat >fcm-make.cfg.0 <<__FCM_MAKE_CFG__
+steps=extract
+extract.ns=t
+extract.location{primary}[t]=$T_REPOS
+__FCM_MAKE_CFG__
+#-------------------------------------------------------------------------------
+base_tests() {
+    run_pass "$TEST_KEY" fcm make --new
+    find extract -type f >"$TEST_KEY.find"
+    file_cmp "$TEST_KEY.find" "$TEST_KEY.find" <<<'extract/t/hello.txt'
+    sed '/^\[info\] location     t: /!d; s/^\[info\] location     t: //' \
+        .fcm-make/log > "$TEST_KEY.log"
+    file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+0: $T_REPOS/trunk at 2 (1)
+__LOG__
+}
+#-------------------------------------------------------------------------------
+diff_tests() {
+    run_pass "$TEST_KEY" fcm make --new
+    find extract -type f | sort >"$TEST_KEY.find"
+    file_cmp "$TEST_KEY.find" "$TEST_KEY.find" <<'__FIND__'
+extract/t/hello.txt
+extract/t/hi.txt
+__FIND__
+    sed '/^\[info\] location     t: /!d; s/^\[info\] location     t: //' \
+        .fcm-make/log > "$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+0: $T_REPOS/trunk at 2 (1)
+1: $T_REPOS/branch at 2 (2)
+__LOG__
+}
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-primary"
+{
+    cat fcm-make.cfg.0
+    echo "extract.location{primary}[t]="
+    echo "extract.location[t]=$T_REPOS/trunk"
+} >fcm-make.cfg
+base_tests
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-base-0"
+{
+    cat fcm-make.cfg.0
+    echo "extract.location[t]="
+} >fcm-make.cfg
+base_tests
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-base-0-with-eq-diff"
+{
+    cat fcm-make.cfg.0
+    echo "extract.location{diff}[t]=$T_REPOS/trunk"
+} >fcm-make.cfg
+base_tests
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-base"
+{
+    cat fcm-make.cfg.0
+    echo "extract.location[t]=$T_REPOS/trunk"
+    echo "extract.location[t]="
+} >fcm-make.cfg
+base_tests
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-base-with-eq-diff"
+{
+    cat fcm-make.cfg.0
+    echo "extract.location[t]=$T_REPOS/trunk"
+    echo "extract.location{diff}[t]=$T_REPOS/trunk"
+} >fcm-make.cfg
+base_tests
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-base-with-diff"
+{
+    cat fcm-make.cfg.0
+    echo "extract.location[t]=$T_REPOS/trunk"
+    echo "extract.location{diff}[t]=$T_REPOS/branch"
+    echo "extract.location[t]="
+} >fcm-make.cfg
+diff_tests
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-base-with-1-eq-diff"
+{
+    cat fcm-make.cfg.0
+    echo "extract.location[t]=$T_REPOS/trunk"
+    echo "extract.location{diff}[t]=$T_REPOS/trunk $T_REPOS/branch"
+    echo "extract.location[t]="
+} >fcm-make.cfg
+diff_tests
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-diff-0"
+{
+    cat fcm-make.cfg.0
+    echo "extract.location{diff}[t]="
+} >fcm-make.cfg
+base_tests
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-diff"
+{
+    cat fcm-make.cfg.0
+    echo "extract.location{diff}[t]=$T_REPOS/branch"
+    echo "extract.location{diff}[t]="
+} >fcm-make.cfg
+base_tests
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-diff-with-base"
+{
+    cat fcm-make.cfg.0
+    echo "extract.location{diff}[t]=$T_REPOS/branch"
+    echo "extract.location[t]=$T_REPOS/trunk"
+    echo "extract.location{diff}[t]="
+} >fcm-make.cfg
+base_tests
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-inherit-base-eq-my-diff"
+mkdir -p i0 i1
+cat fcm-make.cfg.0 >i0/fcm-make.cfg
+fcm make --new -q -C i0
+{
+    echo 'use=$HERE/../i0'
+    echo "extract.location{diff}[t]=$T_REPOS/trunk"
+} >i1/fcm-make.cfg
+run_pass "$TEST_KEY" fcm make --new -C i1
+sed '/^\[info\] location     t: /!d; s/^\[info\] location     t: //' \
+    i1/.fcm-make/log > "$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+0: $T_REPOS/trunk at 2 (1)
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-inherit-base-eq-1-of-my-diff"
+# N.B. The 3 lines below have been done in the test above.
+#      Uncomment them if the above test is removed.
+#mkdir -p i0 i1
+#cat fcm-make.cfg.0 >i0/fcm-make.cfg
+#fcm make --new -q -C i0
+{
+    echo 'use=$HERE/../i0'
+    echo "extract.location{diff}[t]=$T_REPOS/branch $T_REPOS/trunk"
+} >i1/fcm-make.cfg
+run_pass "$TEST_KEY" fcm make --new -C i1
+sed '/^\[info\] location     t: /!d; s/^\[info\] location     t: //' \
+    i1/.fcm-make/log > "$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+0: $T_REPOS/trunk at 2 (1)
+1: $T_REPOS/branch at 2 (2)
+__LOG__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/16-build-dep-o-2.t b/t/fcm-make/16-build-dep-o-2.t
new file mode 100755
index 0000000..3622669
--- /dev/null
+++ b/t/fcm-make/16-build-dep-o-2.t
@@ -0,0 +1,50 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", "build.prop{dep.o}" top namespace, complicated by a module.
+# See also "09-build.dep-o.t".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 2
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_pass "$TEST_KEY" fcm make
+sed '/^\[info\] \(source->target\|target\) /!d' .fcm-make/log >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<'__LOG__'
+[info] source->target / -> (archive) lib/ libo.a
+[info] source->target hello.f90 -> (link) bin/ hello.exe
+[info] source->target hello.f90 -> (install) include/ hello.f90
+[info] source->target hello.f90 -> (compile) o/ hello.o
+[info] source->target hello_mod.f90 -> (install) include/ hello_mod.f90
+[info] source->target hello_mod.f90 -> (compile+) include/ hello_mod.mod
+[info] source->target hello_mod.f90 -> (compile) o/ hello_mod.o
+[info] source->target hello_sub.f90 -> (install) include/ hello_sub.f90
+[info] source->target hello_sub.f90 -> (ext-iface) include/ hello_sub.interface
+[info] source->target hello_sub.f90 -> (compile) o/ hello_sub.o
+[info] target hello.exe
+[info] target  - hello.o
+[info] target  - hello_sub.o
+[info] target  -  - hello_mod.mod
+[info] target  -  -  - hello_mod.o
+[info] target  - hello_mod.o
+__LOG__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/16-build-dep-o-2/fcm-make.cfg b/t/fcm-make/16-build-dep-o-2/fcm-make.cfg
new file mode 100644
index 0000000..eda1238
--- /dev/null
+++ b/t/fcm-make/16-build-dep-o-2/fcm-make.cfg
@@ -0,0 +1,4 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=link
+build.prop{dep.o}=hello_sub.o
diff --git a/t/fcm-make/16-build-dep-o-2/src/hello.f90 b/t/fcm-make/16-build-dep-o-2/src/hello.f90
new file mode 100644
index 0000000..6f92631
--- /dev/null
+++ b/t/fcm-make/16-build-dep-o-2/src/hello.f90
@@ -0,0 +1,3 @@
+program hello
+call hello_sub()
+end program hello
diff --git a/t/fcm-make/16-build-dep-o-2/src/hello_mod.f90 b/t/fcm-make/16-build-dep-o-2/src/hello_mod.f90
new file mode 100644
index 0000000..191124a
--- /dev/null
+++ b/t/fcm-make/16-build-dep-o-2/src/hello_mod.f90
@@ -0,0 +1,3 @@
+module hello_mod
+character(*), parameter :: greet='Hello'
+end module hello_mod
diff --git a/t/fcm-make/16-build-dep-o-2/src/hello_sub.f90 b/t/fcm-make/16-build-dep-o-2/src/hello_sub.f90
new file mode 100644
index 0000000..3e7f146
--- /dev/null
+++ b/t/fcm-make/16-build-dep-o-2/src/hello_sub.f90
@@ -0,0 +1,4 @@
+subroutine hello_sub
+use hello_mod, only: greet
+write(*, '(a,1x,a)') greet, 'world'
+end subroutine hello_sub
diff --git a/t/fcm-make/17-build-cyclic.t b/t/fcm-make/17-build-cyclic.t
new file mode 100755
index 0000000..713c14d
--- /dev/null
+++ b/t/fcm-make/17-build-cyclic.t
@@ -0,0 +1,42 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", self cyclic dependency.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 2
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_fail "$TEST_KEY" fcm make
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<'__ERR__'
+[FAIL] baz.mod: target depends on itself
+[FAIL]     required by: bar.o
+[FAIL]     required by: bar.mod
+[FAIL]     required by: baz.o
+[FAIL]     required by: baz.mod
+[FAIL]     required by: foo.o
+[FAIL]     required by: foo.mod
+[FAIL]     required by: hello.o
+[FAIL]     required by: hello.exe
+
+__ERR__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/17-build-cyclic/fcm-make.cfg b/t/fcm-make/17-build-cyclic/fcm-make.cfg
new file mode 100644
index 0000000..eda29bc
--- /dev/null
+++ b/t/fcm-make/17-build-cyclic/fcm-make.cfg
@@ -0,0 +1,3 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=link
diff --git a/t/fcm-make/17-build-cyclic/src/bar.f90 b/t/fcm-make/17-build-cyclic/src/bar.f90
new file mode 100644
index 0000000..e62620e
--- /dev/null
+++ b/t/fcm-make/17-build-cyclic/src/bar.f90
@@ -0,0 +1,9 @@
+module bar
+use quack,  only: quack_type
+use baz, only: baz_type
+implicit none
+private
+
+type, public, abstract, extends(quack_type) :: bar_type
+end type
+end module bar
diff --git a/t/fcm-make/17-build-cyclic/src/baz.f90 b/t/fcm-make/17-build-cyclic/src/baz.f90
new file mode 100644
index 0000000..096a17a
--- /dev/null
+++ b/t/fcm-make/17-build-cyclic/src/baz.f90
@@ -0,0 +1,10 @@
+module baz
+use quack, only: quack_type
+use bar, only: bar_type
+implicit none
+private
+
+type, public, abstract :: baz_type
+end type
+
+end module baz
diff --git a/t/fcm-make/17-build-cyclic/src/foo.f90 b/t/fcm-make/17-build-cyclic/src/foo.f90
new file mode 100644
index 0000000..22ab0b4
--- /dev/null
+++ b/t/fcm-make/17-build-cyclic/src/foo.f90
@@ -0,0 +1,10 @@
+module foo
+use bar, only: bar_type
+use baz, only: baz_type
+implicit none
+private
+
+type, public, extends(bar_type) :: foo_type
+end type
+
+end module foo
diff --git a/t/fcm-make/17-build-cyclic/src/hello.f90 b/t/fcm-make/17-build-cyclic/src/hello.f90
new file mode 100644
index 0000000..9ed47b3
--- /dev/null
+++ b/t/fcm-make/17-build-cyclic/src/hello.f90
@@ -0,0 +1,7 @@
+program hello 
+  use foo, only: foo_type
+  use meow, only: meow_type
+  use baz, only: baz_type
+  implicit none
+  write(*, '(A)') 'Hello'
+end program hello
diff --git a/t/fcm-make/17-build-cyclic/src/meow.f90 b/t/fcm-make/17-build-cyclic/src/meow.f90
new file mode 100644
index 0000000..bde5706
--- /dev/null
+++ b/t/fcm-make/17-build-cyclic/src/meow.f90
@@ -0,0 +1,11 @@
+module meow
+use quack,  only: quack_type
+use baz, only: baz_type
+use foo,      only: foo_type
+implicit none
+private
+
+type, public, extends(baz_type) :: meow_type
+end type
+
+end module meow
diff --git a/t/fcm-make/17-build-cyclic/src/quack.f90 b/t/fcm-make/17-build-cyclic/src/quack.f90
new file mode 100644
index 0000000..a898744
--- /dev/null
+++ b/t/fcm-make/17-build-cyclic/src/quack.f90
@@ -0,0 +1,9 @@
+module quack
+implicit none
+private
+
+type, public, abstract :: quack_type
+  private
+end type
+
+end module quack
diff --git a/t/fcm-make/18-build-use-intrinsic.t b/t/fcm-make/18-build-use-intrinsic.t
new file mode 100755
index 0000000..ec6ceaa
--- /dev/null
+++ b/t/fcm-make/18-build-use-intrinsic.t
@@ -0,0 +1,60 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", build, Fortran source file, "use, intrinsic" statement.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 7
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_pass "$TEST_KEY" fcm make
+sed '/^\[info\] target /!d' .fcm-make/log >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<'__LOG__'
+[info] target greet.exe
+[info] target  - greet.o
+[info] target  -  - hi.interface
+[info] target  -  - hello.interface
+[info] target  - hello.o
+[info] target  - hi.o
+__LOG__
+file_cmp "$TEST_KEY.hello.interface" "build/include/hello.interface" \
+    <<'__INTERFACE__'
+interface
+subroutine hello()
+end subroutine hello
+end interface
+__INTERFACE__
+file_cmp "$TEST_KEY.hi.interface" "build/include/hi.interface" \
+    <<'__INTERFACE__'
+interface
+subroutine hi()
+use, intrinsic :: iso_fortran_env
+end subroutine hi
+end interface
+__INTERFACE__
+run_pass "$TEST_KEY.exe" ./build/bin/greet.exe
+file_cmp "$TEST_KEY.exe.out" "$TEST_KEY.exe.out" <<'__OUT__'
+Hello
+Hi
+__OUT__
+file_cmp "$TEST_KEY.exe.err" "$TEST_KEY.exe.err" </dev/null
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/18-build-use-intrinsic/fcm-make.cfg b/t/fcm-make/18-build-use-intrinsic/fcm-make.cfg
new file mode 100644
index 0000000..eda29bc
--- /dev/null
+++ b/t/fcm-make/18-build-use-intrinsic/fcm-make.cfg
@@ -0,0 +1,3 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=link
diff --git a/t/fcm-make/18-build-use-intrinsic/src/greet.f90 b/t/fcm-make/18-build-use-intrinsic/src/greet.f90
new file mode 100644
index 0000000..d9c2833
--- /dev/null
+++ b/t/fcm-make/18-build-use-intrinsic/src/greet.f90
@@ -0,0 +1,14 @@
+program greet
+implicit none
+include 'hello.interface'
+include 'hi.interface'
+abstract interface
+subroutine abstract_greet()
+end subroutine abstract_greet
+end interface
+procedure(abstract_greet), pointer :: say
+say => hello
+call say()
+say => hi
+call say()
+end program greet
diff --git a/t/fcm-make/18-build-use-intrinsic/src/hello.f90 b/t/fcm-make/18-build-use-intrinsic/src/hello.f90
new file mode 100644
index 0000000..f59c7d7
--- /dev/null
+++ b/t/fcm-make/18-build-use-intrinsic/src/hello.f90
@@ -0,0 +1,4 @@
+subroutine hello()
+use, intrinsic :: iso_fortran_env, only: output_unit
+write(output_unit, '(a)') 'Hello'
+end subroutine hello
diff --git a/t/fcm-make/18-build-use-intrinsic/src/hi.f90 b/t/fcm-make/18-build-use-intrinsic/src/hi.f90
new file mode 100644
index 0000000..9385278
--- /dev/null
+++ b/t/fcm-make/18-build-use-intrinsic/src/hi.f90
@@ -0,0 +1,4 @@
+subroutine hi()
+use, intrinsic :: iso_fortran_env !, only: output_unit
+write(output_unit, '(a)') 'Hi'
+end subroutine hi
diff --git a/t/fcm-make/19-build-inherit-prop.t b/t/fcm-make/19-build-inherit-prop.t
new file mode 100755
index 0000000..6b1db5d
--- /dev/null
+++ b/t/fcm-make/19-build-inherit-prop.t
@@ -0,0 +1,50 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", ensure that properties can be declared before or after use=
+# declaration.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 2
+set -e
+mkdir -p i0 i1 i2
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* i0
+fcm make -q -C i0
+set +e
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-i1"
+cat >i1/fcm-make.cfg <<'__FCM_MAKE_CFG__'
+use=$HERE/../i0
+build.prop{fc.defs}=WORLD='"Mars"'
+__FCM_MAKE_CFG__
+fcm make -q -C i1
+$PWD/i1/build/bin/hello.exe >"$TEST_KEY.exe.out"
+file_cmp "$TEST_KEY.exe.out" "$TEST_KEY.exe.out" <<<'Hello Mars'
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-i2"
+cat >i2/fcm-make.cfg <<'__FCM_MAKE_CFG__'
+build.prop{fc.defs}=WORLD='"Venus"'
+use=$HERE/../i0
+__FCM_MAKE_CFG__
+fcm make -q -C i2
+$PWD/i2/build/bin/hello.exe >"$TEST_KEY.exe.out"
+file_cmp "$TEST_KEY.exe.out" "$TEST_KEY.exe.out" <<<'Hello Venus'
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/19-build-inherit-prop/fcm-make.cfg b/t/fcm-make/19-build-inherit-prop/fcm-make.cfg
new file mode 100644
index 0000000..9aad525
--- /dev/null
+++ b/t/fcm-make/19-build-inherit-prop/fcm-make.cfg
@@ -0,0 +1,4 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=link
+build.prop{fc.defs}=WORLD='"Earth"'
diff --git a/t/fcm-make/19-build-inherit-prop/src/hello.F90 b/t/fcm-make/19-build-inherit-prop/src/hello.F90
new file mode 100644
index 0000000..e178c67
--- /dev/null
+++ b/t/fcm-make/19-build-inherit-prop/src/hello.F90
@@ -0,0 +1,6 @@
+program hello
+#ifndef WORLD
+#define WORLD "World"
+#endif
+write(*, '(a,1x,a)') 'Hello', WORLD
+end program hello
diff --git a/t/fcm-make/20-args.t b/t/fcm-make/20-args.t
new file mode 100755
index 0000000..b60f227
--- /dev/null
+++ b/t/fcm-make/20-args.t
@@ -0,0 +1,50 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", CLI arguments.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 6
+set -e
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+set +e
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-hello"
+run_pass "$TEST_KEY" fcm make 'build.prop{file-ext.bin}=' 'build.target=hello'
+grep '^\[info\] required-target:' .fcm-make/log >$TEST_KEY.log
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<'__LOG__'
+[info] required-target: link      bin     hello
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-greet"
+run_pass "$TEST_KEY" fcm make 'build.target=greet.exe'
+grep '^\[info\] required-target:' .fcm-make/log >$TEST_KEY.log
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<'__LOG__'
+[info] required-target: link      bin     greet.exe
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-new-greet"
+run_pass "$TEST_KEY" fcm make --new 'build.target=greet.exe'
+grep '^\[info\] required-target:' .fcm-make/log >$TEST_KEY.log
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<'__LOG__'
+[info] required-target: link      bin     greet.exe
+__LOG__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/20-args/fcm-make.cfg b/t/fcm-make/20-args/fcm-make.cfg
new file mode 100644
index 0000000..d614aa9
--- /dev/null
+++ b/t/fcm-make/20-args/fcm-make.cfg
@@ -0,0 +1,3 @@
+steps=build
+build.source=$HERE/src
+build.target=hello.exe
diff --git a/t/fcm-make/20-args/src/greet.f90 b/t/fcm-make/20-args/src/greet.f90
new file mode 100644
index 0000000..60255bf
--- /dev/null
+++ b/t/fcm-make/20-args/src/greet.f90
@@ -0,0 +1,3 @@
+program greet
+write(*, '(a)') 'Greetings'
+end program greet
diff --git a/t/fcm-make/20-args/src/hello.f90 b/t/fcm-make/20-args/src/hello.f90
new file mode 100644
index 0000000..2a3a4b0
--- /dev/null
+++ b/t/fcm-make/20-args/src/hello.f90
@@ -0,0 +1,3 @@
+program hello
+write(*, '(a)') 'Hello'
+end program hello
diff --git a/t/fcm-make/21-inherit-steps.t b/t/fcm-make/21-inherit-steps.t
new file mode 100755
index 0000000..06607df
--- /dev/null
+++ b/t/fcm-make/21-inherit-steps.t
@@ -0,0 +1,68 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", ensure that steps can be declared before or after use=
+# declaration.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 3
+set -e
+mkdir -p i0 i1 i2 i3
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* i0
+fcm make -q -C i0
+set +e
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-i1"
+cat >i1/fcm-make.cfg <<'__FCM_MAKE_CFG__'
+use=$HERE/../i0
+__FCM_MAKE_CFG__
+fcm make -q -C i1
+find i1/*/bin -type f | sort >"$TEST_KEY.ls"
+file_cmp "$TEST_KEY.ls" "$TEST_KEY.ls" <<'__LIST__'
+i1/build1/bin/hello.exe
+i1/build2/bin/salute.exe
+i1/build3/bin/greet.exe
+__LIST__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-i2"
+cat >i2/fcm-make.cfg <<'__FCM_MAKE_CFG__'
+step.class[build1 build2]=build
+steps=build1 build2
+use=$HERE/../i0
+__FCM_MAKE_CFG__
+fcm make -q -C i2
+find i2/*/bin -type f | sort >"$TEST_KEY.ls"
+file_cmp "$TEST_KEY.ls" "$TEST_KEY.ls" <<'__LIST__'
+i2/build1/bin/hello.exe
+i2/build2/bin/salute.exe
+__LIST__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-i3"
+cat >i3/fcm-make.cfg <<'__FCM_MAKE_CFG__'
+use=$HERE/../i0
+steps=build3
+__FCM_MAKE_CFG__
+fcm make -q -C i3
+find i3/*/bin -type f | sort >"$TEST_KEY.ls"
+file_cmp "$TEST_KEY.ls" "$TEST_KEY.ls" <<'__LIST__'
+i3/build3/bin/greet.exe
+__LIST__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/21-inherit-steps/fcm-make.cfg b/t/fcm-make/21-inherit-steps/fcm-make.cfg
new file mode 100644
index 0000000..5bc7b49
--- /dev/null
+++ b/t/fcm-make/21-inherit-steps/fcm-make.cfg
@@ -0,0 +1,8 @@
+step.class[build1 build2 build3]=build
+steps=build1 build2 build3
+build1.source=$HERE/src1
+build1.target{task}=link
+build2.source=$HERE/src2
+build2.target{task}=link
+build3.source=$HERE/src3
+build3.target{task}=link
diff --git a/t/fcm-make/21-inherit-steps/src1/hello.f90 b/t/fcm-make/21-inherit-steps/src1/hello.f90
new file mode 100644
index 0000000..0fecec0
--- /dev/null
+++ b/t/fcm-make/21-inherit-steps/src1/hello.f90
@@ -0,0 +1,3 @@
+program hello
+write(*, '(a)') 'Hello!'
+end program hello
diff --git a/t/fcm-make/21-inherit-steps/src2/salute.f90 b/t/fcm-make/21-inherit-steps/src2/salute.f90
new file mode 100644
index 0000000..a0f056a
--- /dev/null
+++ b/t/fcm-make/21-inherit-steps/src2/salute.f90
@@ -0,0 +1,3 @@
+program salute
+write(*, '(a)') 'Salute!'
+end program salute
diff --git a/t/fcm-make/21-inherit-steps/src3/greet.f90 b/t/fcm-make/21-inherit-steps/src3/greet.f90
new file mode 100644
index 0000000..ab26cc7
--- /dev/null
+++ b/t/fcm-make/21-inherit-steps/src3/greet.f90
@@ -0,0 +1,3 @@
+program greet
+write(*, '(a)') 'Greeting!'
+end program greet
diff --git a/t/fcm-make/22-build-2-bad-mod-over-inherit.t b/t/fcm-make/22-build-2-bad-mod-over-inherit.t
new file mode 100755
index 0000000..4cacfeb
--- /dev/null
+++ b/t/fcm-make/22-build-2-bad-mod-over-inherit.t
@@ -0,0 +1,59 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", inherit build correctness. metomi/fcm#110
+# * 2 source files with bad syntax override.
+# * Build fails on first source file.
+# * Fix first source file.
+# * Build should fail on second source file.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 7
+set -e
+mkdir -p i0 i1
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/{fcm-make.cfg,src} i0
+fcm make -q -C i0
+set +e
+#-------------------------------------------------------------------------------
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/src-i i1/src
+cat >i1/fcm-make.cfg <<'__CFG__'
+use=$HERE/../i0
+build.source=$HERE/src
+__CFG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-i1-1"
+run_fail "$TEST_KEY" fcm make -q -C i1
+file_grep "$TEST_KEY.err" '\[FAIL\].*i1/src/m1.f90' "$TEST_KEY.err"
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-i1-2"
+sed 's/^writ/write/' $TEST_SOURCE_DIR/$TEST_KEY_BASE/src-i/m1.f90 >i1/src/m1.f90
+run_fail "$TEST_KEY" fcm make -q -C i1
+file_grep "$TEST_KEY.err" '\[FAIL\].*i1/src/m2.f90' "$TEST_KEY.err"
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-i1-3"
+sed 's/^writ/write/' $TEST_SOURCE_DIR/$TEST_KEY_BASE/src-i/m2.f90 >i1/src/m2.f90
+run_pass "$TEST_KEY" fcm make -q -C i1 --new
+run_pass "$TEST_KEY.exe" $PWD/i1/build/bin/p1.exe
+file_cmp "$TEST_KEY.exe.out" "$TEST_KEY.exe.out" <<'__OUT__'
+Greet from m1-s1!
+Greet from m2-s2!
+__OUT__
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/fcm-make/22-build-2-bad-mod-over-inherit/fcm-make.cfg b/t/fcm-make/22-build-2-bad-mod-over-inherit/fcm-make.cfg
new file mode 100644
index 0000000..d063951
--- /dev/null
+++ b/t/fcm-make/22-build-2-bad-mod-over-inherit/fcm-make.cfg
@@ -0,0 +1,3 @@
+steps=build
+build.source=$HERE/src
+build.target=p1.exe
diff --git a/t/fcm-make/22-build-2-bad-mod-over-inherit/src-i/m1.f90 b/t/fcm-make/22-build-2-bad-mod-over-inherit/src-i/m1.f90
new file mode 100644
index 0000000..91cdf39
--- /dev/null
+++ b/t/fcm-make/22-build-2-bad-mod-over-inherit/src-i/m1.f90
@@ -0,0 +1,6 @@
+module m1
+contains
+subroutine s1()
+writ(*, '(a)') 'Greet from m1-s1!'
+end subroutine s1
+end module m1
diff --git a/t/fcm-make/22-build-2-bad-mod-over-inherit/src-i/m2.f90 b/t/fcm-make/22-build-2-bad-mod-over-inherit/src-i/m2.f90
new file mode 100644
index 0000000..57b1cd2
--- /dev/null
+++ b/t/fcm-make/22-build-2-bad-mod-over-inherit/src-i/m2.f90
@@ -0,0 +1,6 @@
+module m2
+contains
+subroutine s2()
+writ(*, '(a)') 'Greet from m2-s2!'
+end subroutine s2
+end module m2
diff --git a/t/fcm-make/22-build-2-bad-mod-over-inherit/src/m1.f90 b/t/fcm-make/22-build-2-bad-mod-over-inherit/src/m1.f90
new file mode 100644
index 0000000..87ece14
--- /dev/null
+++ b/t/fcm-make/22-build-2-bad-mod-over-inherit/src/m1.f90
@@ -0,0 +1,6 @@
+module m1
+contains
+subroutine s1()
+write(*, '(a)') 'Hello from m1:s1!'
+end subroutine s1
+end module m1
diff --git a/t/fcm-make/22-build-2-bad-mod-over-inherit/src/m2.f90 b/t/fcm-make/22-build-2-bad-mod-over-inherit/src/m2.f90
new file mode 100644
index 0000000..3640c47
--- /dev/null
+++ b/t/fcm-make/22-build-2-bad-mod-over-inherit/src/m2.f90
@@ -0,0 +1,6 @@
+module m2
+contains
+subroutine s2()
+write(*, '(a)') 'Hello from m2:s2!'
+end subroutine s2
+end module m2
diff --git a/t/fcm-make/22-build-2-bad-mod-over-inherit/src/p1.f90 b/t/fcm-make/22-build-2-bad-mod-over-inherit/src/p1.f90
new file mode 100644
index 0000000..9321db8
--- /dev/null
+++ b/t/fcm-make/22-build-2-bad-mod-over-inherit/src/p1.f90
@@ -0,0 +1,6 @@
+program p1
+use m1, only: s1
+use m2, only: s2
+call s1()
+call s2()
+end program p1
diff --git a/t/fcm-make/23-build-omp.t b/t/fcm-make/23-build-omp.t
new file mode 100755
index 0000000..d370242
--- /dev/null
+++ b/t/fcm-make/23-build-omp.t
@@ -0,0 +1,66 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", build detects dependencies in OMP sentinels.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 16
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+yes 6.0 | head -n 100 >"$TEST_KEY_BASE.exe.on.out"
+yes 1.0 | head -n 100 >"$TEST_KEY_BASE.exe.off.out"
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-on # fc.flag-omp on in new mode
+run_pass "$TEST_KEY" fcm make
+grep ' !\$' fcm-make.log | sort >"$TEST_KEY.log.deps.expected"
+file_cmp  "$TEST_KEY.log.deps" "$TEST_KEY.log.deps.expected" <<'__LOG__'
+[info]              -> (  include) !$i1.f90
+[info]              -> (  include) !$i2.f90
+[info]              -> ( f.module) !$m1
+[info]              -> ( f.module) !$m2
+__LOG__
+run_pass "$TEST_KEY.exe" $PWD/build/bin/p1.exe
+file_cmp "$TEST_KEY.exe.out" "$TEST_KEY_BASE.exe.on.out" "$TEST_KEY.exe.out"
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-on-off # fc.flag-omp on->off in incremental mode
+echo 'build.prop{fc.flag-omp}=' >>fcm-make.cfg
+run_pass "$TEST_KEY" fcm make
+run_pass "$TEST_KEY.exe" $PWD/build/bin/p1.exe
+file_cmp "$TEST_KEY.exe.out" "$TEST_KEY_BASE.exe.off.out" "$TEST_KEY.exe.out"
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-on-off-on # fc.flag-omp on->off->on in incremental mode
+echo 'build.prop{fc.flag-omp}=-fopenmp' >>fcm-make.cfg
+run_pass "$TEST_KEY" fcm make
+run_pass "$TEST_KEY.exe" $PWD/build/bin/p1.exe
+file_cmp "$TEST_KEY.exe.out" "$TEST_KEY_BASE.exe.on.out" "$TEST_KEY.exe.out"
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-off # fc.flag-omp off in new mode
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/fcm-make.cfg .
+echo 'build.prop{fc.flag-omp}=' >>fcm-make.cfg
+run_pass "$TEST_KEY" fcm make --new
+run_pass "$TEST_KEY.exe" $PWD/build/bin/p1.exe
+file_cmp "$TEST_KEY.exe.out" "$TEST_KEY_BASE.exe.off.out" "$TEST_KEY.exe.out"
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-off-on # fc.flag-omp off->on in incremental mode
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/fcm-make.cfg .
+run_pass "$TEST_KEY" fcm make
+run_pass "$TEST_KEY.exe" $PWD/build/bin/p1.exe
+file_cmp "$TEST_KEY.exe.out" "$TEST_KEY_BASE.exe.on.out" "$TEST_KEY.exe.out"
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/fcm-make/23-build-omp/fcm-make.cfg b/t/fcm-make/23-build-omp/fcm-make.cfg
new file mode 100644
index 0000000..4c10ee8
--- /dev/null
+++ b/t/fcm-make/23-build-omp/fcm-make.cfg
@@ -0,0 +1,4 @@
+steps=build
+build.source=$HERE/src
+build.target=p1.exe
+build.prop{fc.flag-omp}=-fopenmp
diff --git a/t/fcm-make/23-build-omp/src/i1.f90 b/t/fcm-make/23-build-omp/src/i1.f90
new file mode 100644
index 0000000..966d4d3
--- /dev/null
+++ b/t/fcm-make/23-build-omp/src/i1.f90
@@ -0,0 +1 @@
+call s1(n, y, x)
diff --git a/t/fcm-make/23-build-omp/src/i2.f90 b/t/fcm-make/23-build-omp/src/i2.f90
new file mode 100644
index 0000000..36dda9b
--- /dev/null
+++ b/t/fcm-make/23-build-omp/src/i2.f90
@@ -0,0 +1 @@
+call s2(n, z, y)
diff --git a/t/fcm-make/23-build-omp/src/m1.f90 b/t/fcm-make/23-build-omp/src/m1.f90
new file mode 100644
index 0000000..0828202
--- /dev/null
+++ b/t/fcm-make/23-build-omp/src/m1.f90
@@ -0,0 +1,14 @@
+module m1
+contains
+subroutine s1(n, y, x)
+integer, intent(in) :: n
+real, intent(out) :: y(:)
+real, intent(in) :: x(:)
+integer :: i
+!$omp parallel do shared(y)
+do i = 1, n
+    y(i) = x(i) * 2.0
+end do
+!$omp end parallel do
+end subroutine s1
+end module m1
diff --git a/t/fcm-make/23-build-omp/src/m2.f90 b/t/fcm-make/23-build-omp/src/m2.f90
new file mode 100644
index 0000000..1d1f6ac
--- /dev/null
+++ b/t/fcm-make/23-build-omp/src/m2.f90
@@ -0,0 +1,14 @@
+module m2
+contains
+subroutine s2(n, z, y)
+integer, intent(in) :: n
+real, intent(out) :: z(:)
+real, intent(in) :: y(:)
+integer :: i
+!$omp parallel do shared(z)
+do i = 1, n
+    z(i) = y(i) * 3.0
+end do
+!$omp end parallel do
+end subroutine s2
+end module m2
diff --git a/t/fcm-make/23-build-omp/src/p1.f90 b/t/fcm-make/23-build-omp/src/p1.f90
new file mode 100644
index 0000000..48b3c64
--- /dev/null
+++ b/t/fcm-make/23-build-omp/src/p1.f90
@@ -0,0 +1,21 @@
+program p1
+
+!$ use omp_lib
+!$ use m1, only: s1
+
+integer, parameter :: n=100
+integer :: i
+real :: x(n), y(n), z(n)
+
+include 's3.interface'
+
+x(:) = 1.0
+y(:) = 1.0
+z(:) = 1.0
+!$ include "i1.f90"
+call s3(n, z, y)
+do i = 1, n
+    write(*, '(f3.1)') z(i)
+end do
+
+end program p1
diff --git a/t/fcm-make/23-build-omp/src/s3.f90 b/t/fcm-make/23-build-omp/src/s3.f90
new file mode 100644
index 0000000..568c1b8
--- /dev/null
+++ b/t/fcm-make/23-build-omp/src/s3.f90
@@ -0,0 +1,7 @@
+subroutine s3(n, z, y)
+!$ use m2, only: s2
+integer, intent(in) :: n
+real, intent(out) :: z(:)
+real, intent(in) :: y(:)
+!$ include "i2.f90"
+end subroutine s3
diff --git a/t/fcm-make/24-build-c-main-camel.t b/t/fcm-make/24-build-c-main-camel.t
new file mode 100755
index 0000000..87604c5
--- /dev/null
+++ b/t/fcm-make/24-build-c-main-camel.t
@@ -0,0 +1,36 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Test build C source file with mixed case name and has main function.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 3
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_pass "$TEST_KEY" fcm make
+grep '^\[info\] target ' fcm-make.log >"$TEST_KEY.target.log"
+file_cmp "$TEST_KEY.target.log" "$TEST_KEY.target.log" <<'__LOG__'
+[info] target Hello
+[info] target  - hello.o
+__LOG__
+run_pass "$TEST_KEY.chello" $PWD/build/bin/Hello
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/24-build-c-main-camel/fcm-make.cfg b/t/fcm-make/24-build-c-main-camel/fcm-make.cfg
new file mode 100644
index 0000000..9216a4d
--- /dev/null
+++ b/t/fcm-make/24-build-c-main-camel/fcm-make.cfg
@@ -0,0 +1,4 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=link
+build.prop{file-ext.bin}=
diff --git a/t/fcm-make/24-build-c-main-camel/src/Hello.c b/t/fcm-make/24-build-c-main-camel/src/Hello.c
new file mode 100644
index 0000000..ccd6fe0
--- /dev/null
+++ b/t/fcm-make/24-build-c-main-camel/src/Hello.c
@@ -0,0 +1,5 @@
+#include <stdio.h>
+int main(void) {
+    printf("Hello World\n");
+    return 0;
+}
diff --git a/t/fcm-make/25-build-cyclic-2.t b/t/fcm-make/25-build-cyclic-2.t
new file mode 100755
index 0000000..bfa79e5
--- /dev/null
+++ b/t/fcm-make/25-build-cyclic-2.t
@@ -0,0 +1,40 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", adjacent cyclic dependency.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 2
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_fail "$TEST_KEY" fcm make
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<'__ERR__'
+[FAIL] m1.mod: target depends on itself
+[FAIL]     required by: m2.o
+[FAIL]     required by: m2.mod
+[FAIL]     required by: m1.o
+[FAIL]     required by: m1.mod
+[FAIL]     required by: foo.o
+[FAIL]     required by: foo.exe
+
+__ERR__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/25-build-cyclic-2/fcm-make.cfg b/t/fcm-make/25-build-cyclic-2/fcm-make.cfg
new file mode 100644
index 0000000..eda29bc
--- /dev/null
+++ b/t/fcm-make/25-build-cyclic-2/fcm-make.cfg
@@ -0,0 +1,3 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=link
diff --git a/t/fcm-make/25-build-cyclic-2/src/foo.f90 b/t/fcm-make/25-build-cyclic-2/src/foo.f90
new file mode 100644
index 0000000..0f5f635
--- /dev/null
+++ b/t/fcm-make/25-build-cyclic-2/src/foo.f90
@@ -0,0 +1,4 @@
+program foo
+use m1, only s1
+call s1()
+end program foo
diff --git a/t/fcm-make/25-build-cyclic-2/src/m1.f90 b/t/fcm-make/25-build-cyclic-2/src/m1.f90
new file mode 100644
index 0000000..af0c4df
--- /dev/null
+++ b/t/fcm-make/25-build-cyclic-2/src/m1.f90
@@ -0,0 +1,12 @@
+module m1
+character(*), parameter :: WHATEVER
+contains
+subroutine s1()
+write(*, '(a)') f1() // ' from s1'
+end subroutine s1
+function f1()
+use m2, only: HELLO
+character(len=len(HELLO)) :: f1
+f1 = hello
+end function f1
+end module m1
diff --git a/t/fcm-make/25-build-cyclic-2/src/m2.f90 b/t/fcm-make/25-build-cyclic-2/src/m2.f90
new file mode 100644
index 0000000..e727c76
--- /dev/null
+++ b/t/fcm-make/25-build-cyclic-2/src/m2.f90
@@ -0,0 +1,8 @@
+module m2
+use m1, only: WHATEVER
+character(*), parameter :: HELLO='Hello'
+contains
+subroutine s2()
+write(*, '(a)') HELLO // ' from s2'
+end subroutine s2
+end module m2
diff --git a/t/fcm-make/26-no-config.t b/t/fcm-make/26-no-config.t
new file mode 100755
index 0000000..ce7ebe8
--- /dev/null
+++ b/t/fcm-make/26-no-config.t
@@ -0,0 +1,33 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", no configuration and no arguments.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 2
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_fail "$TEST_KEY" fcm make
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<'__ERR__'
+[FAIL] no configuration specified or found
+
+__ERR__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/27-args-only.t b/t/fcm-make/27-args-only.t
new file mode 100755
index 0000000..59feaa4
--- /dev/null
+++ b/t/fcm-make/27-args-only.t
@@ -0,0 +1,41 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", no configuration files, only CLI arguments.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 3
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+mkdir src
+cat >src/hello.f90 <<'__FORTRAN__'
+program hello
+write(*, '(a)') 'Hello World!'
+end program hello
+__FORTRAN__
+run_pass "$TEST_KEY" fcm make \
+    'steps=build' "build.source=$PWD/src" 'build.target=hello.exe'
+file_test "$TEST_KEY.hello.exe" $PWD/build/bin/hello.exe
+$PWD/build/bin/hello.exe >"$TEST_KEY.hello.exe.out"
+file_cmp "$TEST_KEY.hello.exe.out" "$TEST_KEY.hello.exe.out" <<'__OUT__'
+Hello World!
+__OUT__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/28-bad-arg.t b/t/fcm-make/28-bad-arg.t
new file mode 100755
index 0000000..fddb3db
--- /dev/null
+++ b/t/fcm-make/28-bad-arg.t
@@ -0,0 +1,41 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", bad arguments.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 4
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_fail "$TEST_KEY" fcm make 'foo'
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<'__ERROR__'
+[FAIL] arg 0 (foo): invalid config declaration
+
+__ERROR__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-cfg"
+run_fail "$TEST_KEY" fcm make 'foo.cfg'
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<'__ERROR__'
+[FAIL] arg 0 (foo.cfg): invalid config declaration
+[FAIL] did you mean "-f foo.cfg"?
+
+__ERROR__
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/29-relative-cfg.t b/t/fcm-make/29-relative-cfg.t
new file mode 100755
index 0000000..3978621
--- /dev/null
+++ b/t/fcm-make/29-relative-cfg.t
@@ -0,0 +1,85 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", config as relative paths
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+
+clean() {
+    rm -fr \
+        .fcm-make \
+        build \
+        fcm-make-as-parsed.cfg \
+        fcm-make-on-success.cfg \
+        fcm-make.log
+}
+#-------------------------------------------------------------------------------
+tests 11
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+
+mkdir etc
+cat >etc/fcm-make.cfg <<'__CFG__'
+steps=build
+build.source=$HERE/../src
+build.target=hello.exe
+__CFG__
+
+mkdir src
+cat >src/hello.f90 <<'__FORTRAN__'
+program hello
+write(*, '(a)') 'Hello World!'
+end program hello
+__FORTRAN__
+
+#-------------------------------------------------------------------------------
+clean
+run_fail "$TEST_KEY-control" fcm make
+file_cmp "$TEST_KEY-control.err" "$TEST_KEY-control.err" <<'__ERROR__'
+[FAIL] no configuration specified or found
+
+__ERROR__
+#-------------------------------------------------------------------------------
+clean
+run_pass "$TEST_KEY-pwd" fcm make -f etc/fcm-make.cfg
+file_test "$TEST_KEY.hello.exe" $PWD/build/bin/hello.exe
+$PWD/build/bin/hello.exe >"$TEST_KEY.hello.exe.out"
+file_cmp "$TEST_KEY.hello.exe.out" "$TEST_KEY.hello.exe.out" <<'__OUT__'
+Hello World!
+__OUT__
+#-------------------------------------------------------------------------------
+clean
+run_pass "$TEST_KEY-path" fcm make -F $PWD/etc
+file_test "$TEST_KEY.hello.exe" $PWD/build/bin/hello.exe
+$PWD/build/bin/hello.exe >"$TEST_KEY.hello.exe.out"
+file_cmp "$TEST_KEY.hello.exe.out" "$TEST_KEY.hello.exe.out" <<'__OUT__'
+Hello World!
+__OUT__
+#-------------------------------------------------------------------------------
+clean
+cd src
+run_pass "$TEST_KEY-path" fcm make -C .. -f etc/fcm-make.cfg
+file_test "$TEST_KEY.hello.exe" ../build/bin/hello.exe
+../build/bin/hello.exe >"$TEST_KEY.hello.exe.out"
+file_cmp "$TEST_KEY.hello.exe.out" "$TEST_KEY.hello.exe.out" <<'__OUT__'
+Hello World!
+__OUT__
+cd ..
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/30-relative-cfg-in-svn.t b/t/fcm-make/30-relative-cfg-in-svn.t
new file mode 100755
index 0000000..ca4fe2a
--- /dev/null
+++ b/t/fcm-make/30-relative-cfg-in-svn.t
@@ -0,0 +1,52 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", relative config in a Subversion repository
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 9
+#-------------------------------------------------------------------------------
+mkdir etc
+cat >etc/fcm-make.cfg <<'__CFG__'
+steps=build
+build.source=src
+build.target=hello.exe
+__CFG__
+
+mkdir src
+cat >src/hello.f90 <<'__FORTRAN__'
+program hello
+write(*, '(a)') 'Hello World!'
+end program hello
+__FORTRAN__
+
+svnadmin create svn-repos
+svn import -m 'test stuff' etc file://$PWD/svn-repos/etc
+rm -fr etc
+
+#-------------------------------------------------------------------------------
+fcm_make_build_hello_tests "$TEST_KEY_BASE" \
+    '.exe' -F "file://$PWD/svn-repos" -f 'etc/fcm-make.cfg'
+fcm_make_build_hello_tests "$TEST_KEY_BASE-1" \
+    '.exe' -F "file://$PWD/svn-repos@1" -f 'etc/fcm-make.cfg'
+fcm_make_build_hello_tests "$TEST_KEY_BASE-HEAD" \
+    '.exe' -F "file://$PWD/svn-repos@HEAD" -f 'etc/fcm-make.cfg'
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/31-relative-cfg-in-ssh.t b/t/fcm-make/31-relative-cfg-in-ssh.t
new file mode 100755
index 0000000..21cd98d
--- /dev/null
+++ b/t/fcm-make/31-relative-cfg-in-ssh.t
@@ -0,0 +1,69 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", relative config in a remote host accessible via SSH
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+# Get a remote host for testing
+T_HOST=
+for FILE in $HOME/.metomi/fcm/t.cfg $FCM_HOME/etc/fcm/t.cfg; do
+    if [[ ! -f $FILE || ! -r $FILE ]]; then
+        continue
+    fi
+    T_HOST=$(fcm cfg $FILE | sed '/^ *host *=/!d; s/^ *host *= *//' | tail -1)
+    if [[ -n $T_HOST ]]; then
+        break
+    fi
+done
+if [[ -z $T_HOST ]]; then
+    skip_all 'fcm/t.cfg: "host" not defined'
+fi
+#-------------------------------------------------------------------------------
+tests 3
+#-------------------------------------------------------------------------------
+mkdir etc
+cat >etc/fcm-make.cfg <<'__CFG__'
+steps=build
+build.source=src
+build.target=hello.exe
+__CFG__
+
+T_HOST_WORK_DIR=$(ssh -oBatchMode=yes $T_HOST mktemp -d)
+rsync -a etc $T_HOST:$T_HOST_WORK_DIR
+rm -r etc
+
+mkdir src
+cat >src/hello.f90 <<'__FORTRAN__'
+program hello
+write(*, '(a)') 'Hello World!'
+end program hello
+__FORTRAN__
+
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+run_pass "$TEST_KEY-pwd" fcm make -F $T_HOST:$T_HOST_WORK_DIR/etc
+file_test "$TEST_KEY.hello.exe" $PWD/build/bin/hello.exe
+$PWD/build/bin/hello.exe >"$TEST_KEY.hello.exe.out"
+file_cmp "$TEST_KEY.hello.exe.out" "$TEST_KEY.hello.exe.out" <<'__OUT__'
+Hello World!
+__OUT__
+#-------------------------------------------------------------------------------
+ssh -oBatchMode=yes $T_HOST rm -r $T_HOST_WORK_DIR
+exit 0
diff --git a/t/fcm-make/32-include-relative-cfg.t b/t/fcm-make/32-include-relative-cfg.t
new file mode 100755
index 0000000..132def8
--- /dev/null
+++ b/t/fcm-make/32-include-relative-cfg.t
@@ -0,0 +1,54 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", include relative config
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 6
+#-------------------------------------------------------------------------------
+mkdir etc
+cat >etc/fcm-make-build.cfg <<'__CFG__'
+steps=build
+build.source=src
+build.target=hello.exe
+__CFG__
+
+mkdir src
+cat >src/hello.f90 <<'__FORTRAN__'
+program hello
+write(*, '(a)') 'Hello World!'
+end program hello
+__FORTRAN__
+
+#-------------------------------------------------------------------------------
+cat >fcm-make.cfg <<'__CFG__'
+include = fcm-make-build.cfg
+__CFG__
+
+fcm_make_build_hello_tests "$TEST_KEY_BASE-config-file-path" '.exe' -F $PWD/etc
+#-------------------------------------------------------------------------------
+cat >fcm-make.cfg <<'__CFG__'
+include-path=$HERE/etc
+include=fcm-make-build.cfg
+__CFG__
+
+fcm_make_build_hello_tests "$TEST_KEY_BASE-include-path" '.exe'
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/33-include-relative-cfg-in-svn.t b/t/fcm-make/33-include-relative-cfg-in-svn.t
new file mode 100755
index 0000000..1eab85f
--- /dev/null
+++ b/t/fcm-make/33-include-relative-cfg-in-svn.t
@@ -0,0 +1,73 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", include relative config in a Subversion repository
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 12
+#-------------------------------------------------------------------------------
+mkdir etc
+cat >etc/fcm-make-build.cfg <<'__CFG__'
+steps=build
+build.source=src
+build.target=hello.exe
+__CFG__
+
+mkdir src
+cat >src/hello.f90 <<'__FORTRAN__'
+program hello
+write(*, '(a)') 'Hello World!'
+end program hello
+__FORTRAN__
+
+svnadmin create svn-repos
+svn import -m 'test stuff' etc file://$PWD/svn-repos/etc
+rm -fr etc
+
+#-------------------------------------------------------------------------------
+cat >fcm-make.cfg <<'__CFG__'
+include = fcm-make-build.cfg
+__CFG__
+
+fcm_make_build_hello_tests \
+    "$TEST_KEY_BASE-config-file-path" '.exe' -F "file://$PWD/svn-repos/etc"
+#-------------------------------------------------------------------------------
+cat >fcm-make.cfg <<'__CFG__'
+include = fcm-make-build.cfg
+__CFG__
+
+fcm_make_build_hello_tests\
+    "$TEST_KEY_BASE-config-file-path-1" '.exe' -F "file://$PWD/svn-repos/etc@1"
+#-------------------------------------------------------------------------------
+cat >fcm-make.cfg <<'__CFG__'
+include-path=file://$PWD/svn-repos/etc
+include=fcm-make-build.cfg
+__CFG__
+
+fcm_make_build_hello_tests "$TEST_KEY_BASE-include-paths" '.exe'
+#-------------------------------------------------------------------------------
+cat >fcm-make.cfg <<'__CFG__'
+include-path=file://$PWD/svn-repos/etc@1
+include=fcm-make-build.cfg
+__CFG__
+
+fcm_make_build_hello_tests "$TEST_KEY_BASE-include-path-1" '.exe'
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/34-include-relative-cfg-in-ssh.t b/t/fcm-make/34-include-relative-cfg-in-ssh.t
new file mode 100755
index 0000000..645e56d
--- /dev/null
+++ b/t/fcm-make/34-include-relative-cfg-in-ssh.t
@@ -0,0 +1,75 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", include relative config in a remote host accessible via SSH
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+# Get a remote host for testing
+T_HOST=
+for FILE in $HOME/.metomi/fcm/t.cfg $FCM_HOME/etc/fcm/t.cfg; do
+    if [[ ! -f $FILE || ! -r $FILE ]]; then
+        continue
+    fi
+    T_HOST=$(fcm cfg $FILE | sed '/^ *host *=/!d; s/^ *host *= *//' | tail -1)
+    if [[ -n $T_HOST ]]; then
+        break
+    fi
+done
+if [[ -z $T_HOST ]]; then
+    skip_all 'fcm/t.cfg: "host" not defined'
+fi
+#-------------------------------------------------------------------------------
+tests 6
+#-------------------------------------------------------------------------------
+mkdir etc
+cat >etc/fcm-make-build.cfg <<'__CFG__'
+steps=build
+build.source=src
+build.target=hello.exe
+__CFG__
+
+T_HOST_WORK_DIR=$(ssh -oBatchMode=yes $T_HOST mktemp -d)
+rsync -a etc $T_HOST:$T_HOST_WORK_DIR
+rm -r etc
+
+mkdir src
+cat >src/hello.f90 <<'__FORTRAN__'
+program hello
+write(*, '(a)') 'Hello World!'
+end program hello
+__FORTRAN__
+
+#-------------------------------------------------------------------------------
+cat >fcm-make.cfg <<'__CFG__'
+include = fcm-make-build.cfg
+__CFG__
+
+fcm_make_build_hello_tests "$TEST_KEY_BASE-config-file-path" '.exe' \
+    -F "$T_HOST:$T_HOST_WORK_DIR/etc"
+#-------------------------------------------------------------------------------
+cat >fcm-make.cfg <<__CFG__
+include-path=$T_HOST:$T_HOST_WORK_DIR/etc
+include=fcm-make-build.cfg
+__CFG__
+
+fcm_make_build_hello_tests "$TEST_KEY_BASE-include-paths" '.exe'
+#-------------------------------------------------------------------------------
+ssh -oBatchMode=yes $T_HOST rm -r $T_HOST_WORK_DIR
+exit 0
diff --git a/t/fcm-make/35-include-relative-cfg-in-2-dirs.t b/t/fcm-make/35-include-relative-cfg-in-2-dirs.t
new file mode 100755
index 0000000..1e0bb22
--- /dev/null
+++ b/t/fcm-make/35-include-relative-cfg-in-2-dirs.t
@@ -0,0 +1,62 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", include relative config
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 6
+#-------------------------------------------------------------------------------
+mkdir cfg1 cfg2
+cat >cfg1/fcm-make-head.cfg <<'__CFG__'
+steps=build
+build.source=src
+__CFG__
+cat >cfg1/fcm-make-tail.cfg <<'__CFG__'
+build.target=hello.exe
+__CFG__
+cat >cfg2/fcm-make-tail.cfg <<'__CFG__'
+build.target=hello
+build.prop{file-ext.bin}=
+__CFG__
+
+mkdir src
+cat >src/hello.f90 <<'__FORTRAN__'
+program hello
+write(*, '(a)') 'Hello World!'
+end program hello
+__FORTRAN__
+
+#-------------------------------------------------------------------------------
+cat >fcm-make.cfg <<'__CFG__'
+include-path = $HERE/cfg1 $HERE/cfg2
+include = fcm-make-head.cfg fcm-make-tail.cfg
+__CFG__
+
+fcm_make_build_hello_tests "$TEST_KEY_BASE-1" '.exe'
+#-------------------------------------------------------------------------------
+cat >fcm-make.cfg <<'__CFG__'
+include-path = $HERE/cfg2
+include-path{+} = $HERE/cfg1
+include = fcm-make-head.cfg fcm-make-tail.cfg
+__CFG__
+
+fcm_make_build_hello_tests "$TEST_KEY_BASE-2" ''
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/36-build-fail-cont-basic.t b/t/fcm-make/36-build-fail-cont-basic.t
new file mode 100644
index 0000000..b62af82
--- /dev/null
+++ b/t/fcm-make/36-build-fail-cont-basic.t
@@ -0,0 +1,135 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Tests "fcm make", build, continue on failure.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+
+task_lines_from_log() {
+    sed '
+        /\[\(info\|FAIL\)\] \(compile\+\?\|ext-iface\|install\|link\)  *\(----\|[0-9][0-9]*\.[0-9][0-9]*\) /!d
+        s/  [0-9][0-9]*\.[0-9][0-9]* / ???? /
+    ' fcm-make.log
+}
+
+fail_lines_from_log() {
+    sed '/\[FAIL\] !/!d' fcm-make.log
+}
+
+#-------------------------------------------------------------------------------
+tests 12
+#-------------------------------------------------------------------------------
+cp -r $TEST_SOURCE_DIR/$TEST_KEY_BASE/* .
+#-------------------------------------------------------------------------------
+# Break hello1 and hello2
+TEST_KEY="$TEST_KEY_BASE-1-2-bad"
+sed -i 's/implicit none/implicit non/' src/greet_mod.f90  # introduce typo
+run_fail "$TEST_KEY" fcm make --new
+task_lines_from_log >"$TEST_KEY-log-tasks"
+file_cmp "$TEST_KEY-log-tasks" "$TEST_KEY-log-tasks" <<'__LOG__'
+[FAIL] compile   ???? ! greet_mod.o          <- greet_mod.f90
+[info] compile   ???? M world_mod.o          <- world_mod.f90
+[FAIL] compile   ---- ! hello.o              <- hello.f90
+[FAIL] compile   ---- ! hello2.o             <- hello2.f90
+[info] compile   ???? M hello_sub.o          <- hello_sub.f90
+[info] ext-iface ???? M hello_sub.interface  <- hello_sub.f90
+[info] compile   ???? M hello3.o             <- hello3.f90
+[info] link      ???? M hello3               <- hello3.f90
+[info] compile   ???? M hello4.o             <- hello4.f90
+[info] link      ???? M hello4               <- hello4.f90
+__LOG__
+fail_lines_from_log >"$TEST_KEY-log-fails"
+file_cmp "$TEST_KEY-log-fails" "$TEST_KEY-log-fails" <<'__LOG__'
+[FAIL] ! greet_mod.mod       : depends on failed target: greet_mod.o
+[FAIL] ! greet_mod.o         : update task failed
+[FAIL] ! hello               : depends on failed target: hello.o
+[FAIL] ! hello.o             : depends on failed target: greet_mod.mod
+[FAIL] ! hello2              : depends on failed target: hello2.o
+[FAIL] ! hello2.o            : depends on failed target: greet_mod.mod
+__LOG__
+
+TEST_KEY="$TEST_KEY_BASE-1-2-fix"
+sed -i 's/implicit non/implicit none/' src/greet_mod.f90  # fix typo
+run_pass "$TEST_KEY" fcm make
+task_lines_from_log >"$TEST_KEY-log-tasks"
+file_cmp "$TEST_KEY-log-tasks" "$TEST_KEY-log-tasks" <<'__LOG__'
+[info] compile   ???? M greet_mod.o          <- greet_mod.f90
+[info] compile   ---- U world_mod.o          <- world_mod.f90
+[info] compile   ???? M hello.o              <- hello.f90
+[info] link      ???? M hello                <- hello.f90
+[info] compile   ???? M hello2.o             <- hello2.f90
+[info] link      ???? M hello2               <- hello2.f90
+[info] compile   ---- U hello_sub.o          <- hello_sub.f90
+[info] ext-iface ---- U hello_sub.interface  <- hello_sub.f90
+[info] compile   ---- U hello3.o             <- hello3.f90
+[info] link      ---- U hello3               <- hello3.f90
+[info] compile   ---- U hello4.o             <- hello4.f90
+[info] link      ---- U hello4               <- hello4.f90
+__LOG__
+fail_lines_from_log >"$TEST_KEY-log-fails"
+file_cmp "$TEST_KEY-log-fails" "$TEST_KEY-log-fails" </dev/null
+#-------------------------------------------------------------------------------
+# Break hello3 and hello4
+TEST_KEY="$TEST_KEY_BASE-3-4-bad"
+sed -i 's/implicit none/implicit non/' src/hello_sub.f90  # introduce typo
+run_fail "$TEST_KEY" fcm make --new
+task_lines_from_log >"$TEST_KEY-log-tasks"
+file_cmp "$TEST_KEY-log-tasks" "$TEST_KEY-log-tasks" <<'__LOG__'
+[info] compile   ???? M greet_mod.o          <- greet_mod.f90
+[info] compile   ???? M world_mod.o          <- world_mod.f90
+[info] compile   ???? M hello.o              <- hello.f90
+[info] link      ???? M hello                <- hello.f90
+[info] compile   ???? M hello2.o             <- hello2.f90
+[info] link      ???? M hello2               <- hello2.f90
+[FAIL] compile   ???? ! hello_sub.o          <- hello_sub.f90
+[info] ext-iface ???? M hello_sub.interface  <- hello_sub.f90
+[info] compile   ???? M hello3.o             <- hello3.f90
+[FAIL] link      ---- ! hello3               <- hello3.f90
+[info] compile   ???? M hello4.o             <- hello4.f90
+[FAIL] link      ---- ! hello4               <- hello4.f90
+__LOG__
+fail_lines_from_log >"$TEST_KEY-log-fails"
+file_cmp "$TEST_KEY-log-fails" "$TEST_KEY-log-fails" <<'__LOG__'
+[FAIL] ! hello3              : depends on failed target: hello_sub.o
+[FAIL] ! hello4              : depends on failed target: hello_sub.o
+[FAIL] ! hello_sub.o         : update task failed
+__LOG__
+
+TEST_KEY="$TEST_KEY_BASE-3-4-fix"
+sed -i 's/implicit non/implicit none/' src/hello_sub.f90  # fix typo
+run_pass "$TEST_KEY" fcm make
+task_lines_from_log >"$TEST_KEY-log-tasks"
+file_cmp "$TEST_KEY-log-tasks" "$TEST_KEY-log-tasks" <<'__LOG__'
+[info] compile   ---- U greet_mod.o          <- greet_mod.f90
+[info] compile   ---- U world_mod.o          <- world_mod.f90
+[info] compile   ---- U hello.o              <- hello.f90
+[info] link      ---- U hello                <- hello.f90
+[info] compile   ---- U hello2.o             <- hello2.f90
+[info] link      ---- U hello2               <- hello2.f90
+[info] compile   ???? M hello_sub.o          <- hello_sub.f90
+[info] ext-iface ???? U hello_sub.interface  <- hello_sub.f90
+[info] compile   ---- U hello3.o             <- hello3.f90
+[info] link      ???? M hello3               <- hello3.f90
+[info] compile   ---- U hello4.o             <- hello4.f90
+[info] link      ???? M hello4               <- hello4.f90
+__LOG__
+fail_lines_from_log >"$TEST_KEY-log-fails"
+file_cmp "$TEST_KEY-log-fails" "$TEST_KEY-log-fails" </dev/null
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/36-build-fail-cont-basic/fcm-make.cfg b/t/fcm-make/36-build-fail-cont-basic/fcm-make.cfg
new file mode 100644
index 0000000..9216a4d
--- /dev/null
+++ b/t/fcm-make/36-build-fail-cont-basic/fcm-make.cfg
@@ -0,0 +1,4 @@
+steps=build
+build.source=$HERE/src
+build.target{task}=link
+build.prop{file-ext.bin}=
diff --git a/t/fcm-make/36-build-fail-cont-basic/src/greet_mod.f90 b/t/fcm-make/36-build-fail-cont-basic/src/greet_mod.f90
new file mode 100644
index 0000000..a701b34
--- /dev/null
+++ b/t/fcm-make/36-build-fail-cont-basic/src/greet_mod.f90
@@ -0,0 +1,9 @@
+module greet_mod
+implicit none
+character(*), parameter :: greet_word = 'Hello'
+contains
+subroutine greet(world)
+character(*), intent(in) :: world
+write(*, '(a,1x,a)') greet_word, world
+end subroutine greet
+end module greet_mod
diff --git a/t/fcm-make/36-build-fail-cont-basic/src/hello.f90 b/t/fcm-make/36-build-fail-cont-basic/src/hello.f90
new file mode 100644
index 0000000..49c2820
--- /dev/null
+++ b/t/fcm-make/36-build-fail-cont-basic/src/hello.f90
@@ -0,0 +1,6 @@
+program hello
+use greet_mod, only: greet
+use world_mod, only: world
+implicit none
+call greet(world)
+end program hello
diff --git a/t/fcm-make/36-build-fail-cont-basic/src/hello2.f90 b/t/fcm-make/36-build-fail-cont-basic/src/hello2.f90
new file mode 100644
index 0000000..0f0a921
--- /dev/null
+++ b/t/fcm-make/36-build-fail-cont-basic/src/hello2.f90
@@ -0,0 +1,6 @@
+program hello2
+use greet_mod, only: greet
+use world_mod, only: world
+implicit none
+call greet(trim(world))
+end program hello2
diff --git a/t/fcm-make/36-build-fail-cont-basic/src/hello3.f90 b/t/fcm-make/36-build-fail-cont-basic/src/hello3.f90
new file mode 100644
index 0000000..2b0cc73
--- /dev/null
+++ b/t/fcm-make/36-build-fail-cont-basic/src/hello3.f90
@@ -0,0 +1,6 @@
+program hello3
+use world_mod, only: world
+implicit none
+include 'hello_sub.interface'
+call hello_sub(world)
+end program hello3
diff --git a/t/fcm-make/36-build-fail-cont-basic/src/hello4.f90 b/t/fcm-make/36-build-fail-cont-basic/src/hello4.f90
new file mode 100644
index 0000000..7a88936
--- /dev/null
+++ b/t/fcm-make/36-build-fail-cont-basic/src/hello4.f90
@@ -0,0 +1,5 @@
+program hello4
+implicit none
+include 'hello_sub.interface'
+call hello_sub('Earth')
+end program hello4
diff --git a/t/fcm-make/36-build-fail-cont-basic/src/hello_sub.f90 b/t/fcm-make/36-build-fail-cont-basic/src/hello_sub.f90
new file mode 100644
index 0000000..c854671
--- /dev/null
+++ b/t/fcm-make/36-build-fail-cont-basic/src/hello_sub.f90
@@ -0,0 +1,5 @@
+subroutine hello_sub(world)
+implicit none
+character(*), intent(in) :: world
+write(*, '(a)'), 'Hello ' // trim(world)
+end subroutine hello_sub
diff --git a/t/fcm-make/36-build-fail-cont-basic/src/world_mod.f90 b/t/fcm-make/36-build-fail-cont-basic/src/world_mod.f90
new file mode 100644
index 0000000..9f9fb72
--- /dev/null
+++ b/t/fcm-make/36-build-fail-cont-basic/src/world_mod.f90
@@ -0,0 +1,4 @@
+module world_mod
+implicit none
+character(*), parameter :: world = 'Earth'
+end module world_mod
diff --git a/t/fcm-make/test_header b/t/fcm-make/test_header
new file mode 120000
index 0000000..90bd5a3
--- /dev/null
+++ b/t/fcm-make/test_header
@@ -0,0 +1 @@
+../lib/bash/test_header
\ No newline at end of file
diff --git a/t/fcm-merge/00-simple.t b/t/fcm-merge/00-simple.t
new file mode 100644
index 0000000..7e3b1d7
--- /dev/null
+++ b/t/fcm-merge/00-simple.t
@@ -0,0 +1,321 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm merge".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 18
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_merge_branches merge1 merge2 $REPOS_URL
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm merge --dry-run
+TEST_KEY=$TEST_KEY_BASE-dry-run
+export SVN_EDITOR="sed -i 1i\foo"
+run_pass "$TEST_KEY" fcm merge --dry-run $ROOT_URL/branches/dev/Share/merge1
+if $SVN_VERSION_IS_16; then
+    START_REV=2
+else
+    START_REV=4
+fi
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Eligible merge(s) from /${PROJECT}branches/dev/Share/merge1 at 9: 5
+--------------------------------------------------------------------------------
+Merge: /${PROJECT}branches/dev/Share/merge1 at 5
+ c.f.: /${PROJECT}trunk at 1
+-------------------------------------------------------------------------dry-run
+--- Merging r$START_REV through r5 into '.':
+U    subroutine/hello_sub_dummy.h
+A    added_file
+A    added_directory
+A    added_directory/hello_constants_dummy.inc
+A    added_directory/hello_constants.inc
+A    added_directory/hello_constants.f90
+A    module/tree_conflict_file
+U    module/hello_constants_dummy.inc
+U    module/hello_constants.inc
+U    module/hello_constants.f90
+U    lib/python/info/poems.py
+-------------------------------------------------------------------------dry-run
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn status result of fcm merge --dry-run
+TEST_KEY=$TEST_KEY_BASE-dry-run-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+?       unversioned_file
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn diff result of fcm merge --dry-run
+TEST_KEY=$TEST_KEY_BASE-dry-run-diff
+run_pass "$TEST_KEY" svn diff
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm merge --non-interactive
+TEST_KEY=$TEST_KEY_BASE-non-interactive
+export SVN_EDITOR="sed -i 1i\foo" 
+run_pass "$TEST_KEY" fcm merge --non-interactive $ROOT_URL/branches/dev/Share/merge1
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Eligible merge(s) from /${PROJECT}branches/dev/Share/merge1 at 9: 5
+--------------------------------------------------------------------------------
+Merge: /${PROJECT}branches/dev/Share/merge1 at 5
+ c.f.: /${PROJECT}trunk at 1
+Merge succeeded.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Eligible merge(s) from /${PROJECT}branches/dev/Share/merge1 at 9: 5
+--------------------------------------------------------------------------------
+Merge: /${PROJECT}branches/dev/Share/merge1 at 5
+ c.f.: /${PROJECT}trunk at 1
+Merge succeeded.
+--------------------------------------------------------------------------actual
+--- Merging r$START_REV through r5 into '.':
+U    subroutine/hello_sub_dummy.h
+A    added_file
+A    added_directory
+A    added_directory/hello_constants_dummy.inc
+A    added_directory/hello_constants.inc
+A    added_directory/hello_constants.f90
+A    module/tree_conflict_file
+U    module/hello_constants_dummy.inc
+U    module/hello_constants.inc
+U    module/hello_constants.f90
+U    lib/python/info/poems.py
+--- Recording mergeinfo for merge of r$START_REV through r5 into '.':
+ U   .
+--------------------------------------------------------------------------actual
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn status result of fcm merge --non-interactive
+TEST_KEY=$TEST_KEY_BASE-non-interactive-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+sort $TEST_DIR/"$TEST_KEY.out" -o $TEST_DIR/"$TEST_KEY.out"
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+?       unversioned_file
+A  +    added_directory
+A  +    added_directory/hello_constants.f90
+A  +    added_directory/hello_constants.inc
+A  +    added_directory/hello_constants_dummy.inc
+A  +    added_file
+A  +    module/tree_conflict_file
+M       lib/python/info/poems.py
+M       module/hello_constants.f90
+M       module/hello_constants.inc
+M       module/hello_constants_dummy.inc
+M       subroutine/hello_sub_dummy.h
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+?       unversioned_file
+A  +    added_directory
+A  +    added_file
+A  +    module/tree_conflict_file
+M       lib/python/info/poems.py
+M       module/hello_constants.f90
+M       module/hello_constants.inc
+M       module/hello_constants_dummy.inc
+M       subroutine/hello_sub_dummy.h
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn diff result of fcm merge --non-interactive
+TEST_KEY=$TEST_KEY_BASE-non-interactive-diff
+run_pass "$TEST_KEY" svn diff
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+
+Property changes on: .
+___________________________________________________________________
+Added: svn:mergeinfo
+   Merged /${PROJECT}branches/dev/Share/merge1:r4-5
+
+Index: subroutine/hello_sub_dummy.h
+===================================================================
+--- subroutine/hello_sub_dummy.h	(revision 9)
++++ subroutine/hello_sub_dummy.h	(working copy)
+@@ -1 +1,2 @@
+ #include "hello_sub.h"
++Modified a line
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	(revision 9)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +1 @@
+-INCLUDE 'hello_constants.inc'
++INCLUDE 'hello_constants.INc'
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	(revision 9)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +1,2 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	(revision 9)
++++ module/hello_constants.f90	(working copy)
+@@ -1,5 +1,5 @@
+ MODULE Hello_Constants
+ 
+-INCLUDE 'hello_constants_dummy.inc'
++INCLUDE 'hello_constants_dummy.INc'
+ 
+ END MODULE Hello_Constants
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	(revision 9)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	(revision 9)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	(revision 9)
++++ module/hello_constants.f90	(working copy)
+@@ -1,5 +1,5 @@
+ MODULE Hello_Constants
+ 
+-INCLUDE 'hello_constants_dummy.inc'
++INCLUDE 'hello_constants_dummy.INc'
+ 
+ END MODULE Hello_Constants
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	(revision 9)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +1,2 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	(revision 9)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +1 @@
+-INCLUDE 'hello_constants.inc'
++INCLUDE 'hello_constants.INc'
+Index: subroutine/hello_sub_dummy.h
+===================================================================
+--- subroutine/hello_sub_dummy.h	(revision 9)
++++ subroutine/hello_sub_dummy.h	(working copy)
+@@ -1 +1,2 @@
+ #include "hello_sub.h"
++Modified a line
+Index: .
+===================================================================
+--- .	(revision 9)
++++ .	(working copy)
+
+Property changes on: .
+___________________________________________________________________
+Added: svn:mergeinfo
+   Merged /${PROJECT}branches/dev/Share/merge1:r4-5
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-merge/01-complex.t b/t/fcm-merge/01-complex.t
new file mode 100644
index 0000000..66fb4f6
--- /dev/null
+++ b/t/fcm-merge/01-complex.t
@@ -0,0 +1,1646 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# More complex tests for "fcm merge".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 90
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_merge_branches merge1 merge2 $REPOS_URL
+export SVN_EDITOR="sed -i 1i\foo"
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm merge of trunk-into-branch (1)
+svn switch -q $ROOT_URL/branches/dev/Share/merge1
+TEST_KEY=$TEST_KEY_BASE-trunk-into-branch-1-non-root
+cd module
+run_pass "$TEST_KEY" fcm merge --non-interactive trunk
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+$TEST_DIR/wc: working directory changed to top of working copy.
+Eligible merge(s) from /${PROJECT}trunk at 9: 9 8
+--------------------------------------------------------------------------------
+Merge: /${PROJECT}trunk at 9
+ c.f.: /${PROJECT}trunk at 1
+Merge succeeded.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+$TEST_DIR/wc: working directory changed to top of working copy.
+Eligible merge(s) from /${PROJECT}trunk at 9: 9 8
+--------------------------------------------------------------------------------
+Merge: /${PROJECT}trunk at 9
+ c.f.: /${PROJECT}trunk at 1
+Merge succeeded.
+--------------------------------------------------------------------------actual
+--- Merging r2 through r9 into '.':
+U    lib/python/info/__init__.py
+--- Recording mergeinfo for merge of r2 through r9 into '.':
+ U   .
+--------------------------------------------------------------------------actual
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+cd ..
+#-------------------------------------------------------------------------------
+# Tests svn status result of fcm merge (1)
+TEST_KEY=$TEST_KEY_BASE-trunk-into-branch-1-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+sort $TEST_DIR/"$TEST_KEY.out" -o $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+?       unversioned_file
+M       lib/python/info/__init__.py
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn diff result of fcm merge (1)
+TEST_KEY=$TEST_KEY_BASE-trunk-into-branch-1-diff
+run_pass "$TEST_KEY" svn diff
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+
+Property changes on: .
+___________________________________________________________________
+Added: svn:mergeinfo
+   Merged /${PROJECT}trunk:r2-9
+
+Index: lib/python/info/__init__.py
+===================================================================
+--- lib/python/info/__init__.py	(revision 9)
++++ lib/python/info/__init__.py	(working copy)
+@@ -0,0 +1,2 @@
++trunk change
++another trunk change
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: lib/python/info/__init__.py
+===================================================================
+--- lib/python/info/__init__.py	(revision 9)
++++ lib/python/info/__init__.py	(working copy)
+@@ -0,0 +1,2 @@
++trunk change
++another trunk change
+Index: .
+===================================================================
+--- .	(revision 9)
++++ .	(working copy)
+
+Property changes on: .
+___________________________________________________________________
+Added: svn:mergeinfo
+   Merged /${PROJECT}trunk:r2-9
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm commit of fcm merge (1)
+TEST_KEY=$TEST_KEY_BASE-trunk-into-branch-1-commit
+run_pass "$TEST_KEY" fcm commit <<__IN__
+y
+__IN__
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] sed -i 1i\foo: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Root   : $REPOS_URL]
+[Project: ${TEST_PROJECT:-}]
+[Branch : branches/dev/Share/merge1]
+[Sub-dir: ]
+
+ M      .
+M       lib/python/info/__init__.py
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+foo
+Merged into /${PROJECT}branches/dev/Share/merge1: /${PROJECT}trunk at 9 cf. /${PROJECT}trunk at 1
+--------------------------------------------------------------------------------
+
+*** WARNING: YOU ARE COMMITTING TO A Share BRANCH.
+*** Please ensure that you have the owner's permission.
+
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): Sending        .
+Sending        lib/python/info/__init__.py
+Transmitting file data .
+Committed revision 10.
+At revision 10.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] sed -i 1i\foo: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Root   : $REPOS_URL]
+[Project: ${TEST_PROJECT:-}]
+[Branch : branches/dev/Share/merge1]
+[Sub-dir: ]
+
+ M      .
+M       lib/python/info/__init__.py
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+foo
+Merged into /${PROJECT}branches/dev/Share/merge1: /${PROJECT}trunk at 9 cf. /${PROJECT}trunk at 1
+--------------------------------------------------------------------------------
+
+*** WARNING: YOU ARE COMMITTING TO A Share BRANCH.
+*** Please ensure that you have the owner's permission.
+
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): Sending        .
+Sending        lib/python/info/__init__.py
+Transmitting file data .
+Committed revision 10.
+Updating '.':
+At revision 10.
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm log of fcm merge (1)
+TEST_KEY=$TEST_KEY_BASE-trunk-into-branch-1-log
+run_pass "$TEST_KEY" fcm log
+sed -i "s/\(.*|.*|\).*\(|.*\)$/\1 date \2/g" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+------------------------------------------------------------------------
+r10 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}branches/dev/Share/merge1: /${PROJECT}trunk at 9 cf. /${PROJECT}trunk at 1
+
+------------------------------------------------------------------------
+r5 | $LOGNAME | date | 1 line
+
+Made changes for future merge of this branch
+------------------------------------------------------------------------
+r4 | $LOGNAME | date | 1 line
+
+Made a branch Created /${PROJECT}branches/dev/Share/merge1 from /trunk at 1.
+------------------------------------------------------------------------
+r1 | $LOGNAME | date | 1 line
+
+initial trunk import
+------------------------------------------------------------------------
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm merge of branch-into-trunk (1)
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-1
+BRANCH_MOD_FILE=$(find . -type f | sed "/\.svn/d" | sort | head -3| tail -1)
+echo "# added this line for simple repeat testing" >>$BRANCH_MOD_FILE
+svn commit -q -m "edit on branch for merge repeat test"
+svn update -q
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/trunk $TEST_DIR/wc
+cd $TEST_DIR/wc
+run_pass "$TEST_KEY" fcm merge --non-interactive branches/dev/Share/merge1
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Eligible merge(s) from /${PROJECT}branches/dev/Share/merge1 at 11: 11 10
+--------------------------------------------------------------------------------
+Merge: /${PROJECT}branches/dev/Share/merge1 at 11
+ c.f.: /${PROJECT}trunk at 9
+Merge succeeded.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Eligible merge(s) from /${PROJECT}branches/dev/Share/merge1 at 11: 11 10
+--------------------------------------------------------------------------------
+Merge: /${PROJECT}branches/dev/Share/merge1 at 11
+ c.f.: /${PROJECT}trunk at 9
+Merge succeeded.
+--------------------------------------------------------------------------actual
+--- Merging differences between repository URLs into '.':
+U    subroutine/hello_sub_dummy.h
+A    added_file
+A    added_directory
+A    added_directory/hello_constants_dummy.inc
+A    added_directory/hello_constants.inc
+A    added_directory/hello_constants.f90
+A    module/tree_conflict_file
+U    module/hello_constants_dummy.inc
+U    module/hello_constants.inc
+U    module/hello_constants.f90
+U    lib/python/info/poems.py
+--- Recording mergeinfo for merge between repository URLs into '.':
+ U   .
+--------------------------------------------------------------------------actual
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn status result of fcm merge (1)
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-1-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+M       subroutine/hello_sub_dummy.h
+A  +    added_file
+A  +    module/tree_conflict_file
+M       module/hello_constants_dummy.inc
+M       module/hello_constants.inc
+M       module/hello_constants.f90
+A  +    added_directory
+A  +    added_directory/hello_constants_dummy.inc
+A  +    added_directory/hello_constants.inc
+A  +    added_directory/hello_constants.f90
+M       lib/python/info/poems.py
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+A  +    added_directory
+A  +    added_file
+M       lib/python/info/poems.py
+M       module/hello_constants.f90
+M       module/hello_constants.inc
+M       module/hello_constants_dummy.inc
+A  +    module/tree_conflict_file
+M       subroutine/hello_sub_dummy.h
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn diff result of fcm merge (1)
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-1-diff
+run_pass "$TEST_KEY" svn diff
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+
+Property changes on: .
+___________________________________________________________________
+Added: svn:mergeinfo
+   Merged /${PROJECT}branches/dev/Share/merge1:r2-11
+
+Index: subroutine/hello_sub_dummy.h
+===================================================================
+--- subroutine/hello_sub_dummy.h	(revision 11)
++++ subroutine/hello_sub_dummy.h	(working copy)
+@@ -1 +1,2 @@
+ #include "hello_sub.h"
++Modified a line
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	(revision 11)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +1 @@
+-INCLUDE 'hello_constants.inc'
++INCLUDE 'hello_constants.INc'
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	(revision 11)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +1,2 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	(revision 11)
++++ module/hello_constants.f90	(working copy)
+@@ -1,5 +1,5 @@
+ MODULE Hello_Constants
+ 
+-INCLUDE 'hello_constants_dummy.inc'
++INCLUDE 'hello_constants_dummy.INc'
+ 
+ END MODULE Hello_Constants
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	(revision 11)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	(revision 11)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	(revision 11)
++++ module/hello_constants.f90	(working copy)
+@@ -1,5 +1,5 @@
+ MODULE Hello_Constants
+ 
+-INCLUDE 'hello_constants_dummy.inc'
++INCLUDE 'hello_constants_dummy.INc'
+ 
+ END MODULE Hello_Constants
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	(revision 11)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +1,2 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	(revision 11)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +1 @@
+-INCLUDE 'hello_constants.inc'
++INCLUDE 'hello_constants.INc'
+Index: subroutine/hello_sub_dummy.h
+===================================================================
+--- subroutine/hello_sub_dummy.h	(revision 11)
++++ subroutine/hello_sub_dummy.h	(working copy)
+@@ -1 +1,2 @@
+ #include "hello_sub.h"
++Modified a line
+Index: .
+===================================================================
+--- .	(revision 11)
++++ .	(working copy)
+
+Property changes on: .
+___________________________________________________________________
+Added: svn:mergeinfo
+   Merged /${PROJECT}branches/dev/Share/merge1:r4-11
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm commit of fcm merge (1)
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-1-commit
+run_pass "$TEST_KEY" fcm commit <<__IN__
+y
+__IN__
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] sed -i 1i\foo: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Root   : $REPOS_URL]
+[Project: ${TEST_PROJECT:-}]
+[Branch : trunk]
+[Sub-dir: ]
+
+ M      .
+M       subroutine/hello_sub_dummy.h
+A  +    added_file
+A  +    module/tree_conflict_file
+M       module/hello_constants_dummy.inc
+M       module/hello_constants.inc
+M       module/hello_constants.f90
+A  +    added_directory
+A  +    added_directory/hello_constants_dummy.inc
+A  +    added_directory/hello_constants.inc
+A  +    added_directory/hello_constants.f90
+M       lib/python/info/poems.py
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+foo
+Merged into /${PROJECT}trunk: /${PROJECT}branches/dev/Share/merge1 at 11 cf. /${PROJECT}trunk at 9
+--------------------------------------------------------------------------------
+
+*** WARNING: YOU ARE COMMITTING TO THE TRUNK.
+*** Please ensure that your change conforms to your project's working practices.
+
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): Sending        .
+Adding         added_directory
+Adding         added_directory/hello_constants.f90
+Adding         added_directory/hello_constants.inc
+Adding         added_directory/hello_constants_dummy.inc
+Adding         added_file
+Sending        lib/python/info/poems.py
+Sending        module/hello_constants.f90
+Sending        module/hello_constants.inc
+Sending        module/hello_constants_dummy.inc
+Adding         module/tree_conflict_file
+Sending        subroutine/hello_sub_dummy.h
+Transmitting file data .....
+Committed revision 12.
+At revision 12.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] sed -i 1i\foo: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Root   : $REPOS_URL]
+[Project: ${TEST_PROJECT:-}]
+[Branch : trunk]
+[Sub-dir: ]
+
+ M      .
+A  +    added_directory
+A  +    added_file
+M       lib/python/info/poems.py
+M       module/hello_constants.f90
+M       module/hello_constants.inc
+M       module/hello_constants_dummy.inc
+A  +    module/tree_conflict_file
+M       subroutine/hello_sub_dummy.h
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+foo
+Merged into /${PROJECT}trunk: /${PROJECT}branches/dev/Share/merge1 at 11 cf. /${PROJECT}trunk at 9
+--------------------------------------------------------------------------------
+
+*** WARNING: YOU ARE COMMITTING TO THE TRUNK.
+*** Please ensure that your change conforms to your project's working practices.
+
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): Sending        .
+Adding         added_directory
+Adding         added_file
+Sending        lib/python/info/poems.py
+Sending        module/hello_constants.f90
+Sending        module/hello_constants.inc
+Sending        module/hello_constants_dummy.inc
+Adding         module/tree_conflict_file
+Sending        subroutine/hello_sub_dummy.h
+Transmitting file data .....
+Committed revision 12.
+Updating '.':
+At revision 12.
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm log of fcm merge branch-into-trunk (1)
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-1-log
+run_pass "$TEST_KEY" fcm log
+sed -i "s/\(.*|.*|\).*\(|.*\)$/\1 date \2/g" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+------------------------------------------------------------------------
+r12 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}trunk: /${PROJECT}branches/dev/Share/merge1 at 11 cf. /${PROJECT}trunk at 9
+
+------------------------------------------------------------------------
+r9 | $LOGNAME | date | 1 line
+
+Made another trunk change
+------------------------------------------------------------------------
+r8 | $LOGNAME | date | 1 line
+
+Made trunk change
+------------------------------------------------------------------------
+r1 | $LOGNAME | date | 1 line
+
+initial trunk import
+------------------------------------------------------------------------
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm merge of branch-into-trunk (2)
+svn switch -q $ROOT_URL/branches/dev/Share/merge1
+MOD_FILE=$(find . -type f | sed "/\.svn/d" | sort | head -4 | tail -1)
+echo "call_extra_feature()" >>$MOD_FILE
+svn commit -q -m "Made branch change to add extra feature"
+svn update -q
+svn switch -q $ROOT_URL/trunk
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-2
+run_pass "$TEST_KEY" fcm merge --non-interactive branches/dev/Share/merge1
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Eligible merge(s) from /${PROJECT}branches/dev/Share/merge1 at 13: 13
+--------------------------------------------------------------------------------
+Merge: /${PROJECT}branches/dev/Share/merge1 at 13
+ c.f.: /${PROJECT}branches/dev/Share/merge1 at 11
+Merge succeeded.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Eligible merge(s) from /${PROJECT}branches/dev/Share/merge1 at 13: 13
+--------------------------------------------------------------------------------
+Merge: /${PROJECT}branches/dev/Share/merge1 at 13
+ c.f.: /${PROJECT}branches/dev/Share/merge1 at 11
+Merge succeeded.
+--------------------------------------------------------------------------actual
+--- Merging r12 through r13 into '.':
+U    added_file
+--- Recording mergeinfo for merge of r12 through r13 into '.':
+ U   .
+--------------------------------------------------------------------------actual
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn status result of fcm merge branch-into-trunk (2)
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-2-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+M       added_file
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn diff result of fcm merge branch-into-trunk (2)
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-2-diff
+run_pass "$TEST_KEY" svn diff
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+
+Property changes on: .
+___________________________________________________________________
+Modified: svn:mergeinfo
+   Merged /${PROJECT}branches/dev/Share/merge1:r12-13
+
+Index: added_file
+===================================================================
+--- added_file	(revision 13)
++++ added_file	(working copy)
+@@ -1 +1,2 @@
+ INCLUDE 'hello_constants.INc'
++call_extra_feature()
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: added_file
+===================================================================
+--- added_file	(revision 13)
++++ added_file	(working copy)
+@@ -1 +1,2 @@
+ INCLUDE 'hello_constants.INc'
++call_extra_feature()
+Index: .
+===================================================================
+--- .	(revision 13)
++++ .	(working copy)
+
+Property changes on: .
+___________________________________________________________________
+Modified: svn:mergeinfo
+   Merged /${PROJECT}branches/dev/Share/merge1:r12-13
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm commit of fcm merge branch-into-trunk (2)
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-2-commit
+run_pass "$TEST_KEY" fcm commit <<__IN__
+y
+__IN__
+sed -i "/^Updating '.':$/d" $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] sed -i 1i\foo: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Root   : $REPOS_URL]
+[Project: ${TEST_PROJECT:-}]
+[Branch : trunk]
+[Sub-dir: ]
+
+ M      .
+M       added_file
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+foo
+Merged into /${PROJECT}trunk: /${PROJECT}branches/dev/Share/merge1 at 13 cf. /${PROJECT}branches/dev/Share/merge1 at 11
+--------------------------------------------------------------------------------
+
+*** WARNING: YOU ARE COMMITTING TO THE TRUNK.
+*** Please ensure that your change conforms to your project's working practices.
+
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): Sending        .
+Sending        added_file
+Transmitting file data .
+Committed revision 14.
+At revision 14.
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm log of fcm merge branch-into-trunk (2)
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-2-log
+run_pass "$TEST_KEY" fcm log
+sed -i "s/\(.*|.*|\).*\(|.*\)$/\1 date \2/g" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+------------------------------------------------------------------------
+r14 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}trunk: /${PROJECT}branches/dev/Share/merge1 at 13 cf. /${PROJECT}branches/dev/Share/merge1 at 11
+
+------------------------------------------------------------------------
+r12 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}trunk: /${PROJECT}branches/dev/Share/merge1 at 11 cf. /${PROJECT}trunk at 9
+
+------------------------------------------------------------------------
+r9 | $LOGNAME | date | 1 line
+
+Made another trunk change
+------------------------------------------------------------------------
+r8 | $LOGNAME | date | 1 line
+
+Made trunk change
+------------------------------------------------------------------------
+r1 | $LOGNAME | date | 1 line
+
+initial trunk import
+------------------------------------------------------------------------
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm merge of trunk-into-branch (2)
+echo "# trunk modification" >>$MOD_FILE
+svn commit -q -m "Made trunk change - a simple edit of $MOD_FILE"
+svn update -q
+svn switch -q $ROOT_URL/branches/dev/Share/merge1
+TEST_KEY=$TEST_KEY_BASE-trunk-into-branch-2
+echo "# added another line for simple repeat testing" >>$BRANCH_MOD_FILE
+svn commit -q -m "Made branch change for merge repeat test"
+svn update -q
+run_pass "$TEST_KEY" fcm merge --non-interactive trunk
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Eligible merge(s) from /${PROJECT}trunk at 16: 15 14
+--------------------------------------------------------------------------------
+Merge: /${PROJECT}trunk at 15
+ c.f.: /${PROJECT}branches/dev/Share/merge1 at 13
+Merge succeeded.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Eligible merge(s) from /${PROJECT}trunk at 16: 15 14
+--------------------------------------------------------------------------------
+Merge: /${PROJECT}trunk at 15
+ c.f.: /${PROJECT}branches/dev/Share/merge1 at 13
+Merge succeeded.
+--------------------------------------------------------------------------actual
+--- Merging differences between repository URLs into '.':
+U    added_file
+--- Recording mergeinfo for merge between repository URLs into '.':
+ U   .
+--------------------------------------------------------------------------actual
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn status result of fcm merge trunk-into-branch (2)
+TEST_KEY=$TEST_KEY_BASE-trunk-into-branch-2-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+M       added_file
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn diff result of fcm merge trunk-into-branch (2)
+TEST_KEY=$TEST_KEY_BASE-trunk-into-branch-2-diff
+run_pass "$TEST_KEY" svn diff
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+
+Property changes on: .
+___________________________________________________________________
+Modified: svn:mergeinfo
+   Merged /${PROJECT}trunk:r10-15
+   Merged /${PROJECT}branches/dev/Share/merge1:r2-3
+
+Index: added_file
+===================================================================
+--- added_file	(revision 16)
++++ added_file	(working copy)
+@@ -1,2 +1,3 @@
+ INCLUDE 'hello_constants.INc'
+ call_extra_feature()
++# trunk modification
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: added_file
+===================================================================
+--- added_file	(revision 16)
++++ added_file	(working copy)
+@@ -1,2 +1,3 @@
+ INCLUDE 'hello_constants.INc'
+ call_extra_feature()
++# trunk modification
+Index: .
+===================================================================
+--- .	(revision 16)
++++ .	(working copy)
+
+Property changes on: .
+___________________________________________________________________
+Modified: svn:mergeinfo
+   Merged /${PROJECT}trunk:r10-15
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm commit of fcm merge trunk-into-branch (2)
+TEST_KEY=$TEST_KEY_BASE-trunk-into-branch-2-commit
+run_pass "$TEST_KEY" fcm commit <<__IN__
+y
+__IN__
+sed -i "/^Updating '.':$/d" $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] sed -i 1i\foo: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Root   : $REPOS_URL]
+[Project: ${TEST_PROJECT:-}]
+[Branch : branches/dev/Share/merge1]
+[Sub-dir: ]
+
+ M      .
+M       added_file
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+foo
+Merged into /${PROJECT}branches/dev/Share/merge1: /${PROJECT}trunk at 15 cf. /${PROJECT}branches/dev/Share/merge1 at 13
+--------------------------------------------------------------------------------
+
+*** WARNING: YOU ARE COMMITTING TO A Share BRANCH.
+*** Please ensure that you have the owner's permission.
+
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): Sending        .
+Sending        added_file
+Transmitting file data .
+Committed revision 17.
+At revision 17.
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm log of fcm merge trunk-into-branch (2)
+TEST_KEY=$TEST_KEY_BASE-trunk-into-branch-2-log
+run_pass "$TEST_KEY" fcm log
+sed -i "s/\(.*|.*|\).*\(|.*\)$/\1 date \2/g" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+------------------------------------------------------------------------
+r17 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}branches/dev/Share/merge1: /${PROJECT}trunk at 15 cf. /${PROJECT}branches/dev/Share/merge1 at 13
+
+------------------------------------------------------------------------
+r16 | $LOGNAME | date | 1 line
+
+Made branch change for merge repeat test
+------------------------------------------------------------------------
+r13 | $LOGNAME | date | 1 line
+
+Made branch change to add extra feature
+------------------------------------------------------------------------
+r11 | $LOGNAME | date | 1 line
+
+edit on branch for merge repeat test
+------------------------------------------------------------------------
+r10 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}branches/dev/Share/merge1: /${PROJECT}trunk at 9 cf. /${PROJECT}trunk at 1
+
+------------------------------------------------------------------------
+r5 | $LOGNAME | date | 1 line
+
+Made changes for future merge of this branch
+------------------------------------------------------------------------
+r4 | $LOGNAME | date | 1 line
+
+Made a branch Created /${PROJECT}branches/dev/Share/merge1 from /trunk at 1.
+------------------------------------------------------------------------
+r1 | $LOGNAME | date | 1 line
+
+initial trunk import
+------------------------------------------------------------------------
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm merge of branch-into-trunk (3)
+svn delete -q $BRANCH_MOD_FILE
+svn copy -q $MOD_FILE $MOD_FILE.add
+svn commit -q -m "Made branch change - deleted $BRANCH_MOD_FILE, copied $MOD_FILE"
+svn update -q
+svn switch -q $ROOT_URL/trunk
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-3
+run_pass "$TEST_KEY" fcm merge --non-interactive branches/dev/Share/merge1
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Eligible merge(s) from /${PROJECT}branches/dev/Share/merge1 at 18: 18 17
+--------------------------------------------------------------------------------
+Merge: /${PROJECT}branches/dev/Share/merge1 at 18
+ c.f.: /${PROJECT}trunk at 15
+Merge succeeded.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Eligible merge(s) from /${PROJECT}branches/dev/Share/merge1 at 18: 18 17
+--------------------------------------------------------------------------------
+Merge: /${PROJECT}branches/dev/Share/merge1 at 18
+ c.f.: /${PROJECT}trunk at 15
+Merge succeeded.
+--------------------------------------------------------------------------actual
+--- Merging differences between repository URLs into '.':
+D    added_directory/hello_constants_dummy.inc
+A    added_file.add
+--- Recording mergeinfo for merge between repository URLs into '.':
+ U   .
+--------------------------------------------------------------------------actual
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn status result of fcm merge branch-into-trunk (3)
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-3-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+D       added_directory/hello_constants_dummy.inc
+A  +    added_file.add
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn diff result of fcm merge branch-into-trunk (3)
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-3-diff
+run_pass "$TEST_KEY" svn diff
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+
+Property changes on: .
+___________________________________________________________________
+Modified: svn:mergeinfo
+   Merged /${PROJECT}branches/dev/Share/merge1:r14-18
+
+Index: added_directory/hello_constants_dummy.inc
+===================================================================
+--- added_directory/hello_constants_dummy.inc	(revision 18)
++++ added_directory/hello_constants_dummy.inc	(working copy)
+@@ -1,2 +0,0 @@
+-INCLUDE 'hello_constants.INc'
+-# added this line for simple repeat testing
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: added_directory/hello_constants_dummy.inc
+===================================================================
+--- added_directory/hello_constants_dummy.inc	(revision 18)
++++ added_directory/hello_constants_dummy.inc	(working copy)
+@@ -1,2 +0,0 @@
+-INCLUDE 'hello_constants.INc'
+-# added this line for simple repeat testing
+Index: .
+===================================================================
+--- .	(revision 18)
++++ .	(working copy)
+
+Property changes on: .
+___________________________________________________________________
+Modified: svn:mergeinfo
+   Merged /${PROJECT}branches/dev/Share/merge1:r14-18
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm commit of fcm merge branch-into-trunk (3)
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-3-commit
+run_pass "$TEST_KEY" fcm commit <<__IN__
+y
+__IN__
+sed -i "/^Updating '.':$/d" $TEST_DIR/"$TEST_KEY.out"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] sed -i 1i\foo: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Root   : $REPOS_URL]
+[Project: ${TEST_PROJECT:-}]
+[Branch : trunk]
+[Sub-dir: ]
+
+ M      .
+D       added_directory/hello_constants_dummy.inc
+A  +    added_file.add
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+foo
+Merged into /${PROJECT}trunk: /${PROJECT}branches/dev/Share/merge1 at 18 cf. /${PROJECT}trunk at 15
+--------------------------------------------------------------------------------
+
+*** WARNING: YOU ARE COMMITTING TO THE TRUNK.
+*** Please ensure that your change conforms to your project's working practices.
+
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): Sending        .
+Deleting       added_directory/hello_constants_dummy.inc
+Adding         added_file.add
+
+Committed revision 19.
+At revision 19.
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm log of fcm merge branch-into-trunk (3)
+TEST_KEY=$TEST_KEY_BASE-branch-into-trunk-3-log
+run_pass "$TEST_KEY" fcm log $ROOT_URL
+sed -i "s/\(.*|.*|\).*\(|.*\)$/\1 date \2/g" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+------------------------------------------------------------------------
+r19 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}trunk: /${PROJECT}branches/dev/Share/merge1 at 18 cf. /${PROJECT}trunk at 15
+
+------------------------------------------------------------------------
+r18 | $LOGNAME | date | 1 line
+
+Made branch change - deleted ./added_directory/hello_constants_dummy.inc, copied ./added_file
+------------------------------------------------------------------------
+r17 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}branches/dev/Share/merge1: /${PROJECT}trunk at 15 cf. /${PROJECT}branches/dev/Share/merge1 at 13
+
+------------------------------------------------------------------------
+r16 | $LOGNAME | date | 1 line
+
+Made branch change for merge repeat test
+------------------------------------------------------------------------
+r15 | $LOGNAME | date | 1 line
+
+Made trunk change - a simple edit of ./added_file
+------------------------------------------------------------------------
+r14 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}trunk: /${PROJECT}branches/dev/Share/merge1 at 13 cf. /${PROJECT}branches/dev/Share/merge1 at 11
+
+------------------------------------------------------------------------
+r13 | $LOGNAME | date | 1 line
+
+Made branch change to add extra feature
+------------------------------------------------------------------------
+r12 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}trunk: /${PROJECT}branches/dev/Share/merge1 at 11 cf. /${PROJECT}trunk at 9
+
+------------------------------------------------------------------------
+r11 | $LOGNAME | date | 1 line
+
+edit on branch for merge repeat test
+------------------------------------------------------------------------
+r10 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}branches/dev/Share/merge1: /${PROJECT}trunk at 9 cf. /${PROJECT}trunk at 1
+
+------------------------------------------------------------------------
+r9 | $LOGNAME | date | 1 line
+
+Made another trunk change
+------------------------------------------------------------------------
+r8 | $LOGNAME | date | 1 line
+
+Made trunk change
+------------------------------------------------------------------------
+r7 | $LOGNAME | date | 1 line
+
+Made changes for future merge
+------------------------------------------------------------------------
+r6 | $LOGNAME | date | 1 line
+
+Made a branch Created /${PROJECT}branches/dev/Share/merge2 from /trunk at 1.
+------------------------------------------------------------------------
+r5 | $LOGNAME | date | 1 line
+
+Made changes for future merge of this branch
+------------------------------------------------------------------------
+r4 | $LOGNAME | date | 1 line
+
+Made a branch Created /${PROJECT}branches/dev/Share/merge1 from /trunk at 1.
+------------------------------------------------------------------------
+r3 | $LOGNAME | date | 1 line
+
+ 
+------------------------------------------------------------------------
+r2 | $LOGNAME | date | 1 line
+
+make tags
+------------------------------------------------------------------------
+r1 | $LOGNAME | date | 1 line
+
+initial trunk import
+------------------------------------------------------------------------
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm merge of branch-into-branch (1)
+TEST_KEY=$TEST_KEY_BASE-branch-into-branch-1
+cd $TEST_DIR
+rm -rf $TEST_DIR/wc
+mkdir $TEST_DIR/wc
+svn checkout -q $ROOT_URL/branches/dev/Share/merge2 $TEST_DIR/wc
+cd $TEST_DIR/wc
+BRANCH_2_MOD_FILE=$(find . -type f | sed "/\.svn/d" | sort | head -3| tail -1)
+echo "Second branch change" >>$BRANCH_2_MOD_FILE
+svn commit -q -m "Made branch change - added to $BRANCH_2_MOD_FILE"
+svn update -q
+run_pass "$TEST_KEY" fcm merge $ROOT_URL/branches/dev/Share/merge1 <<__IN__
+13
+y
+__IN__
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Eligible merge(s) from /${PROJECT}branches/dev/Share/merge1 at 20: 18 17 16 13 11 10 5
+Enter a revision (or just press <return> for "18"): --------------------------------------------------------------------------------
+Merge: /${PROJECT}branches/dev/Share/merge1 at 13
+ c.f.: /${PROJECT}trunk at 1
+-------------------------------------------------------------------------dry-run
+--- Merging r2 through r13 into '.':
+U    subroutine/hello_sub_dummy.h
+A    added_file
+A    added_directory
+A    added_directory/hello_constants_dummy.inc
+A    added_directory/hello_constants.inc
+A    added_directory/hello_constants.f90
+A    module/tree_conflict_file
+U    module/hello_constants_dummy.inc
+U    module/hello_constants.inc
+U    module/hello_constants.f90
+U    lib/python/info/__init__.py
+U    lib/python/info/poems.py
+ U   .
+-------------------------------------------------------------------------dry-run
+Would you like to go ahead with the merge?
+Enter "y" or "n" (or just press <return> for "n"): Merge succeeded.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Eligible merge(s) from /${PROJECT}branches/dev/Share/merge1 at 20: 18 17 16 13 11 10 5
+Enter a revision (or just press <return> for "18"): --------------------------------------------------------------------------------
+Merge: /${PROJECT}branches/dev/Share/merge1 at 13
+ c.f.: /${PROJECT}trunk at 1
+-------------------------------------------------------------------------dry-run
+--- Merging r4 through r13 into '.':
+U    subroutine/hello_sub_dummy.h
+A    added_file
+A    added_directory
+A    added_directory/hello_constants_dummy.inc
+A    added_directory/hello_constants.inc
+A    added_directory/hello_constants.f90
+A    module/tree_conflict_file
+U    module/hello_constants_dummy.inc
+U    module/hello_constants.inc
+U    module/hello_constants.f90
+U    lib/python/info/__init__.py
+U    lib/python/info/poems.py
+ U   .
+-------------------------------------------------------------------------dry-run
+Would you like to go ahead with the merge?
+Enter "y" or "n" (or just press <return> for "n"): Merge succeeded.
+--------------------------------------------------------------------------actual
+--- Merging r4 through r13 into '.':
+U    subroutine/hello_sub_dummy.h
+A    added_file
+A    added_directory
+A    added_directory/hello_constants_dummy.inc
+A    added_directory/hello_constants.inc
+A    added_directory/hello_constants.f90
+A    module/tree_conflict_file
+U    module/hello_constants_dummy.inc
+U    module/hello_constants.inc
+U    module/hello_constants.f90
+U    lib/python/info/__init__.py
+U    lib/python/info/poems.py
+ U   .
+--- Recording mergeinfo for merge of r4 through r13 into '.':
+ G   .
+--------------------------------------------------------------------------actual
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn status result of fcm merge branch-into-branch (1)
+TEST_KEY=$TEST_KEY_BASE-branch-into-branch-1-status
+run_pass "$TEST_KEY" svn status --config-dir=$TEST_DIR/.subversion/
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+M       subroutine/hello_sub_dummy.h
+A  +    added_file
+A  +    module/tree_conflict_file
+M       module/hello_constants_dummy.inc
+M       module/hello_constants.inc
+M       module/hello_constants.f90
+A  +    added_directory
+A  +    added_directory/hello_constants_dummy.inc
+A  +    added_directory/hello_constants.inc
+A  +    added_directory/hello_constants.f90
+M       lib/python/info/__init__.py
+M       lib/python/info/poems.py
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+A  +    added_directory
+A  +    added_file
+M       lib/python/info/__init__.py
+M       lib/python/info/poems.py
+M       module/hello_constants.f90
+M       module/hello_constants.inc
+M       module/hello_constants_dummy.inc
+A  +    module/tree_conflict_file
+M       subroutine/hello_sub_dummy.h
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests svn diff result of fcm merge branch-into-branch (1)
+TEST_KEY=$TEST_KEY_BASE-branch-into-branch-1-diff
+run_pass "$TEST_KEY" svn diff
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+
+Property changes on: .
+___________________________________________________________________
+Added: svn:mergeinfo
+   Merged /${PROJECT}trunk:r2-9
+   Merged /${PROJECT}branches/dev/Share/merge1:r4-13
+
+Index: subroutine/hello_sub_dummy.h
+===================================================================
+--- subroutine/hello_sub_dummy.h	(revision 20)
++++ subroutine/hello_sub_dummy.h	(working copy)
+@@ -1 +1,2 @@
+ #include "hello_sub.h"
++Modified a line
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	(revision 20)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +1 @@
+-INCLUDE 'hello_constants.inc'
++INCLUDE 'hello_constants.INc'
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	(revision 20)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +1,2 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	(revision 20)
++++ module/hello_constants.f90	(working copy)
+@@ -1,6 +1,6 @@
+ MODULE Hello_Constants
+ 
+-INCLUDE 'hello_constants_dummy.inc'
++INCLUDE 'hello_constants_dummy.INc'
+ 
+ END MODULE Hello_Constants
+ Second branch change
+Index: lib/python/info/__init__.py
+===================================================================
+--- lib/python/info/__init__.py	(revision 20)
++++ lib/python/info/__init__.py	(working copy)
+@@ -0,0 +1,2 @@
++trunk change
++another trunk change
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	(revision 20)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+Index: lib/python/info/__init__.py
+===================================================================
+--- lib/python/info/__init__.py	(revision 20)
++++ lib/python/info/__init__.py	(working copy)
+@@ -0,0 +1,2 @@
++trunk change
++another trunk change
+Index: lib/python/info/poems.py
+===================================================================
+--- lib/python/info/poems.py	(revision 20)
++++ lib/python/info/poems.py	(working copy)
+@@ -1,24 +1,23 @@
+-#!/usr/bin/env python
+-# -*- coding: utf-8 -*-
+ """The Python, by Hilaire Belloc
+ 
+ A Python I should not advise,--
+-It needs a doctor for its eyes,
++It needs a doctor FOR its eyes,
+ And has the measles yearly.
+-However, if you feel inclined
+-To get one (to improve your mind,
++However, if you feel INclINed
++To get one (
++to improve your mINd,
+ And not from fashion merely),
+ Allow no music near its cage;
+-And when it flies into a rage
++And when it flies INto a rage
+ Chastise it, most severely.
+-I had an aunt in Yucatan
++I had an aunt IN Yucatan
+ Who bought a Python from a man
+-And kept it for a pet.
++And kept it FOR a pet.
+ She died, because she never knew
+ These simple little rules and few;--
+-The Snake is living yet.
++The Snake is livINg yet.
+ """
+ 
+ import this
+ 
+-print "\n",  __doc__
++prINt "\n",  __doc__
+Index: module/hello_constants.f90
+===================================================================
+--- module/hello_constants.f90	(revision 20)
++++ module/hello_constants.f90	(working copy)
+@@ -1,6 +1,6 @@
+ MODULE Hello_Constants
+ 
+-INCLUDE 'hello_constants_dummy.inc'
++INCLUDE 'hello_constants_dummy.INc'
+ 
+ END MODULE Hello_Constants
+ Second branch change
+Index: module/hello_constants.inc
+===================================================================
+--- module/hello_constants.inc	(revision 20)
++++ module/hello_constants.inc	(working copy)
+@@ -1 +1,2 @@
+-CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
++CHARACTER (
++LEN=80), PARAMETER :: hello_strINg = 'Hello Earth!!'
+Index: module/hello_constants_dummy.inc
+===================================================================
+--- module/hello_constants_dummy.inc	(revision 20)
++++ module/hello_constants_dummy.inc	(working copy)
+@@ -1 +1 @@
+-INCLUDE 'hello_constants.inc'
++INCLUDE 'hello_constants.INc'
+Index: subroutine/hello_sub_dummy.h
+===================================================================
+--- subroutine/hello_sub_dummy.h	(revision 20)
++++ subroutine/hello_sub_dummy.h	(working copy)
+@@ -1 +1,2 @@
+ #include "hello_sub.h"
++Modified a line
+Index: .
+===================================================================
+--- .	(revision 20)
++++ .	(working copy)
+
+Property changes on: .
+___________________________________________________________________
+Added: svn:mergeinfo
+   Merged /${PROJECT}trunk:r2-9
+   Merged /${PROJECT}branches/dev/Share/merge1:r4-13
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm commit of fcm merge branch-into-branch (1)
+TEST_KEY=$TEST_KEY_BASE-branch-into-branch-1-commit
+run_pass "$TEST_KEY" fcm commit <<__IN__
+y
+__IN__
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] sed -i 1i\foo: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Root   : $REPOS_URL]
+[Project: ${TEST_PROJECT:-}]
+[Branch : branches/dev/Share/merge2]
+[Sub-dir: ]
+
+ M      .
+M       subroutine/hello_sub_dummy.h
+A  +    added_file
+A  +    module/tree_conflict_file
+M       module/hello_constants_dummy.inc
+M       module/hello_constants.inc
+M       module/hello_constants.f90
+A  +    added_directory
+A  +    added_directory/hello_constants_dummy.inc
+A  +    added_directory/hello_constants.inc
+A  +    added_directory/hello_constants.f90
+M       lib/python/info/__init__.py
+M       lib/python/info/poems.py
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+foo
+Merged into /${PROJECT}branches/dev/Share/merge2: /${PROJECT}branches/dev/Share/merge1 at 13 cf. /${PROJECT}trunk at 1
+--------------------------------------------------------------------------------
+
+*** WARNING: YOU ARE COMMITTING TO A Share BRANCH.
+*** Please ensure that you have the owner's permission.
+
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): Sending        .
+Adding         added_directory
+Adding         added_directory/hello_constants.f90
+Adding         added_directory/hello_constants.inc
+Adding         added_directory/hello_constants_dummy.inc
+Adding         added_file
+Sending        lib/python/info/__init__.py
+Sending        lib/python/info/poems.py
+Sending        module/hello_constants.f90
+Sending        module/hello_constants.inc
+Sending        module/hello_constants_dummy.inc
+Adding         module/tree_conflict_file
+Sending        subroutine/hello_sub_dummy.h
+Transmitting file data ......
+Committed revision 21.
+At revision 21.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] sed -i 1i\foo: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+[Root   : $REPOS_URL]
+[Project: ${TEST_PROJECT:-}]
+[Branch : branches/dev/Share/merge2]
+[Sub-dir: ]
+
+ M      .
+A  +    added_directory
+A  +    added_file
+M       lib/python/info/__init__.py
+M       lib/python/info/poems.py
+M       module/hello_constants.f90
+M       module/hello_constants.inc
+M       module/hello_constants_dummy.inc
+A  +    module/tree_conflict_file
+M       subroutine/hello_sub_dummy.h
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+foo
+Merged into /${PROJECT}branches/dev/Share/merge2: /${PROJECT}branches/dev/Share/merge1 at 13 cf. /${PROJECT}trunk at 1
+--------------------------------------------------------------------------------
+
+*** WARNING: YOU ARE COMMITTING TO A Share BRANCH.
+*** Please ensure that you have the owner's permission.
+
+Would you like to commit this change?
+Enter "y" or "n" (or just press <return> for "n"): Sending        .
+Adding         added_directory
+Adding         added_file
+Sending        lib/python/info/__init__.py
+Sending        lib/python/info/poems.py
+Sending        module/hello_constants.f90
+Sending        module/hello_constants.inc
+Sending        module/hello_constants_dummy.inc
+Adding         module/tree_conflict_file
+Sending        subroutine/hello_sub_dummy.h
+Transmitting file data ......
+Committed revision 21.
+Updating '.':
+At revision 21.
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm log of fcm merge branch-into-branch (1)
+TEST_KEY=$TEST_KEY_BASE-branch-into-branch-1-log
+run_pass "$TEST_KEY" fcm log $REPOS_URL
+sed -i "s/\(.*|.*|\).*\(|.*\)$/\1 date \2/g" $TEST_DIR/$TEST_KEY.out
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+------------------------------------------------------------------------
+r21 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}branches/dev/Share/merge2: /${PROJECT}branches/dev/Share/merge1 at 13 cf. /${PROJECT}trunk at 1
+
+------------------------------------------------------------------------
+r20 | $LOGNAME | date | 1 line
+
+Made branch change - added to ./module/hello_constants.f90
+------------------------------------------------------------------------
+r19 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}trunk: /${PROJECT}branches/dev/Share/merge1 at 18 cf. /${PROJECT}trunk at 15
+
+------------------------------------------------------------------------
+r18 | $LOGNAME | date | 1 line
+
+Made branch change - deleted ./added_directory/hello_constants_dummy.inc, copied ./added_file
+------------------------------------------------------------------------
+r17 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}branches/dev/Share/merge1: /${PROJECT}trunk at 15 cf. /${PROJECT}branches/dev/Share/merge1 at 13
+
+------------------------------------------------------------------------
+r16 | $LOGNAME | date | 1 line
+
+Made branch change for merge repeat test
+------------------------------------------------------------------------
+r15 | $LOGNAME | date | 1 line
+
+Made trunk change - a simple edit of ./added_file
+------------------------------------------------------------------------
+r14 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}trunk: /${PROJECT}branches/dev/Share/merge1 at 13 cf. /${PROJECT}branches/dev/Share/merge1 at 11
+
+------------------------------------------------------------------------
+r13 | $LOGNAME | date | 1 line
+
+Made branch change to add extra feature
+------------------------------------------------------------------------
+r12 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}trunk: /${PROJECT}branches/dev/Share/merge1 at 11 cf. /${PROJECT}trunk at 9
+
+------------------------------------------------------------------------
+r11 | $LOGNAME | date | 1 line
+
+edit on branch for merge repeat test
+------------------------------------------------------------------------
+r10 | $LOGNAME | date | 3 lines
+
+foo
+Merged into /${PROJECT}branches/dev/Share/merge1: /${PROJECT}trunk at 9 cf. /${PROJECT}trunk at 1
+
+------------------------------------------------------------------------
+r9 | $LOGNAME | date | 1 line
+
+Made another trunk change
+------------------------------------------------------------------------
+r8 | $LOGNAME | date | 1 line
+
+Made trunk change
+------------------------------------------------------------------------
+r7 | $LOGNAME | date | 1 line
+
+Made changes for future merge
+------------------------------------------------------------------------
+r6 | $LOGNAME | date | 1 line
+
+Made a branch Created /${PROJECT}branches/dev/Share/merge2 from /trunk at 1.
+------------------------------------------------------------------------
+r5 | $LOGNAME | date | 1 line
+
+Made changes for future merge of this branch
+------------------------------------------------------------------------
+r4 | $LOGNAME | date | 1 line
+
+Made a branch Created /${PROJECT}branches/dev/Share/merge1 from /trunk at 1.
+------------------------------------------------------------------------
+r3 | $LOGNAME | date | 1 line
+
+ 
+------------------------------------------------------------------------
+r2 | $LOGNAME | date | 1 line
+
+make tags
+------------------------------------------------------------------------
+r1 | $LOGNAME | date | 1 line
+
+initial trunk import
+------------------------------------------------------------------------
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-merge/test_header b/t/fcm-merge/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/fcm-merge/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/fcm-recover-svn-repos/00-basic.t b/t/fcm-recover-svn-repos/00-basic.t
new file mode 100755
index 0000000..d334ec2
--- /dev/null
+++ b/t/fcm-recover-svn-repos/00-basic.t
@@ -0,0 +1,108 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Basic tests for "fcm-recover-svn-repos".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+if ! which svnadmin 1>/dev/null 2>/dev/null; then
+    skip_all 'svnadmin not available'
+fi
+tests 26
+#-------------------------------------------------------------------------------
+set -e
+mkdir -p etc srv/svn var/svn/{backups,cache,dumps}
+# Configuration
+export FCM_CONF_PATH="$PWD/etc"
+cat >etc/admin.cfg <<__CONF__
+svn_backup_dir=$PWD/var/svn/backups
+svn_dump_dir=$PWD/var/svn/dumps
+svn_group=
+svn_live_dir=$PWD/srv/svn
+__CONF__
+# Create some repositories and populate them
+# Repository 1
+svnadmin create srv/svn/bar
+svn co -q file://$PWD/srv/svn/bar
+echo 'Barley drink.' >bar/barley
+svn add -q bar/*
+svn ci -q -m'test 1' bar
+svnadmin hotcopy srv/svn/bar var/svn/backups/bar
+tar -C var/svn/backups -czf $PWD/var/svn/backups/bar.tgz bar
+svnadmin dump srv/svn/bar -r 1 --incremental --deltas -q \
+        | gzip >var/svn/dumps/bar-1.gz
+# Repository 2
+svnadmin create srv/svn/foo
+svn co -q file://$PWD/srv/svn/foo
+echo 'Number of football players = 0' >foo/football
+echo 'Food is yummy.' >foo/food
+svn add -q foo/*
+svn ci -q -m'test 1' foo
+svnadmin hotcopy srv/svn/foo var/svn/backups/foo
+tar -C var/svn/backups -czf $PWD/var/svn/backups/foo.tgz foo
+rm -fr var/svn/backups/foo
+echo 'Fool is a clown.' >foo/fool
+svn add -q foo/fool
+svn ci -q -m'test 2' foo
+for I in {1..11}; do
+    echo "Number of football players = $I" >foo/football
+    svn ci -q -m"incr football player" foo
+done
+for I in {1..13}; do
+    svnadmin dump srv/svn/foo -r $I --incremental --deltas -q \
+        | gzip >var/svn/dumps/foo-$I.gz
+done
+rm -fr bar foo
+set +e
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-live-exists"
+run_fail "$TEST_KEY" "$FCM_HOME/sbin/fcm-recover-svn-repos"
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" </dev/null
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" <<__ERR__
+$PWD/srv/svn/bar: live repository exists.
+__ERR__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE"
+mv srv/svn/{bar,foo} var/svn/cache
+run_pass "$TEST_KEY" "$FCM_HOME/sbin/fcm-recover-svn-repos"
+# Check revisions
+for NAME in bar foo; do
+    svnlook youngest var/svn/cache/$NAME >"$TEST_KEY-youngest-$NAME.exp"
+    svnlook youngest srv/svn/$NAME >"$TEST_KEY-youngest-$NAME"
+    file_cmp "$TEST_KEY-youngest-$NAME" \
+        "$TEST_KEY-youngest-$NAME" "$TEST_KEY-youngest-$NAME.exp"
+    for I in $(seq 1 $(<"$TEST_KEY-youngest-$NAME.exp")); do
+        svnlook changed -r $I var/svn/cache/$NAME \
+            >"$TEST_KEY-changed-$NAME-$I.exp"
+        svnlook changed -r $I srv/svn/$NAME >"$TEST_KEY-changed-$NAME-$I"
+        file_cmp "$TEST_KEY-changed-$NAME-$I" \
+            "$TEST_KEY-changed-$NAME-$I" "$TEST_KEY-changed-$NAME-$I.exp"
+    done
+    svn export -q file://$PWD/var/svn/cache/$NAME $NAME.orig
+    svn export -q file://$PWD/srv/svn/$NAME $NAME
+    FILES_ORIG=$(cd $NAME.orig; find -type f)
+    FILES=$(cd $NAME; find -type f)
+    run_pass "$TEST_KEY-$NAME-n-files" \
+        test $(wc -l <<<"$FILES_ORIG") -eq $(wc -l <<<"$FILES")
+    for FILE in $FILES_ORIG; do
+        file_cmp "$TEST_KEY-cmp-$NAME-$FILE" "$NAME.orig/$FILE" "$NAME/$FILE"
+    done
+done
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/fcm-recover-svn-repos/test_header b/t/fcm-recover-svn-repos/test_header
new file mode 120000
index 0000000..90bd5a3
--- /dev/null
+++ b/t/fcm-recover-svn-repos/test_header
@@ -0,0 +1 @@
+../lib/bash/test_header
\ No newline at end of file
diff --git a/t/fcm-status/00-simple.t b/t/fcm-status/00-simple.t
new file mode 100644
index 0000000..9d2bad3
--- /dev/null
+++ b/t/fcm-status/00-simple.t
@@ -0,0 +1,83 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm status".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 4
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_merge_branches merge1 merge2 $REPOS_URL
+export SVN_EDITOR="sed -i 1i\foo"
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests the setup for fcm status testing
+svn switch -q $ROOT_URL/trunk
+touch added_file
+svn add -q added_file
+svn commit -q -m "trunk modifications"
+svn update -q
+TEST_KEY=$TEST_KEY_BASE-setup
+run_pass "$TEST_KEY" fcm merge --non-interactive branches/dev/Share/merge1
+rm subroutine/hello_sub.h
+svn delete -q --force lib/python/info/poems.py
+#-------------------------------------------------------------------------------
+# Tests fcm status result of fcm merge (1)
+TEST_KEY=$TEST_KEY_BASE-status
+run_pass "$TEST_KEY" fcm status --config-dir=$TEST_DIR/.subversion
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+?       unversioned_file
+!       subroutine/hello_sub.h
+M       subroutine/hello_sub_dummy.h
+      C added_file
+      >   local add, incoming add upon merge
+A  +    module/tree_conflict_file
+M       module/hello_constants_dummy.inc
+M       module/hello_constants.inc
+M       module/hello_constants.f90
+A  +    added_directory
+A  +    added_directory/hello_constants_dummy.inc
+A  +    added_directory/hello_constants.inc
+A  +    added_directory/hello_constants.f90
+D       lib/python/info/poems.py
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+ M      .
+A  +    added_directory
+      C added_file
+      >   local file obstruction, incoming file add upon merge
+D       lib/python/info/poems.py
+M       module/hello_constants.f90
+M       module/hello_constants.inc
+M       module/hello_constants_dummy.inc
+A  +    module/tree_conflict_file
+!       subroutine/hello_sub.h
+M       subroutine/hello_sub_dummy.h
+?       unversioned_file
+Summary of conflicts:
+  Tree conflicts: 1
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-status/test_header b/t/fcm-status/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/fcm-status/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/fcm-switch/00-simple.t b/t/fcm-switch/00-simple.t
new file mode 100644
index 0000000..62583aa
--- /dev/null
+++ b/t/fcm-switch/00-simple.t
@@ -0,0 +1,109 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm switch".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 12
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_merge_branches merge1 merge2 $REPOS_URL
+export SVN_EDITOR="sed -i 1i\foo"
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm switch trunk
+svn switch -q $ROOT_URL/branches/dev/Share/merge1
+TEST_KEY=$TEST_KEY_BASE-trunk
+run_pass "$TEST_KEY" fcm switch trunk <<<'y'
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+switch: status of "$TEST_DIR/wc":
+?       unversioned_file
+switch: continue?
+Enter "y" or "n" (or just press <return> for "n"): D    added_file
+D    added_directory
+U    subroutine/hello_sub_dummy.h
+D    module/tree_conflict_file
+U    module/hello_constants_dummy.inc
+U    module/hello_constants.inc
+U    module/hello_constants.f90
+U    lib/python/info/__init__.py
+U    lib/python/info/poems.py
+Updated to revision 9.
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm switch merge1 branch
+rm unversioned_file
+TEST_KEY=$TEST_KEY_BASE-branch-1
+run_pass "$TEST_KEY" fcm switch branches/dev/Share/merge1 <<<'y'
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+U    subroutine/hello_sub_dummy.h
+A    added_file
+A    added_directory
+A    added_directory/hello_constants_dummy.inc
+A    added_directory/hello_constants.inc
+A    added_directory/hello_constants.f90
+A    module/tree_conflict_file
+U    module/hello_constants_dummy.inc
+U    module/hello_constants.inc
+U    module/hello_constants.f90
+U    lib/python/info/__init__.py
+U    lib/python/info/poems.py
+Updated to revision 9.
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm switch merge2 branch
+TEST_KEY=$TEST_KEY_BASE-branch-2
+run_pass "$TEST_KEY" fcm switch --non-interactive dev/Share/merge2
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+D    added_file
+D    added_directory
+ U   subroutine/hello_sub.h
+U    subroutine/hello_sub_dummy.h
+D    module/tree_conflict_file
+U    module/hello_constants_dummy.inc
+U    module/hello_constants.inc
+U    module/hello_constants.f90
+U    lib/python/info/poems.py
+A    renamed_added_file
+Updated to revision 9.
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm switch trunk, without .svn/entries
+TEST_KEY=$TEST_KEY_BASE-trunk-2
+if $SVN_VERSION_IS_16; then
+    skip 3 "$TEST_KEY won't work under Subversion 1.6"
+else
+    rm -f .svn/entries
+    run_pass "$TEST_KEY" fcm switch trunk <<<'y'
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+D    renamed_added_file
+ U   subroutine/hello_sub.h
+U    lib/python/info/__init__.py
+Updated to revision 9.
+__OUT__
+    file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+fi
+#-------------------------------------------------------------------------------
+teardown
+exit
diff --git a/t/fcm-switch/01-subtree.t b/t/fcm-switch/01-subtree.t
new file mode 100644
index 0000000..cccb0b7
--- /dev/null
+++ b/t/fcm-switch/01-subtree.t
@@ -0,0 +1,149 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm switch".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 9
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_merge_branches merge1 merge2 $REPOS_URL
+export SVN_EDITOR="sed -i 1i\foo"
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm switch trunk
+svn switch -q $ROOT_URL/branches/dev/Share/merge1
+TEST_KEY=$TEST_KEY_BASE-trunk
+cd module
+run_pass "$TEST_KEY" fcm switch trunk <<__IN__
+y
+__IN__
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+switch: status of "$TEST_DIR/wc":
+?       $TEST_DIR/wc/unversioned_file
+switch: continue?
+Enter "y" or "n" (or just press <return> for "n"): D    $TEST_DIR/wc/added_file
+D    $TEST_DIR/wc/added_directory
+U    $TEST_DIR/wc/subroutine/hello_sub_dummy.h
+D    $TEST_DIR/wc/module/tree_conflict_file
+U    $TEST_DIR/wc/module/hello_constants_dummy.inc
+U    $TEST_DIR/wc/module/hello_constants.inc
+U    $TEST_DIR/wc/module/hello_constants.f90
+U    $TEST_DIR/wc/lib/python/info/__init__.py
+U    $TEST_DIR/wc/lib/python/info/poems.py
+Updated to revision 9.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+switch: status of "$TEST_DIR/wc":
+?       $TEST_DIR/wc/unversioned_file
+switch: continue?
+Enter "y" or "n" (or just press <return> for "n"): D    $TEST_DIR/wc/added_file
+D    $TEST_DIR/wc/added_directory
+U    $TEST_DIR/wc/subroutine/hello_sub_dummy.h
+D    tree_conflict_file
+U    hello_constants_dummy.inc
+U    hello_constants.inc
+U    hello_constants.f90
+U    $TEST_DIR/wc/lib/python/info/__init__.py
+U    $TEST_DIR/wc/lib/python/info/poems.py
+Updated to revision 9.
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm switch merge1 branch
+rm ../unversioned_file
+TEST_KEY=$TEST_KEY_BASE-branch-1
+run_pass "$TEST_KEY" fcm switch branches/dev/Share/merge1 <<__IN__
+y
+__IN__
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+U    $TEST_DIR/wc/subroutine/hello_sub_dummy.h
+A    $TEST_DIR/wc/added_file
+A    $TEST_DIR/wc/added_directory
+A    $TEST_DIR/wc/added_directory/hello_constants_dummy.inc
+A    $TEST_DIR/wc/added_directory/hello_constants.inc
+A    $TEST_DIR/wc/added_directory/hello_constants.f90
+A    $TEST_DIR/wc/module/tree_conflict_file
+U    $TEST_DIR/wc/module/hello_constants_dummy.inc
+U    $TEST_DIR/wc/module/hello_constants.inc
+U    $TEST_DIR/wc/module/hello_constants.f90
+U    $TEST_DIR/wc/lib/python/info/__init__.py
+U    $TEST_DIR/wc/lib/python/info/poems.py
+Updated to revision 9.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+U    $TEST_DIR/wc/subroutine/hello_sub_dummy.h
+A    $TEST_DIR/wc/added_file
+A    $TEST_DIR/wc/added_directory
+A    $TEST_DIR/wc/added_directory/hello_constants_dummy.inc
+A    $TEST_DIR/wc/added_directory/hello_constants.inc
+A    $TEST_DIR/wc/added_directory/hello_constants.f90
+A    tree_conflict_file
+U    hello_constants_dummy.inc
+U    hello_constants.inc
+U    hello_constants.f90
+U    $TEST_DIR/wc/lib/python/info/__init__.py
+U    $TEST_DIR/wc/lib/python/info/poems.py
+Updated to revision 9.
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm switch merge2 branch
+TEST_KEY=$TEST_KEY_BASE-branch-2
+run_pass "$TEST_KEY" fcm switch --non-interactive dev/Share/merge2
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+D    $TEST_DIR/wc/added_file
+D    $TEST_DIR/wc/added_directory
+ U   $TEST_DIR/wc/subroutine/hello_sub.h
+U    $TEST_DIR/wc/subroutine/hello_sub_dummy.h
+D    $TEST_DIR/wc/module/tree_conflict_file
+U    $TEST_DIR/wc/module/hello_constants_dummy.inc
+U    $TEST_DIR/wc/module/hello_constants.inc
+U    $TEST_DIR/wc/module/hello_constants.f90
+U    $TEST_DIR/wc/lib/python/info/poems.py
+A    $TEST_DIR/wc/renamed_added_file
+Updated to revision 9.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+D    $TEST_DIR/wc/added_file
+D    $TEST_DIR/wc/added_directory
+ U   $TEST_DIR/wc/subroutine/hello_sub.h
+U    $TEST_DIR/wc/subroutine/hello_sub_dummy.h
+D    tree_conflict_file
+U    hello_constants_dummy.inc
+U    hello_constants.inc
+U    hello_constants.f90
+U    $TEST_DIR/wc/lib/python/info/poems.py
+A    $TEST_DIR/wc/renamed_added_file
+Updated to revision 9.
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-switch/test_header b/t/fcm-switch/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/fcm-switch/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/fcm-update/00-simple.t b/t/fcm-update/00-simple.t
new file mode 100644
index 0000000..1948c9d
--- /dev/null
+++ b/t/fcm-update/00-simple.t
@@ -0,0 +1,144 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm update".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 6
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_merge_branches merge1 merge2 $REPOS_URL
+export SVN_EDITOR="sed -i 1i\foo"
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm update -r PREV
+svn switch -q $ROOT_URL/branches/dev/Share/merge1
+TEST_KEY=$TEST_KEY_BASE-r-PREV
+run_pass "$TEST_KEY" fcm update -r PREV <<__IN__
+y
+__IN__
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+update: status of ".":
+?       unversioned_file
+update: continue?
+Enter "y" or "n" (or just press <return> for "n"): D    added_file
+D    added_directory
+U    subroutine/hello_sub_dummy.h
+D    module/tree_conflict_file
+U    module/hello_constants_dummy.inc
+U    module/hello_constants.inc
+U    module/hello_constants.f90
+U    lib/python/info/poems.py
+Updated to revision 4.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+update: status of ".":
+?       unversioned_file
+update: continue?
+Enter "y" or "n" (or just press <return> for "n"): Updating '.':
+D    added_file
+D    added_directory
+U    subroutine/hello_sub_dummy.h
+D    module/tree_conflict_file
+U    module/hello_constants_dummy.inc
+U    module/hello_constants.inc
+U    module/hello_constants.f90
+U    lib/python/info/poems.py
+Updated to revision 4.
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm update
+rm unversioned_file
+TEST_KEY=$TEST_KEY_BASE-normal
+run_pass "$TEST_KEY" fcm update <<__IN__
+y
+__IN__
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+update: status of ".":
+        *        4   subroutine/hello_sub_dummy.h
+        *            added_directory/hello_constants.f90
+        *            added_directory/hello_constants_dummy.inc
+        *            added_directory/hello_constants.inc
+        *            added_directory
+        *        4   module/hello_constants.f90
+        *            module/tree_conflict_file
+        *        4   module/hello_constants_dummy.inc
+        *        4   module/hello_constants.inc
+        *        4   module
+        *        4   lib/python/info/poems.py
+        *            added_file
+        *        4   .
+update: continue?
+Enter "y" or "n" (or just press <return> for "n"): U    subroutine/hello_sub_dummy.h
+A    added_file
+A    added_directory
+A    added_directory/hello_constants_dummy.inc
+A    added_directory/hello_constants.inc
+A    added_directory/hello_constants.f90
+A    module/tree_conflict_file
+U    module/hello_constants_dummy.inc
+U    module/hello_constants.inc
+U    module/hello_constants.f90
+U    lib/python/info/poems.py
+Updated to revision 9.
+__OUT__
+else
+    # The output is now not deterministic for svn update!!
+    sort $TEST_DIR/"$TEST_KEY.out" -o $TEST_DIR/"$TEST_KEY.out"
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+        *            added_directory
+        *            added_directory/hello_constants.f90
+        *            added_directory/hello_constants.inc
+        *            added_directory/hello_constants_dummy.inc
+        *            added_file
+        *            module/tree_conflict_file
+        *        4   .
+        *        4   lib/python/info/poems.py
+        *        4   module
+        *        4   module/hello_constants.f90
+        *        4   module/hello_constants.inc
+        *        4   module/hello_constants_dummy.inc
+        *        4   subroutine/hello_sub_dummy.h
+A    added_directory
+A    added_directory/hello_constants.f90
+A    added_directory/hello_constants.inc
+A    added_directory/hello_constants_dummy.inc
+A    added_file
+A    module/tree_conflict_file
+Enter "y" or "n" (or just press <return> for "n"): Updating '.':
+U    lib/python/info/poems.py
+U    module/hello_constants.f90
+U    module/hello_constants.inc
+U    module/hello_constants_dummy.inc
+U    subroutine/hello_sub_dummy.h
+Updated to revision 9.
+update: continue?
+update: status of ".":
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-update/01-subtree.t b/t/fcm-update/01-subtree.t
new file mode 100644
index 0000000..061eba4
--- /dev/null
+++ b/t/fcm-update/01-subtree.t
@@ -0,0 +1,146 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Basic tests for "fcm update".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+tests 6
+#-------------------------------------------------------------------------------
+setup
+init_repos
+init_merge_branches merge1 merge2 $REPOS_URL
+export SVN_EDITOR="sed -i 1i\foo"
+cd $TEST_DIR/wc
+#-------------------------------------------------------------------------------
+# Tests fcm update -r PREV
+svn switch -q $ROOT_URL/branches/dev/Share/merge1
+TEST_KEY=$TEST_KEY_BASE-r-PREV
+cd module
+run_pass "$TEST_KEY" fcm update -r PREV <<__IN__
+y
+__IN__
+if $SVN_VERSION_IS_16; then
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+update: status of "$TEST_DIR/wc":
+?       $TEST_DIR/wc/unversioned_file
+update: continue?
+Enter "y" or "n" (or just press <return> for "n"): D    $TEST_DIR/wc/added_file
+D    $TEST_DIR/wc/added_directory
+U    $TEST_DIR/wc/subroutine/hello_sub_dummy.h
+D    $TEST_DIR/wc/module/tree_conflict_file
+U    $TEST_DIR/wc/module/hello_constants_dummy.inc
+U    $TEST_DIR/wc/module/hello_constants.inc
+U    $TEST_DIR/wc/module/hello_constants.f90
+U    $TEST_DIR/wc/lib/python/info/poems.py
+Updated to revision 4.
+__OUT__
+else
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+update: status of "$TEST_DIR/wc":
+?       $TEST_DIR/wc/unversioned_file
+update: continue?
+Enter "y" or "n" (or just press <return> for "n"): Updating '$TEST_DIR/wc':
+D    $TEST_DIR/wc/added_file
+D    $TEST_DIR/wc/added_directory
+U    $TEST_DIR/wc/subroutine/hello_sub_dummy.h
+D    tree_conflict_file
+U    hello_constants_dummy.inc
+U    hello_constants.inc
+U    hello_constants.f90
+U    $TEST_DIR/wc/lib/python/info/poems.py
+Updated to revision 4.
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+# Tests fcm update
+rm ../unversioned_file
+TEST_KEY=$TEST_KEY_BASE-normal
+run_pass "$TEST_KEY" fcm update <<__IN__
+y
+__IN__
+if $SVN_VERSION_IS_16; then
+    sort $TEST_DIR/"$TEST_KEY.out" -o $TEST_DIR/"$TEST_KEY.out"
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+        *            $TEST_DIR/wc/added_directory
+        *            $TEST_DIR/wc/added_directory/hello_constants.f90
+        *            $TEST_DIR/wc/added_directory/hello_constants.inc
+        *            $TEST_DIR/wc/added_directory/hello_constants_dummy.inc
+        *            $TEST_DIR/wc/added_file
+        *            $TEST_DIR/wc/module/tree_conflict_file
+        *        4   $TEST_DIR/wc
+        *        4   $TEST_DIR/wc/lib/python/info/poems.py
+        *        4   $TEST_DIR/wc/module
+        *        4   $TEST_DIR/wc/module/hello_constants.f90
+        *        4   $TEST_DIR/wc/module/hello_constants.inc
+        *        4   $TEST_DIR/wc/module/hello_constants_dummy.inc
+        *        4   $TEST_DIR/wc/subroutine/hello_sub_dummy.h
+A    $TEST_DIR/wc/added_directory
+A    $TEST_DIR/wc/added_directory/hello_constants.f90
+A    $TEST_DIR/wc/added_directory/hello_constants.inc
+A    $TEST_DIR/wc/added_directory/hello_constants_dummy.inc
+A    $TEST_DIR/wc/added_file
+A    $TEST_DIR/wc/module/tree_conflict_file
+Enter "y" or "n" (or just press <return> for "n"): U    $TEST_DIR/wc/subroutine/hello_sub_dummy.h
+U    $TEST_DIR/wc/lib/python/info/poems.py
+U    $TEST_DIR/wc/module/hello_constants.f90
+U    $TEST_DIR/wc/module/hello_constants.inc
+U    $TEST_DIR/wc/module/hello_constants_dummy.inc
+Updated to revision 9.
+update: continue?
+update: status of "$TEST_DIR/wc":
+__OUT__
+else
+    # The output is now not deterministic for svn update!!
+    sort $TEST_DIR/"$TEST_KEY.out" -o $TEST_DIR/"$TEST_KEY.out"
+    file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+        *            $TEST_DIR/wc/added_directory
+        *            $TEST_DIR/wc/added_directory/hello_constants.f90
+        *            $TEST_DIR/wc/added_directory/hello_constants.inc
+        *            $TEST_DIR/wc/added_directory/hello_constants_dummy.inc
+        *            $TEST_DIR/wc/added_file
+        *            $TEST_DIR/wc/module/tree_conflict_file
+        *        4   $TEST_DIR/wc
+        *        4   $TEST_DIR/wc/lib/python/info/poems.py
+        *        4   $TEST_DIR/wc/module
+        *        4   $TEST_DIR/wc/module/hello_constants.f90
+        *        4   $TEST_DIR/wc/module/hello_constants.inc
+        *        4   $TEST_DIR/wc/module/hello_constants_dummy.inc
+        *        4   $TEST_DIR/wc/subroutine/hello_sub_dummy.h
+A    $TEST_DIR/wc/added_directory
+A    $TEST_DIR/wc/added_directory/hello_constants.f90
+A    $TEST_DIR/wc/added_directory/hello_constants.inc
+A    $TEST_DIR/wc/added_directory/hello_constants_dummy.inc
+A    $TEST_DIR/wc/added_file
+A    tree_conflict_file
+Enter "y" or "n" (or just press <return> for "n"): Updating '$TEST_DIR/wc':
+U    $TEST_DIR/wc/lib/python/info/poems.py
+U    $TEST_DIR/wc/subroutine/hello_sub_dummy.h
+U    hello_constants.f90
+U    hello_constants.inc
+U    hello_constants_dummy.inc
+Updated to revision 9.
+update: continue?
+update: status of "$TEST_DIR/wc":
+__OUT__
+fi
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+teardown
+#-------------------------------------------------------------------------------
diff --git a/t/fcm-update/test_header b/t/fcm-update/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/fcm-update/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/t/lib/bash/test_header b/t/lib/bash/test_header
new file mode 100644
index 0000000..1d5e010
--- /dev/null
+++ b/t/lib/bash/test_header
@@ -0,0 +1,216 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# NAME
+#     test_header
+#
+# SYNOPSIS
+#     . $FCM_HOME/t/lib/bash/test_header
+#
+# DESCRIPTION
+#     Provide bash shell functions for writing tests for "fcm" commands to
+#     output in Perl's TAP format. Add "set -eu". Create a temporary working
+#     directory $TEST_DIR and change to it. Automatically increment test number.
+#     If $FCM_HOME is not specified, set it to point to the "fcm" source tree
+#     containing this script. Add $FCM_HOME/bin to the front of $PATH.
+#
+# FUNCTIONS
+#     tests N
+#         echo "1..$N".
+#     skip N REASON
+#         echo "ok $((++T)) # skip REASON" N times, where T is the test number.
+#     skip_all REASON
+#         echo "1..0 # SKIP $REASON" and exit.
+#     pass TEST_KEY
+#         echo "ok $T - $TEST_KEY" where T is the current test number.
+#     fail TEST_KEY
+#         echo "not ok $T - $TEST_KEY" where T is the current test number.
+#     run_pass TEST_KEY COMMAND ...
+#         Run $COMMAND. pass/fail $TEST_KEY if $COMMAND returns true/false.
+#         Write STDOUT and STDERR in $TEST_KEY.out and $TEST_KEY.err.
+#     run_fail TEST_KEY COMMAND ...
+#         Run $COMMAND. pass/fail $TEST_KEY if $COMMAND returns false/true.
+#         Write STDOUT and STDERR in $TEST_KEY.out and $TEST_KEY.err.
+#     file_cmp TEST_KEY FILE_ACTUAL [$FILE_EXPECT]
+#         Compare contents in $FILE_ACTUAL and $FILE_EXPECT. pass/fail
+#         $TEST_KEY if contents are identical/different. If $FILE_EXPECT is "-"
+#         or not defined, compare $FILE_ACTUAL with STDIN to this function.
+#     file_test TEST_KEY FILE [OPTION]
+#         pass/fail $TEST_KEY if "test $OPTION $FILE" returns 0/1. $OPTION is
+#         -e if not specified.
+#     file_grep TEST_KEY PATTERN FILE
+#         Run "grep -q PATTERN FILE". pass/fail $TEST_KEY accordingly.
+#     FINALLY
+#         This is run on EXIT or INT to remove the temporary working directory
+#         for the test. Call FINALLY_MORE if it is declared.
+#
+# VARIABLES
+#     FCM_HOME
+#         Root of FCM's installation. (Exported.)
+#     SIGNALS
+#         List of signals trapped by FINALLY, currently EXIT and INT.
+#     SVN_VERSION_IS_16
+#         True if "svn --version" returns 1.6*.
+#     TEST_DIR
+#         Temporary directory that is also the working directory for this test.
+#     TEST_KEY_BASE
+#         Base root name of current test file.
+#     TEST_NUMBER
+#         Test number of latest test.
+#     TEST_SOURCE_DIR
+#         Directory containing the current test file.
+#-------------------------------------------------------------------------------
+set -eu
+
+SIGNALS="EXIT INT"
+TEST_DIR=
+function FINALLY() {
+    for S in $SIGNALS; do
+        trap '' $S
+    done
+    if [[ -n $TEST_DIR ]]; then
+        cd ~
+        rm -rf $TEST_DIR
+    fi
+    if declare -F FINALLY_MORE >/dev/null; then
+        FINALLY_MORE
+    fi
+
+}
+for S in $SIGNALS; do
+    trap "FINALLY $S" $S
+done
+
+TEST_NUMBER=0
+
+function tests() {
+    echo "1..$1"
+}
+
+function skip() {
+    local N_SKIPS=$1
+    shift 1
+    local I=0
+    while ((I++ < N_SKIPS)); do
+        echo "ok $((++TEST_NUMBER)) # skip $@"
+    done
+}
+
+function skip_all() {
+    echo "1..0 # SKIP $@"
+    exit
+}
+
+function pass() {
+    echo "ok $((++TEST_NUMBER)) - $@"
+}
+
+function fail() {
+    echo "not ok $((++TEST_NUMBER)) - $@"
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_KEY.out 2>$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_KEY.out 2>$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if diff -u $FILE_EXPECT $FILE_ACTUAL >&2; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function fcm_make_build_hello_tests() {
+    local TEST_KEY=$1
+    local HELLO_EXT=${2:-}
+    shift 2
+    rm -fr \
+        .fcm-make \
+        build \
+        fcm-make-as-parsed.cfg \
+        fcm-make-on-success.cfg \
+        fcm-make.log
+    run_pass "$TEST_KEY" fcm make "$@"
+    file_test "$TEST_KEY.hello$HELLO_EXT" "$PWD/build/bin/hello$HELLO_EXT"
+    "$PWD/build/bin/hello$HELLO_EXT" >"$TEST_KEY.hello$HELLO_EXT.out"
+    file_cmp "$TEST_KEY.hello$HELLO_EXT.out" \
+        "$TEST_KEY.hello$HELLO_EXT.out" <<'__OUT__'
+Hello World!
+__OUT__
+}
+
+FCM_HOME=${FCM_HOME:-$(cd $(dirname $(readlink -f $BASH_SOURCE))/../../.. && pwd)}
+export FCM_HOME
+PATH=$FCM_HOME/bin:$PATH
+SVN_VERSION_IS_16=false
+SVN_VERSION=$(svn --version)
+if [[ $(echo $SVN_VERSION | head -1 | grep "^svn, version 1.6") ]]; then
+    SVN_VERSION_IS_16=true
+fi
+unset SVN_VERSION
+
+TEST_KEY_BASE=$(basename $0 .t)
+TEST_SOURCE_DIR=$(cd $(dirname $0) && pwd)
+TEST_DIR=$(mktemp -d)
+export LANG=C
+cd $TEST_DIR
+
+set +e
diff --git a/t/svn-hooks/00-pre-revprop-change.t b/t/svn-hooks/00-pre-revprop-change.t
new file mode 100755
index 0000000..a97e9eb
--- /dev/null
+++ b/t/svn-hooks/00-pre-revprop-change.t
@@ -0,0 +1,83 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Basic tests for "pre-revprop-change".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+FCM_SVN_HOOK_ADMIN_EMAIL=your.admin.team
+. $TEST_SOURCE_DIR/test_header_more
+#-------------------------------------------------------------------------------
+tests 16
+#-------------------------------------------------------------------------------
+cp -p "$FCM_HOME/etc/svn-hooks/pre-revprop-change" "$REPOS_PATH/hooks/"
+echo Hello >file
+svn import -q -m'test' file "$REPOS_URL/file"
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE
+rm -f mail.out
+run_pass "$TEST_KEY" \
+    svn propset -q --revprop -r 1 'svn:log' 'Add hello file' "$REPOS_URL"
+run_fail "$TEST_KEY.mail.out" test -f mail.out
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-bad-prop
+run_fail "$TEST_KEY" \
+    svn propset -q --revprop -r 1 'svn:author' 'boogeyman' "$REPOS_URL"
+file_grep "$TEST_KEY.err" \
+    "\[M svn:author\] permission denied." \
+    "$TEST_KEY.err"
+EXPR="\[! .....*-..-..T..:..:..Z\] $REPOS_PATH 1 $USER svn:author M"
+file_grep "$TEST_KEY.log" "$EXPR" "$REPOS_PATH/log/pre-revprop-change.log"
+file_grep "$TEST_KEY.mail.out" "$EXPR" mail.out
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-bad-action
+run_fail "$TEST_KEY" \
+    svn propdel -q --revprop -r 1 'svn:log' "$REPOS_URL"
+file_grep "$TEST_KEY.err" \
+    "\[D svn:log\] permission denied. Can only do: \[M svn:log\]" \
+    "$TEST_KEY.err"
+EXPR="\[! .....*-..-..T..:..:..Z\] $REPOS_PATH 1 $USER svn:log D"
+file_grep "$TEST_KEY.log" "$EXPR" "$REPOS_PATH/log/pre-revprop-change.log"
+file_grep "$TEST_KEY.mail.out" "$EXPR" mail.out
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-conf-bad
+cat >"$REPOS_PATH/hooks/pre-revprop-change-ok.conf" <<'__CONF__'
+M svn:author
+M svn:log
+__CONF__
+run_fail "$TEST_KEY" svn propdel -q --revprop -r 1 'svn:author' "$REPOS_URL"
+file_grep "$TEST_KEY.err" \
+    "\[D svn:author\] permission denied. Can only do: \[M svn:author\] \[M svn:log\]" \
+    "$TEST_KEY.err"
+EXPR="\[! .....*-..-..T..:..:..Z\] $REPOS_PATH 1 $USER svn:author D"
+file_grep "$TEST_KEY.log" "$EXPR" "$REPOS_PATH/log/pre-revprop-change.log"
+file_grep "$TEST_KEY.mail.out" "$EXPR" mail.out
+rm -f "$REPOS_PATH/hooks/pre-revprop-change-ok.conf"
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-conf-good
+rm -f mail.out
+cat >"$REPOS_PATH/hooks/pre-revprop-change-ok.conf" <<'__CONF__'
+M svn:author
+M svn:log
+__CONF__
+run_pass "$TEST_KEY" \
+    svn propset -q --revprop -r 1 'svn:author' 'arthur' "$REPOS_URL"
+run_fail "$TEST_KEY.mail.out" test -f mail.out
+rm -f "$REPOS_PATH/hooks/pre-revprop-change-ok.conf"
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/svn-hooks/01-post-revprop-change-bg.t b/t/svn-hooks/01-post-revprop-change-bg.t
new file mode 100755
index 0000000..1d28961
--- /dev/null
+++ b/t/svn-hooks/01-post-revprop-change-bg.t
@@ -0,0 +1,119 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Basic tests for "post-revprop-change-bg".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+. $TEST_SOURCE_DIR/test_header_more
+#-------------------------------------------------------------------------------
+tests 9
+#-------------------------------------------------------------------------------
+# Add pre-revprop-change to allow revprop change.
+cat >"$REPOS_PATH/hooks/pre-revprop-change" <<__BASH__
+#!/bin/bash
+exit
+__BASH__
+chmod +x "$REPOS_PATH/hooks/pre-revprop-change"
+# Add post-revprop-change
+cp -p "$FCM_HOME/etc/svn-hooks/post-revprop-change" \
+    "$REPOS_PATH/hooks/post-revprop-change"
+echo Hello >file
+svn import --no-auth-cache -q -m'test' file "$REPOS_URL/file"
+if [[ -n ${TRAC_ENV_PATH:-} ]]; then
+    if $TRAC_RESYNC; then
+        trac-admin "$TRAC_ENV_PATH" resync 1>/dev/null
+    else
+        trac-admin "$TRAC_ENV_PATH" changeset added "$REPOS_PATH" 1 1>/dev/null
+    fi
+fi
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE
+run_pass "$TEST_KEY" \
+    svn propset --no-auth-cache -q --revprop -r 1 'svn:log' 'Add hello file' \
+    "$REPOS_URL"
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-revprop-change.log"
+date2datefmt "$REPOS_PATH/log/post-revprop-change.log" \
+    | sed '/^trac-admin/,$d; /^RET_CODE=/d' >"$TEST_KEY.log.expected"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log.expected" <<__LOG__
+YYYY-mm-ddTHH:MM:SSZ+ M svn:log @1 by $USER
+--- old-value
++++ new-value
+@@ -1 +1 @@
+-test
+\ No newline at end of file
++Add hello file
+\ No newline at end of file
+__LOG__
+run_fail "$TEST_KEY.mail.out" test -f mail.out
+if [[ -z ${TRAC_ENV_PATH:-} ]]; then
+    skip 1 "$TEST_KEY.trac.db: Trac unavailable"
+else
+    sqlite3 "$TRAC_ENV_PATH/db/trac.db" \
+        'SELECT cast(rev as integer),message FROM revision;' \
+        >"$TEST_KEY.trac.db.expected"
+    file_cmp "$TEST_KEY.trac.db" \
+        "$TEST_KEY.trac.db.expected" <<<'1|Add hello file'
+fi
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-author
+cat /dev/null >"$REPOS_PATH/log/post-revprop-change.log"
+run_pass "$TEST_KEY" \
+    svn propset --no-auth-cache --username=not-a-user --revprop -r 1 'svn:log' \
+    'Add welcome file' "$REPOS_URL"
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-revprop-change.log"
+date2datefmt "$REPOS_PATH/log/post-revprop-change.log" \
+    | sed '/^trac-admin/,$d; /^RET_CODE=/d' >"$TEST_KEY.log.expected"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log.expected" <<'__LOG__'
+YYYY-mm-ddTHH:MM:SSZ+ M svn:log @1 by not-a-user
+--- old-value
++++ new-value
+@@ -1 +1 @@
+-Add hello file
+\ No newline at end of file
++Add welcome file
+\ No newline at end of file
+__LOG__
+if [[ -z ${TRAC_ENV_PATH:-} ]]; then
+    skip 1 "$TEST_KEY.trac.db: Trac unavailable"
+else
+    sqlite3 "$TRAC_ENV_PATH/db/trac.db" \
+        'SELECT cast(rev as integer),message FROM revision;' \
+        >"$TEST_KEY.trac.db.expected"
+    file_cmp "$TEST_KEY.trac.db" \
+        "$TEST_KEY.trac.db.expected" <<<'1|Add welcome file'
+fi
+date2datefmt mail.out | sed '/^trac-admin/,$d; /^RET_CODE=/d' \
+    >"$TEST_KEY.mail.out.expected"
+file_grep  "$TEST_KEY.mail.out.01" \
+    '-rnotifications at localhost -sfoo at 1 \[M svn:log\] by not-a-user' \
+    "$TEST_KEY.mail.out.expected"
+sed '1d' "$TEST_KEY.mail.out.expected" >"$TEST_KEY.mail.out.expected.02"
+file_cmp  "$TEST_KEY.mail.out.02" "$TEST_KEY.mail.out.expected.02" <<__LOG__
+YYYY-mm-ddTHH:MM:SSZ+ M svn:log @1 by not-a-user
+========================================================================
+--- old-value
++++ new-value
+@@ -1 +1 @@
+-Add hello file
+\ No newline at end of file
++Add welcome file
+\ No newline at end of file
+__LOG__
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/svn-hooks/02-pre-commit.t b/t/svn-hooks/02-pre-commit.t
new file mode 100755
index 0000000..2194919
--- /dev/null
+++ b/t/svn-hooks/02-pre-commit.t
@@ -0,0 +1,287 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Basic tests for "pre-commit".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+FCM_SVN_HOOK_ADMIN_EMAIL='your.admin.team'
+. $TEST_SOURCE_DIR/test_header_more
+
+test_tidy() {
+    rm -f \
+        "$REPOS_PATH/hooks/pre-commit-custom" \
+        "$REPOS_PATH/hooks/pre-commit-size-threshold.conf" \
+        "$REPOS_PATH/hooks/commit.conf" \
+        "$REPOS_PATH/hooks/svnperms.conf" \
+        "$REPOS_PATH/log/pre-commit.log" \
+        README \
+        bin/svnperms.py \
+        file1 \
+        file2 \
+        file3 \
+        file4 \
+        mail.out \
+        pre-commit-custom.out \
+        svnperms.py.out
+}
+#-------------------------------------------------------------------------------
+tests 50
+#-------------------------------------------------------------------------------
+cp -p "$FCM_HOME/etc/svn-hooks/pre-commit" "$REPOS_PATH/hooks/"
+sed -i "/set -eu/a\
+echo \$2 >$PWD/txn" "$REPOS_PATH/hooks/pre-commit"
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-svnperm-1" # Blocked by svnperms.py
+# Install fake svnperms.py
+test_tidy
+cat >bin/svnperms.py <<__BASH__
+#!/bin/bash
+echo "\$@" >$PWD/svnperms.py.out
+echo "Access denied!" >&2
+false
+__BASH__
+chmod +x "bin/svnperms.py"
+echo '[foo]' >"$REPOS_PATH/hooks/svnperms.conf"
+# Try commit
+touch file1
+run_fail "$TEST_KEY" \
+    svn import --no-auth-cache -q -m'test' file1 "$REPOS_URL/file1"
+TXN=$(<txn)
+# Tests
+file_grep "$TEST_KEY.err" 'Access denied!' "$TEST_KEY.err"
+date2datefmt "$REPOS_PATH/log/pre-commit.log" \
+    >"$TEST_KEY.pre-commit.log.expected"
+file_cmp "$TEST_KEY.pre-commit.log" "$TEST_KEY.pre-commit.log.expected" <<__LOG__
+YYYY-mm-ddTHH:MM:SSZ+ $TXN by $USER
+A   file1
+Access denied!
+__LOG__
+file_cmp "$TEST_KEY.svnperms.py.out" svnperms.py.out <<__OUT__
+-r $REPOS_PATH -t $TXN -f $REPOS_PATH/hooks/svnperms.conf
+__OUT__
+date2datefmt mail.out >"$TEST_KEY.mail.out.expected"
+file_cmp  "$TEST_KEY.mail.out" "$TEST_KEY.mail.out.expected" <<__LOG__
+-s [pre-commit] $REPOS_PATH@$TXN your.admin.team
+YYYY-mm-ddTHH:MM:SSZ+ $TXN by $USER
+A   file1
+Access denied!
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-svnperm-2" # svnperms.conf is bad symlink
+# Install fake svnperms.py
+test_tidy
+ln -f -s "no-such-file" "$REPOS_PATH/hooks/svnperms.conf"
+# Try commit
+touch file1
+run_fail "$TEST_KEY" \
+    svn import --no-auth-cache -q -m'test' file1 "$REPOS_URL/file1"
+TXN=$(<txn)
+# Tests
+file_grep "$TEST_KEY.err" 'foo: permission configuration file not found.' \
+    "$TEST_KEY.err"
+file_grep "$TEST_KEY.err-2" 'your.admin.team has been notified.' "$TEST_KEY.err"
+date2datefmt "$REPOS_PATH/log/pre-commit.log" \
+    >"$TEST_KEY.pre-commit.log.expected"
+file_cmp "$TEST_KEY.pre-commit.log" "$TEST_KEY.pre-commit.log.expected" <<__LOG__
+YYYY-mm-ddTHH:MM:SSZ+ $TXN by $USER
+A   file1
+foo: permission configuration file not found.
+your.admin.team has been notified.
+__LOG__
+run_fail "$TEST_KEY.svnperms.py.out" test -e svnperms.py.out
+date2datefmt mail.out >"$TEST_KEY.mail.out.expected"
+file_cmp "$TEST_KEY.mail.out" "$TEST_KEY.mail.out.expected" <<__LOG__
+-s [pre-commit] $REPOS_PATH@$TXN your.admin.team
+YYYY-mm-ddTHH:MM:SSZ+ $TXN by $USER
+A   file1
+foo: permission configuration file not found.
+your.admin.team has been notified.
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-svnperm-3" # Good svnperms.conf
+test_tidy
+cat >bin/svnperms.py <<__BASH__
+#!/bin/bash
+echo "\$@" >$PWD/svnperms.py.out
+__BASH__
+chmod +x bin/svnperms.py
+echo '[foo]' >"$REPOS_PATH/hooks/svnperms.conf"
+# Try commit
+touch file1
+run_pass "$TEST_KEY" \
+    svn import --no-auth-cache -q -m'test' file1 "$REPOS_URL/file1"
+TXN=$(<txn)
+# Tests
+run_fail "$TEST_KEY.pre-commit.log" test -s "$REPOS_PATH/log/pre-commit.log"
+file_cmp "$TEST_KEY.svnperms.py.out" "svnperms.py.out" <<__OUT__
+-r $REPOS_PATH -t $TXN -f $REPOS_PATH/hooks/svnperms.conf
+__OUT__
+run_fail "$TEST_KEY.mail.out" test -e mail.out
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-svnperm-4" # No svnperms.conf
+test_tidy
+# Try commit
+touch file2
+run_pass "$TEST_KEY" \
+    svn import --no-auth-cache -q -m'test' file2 "$REPOS_URL/file2"
+# Tests
+run_fail "$TEST_KEY.pre-commit.log" test -s "$REPOS_PATH/log/pre-commit.log"
+run_fail "$TEST_KEY.svnperms.py.out" test -e svnperms.py.out
+run_fail "$TEST_KEY.mail.out" test -e mail.out
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-size-1" # bigger than default
+test_tidy
+perl -e 'map {print(rand())} 1..2097152' >file3 # a large file
+run_fail "$TEST_KEY" \
+    svn import --no-auth-cache -q -m'test' file3 "$REPOS_URL/file3"
+TXN=$(<txn)
+file_grep "$TEST_KEY.err" "foo@$TXN: changeset size ..MB exceeds 10MB." \
+    "$TEST_KEY.err"
+file_grep "$TEST_KEY.err-2" \
+    'Email your.admin.team if you need to bypass this restriction.' "$TEST_KEY.err"
+date2datefmt "$REPOS_PATH/log/pre-commit.log" \
+    | sed 's/\(size \).*\(MB exceeds\)/\1??\2/' \
+    >"$TEST_KEY.pre-commit.log.expected"
+file_cmp "$TEST_KEY.pre-commit.log" "$TEST_KEY.pre-commit.log.expected" <<__LOG__
+YYYY-mm-ddTHH:MM:SSZ+ $TXN by $USER
+A   file3
+foo@$TXN: changeset size ??MB exceeds 10MB.
+Email your.admin.team if you need to bypass this restriction.
+__LOG__
+date2datefmt mail.out | sed 's/\(size \).*\(MB exceeds\)/\1??\2/' \
+     >"$TEST_KEY.mail.out.expected"
+file_cmp "$TEST_KEY.mail.out.expected" "$TEST_KEY.mail.out.expected" <<__OUT__
+-s [pre-commit] $REPOS_PATH@$TXN your.admin.team
+YYYY-mm-ddTHH:MM:SSZ+ $TXN by $USER
+A   file3
+foo@$TXN: changeset size ??MB exceeds 10MB.
+Email your.admin.team if you need to bypass this restriction.
+__OUT__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-size-2" # bigger than default, threshold increased
+test_tidy
+echo '20' >"$REPOS_PATH/hooks/pre-commit-size-threshold.conf"
+perl -e 'map {print(rand())} 1..2097152' >file3 # a large file
+run_pass "$TEST_KEY" \
+    svn import --no-auth-cache -q -m'test' file3 "$REPOS_URL/file3"
+# Tests
+run_fail "$TEST_KEY.pre-commit.log" test -s "$REPOS_PATH/log/pre-commit.log"
+run_fail "$TEST_KEY.mail.out" test -e mail.out
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-custom-1" # block by custom script
+test_tidy
+cat >"$REPOS_PATH/hooks/pre-commit-custom" <<__BASH__
+#!/bin/bash
+echo "\$@" >$PWD/pre-commit-custom.out
+echo 'I am a blocker.' >&2
+false
+__BASH__
+chmod +x "$REPOS_PATH/hooks/pre-commit-custom"
+touch file4
+run_fail "$TEST_KEY" \
+    svn import --no-auth-cache -q -m'test' file4 "$REPOS_URL/file4"
+TXN=$(<txn)
+# Tests
+file_grep "$TEST_KEY.err" 'I am a blocker.' "$TEST_KEY.err"
+file_cmp "$TEST_KEY-custom.out" pre-commit-custom.out <<__OUT__
+$REPOS_PATH $TXN
+__OUT__
+date2datefmt "$REPOS_PATH/log/pre-commit.log" \
+    | sed 's/\(size \).*\(MB exceeds\)/\1??\2/' \
+     >"$TEST_KEY.pre-commit.log"
+file_cmp "$TEST_KEY.pre-commit.log" "$TEST_KEY.pre-commit.log" <<__OUT__
+YYYY-mm-ddTHH:MM:SSZ+ $TXN by $USER
+A   file4
+I am a blocker.
+__OUT__
+date2datefmt mail.out | sed 's/\(size \).*\(MB exceeds\)/\1??\2/' \
+     >"$TEST_KEY.mail.out.expected"
+file_cmp "$TEST_KEY.mail.out.expected" "$TEST_KEY.mail.out.expected" <<__OUT__
+-s [pre-commit] $REPOS_PATH@$TXN your.admin.team
+YYYY-mm-ddTHH:MM:SSZ+ $TXN by $USER
+A   file4
+I am a blocker.
+__OUT__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-custom-2" # custom script OK
+test_tidy
+cat >"$REPOS_PATH/hooks/pre-commit-custom" <<__BASH__
+#!/bin/bash
+echo "\$@" >$PWD/pre-commit-custom.out
+__BASH__
+chmod +x "$REPOS_PATH/hooks/pre-commit-custom"
+touch file4
+run_pass "$TEST_KEY" \
+    svn import --no-auth-cache -q -m'test' file4 "$REPOS_URL/file4"
+TXN=$(<txn)
+# Tests
+file_cmp "$TEST_KEY-custom.out" pre-commit-custom.out <<__OUT__
+$REPOS_PATH $TXN
+__OUT__
+run_fail "$TEST_KEY.pre-commit.log" test -s "$REPOS_PATH/log/pre-commit.log"
+run_fail "$TEST_KEY.mail.out" test -e mail.out
+#-------------------------------------------------------------------------------
+# Branch create owner verify, goods
+echo 'Hello World' >README
+svn import -m "hello: new project" README "$REPOS_URL/hello/trunk/README"
+rm README
+for KEY in $USER Share Config Rel; do
+    test_tidy
+    TEST_KEY="$TEST_KEY_BASE-branch-owner-$KEY"
+    run_pass "$TEST_KEY" svn cp --parents -m "$TEST_KEY" \
+        "$REPOS_URL/hello/trunk" "$REPOS_URL/hello/branches/dev/$KEY/whatever"
+    run_fail "$TEST_KEY.pre-commit.log" test -s "$REPOS_PATH/log/pre-commit.log"
+done
+#-------------------------------------------------------------------------------
+# Branch create owner verify, bad
+test_tidy
+TEST_KEY="$TEST_KEY_BASE-branch-owner-bad"
+run_fail "$TEST_KEY" svn cp --parents -m "$TEST_KEY" \
+    "$REPOS_URL/hello/trunk" "$REPOS_URL/hello/branches/dev/nosuchuser/whatever"
+TXN=$(<txn)
+file_grep "$TEST_KEY.err" \
+    '\[INVALID BRANCH OWNER\] A   hello/branches/dev/nosuchuser/whatever/' \
+    "$TEST_KEY.err"
+date2datefmt "$REPOS_PATH/log/pre-commit.log" \
+    >"$TEST_KEY.pre-commit.log.expected"
+file_cmp "$TEST_KEY.pre-commit.log" \
+    "$TEST_KEY.pre-commit.log.expected" <<__LOG__
+YYYY-mm-ddTHH:MM:SSZ+ $TXN by $USER
+A   hello/branches/dev/nosuchuser/
+A   hello/branches/dev/nosuchuser/whatever/
+[INVALID BRANCH OWNER] A   hello/branches/dev/nosuchuser/whatever/
+__LOG__
+date2datefmt mail.out >"$TEST_KEY.mail.out.expected"
+file_cmp  "$TEST_KEY.mail.out" "$TEST_KEY.mail.out.expected" <<__LOG__
+-s [pre-commit] $REPOS_PATH@$TXN your.admin.team
+YYYY-mm-ddTHH:MM:SSZ+ $TXN by $USER
+A   hello/branches/dev/nosuchuser/
+A   hello/branches/dev/nosuchuser/whatever/
+[INVALID BRANCH OWNER] A   hello/branches/dev/nosuchuser/whatever/
+__LOG__
+#-------------------------------------------------------------------------------
+# Branch create owner no verify, bad
+test_tidy
+TEST_KEY="$TEST_KEY_BASE-branch-owner-no-verify-bad"
+echo 'no-verify-branch-owner' >"$REPOS_PATH/hooks/commit.conf"
+run_pass "$TEST_KEY" svn cp --parents -m "$TEST_KEY" \
+    "$REPOS_URL/hello/trunk" "$REPOS_URL/hello/branches/dev/nosuchuser/whatever"
+run_fail "$TEST_KEY.pre-commit.log" test -s "$REPOS_PATH/log/pre-commit.log"
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/svn-hooks/03-post-commit-bg.t b/t/svn-hooks/03-post-commit-bg.t
new file mode 100755
index 0000000..d596f83
--- /dev/null
+++ b/t/svn-hooks/03-post-commit-bg.t
@@ -0,0 +1,301 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Basic tests for "post-commit-bg".
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+FCM_SVN_HOOK_ADMIN_EMAIL=fcm.admin.team
+. $TEST_SOURCE_DIR/test_header_more
+
+test_tidy() {
+    rm -f \
+        "$REPOS_PATH/hooks/post-commit-bg-custom" \
+        "$REPOS_PATH/hooks/post-commit-background-custom" \
+        "$REPOS_PATH/hooks/commit.conf" \
+        "$REPOS_PATH/log/post-commit.log" \
+        file1 \
+        file2 \
+        file3 \
+        file4 \
+        svnperms.conf \
+        mail.out
+}
+#-------------------------------------------------------------------------------
+tests 32
+#-------------------------------------------------------------------------------
+cp -p "$FCM_HOME/etc/svn-hooks/post-commit" "$REPOS_PATH/hooks/"
+sed -i "/set -eu/a\
+echo \$2 >$PWD/rev; echo \$3 >$PWD/txn" "$REPOS_PATH/hooks/post-commit"
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-basic"
+test_tidy
+touch file1
+svn import --no-auth-cache -q -m"$TEST_KEY" file1 "$REPOS_URL/file1"
+REV=$(<rev)
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+date2datefmt "$REPOS_PATH/log/post-commit.log" \
+    | sed '/^trac-admin/d; s/^\(REV_FILE_SIZE=\).*\( #\)/\1???\2/' \
+    >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+YYYY-mm-ddTHH:MM:SSZ+ $REV by $USER
+svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip 1>$PWD/svn-dumps/foo-$REV.gz
+* Dumped revision $REV.
+REV_FILE_SIZE=??? # within 1048576
+RET_CODE=0
+__LOG__
+if [[ -n ${TRAC_ENV_PATH:-} ]] && ! $TRAC_RESYNC; then
+    sqlite3 "$TRAC_ENV_PATH/db/trac.db" \
+        'SELECT cast(rev as integer),message FROM revision;' \
+        >"$TEST_KEY.trac.db.expected"
+    file_cmp "$TEST_KEY.trac.db" \
+        "$TEST_KEY.trac.db.expected" <<<"$REV|$TEST_KEY"
+    cat "$TEST_KEY.trac.db.expected"
+else
+    skip 1 '"trac-admin changeset added" not available'
+fi
+run_pass "$TEST_KEY.dump" test -s "$PWD/svn-dumps/foo-$REV.gz"
+run_fail "$TEST_KEY.mail.out" test -e mail.out
+#-------------------------------------------------------------------------------
+# Install and remove commit.conf, svnperms.conf
+for NAME in 'commit.conf' 'svnperms.conf'; do
+    TEST_KEY="$TEST_KEY_BASE-add-${NAME}"
+    test_tidy
+    # (Use "svnperms.conf" syntax. Doesn't matter for the purpose of this test.)
+    cat >${NAME} <<'__CONF__'
+[foo]
+.*=*(add,remove,update)
+__CONF__
+    svn import --no-auth-cache -q -m"$TEST_KEY" ${NAME} \
+        "$REPOS_URL/${NAME}"
+    REV=$(<rev)
+    poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+    date2datefmt "$REPOS_PATH/log/post-commit.log" \
+        | sed '/^trac-admin/d; s/^\(REV_FILE_SIZE=\).*\( #\)/\1???\2/' \
+        >"$TEST_KEY.log"
+    file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+YYYY-mm-ddTHH:MM:SSZ+ $REV by $USER
+svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip 1>$PWD/svn-dumps/foo-$REV.gz
+* Dumped revision $REV.
+REV_FILE_SIZE=??? # within 1048576
+svnlook cat $REPOS_PATH ${NAME} >$REPOS_PATH/hooks/${NAME}
+RET_CODE=0
+__LOG__
+    file_cmp "$TEST_KEY.conf" ${NAME} "$REPOS_PATH/hooks/${NAME}"
+
+    TEST_KEY="$TEST_KEY_BASE-modify-${NAME}"
+    test_tidy
+    svn co -q "$REPOS_URL" work
+    # (Use "svnperms.conf" syntax. Doesn't matter for the purpose of this test.)
+    cat >work/${NAME} <<'__CONF__'
+[foo]
+.*=*(add,remove,update)
+
+[bar]
+__CONF__
+    svn commit --no-auth-cache -q -m"$TEST_KEY" work
+    REV=$(<rev)
+    poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+    date2datefmt "$REPOS_PATH/log/post-commit.log" \
+        | sed '/^trac-admin/d; s/^\(REV_FILE_SIZE=\).*\( #\)/\1???\2/' \
+        >"$TEST_KEY.log"
+    file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+YYYY-mm-ddTHH:MM:SSZ+ $REV by $USER
+svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip 1>$PWD/svn-dumps/foo-$REV.gz
+* Dumped revision $REV.
+REV_FILE_SIZE=??? # within 1048576
+svnlook cat $REPOS_PATH ${NAME} >$REPOS_PATH/hooks/${NAME}
+RET_CODE=0
+__LOG__
+    file_cmp "$TEST_KEY.conf" work/${NAME} "$REPOS_PATH/hooks/${NAME}"
+    rm -f -r work
+
+    TEST_KEY="$TEST_KEY_BASE-remove-${NAME}"
+    test_tidy
+    touch "$REPOS_PATH/hooks/${NAME}"
+    svn rm --no-auth-cache -q -m'remove ${NAME}' "$REPOS_URL/${NAME}"
+    REV=$(<rev)
+    poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+    date2datefmt "$REPOS_PATH/log/post-commit.log" \
+        | sed '/^trac-admin/d; s/^\(REV_FILE_SIZE=\).*\( #\)/\1???\2/' \
+        >"$TEST_KEY.log"
+    file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+YYYY-mm-ddTHH:MM:SSZ+ $REV by $USER
+svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip 1>$PWD/svn-dumps/foo-$REV.gz
+* Dumped revision $REV.
+REV_FILE_SIZE=??? # within 1048576
+rm -f $REPOS_PATH/hooks/${NAME}
+RET_CODE=0
+__LOG__
+    run_fail "$TEST_KEY.conf" test -e "$REPOS_PATH/hooks/${NAME}"
+done
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-size"
+test_tidy
+perl -e 'map {print(rand())} 1..524288' >file2 # compress should be >1MB
+svn import --no-auth-cache -q -m"$TEST_KEY" file2 "$REPOS_URL/file2"
+REV=$(<rev)
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+date2datefmt "$REPOS_PATH/log/post-commit.log" \
+    | sed '/^trac-admin/d; s/^\(REV_FILE_SIZE=\).*\( #\)/\1???\2/' \
+    >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+YYYY-mm-ddTHH:MM:SSZ+ $REV by $USER
+svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip 1>$PWD/svn-dumps/foo-$REV.gz
+* Dumped revision $REV.
+REV_FILE_SIZE=??? # EXCEED 1048576
+RET_CODE=1
+__LOG__
+date2datefmt mail.out \
+    | sed '/^trac-admin/d; s/^\(REV_FILE_SIZE=\).*\( #\)/\1???\2/' \
+    >"$TEST_KEY.mail.out"
+file_cmp "$TEST_KEY.mail.out" "$TEST_KEY.mail.out" <<__LOG__
+-s [post-commit-bg] $REPOS_PATH@$REV fcm.admin.team
+YYYY-mm-ddTHH:MM:SSZ+ $REV by $USER
+svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip 1>$PWD/svn-dumps/foo-$REV.gz
+* Dumped revision $REV.
+REV_FILE_SIZE=??? # EXCEED 1048576
+RET_CODE=1
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-custom-1" # good custom
+test_tidy
+touch file3
+cat >"$REPOS_PATH/hooks/post-commit-bg-custom" <<'__BASH__'
+#!/bin/bash
+echo "$@"
+__BASH__
+chmod +x "$REPOS_PATH/hooks/post-commit-bg-custom"
+svn import --no-auth-cache -q -m"$TEST_KEY" file3 "$REPOS_URL/file3"
+REV=$(<rev)
+TXN=$(<txn)
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+date2datefmt "$REPOS_PATH/log/post-commit.log" \
+    | sed '/^trac-admin/d; s/^\(REV_FILE_SIZE=\).*\( #\)/\1???\2/' \
+    >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+YYYY-mm-ddTHH:MM:SSZ+ $REV by $USER
+svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip 1>$PWD/svn-dumps/foo-$REV.gz
+* Dumped revision $REV.
+REV_FILE_SIZE=??? # within 1048576
+$REPOS_PATH/hooks/post-commit-bg-custom $REPOS_PATH $REV $TXN
+$REPOS_PATH $REV $TXN
+RET_CODE=0
+__LOG__
+run_fail "$TEST_KEY.mail.out" test -e mail.out
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-custom-2" # bad custom
+test_tidy
+cat >"$REPOS_PATH/hooks/post-commit-background-custom" <<'__BASH__'
+#!/bin/bash
+echo 'I have gone to the dark side.' >&2
+false
+__BASH__
+chmod +x "$REPOS_PATH/hooks/post-commit-background-custom"
+touch file4
+svn import --no-auth-cache -q -m"$TEST_KEY" file4 "$REPOS_URL/file4"
+REV=$(<rev)
+TXN=$(<txn)
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+date2datefmt "$REPOS_PATH/log/post-commit.log" \
+    | sed '/^trac-admin/d; s/^\(REV_FILE_SIZE=\).*\( #\)/\1???\2/' \
+    >"$TEST_KEY.log"
+file_cmp "$TEST_KEY.log" "$TEST_KEY.log" <<__LOG__
+YYYY-mm-ddTHH:MM:SSZ+ $REV by $USER
+svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip 1>$PWD/svn-dumps/foo-$REV.gz
+* Dumped revision $REV.
+REV_FILE_SIZE=??? # within 1048576
+$REPOS_PATH/hooks/post-commit-background-custom $REPOS_PATH $REV $TXN
+I have gone to the dark side.
+RET_CODE=1
+__LOG__
+file_test "$TEST_KEY.mail.out" mail.out
+#-------------------------------------------------------------------------------
+# Test branch owner notification
+echo 'Hello World' >file
+svn import -q -m'hello world' file "$REPOS_URL/hello/trunk/file"
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+
+TEST_KEY="$TEST_KEY_BASE-branch-create-owner-1" # create author is owner
+test_tidy
+svn cp -q -m '' --parents \
+    "$REPOS_URL/hello/trunk" \
+    "$REPOS_URL/hello/branches/dev/$USER/whatever"
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+run_fail "$TEST_KEY.mail.out" test -s mail.out
+
+TEST_KEY="$TEST_KEY_BASE-branch-create-owner-2" # create author not owner
+test_tidy
+svn cp -q -m '' --parents \
+    --username=root \
+    --no-auth-cache \
+    "$REPOS_URL/hello/trunk" \
+    "$REPOS_URL/hello/branches/test/$USER/whatever"
+REV=$(<rev)
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+file_grep "$TEST_KEY.mail.out.1" \
+    "^-rnotifications at localhost -sfoo@${REV} by root" mail.out
+file_grep "$TEST_KEY.mail.out.2" "^r${REV} | root" mail.out
+
+TEST_KEY="$TEST_KEY_BASE-branch-create-owner-3" # same as 2, but no notify
+test_tidy
+echo 'no-notify-branch-owner' >"${REPOS_PATH}/hooks/commit.conf"
+svn cp -q -m '' --parents \
+    --username=root \
+    --no-auth-cache \
+    "$REPOS_URL/hello/trunk" \
+    "$REPOS_URL/hello/branches/test/$USER/whatever2"
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+run_fail "$TEST_KEY.mail.out" test -s mail.out
+
+TEST_KEY="$TEST_KEY_BASE-branch-modify-owner-1" # modify author is owner
+test_tidy
+svn co -q "$REPOS_URL/hello/branches/dev/$USER/whatever" hello
+echo 'Hello Earth' >hello/file
+svn ci -q -m'Hello Earth' hello/file
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+run_fail "$TEST_KEY.mail.out" test -s mail.out
+
+TEST_KEY="$TEST_KEY_BASE-branch-modify-owner-2" # modify author not owner
+test_tidy
+#svn co -q "$REPOS_URL/hello/branches/dev/$USER/whatever" hello
+echo 'Hello Alien' >hello/file
+svn ci -q -m'Hello Earth' --username=root --no-auth-cache hello/file
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+REV=$(<rev)
+file_grep "$TEST_KEY.mail.out.1" \
+    "^-rnotifications at localhost -sfoo@${REV} by root" mail.out
+file_grep "$TEST_KEY.mail.out.2" "^r${REV} | root" mail.out
+
+TEST_KEY="$TEST_KEY_BASE-branch-delete-owner-1" # delete author is owner
+test_tidy
+svn rm -q -m'No Hello' "$REPOS_URL/hello/branches/dev/$USER/whatever"
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+run_fail "$TEST_KEY.mail.out" test -s mail.out
+
+TEST_KEY="$TEST_KEY_BASE-branch-delete-owner-2" # delete author not owner
+test_tidy
+svn rm -q -m'No Hello' --username=root --no-auth-cache \
+    "$REPOS_URL/hello/branches/test/$USER/whatever"
+poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
+REV=$(<rev)
+file_grep "$TEST_KEY.mail.out.1" \
+    "^-rnotifications at localhost -sfoo@${REV} by root" mail.out
+file_grep "$TEST_KEY.mail.out.2" "^r${REV} | root" mail.out
+#-------------------------------------------------------------------------------
+exit
diff --git a/t/svn-hooks/test_header b/t/svn-hooks/test_header
new file mode 120000
index 0000000..90bd5a3
--- /dev/null
+++ b/t/svn-hooks/test_header
@@ -0,0 +1 @@
+../lib/bash/test_header
\ No newline at end of file
diff --git a/t/svn-hooks/test_header_more b/t/svn-hooks/test_header_more
new file mode 100644
index 0000000..78ec710
--- /dev/null
+++ b/t/svn-hooks/test_header_more
@@ -0,0 +1,114 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# NAME
+#     test_header_more
+#
+# SYNOPSIS
+#     . $(dirname $0)/test_header
+#     . $(dirname $0)/test_header_more
+#
+# DESCRIPTION
+#     Provide more bash shell functions for testing svn-hooks. Create a
+#     subversion repository. Create a Trac environment if possible. Set up
+#     repository hooks environment file.
+#
+# FUNCTIONS
+#     date2datefmt FILE
+#         Convert date time in YYYY-mm-ddTHH:MM:SSZ format into the format
+#         string itself.
+#     poll HOW_LONG COND ...
+#         Poll for COND to become true for HOW_LONG seconds.
+#
+# VARIABLES
+#     FCM_SVN_HOOK_REPOS_SUFFIX
+#     FCM_SVN_HOOK_ADMIN_EMAIL
+#         Optional. See definition in hook scripts.
+#     HOOK_PATH
+#         PATH for the hook scripts.
+#     REPOS_PATH
+#         Path to the Subversion repository ($PWD/svn-repos/foo).
+#     REPOS_URL
+#         URL of the Subversion repository root (file://$REPOS_PATH).
+#     TRAC_ENV_PATH
+#         Path to the Trac environment ($PWD/trac-env/foo).
+#     TRAC_RESYNC
+#         If true, use "trac-admin resync". If false, use "trac-admin
+#         changeset added|modified".
+#-------------------------------------------------------------------------------
+
+date2datefmt() {
+    perl -p -e 's/\d+-\d\d-\d\dT\d\d:\d\d:\d\dZ/YYYY-mm-ddTHH:MM:SSZ/' "$@"
+}
+
+poll() {
+    local HOW_LONG=$1
+    shift 1
+    local WAIT_UNTIL=$(($(date +%s) + $HOW_LONG))
+    while (($(date +%s) < $WAIT_UNTIL)) && ! "$@" 2>/dev/null; do
+        sleep 1
+    done
+    "$@"
+}
+
+HOOK_PATH='/usr/local/bin:/usr/bin:/bin'
+mkdir bin svn-dumps svn-repos trac-env
+svnadmin create svn-repos/foo 2>/dev/null \
+    || skip_all 'cannot create Subversion repository'
+REPOS_PATH="$PWD/svn-repos/foo${FCM_SVN_HOOK_REPOS_SUFFIX:-}"
+REPOS_URL="file://$REPOS_PATH"
+ADMIN_DIR=$(dirname "$(which svnadmin)")
+if ! grep -q '^\(/usr/local/bin\|/usr/bin\|/bin\)$' <<<"$ADMIN_DIR"; then
+    HOOK_PATH="$ADMIN_DIR:$HOOK_PATH"
+fi
+if trac-admin trac-env/foo initenv foo sqlite:db/trac.db svn "$REPOS_PATH" \
+    1>/dev/null 2>&1
+then
+    FCM_SVN_HOOK_TRAC_ROOT_DIR="$PWD/trac-env"
+    TRAC_ENV_PATH="$FCM_SVN_HOOK_TRAC_ROOT_DIR/foo"
+    ADMIN_DIR=$(dirname "$(which trac-admin)")
+    if ! grep -q '^\(/usr/local/bin\|/usr/bin\|/bin\)$' <<<"$ADMIN_DIR"; then
+        HOOK_PATH="$ADMIN_DIR:$HOOK_PATH"
+    fi
+    if [[ $(trac-admin --version) == trac-admin\ 0.11* ]]; then
+        TRAC_RESYNC=true
+    else
+        TRAC_RESYNC=false
+    fi
+fi
+unset ADMIN_DIR
+cat >bin/mail <<__BASH__
+#!/bin/bash
+{
+    echo "\$@"
+    cat
+} >$PWD/mail.out
+__BASH__
+chmod +x bin/mail
+HOOK_PATH="$PWD/bin:$HOOK_PATH"
+cat >"$REPOS_PATH/conf/hooks-env" <<__CONF__
+[default]
+FCM_HOME=$FCM_HOME
+FCM_SVN_HOOK_ADMIN_EMAIL=${FCM_SVN_HOOK_ADMIN_EMAIL:-}
+FCM_SVN_HOOK_COMMIT_DUMP_DIR=$PWD/svn-dumps
+FCM_SVN_HOOK_NOTIFICATION_FROM=notifications at localhost
+FCM_SVN_HOOK_REPOS_SUFFIX=${FCM_SVN_HOOK_REPOS_SUFFIX:-}
+FCM_SVN_HOOK_TRAC_ROOT_DIR=${FCM_SVN_HOOK_TRAC_ROOT_DIR:-}
+PATH=$HOOK_PATH
+__CONF__
diff --git a/t/svn-username/00-branch.t b/t/svn-username/00-branch.t
new file mode 100755
index 0000000..02b2edd
--- /dev/null
+++ b/t/svn-username/00-branch.t
@@ -0,0 +1,103 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Test "fcm branch-create" and "fcm branch-list", alternate username.
+#-------------------------------------------------------------------------------
+. $(dirname $0)/test_header
+#-------------------------------------------------------------------------------
+setup
+init_repos
+cd $TEST_DIR
+svn cp -m 't1' --parents -q \
+    $ROOT_URL/trunk at 1 $ROOT_URL/branches/dev/barn.owl/r1_wing
+if ! svnserve -r $TEST_DIR -d --pid-file pid-file; then
+    if [[ -s pid-file ]]; then
+        kill $(cat pid-file)
+    fi
+    skip_all 'svnserve failed'
+    teardown
+    exit
+fi
+tests 9
+#-------------------------------------------------------------------------------
+# Tests fcm branch-create, alternate username
+TEST_KEY="$TEST_KEY_BASE"
+FCM_SUBVERSION_SERVERS_CONF="$PWD/$TEST_KEY-svn-servers-conf"
+cat >$FCM_SUBVERSION_SERVERS_CONF <<'__CONF__'
+[groups]
+bar=localhost
+
+[bar]
+username=barn.owl
+__CONF__
+SVN_EDITOR=true \
+FCM_SUBVERSION_SERVERS_CONF=$FCM_SUBVERSION_SERVERS_CONF run_pass "$TEST_KEY" \
+    fcm branch-create hello svn://localhost/test_repos <<<'n'
+echo >>"$TEST_KEY.out" # Insert newline
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__
+[info] Source: svn://localhost/test_repos/trunk@1 (4)
+[info] true: starting commit message editor...
+Change summary:
+--------------------------------------------------------------------------------
+A    svn://localhost/test_repos/branches/dev/barn.owl/r1_hello
+--------------------------------------------------------------------------------
+Commit message is as follows:
+--------------------------------------------------------------------------------
+Created /branches/dev/barn.owl/r1_hello from /trunk at 1.
+--------------------------------------------------------------------------------
+Create the branch?
+Enter "y" or "n" (or just press <return> for "n") 
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-1
+FCM_SUBVERSION_SERVERS_CONF="$PWD/$TEST_KEY-svn-servers-conf"
+cat >$FCM_SUBVERSION_SERVERS_CONF <<'__CONF__'
+[groups]
+bar=localhost
+
+[bar]
+username=barn.owl
+__CONF__
+FCM_SUBVERSION_SERVERS_CONF=$FCM_SUBVERSION_SERVERS_CONF run_pass "$TEST_KEY" \
+    fcm branch-list svn://localhost/test_repos
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+[info] svn://localhost/test_repos@4: 1 match(es)
+svn://localhost/test_repos/branches/dev/barn.owl/r1_wing@4
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+TEST_KEY=$TEST_KEY_BASE-0
+cat >$FCM_SUBVERSION_SERVERS_CONF <<'__CONF__'
+[groups]
+bar=localhost
+
+[bar]
+username=honey.bee
+__CONF__
+FCM_SUBVERSION_SERVERS_CONF=$FCM_SUBVERSION_SERVERS_CONF run_pass "$TEST_KEY" \
+    fcm branch-list svn://localhost/test_repos
+file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__'
+[info] svn://localhost/test_repos@4: 0 match(es)
+__OUT__
+file_cmp "$TEST_KEY.err" "$TEST_KEY.err" </dev/null
+#-------------------------------------------------------------------------------
+kill $(cat pid-file)
+teardown
+exit
diff --git a/t/svn-username/test_header b/t/svn-username/test_header
new file mode 100644
index 0000000..672ce12
--- /dev/null
+++ b/t/svn-username/test_header
@@ -0,0 +1,232 @@
+#!/bin/bash
+# ------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+# ------------------------------------------------------------------------------
+# Optional enviroment variables:
+#   TEST_PROJECT (tests using given project name)
+#   TEST_REMOTE_HOST (tests using svn+ssh repositories located on given host)
+# ------------------------------------------------------------------------------
+
+. $(dirname $0)/../lib/bash/test_header
+
+function file_cmp() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if cmp $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_grep() {
+    local TEST_KEY=$1
+    local PATTERN=$2
+    local FILE=$3
+    if grep -q -e "$PATTERN" $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function file_test() {
+    local TEST_KEY=$1
+    local FILE=$2
+    local OPTION=${3:--e}
+    if test $OPTION $TEST_DIR/$FILE; then
+        pass $TEST_KEY
+    else
+        fail $TEST_KEY
+    fi
+}
+
+function file_xxdiff() {
+    local TEST_KEY=$1
+    local FILE_ACTUAL=$2
+    local FILE_EXPECT=${3:--}
+    if xxdiff -D $TEST_DIR/$FILE_ACTUAL $FILE_EXPECT; then
+        pass $TEST_KEY
+        return
+    fi
+    fail $TEST_KEY
+}
+
+function init_repos() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    ROOT_URL=$REPOS_URL
+    PROJECT=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        PROJECT=$TEST_PROJECT"/"
+    fi
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/$PROJECT/trunk -m "initial trunk import"
+    svn mkdir -q $REPOS_URL/$PROJECT/tags -m "make tags"
+    svn mkdir -q --parents $REPOS_URL/$PROJECT/branches/dev/Share -m " "
+}
+
+function init_repos_layout_roses() {
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        TEST_REMOTE_DIR=$(ssh $TEST_REMOTE_HOST "mktemp -d")
+        ssh $TEST_REMOTE_HOST "svnadmin create --fs-type fsfs $TEST_REMOTE_DIR"
+        REPOS_URL="svn+ssh://${TEST_REMOTE_HOST}$TEST_REMOTE_DIR"
+    else
+        svnadmin create --fs-type fsfs $TEST_DIR/test_repos
+        REPOS_URL="file://$TEST_DIR/test_repos"
+    fi
+    svn mkdir -q --parents $REPOS_URL/a/a/0/0/0/trunk
+    svn import -q $TEST_SOURCE_DIR/../etc/repo_files \
+        $REPOS_URL/a/a/0/0/0/trunk -m "initial trunk import"
+    TMPFILE=$(mktemp)
+    cat >$TMPFILE <<__LAYOUT__
+depth-project = 5
+depth-branch = 1
+depth-tag = 1
+dir-trunk = trunk
+dir-branch =
+dir-tag =
+level-owner-branch =
+level-owner-tag =
+template-branch =
+template-tag =
+__LAYOUT__
+    TMPDIR=$(mktemp -d)
+    svn checkout -q $REPOS_URL $TMPDIR
+    svn propset -q --file=$TMPFILE fcm:layout $TMPDIR
+    svn commit -q -m " " $TMPDIR
+    rm -f $TMPFILE
+    rm -rf $TMPDIR
+    ROOT_URL=$REPOS_URL
+}
+
+function init_branch() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    local ROOT_PATH=
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+        ROOT_PATH=$ROOT_PATH/$TEST_PROJECT
+    fi
+    MESSAGE=$(echo -e "Created $ROOT_PATH/branches/dev/Share/$BRANCH_NAME from /trunk at 1.")
+    svn copy -q -r1 $ROOT_URL/trunk $ROOT_URL/branches/dev/Share/$BRANCH_NAME \
+                    -m "Made a branch $MESSAGE"
+}
+
+function init_branch_wc() {
+    local BRANCH_NAME=$1
+    local REPOS_URL=$2
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch $BRANCH_NAME $REPOS_URL
+    svn checkout -q $ROOT_URL/branches/dev/Share/$BRANCH_NAME $TEST_DIR/wc
+}
+
+function init_merge_branches() {
+    local BRANCH_NAME=$1
+    local OTHER_BRANCH_NAME=$2
+    local REPOS_URL=$3
+    local ROOT_URL=$REPOS_URL
+    if [[ -n ${TEST_PROJECT:-} ]]; then
+        ROOT_URL=$REPOS_URL/$TEST_PROJECT
+    fi
+    init_branch_wc $BRANCH_NAME $REPOS_URL
+    cd $TEST_DIR/wc
+    file_list=$(find . -type f | sed "/\.svn/d" | sort | head -5)
+    other_file=$(find . -type f | sed "/\.svn/d" | sort | tail -1)
+    for file in $file_list; do
+        sed -i "s/for/FOR/g; s/fi/end if/g; s/in/IN/g;" $file
+        sed -i "/#/d; /^ *!/d" $file
+        sed -i "s/!/!!/g; s/q/\nq/g; s/[(]/(\n/g" $file
+    done
+    file_dir=$(dirname $file)
+    svn copy -q $file ./added_file
+    svn copy -q module added_directory
+    touch module/tree_conflict_file
+    svn add -q $file_dir/tree_conflict_file
+    echo "Modified a line" >>$other_file
+    svn commit -q -m "Made changes for future merge of this branch"
+    svn update -q
+    init_branch $OTHER_BRANCH_NAME $REPOS_URL
+    svn switch -q $ROOT_URL/branches/dev/Share/$OTHER_BRANCH_NAME
+    echo " " > unversioned_file
+    properties_file=$(find . -type f | sed " /\.svn/d" | sort | tail -3 | head -1)
+    svn propset -q svn:executable "executable" $properties_file
+    svn copy -q $file renamed_added_file
+    svn commit -q -m "Made changes for future merge"
+    svn update -q
+    svn switch -q $ROOT_URL/trunk
+    echo "trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made trunk change"
+    svn update -q
+    echo "another trunk change" >>$(find . -type f | sed "/\.svn/d" | sort | head -1)
+    svn commit -q -m "Made another trunk change"
+    svn update -q
+}
+
+function run_pass() {
+    local TEST_KEY=$1
+    shift 1
+    if ! "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function run_fail() {
+    local TEST_KEY=$1
+    shift 1
+    if "$@" 1>$TEST_DIR/$TEST_KEY.out 2>$TEST_DIR/$TEST_KEY.err; then
+        fail $TEST_KEY
+        return
+    fi
+    pass $TEST_KEY
+}
+
+function setup() {
+    mkdir -p $TEST_DIR/.subversion
+    mkdir -p $TEST_DIR/run
+    cd $TEST_DIR/run
+}
+
+function teardown() {
+    cd $TEST_DIR
+    rm -rf $TEST_DIR/test_repos
+    rm -rf $TEST_DIR/wc
+    rm -rf $TEST_DIR/run
+    rm -rf $TEST_DIR/.subversion
+    if [[ -n ${TEST_REMOTE_HOST:-} ]]; then
+        ssh $TEST_REMOTE_HOST "rm -rf $TEST_REMOTE_DIR"
+    fi
+}
+
+REPOS_URL=
+ROOT_URL=
+PROJECT=
diff --git a/test/compare_results_fcm1 b/test/compare_results_fcm1
new file mode 100755
index 0000000..7a76edc
--- /dev/null
+++ b/test/compare_results_fcm1
@@ -0,0 +1,217 @@
+#!/bin/ksh
+
+TEST_NAME=$1
+BASE_DIR=$PWD
+DIFF=${DIFF:-diff}
+
+if [[ ! -f control/$TEST_NAME/.tests.complete ]]; then
+  echo "WARNING: Control tests did not complete, skipping comparisons"
+  exit
+fi
+
+DIFF_OUTPUT=$BASE_DIR/$TEST_NAME.diff
+
+# Compare directory contents
+for DIR in done .cache/.bld etc flags src
+do
+  if [[ -d test/$TEST_NAME/$DIR ]]; then
+    if [[ $DEBUG == true ]]; then
+      echo "Comparing $DIR directory contents ..."
+    fi
+    $DIFF -r control/$TEST_NAME/$DIR test/$TEST_NAME/$DIR >$DIFF_OUTPUT
+    if [[ $? != 0 ]]; then
+      echo "FAILED: $TEST_NAME - $DIR directory contents differ from control"
+      exit 1
+    fi
+  fi
+done
+
+# Compare file listings from directories
+for DIR in .cache/.ext bin obj lib inc ppsrc
+do
+  if [[ -d test/$TEST_NAME/$DIR ]]; then
+    if [[ $DEBUG == true ]]; then
+      echo "Comparing $DIR directory listings ..."
+    fi
+    cd $BASE_DIR/control/$TEST_NAME
+    ls -R -1 $DIR >$BASE_DIR/$TEST_NAME.control_files
+    cd $BASE_DIR/test/$TEST_NAME
+    ls -R -1 $DIR >$BASE_DIR/$TEST_NAME.test_files
+    cd $BASE_DIR
+    $DIFF $TEST_NAME.control_files $TEST_NAME.test_files >$DIFF_OUTPUT
+    if [[ $? != 0 ]]; then
+      echo "FAILED: $TEST_NAME - $DIR file listing differs from control"
+      exit 1
+    fi
+    rm $TEST_NAME.control_files $TEST_NAME.test_files
+  fi
+done
+
+# Compare file listings from directories (non-recursive)
+for DIR in .
+do
+  if [[ -d test/$TEST_NAME/$DIR ]]; then
+    if [[ $DEBUG == true ]]; then
+      echo "Comparing $DIR directory listings ..."
+    fi
+    cd $BASE_DIR/control/$TEST_NAME
+    ls -1 $DIR >$BASE_DIR/$TEST_NAME.control_files
+    cd $BASE_DIR/test/$TEST_NAME
+    ls -1 $DIR >$BASE_DIR/$TEST_NAME.test_files
+    cd $BASE_DIR/
+    $DIFF $TEST_NAME.control_files $TEST_NAME.test_files >$DIFF_OUTPUT
+    if [[ $? != 0 ]]; then
+      echo "FAILED: $TEST_NAME - $DIR file listing differs from control"
+      exit 1
+    fi
+    rm $TEST_NAME.control_files $TEST_NAME.test_files
+  fi
+done
+
+# Compare files in inc directory (except *.mod)
+if [[ -d test/$TEST_NAME/inc ]]; then
+  if [[ $DEBUG == true ]]; then
+    echo "Comparing inc directory contents ..."
+  fi
+  cd test/$TEST_NAME/inc
+  for FILE in $(ls -1 | grep -v "\.mod$")
+  do
+    $DIFF $BASE_DIR/control/$TEST_NAME/inc/$FILE $FILE >$DIFF_OUTPUT
+    if [[ $? != 0 ]]; then
+      echo "FAILED: $TEST_NAME - $FILE contents differ from control"
+      exit 1
+    fi
+  done
+fi
+cd $BASE_DIR
+
+# Compare files in ppsrc directory (ignoring RUN_DIR in *.c files)
+if [[ -d test/$TEST_NAME/ppsrc ]]; then
+  if [[ $DEBUG == true ]]; then
+    echo "Comparing ppsrc directory contents ..."
+  fi
+  cd test/$TEST_NAME
+  FILES=$(find ppsrc -type f)
+  cd $BASE_DIR
+  for FILE in $FILES
+  do
+    if [[ $FILE == ${FILE%.c} ]]; then
+      $DIFF control/$TEST_NAME/$FILE test/$TEST_NAME/$FILE >$DIFF_OUTPUT
+    else
+      cat control/$TEST_NAME/$FILE | sed "s#/.*/fcm_test_suite/control/##g" >$TEST_NAME.control_file
+      cat test/$TEST_NAME/$FILE | sed "s#/.*/fcm_test_suite/test/##g" >$TEST_NAME.test_file
+      $DIFF $TEST_NAME.control_file $TEST_NAME.test_file >$DIFF_OUTPUT
+    fi
+    if [[ $? != 0 ]]; then
+      echo "FAILED: $TEST_NAME - $FILE contents differ from control"
+      exit 1
+    fi
+    rm -f $TEST_NAME.control_file $TEST_NAME.test_file
+  done
+fi
+
+# Compare build command files
+cd test
+unset TEST_FILES CONTROL_FILES
+if [[ -f $TEST_NAME.build.commands.1 ]]; then
+  TEST_FILES=$(ls -1 $TEST_NAME.build.commands.*)
+fi
+cd $BASE_DIR/control
+if [[ -f $TEST_NAME.build.commands.1 ]]; then
+  CONTROL_FILES=$(ls -1 $TEST_NAME.build.commands.*)
+fi
+if [[ $TEST_FILES != $CONTROL_FILES ]]; then
+  echo "FAILED: $TEST_NAME - number of build command files differs from control"
+  exit 1
+fi
+cd $BASE_DIR
+for FILE in $TEST_FILES
+do
+  if [[ $DEBUG == true ]]; then
+    echo "Comparing $FILE contents ..."
+  fi
+  if [[ $NPROC = 1 ]]; then
+    $DIFF control/$FILE test/$FILE >$DIFF_OUTPUT
+  else
+    cat control/$FILE | grep -v wrap_ar | sort >$TEST_NAME.control_file
+    cat test/$FILE | grep -v wrap_ar | sort >$TEST_NAME.test_file
+    $DIFF $TEST_NAME.control_file $TEST_NAME.test_file >$DIFF_OUTPUT
+  fi
+  if [[ $? != 0 ]]; then
+    echo "FAILED: $TEST_NAME - $FILE contents differ from control"
+    exit 1
+  fi
+  rm -f $TEST_NAME.control_file $TEST_NAME.test_file
+done
+
+# Compare run output files
+cd test
+unset TEST_FILES CONTROL_FILES
+if [[ -f $TEST_NAME.exe.stdout.1 ]]; then
+  TEST_FILES="$TEST_FILES $(ls -1 $TEST_NAME.exe.stdout.*)"
+fi
+cd $BASE_DIR/control
+if [[ -f $TEST_NAME.exe.stdout.1 ]]; then
+  CONTROL_FILES="$CONTROL_FILES $(ls -1 $TEST_NAME.exe.stdout.*)"
+fi
+if [[ $TEST_FILES != $CONTROL_FILES ]]; then
+  echo "FAILED: $TEST_NAME - number of run output files differs from control"
+  exit 1
+fi
+cd $BASE_DIR
+for FILE in $TEST_FILES
+do
+  if [[ $DEBUG == true ]]; then
+    echo "Comparing $FILE contents ..."
+  fi
+  $DIFF control/$FILE test/$FILE >$DIFF_OUTPUT
+  if [[ $? != 0 ]]; then
+    echo "FAILED: $TEST_NAME - $FILE contents differ from control"
+    exit 1
+  fi
+done
+
+# Compare file contents ignoring RUN_DIR
+for FILE in Makefile fcm_env.sh cfg/bld.cfg cfg/parsed_bld.cfg cfg/ext.cfg cfg/parsed_ext.cfg
+do
+  if [[ -f test/$TEST_NAME/$FILE ]]; then
+    if [[ $DEBUG == true ]]; then
+      echo "Comparing $FILE contents ..."
+    fi
+    cat control/$TEST_NAME/$FILE | sed "s#/.*/fcm_test_suite/control/##g" >$TEST_NAME.control_file
+    cat test/$TEST_NAME/$FILE | sed "s#/.*/fcm_test_suite/test/##g" >$TEST_NAME.test_file
+    $DIFF $TEST_NAME.control_file $TEST_NAME.test_file >$DIFF_OUTPUT
+    if [[ $? != 0 ]]; then
+      echo "FAILED: $TEST_NAME - $FILE file contents differ from control"
+      exit 1
+    fi
+    rm $TEST_NAME.control_file $TEST_NAME.test_file
+  fi
+done
+
+if [[ $COMPARE_TIMES == true ]]; then
+  cd control
+  if [[ -f $TEST_NAME.extract.stdout.1 ]]; then
+    for FILE in $(ls -1 $TEST_NAME.extract.stdout.*)
+    do
+      echo "Extract times:"
+      cat $FILE | grep "\->.*second" > $TEST_NAME.control_extract_times
+      cat $BASE_DIR/test/$FILE | grep "\->.*second" > $TEST_NAME.test_extract_times
+      $DIFF --side-by-side $TEST_NAME.control_extract_times $TEST_NAME.test_extract_times
+      rm $TEST_NAME.control_extract_times $TEST_NAME.test_extract_times
+    done
+  fi
+  if [[ -f $TEST_NAME.build.stdout.1 ]]; then
+    for FILE in $(ls -1 $TEST_NAME.build.stdout.*)
+    do
+      echo "Build times:"
+      cat $FILE | grep "\->.*second" > $TEST_NAME.control_build_times
+      cat $BASE_DIR/test/$FILE | grep "\->.*second" > $TEST_NAME.test_build_times
+      $DIFF --side-by-side $TEST_NAME.control_build_times $TEST_NAME.test_build_times
+      rm $TEST_NAME.control_build_times $TEST_NAME.test_build_times
+    done
+  fi
+fi
+
+rm -f $DIFF_OUTPUT
+exit 0
diff --git a/test/compare_results_fcm2 b/test/compare_results_fcm2
new file mode 100755
index 0000000..ceb0128
--- /dev/null
+++ b/test/compare_results_fcm2
@@ -0,0 +1,205 @@
+#!/bin/ksh
+
+TEST_NAME=$1
+BASE_DIR=$PWD
+DIFF=${DIFF:-diff}
+
+if [[ ! -f control/$TEST_NAME/.tests.complete ]]; then
+  echo "WARNING: Control tests did not complete, skipping comparisons"
+  exit
+fi
+
+DIFF_OUTPUT=$BASE_DIR/$TEST_NAME.diff
+
+# Compare directory contents
+for DIR in extract build/etc .fcm-make/cache
+do
+  if [[ -d test/$TEST_NAME/$DIR ]]; then
+    if [[ $DEBUG == true ]]; then
+      echo "Comparing $DIR directory contents ..."
+    fi
+    $DIFF -r --exclude=hello_sub2.F90 control/$TEST_NAME/$DIR test/$TEST_NAME/$DIR >$DIFF_OUTPUT
+    if [[ $? != 0 ]]; then
+      echo "FAILED: $TEST_NAME - $DIR directory contents differ from control"
+      exit 1
+    fi
+  fi
+done
+
+# Compare file listings from directories
+for DIR in build/bin build/o build/include
+do
+  if [[ -d test/$TEST_NAME/$DIR ]]; then
+    if [[ $DEBUG == true ]]; then
+      echo "Comparing $DIR directory listings ..."
+    fi
+    cd $BASE_DIR/control/$TEST_NAME
+    ls -R -1 $DIR >$BASE_DIR/$TEST_NAME.control_files
+    cd $BASE_DIR/test/$TEST_NAME
+    ls -R -1 $DIR >$BASE_DIR/$TEST_NAME.test_files
+    cd $BASE_DIR
+    $DIFF $TEST_NAME.control_files $TEST_NAME.test_files >$DIFF_OUTPUT
+    if [[ $? != 0 ]]; then
+      echo "FAILED: $TEST_NAME - $DIR file listing differs from control"
+      exit 1
+    fi
+    rm $TEST_NAME.control_files $TEST_NAME.test_files
+  fi
+done
+
+# Compare file listings from directories (non-recursive)
+for DIR in . extract build preprocess
+do
+  if [[ -d test/$TEST_NAME/$DIR ]]; then
+    if [[ $DEBUG == true ]]; then
+      echo "Comparing $DIR directory listings ..."
+    fi
+    cd $BASE_DIR/control/$TEST_NAME
+    ls -1 $DIR >$BASE_DIR/$TEST_NAME.control_files
+    cd $BASE_DIR/test/$TEST_NAME
+    ls -1 $DIR >$BASE_DIR/$TEST_NAME.test_files
+    cd $BASE_DIR/
+    $DIFF $TEST_NAME.control_files $TEST_NAME.test_files >$DIFF_OUTPUT
+    if [[ $? != 0 ]]; then
+      echo "FAILED: $TEST_NAME - $DIR file listing differs from control"
+      exit 1
+    fi
+    rm $TEST_NAME.control_files $TEST_NAME.test_files
+  fi
+done
+
+# Compare files in build/include directory (except *.mod)
+if [[ -d test/$TEST_NAME/build/include ]]; then
+  if [[ $DEBUG == true ]]; then
+    echo "Comparing build/include directory contents ..."
+  fi
+  cd test/$TEST_NAME/build/include
+  for FILE in $(ls -1 | grep -v "\.mod$")
+  do
+    $DIFF $BASE_DIR/control/$TEST_NAME/build/include/$FILE $FILE >$DIFF_OUTPUT
+    if [[ $? != 0 ]]; then
+      echo "FAILED: $TEST_NAME - $FILE contents differ from control"
+      exit 1
+    fi
+  done
+fi
+cd $BASE_DIR
+
+# Compare files in preprocess directory (ignoring RUN_DIR in *.c & *.cpp files)
+if [[ -d test/$TEST_NAME/preprocess ]]; then
+  if [[ $DEBUG == true ]]; then
+    echo "Comparing preprocess directory contents ..."
+  fi
+  cd test/$TEST_NAME
+  FILES=$(find preprocess -type f)
+  cd $BASE_DIR
+  for FILE in $FILES
+  do
+    if [[ $FILE == ${FILE%.c} && $FILE == ${FILE%.cpp} ]]; then
+      $DIFF control/$TEST_NAME/$FILE test/$TEST_NAME/$FILE >$DIFF_OUTPUT
+    else
+      sed "s#/.*/fcm_test_suite/control/##g" control/$TEST_NAME/$FILE >$TEST_NAME.control_file
+      sed "s#/.*/fcm_test_suite/test/##g" test/$TEST_NAME/$FILE >$TEST_NAME.test_file
+      $DIFF $TEST_NAME.control_file $TEST_NAME.test_file >$DIFF_OUTPUT
+    fi
+    if [[ $? != 0 ]]; then
+      echo "FAILED: $TEST_NAME - $FILE contents differ from control"
+      exit 1
+    fi
+    rm -f $TEST_NAME.control_file $TEST_NAME.test_file
+  done
+fi
+
+# Compare build command files
+cd test
+unset TEST_FILES CONTROL_FILES
+if [[ -f $TEST_NAME.build.commands.1 ]]; then
+  TEST_FILES=$(ls -1 $TEST_NAME.build.commands.*)
+fi
+cd $BASE_DIR/control
+if [[ -f $TEST_NAME.build.commands.1 ]]; then
+  CONTROL_FILES=$(ls -1 $TEST_NAME.build.commands.*)
+fi
+if [[ $TEST_FILES != $CONTROL_FILES ]]; then
+  echo "FAILED: $TEST_NAME - number of build command files differs from control"
+  exit 1
+fi
+cd $BASE_DIR
+for FILE in $TEST_FILES
+do
+  if [[ $DEBUG == true ]]; then
+    echo "Comparing $FILE contents ..."
+  fi
+  if [[ $NPROC = 1 ]]; then
+    $DIFF control/$FILE test/$FILE >$DIFF_OUTPUT
+  else
+    sort control/$FILE >$TEST_NAME.control_file
+    sort test/$FILE >$TEST_NAME.test_file
+    $DIFF $TEST_NAME.control_file $TEST_NAME.test_file >$DIFF_OUTPUT
+  fi
+  if [[ $? != 0 ]]; then
+    echo "FAILED: $TEST_NAME - $FILE contents differ from control"
+    exit 1
+  fi
+  rm -f $TEST_NAME.control_file $TEST_NAME.test_file
+done
+
+# Compare run output files
+cd test
+unset TEST_FILES CONTROL_FILES
+if [[ -f $TEST_NAME.exe.stdout.1 ]]; then
+  TEST_FILES="$TEST_FILES $(ls -1 $TEST_NAME.exe.stdout.*)"
+fi
+cd $BASE_DIR/control
+if [[ -f $TEST_NAME.exe.stdout.1 ]]; then
+  CONTROL_FILES="$CONTROL_FILES $(ls -1 $TEST_NAME.exe.stdout.*)"
+fi
+if [[ $TEST_FILES != $CONTROL_FILES ]]; then
+  echo "FAILED: $TEST_NAME - number of run output files differs from control"
+  exit 1
+fi
+cd $BASE_DIR
+for FILE in $TEST_FILES
+do
+  if [[ $DEBUG == true ]]; then
+    echo "Comparing $FILE contents ..."
+  fi
+  $DIFF control/$FILE test/$FILE >$DIFF_OUTPUT
+  if [[ $? != 0 ]]; then
+    echo "FAILED: $TEST_NAME - $FILE contents differ from control"
+    exit 1
+  fi
+done
+
+# Compare file contents ignoring RUN_DIR
+for FILE in .fcm-make/config-as-parsed.cfg .fcm-make/config-on-success.cfg
+do
+  if [[ -f test/$TEST_NAME/$FILE ]]; then
+    if [[ $DEBUG == true ]]; then
+      echo "Comparing $FILE contents ..."
+    fi
+    cat control/$TEST_NAME/$FILE | sed "s#/.*/fcm_test_suite/control/##g" >$TEST_NAME.control_file
+    cat test/$TEST_NAME/$FILE | sed "s#/.*/fcm_test_suite/test/##g" >$TEST_NAME.test_file
+    $DIFF $TEST_NAME.control_file $TEST_NAME.test_file >$DIFF_OUTPUT
+    if [[ $? != 0 ]]; then
+      echo "FAILED: $TEST_NAME - $FILE file contents differ from control"
+      exit 1
+    fi
+    rm $TEST_NAME.control_file $TEST_NAME.test_file
+  fi
+done
+
+if [[ $COMPARE_TIMES == true ]]; then
+  cd control
+  for FILE in $(ls -1 $TEST_NAME.make.stdout.*)
+  do
+    echo "Make times:"
+    grep -e '\[done\]' -e '\[FAIL\]' $FILE >$TEST_NAME.control_make_times
+    grep -e '\[done\]' -e '\[FAIL\]' $BASE_DIR/test/$FILE >$TEST_NAME.test_make_times
+    $DIFF --side-by-side $TEST_NAME.control_make_times $TEST_NAME.test_make_times
+    rm $TEST_NAME.control_make_times $TEST_NAME.test_make_times
+  done
+fi
+
+rm -f $DIFF_OUTPUT
+exit 0
diff --git a/test/compare_times_fcm1-2 b/test/compare_times_fcm1-2
new file mode 100755
index 0000000..60ae23d
--- /dev/null
+++ b/test/compare_times_fcm1-2
@@ -0,0 +1,27 @@
+#!/bin/ksh
+
+TEST=$1
+DIFF=${DIFF:-diff}
+
+for FILE in $(ls -1 $TEST.make.stdout.*)
+do
+  FCM1_EXTRACT=$(echo "fcm1_${FILE#fcm2_}" | sed 's/make/extract/')
+  FCM1_BUILD=$(echo "fcm1_${FILE#fcm2_}" | sed 's/make/build/')
+  if [[ -f $FCM1_EXTRACT || -f $FCM1_BUILD ]]; then
+    if [[ $DEBUG == true ]]; then
+      echo "Comparing $FILE with $FCM1_EXTRACT & $FCM1_BUILD ..."
+    fi
+    echo "Times:"
+    if [[ -f $FCM1_EXTRACT ]]; then
+      grep "\->.*second" $FCM1_EXTRACT | grep -v Setup | grep -v Parse >> $TEST.fcm1_times
+    fi
+    if [[ -f $FCM1_BUILD ]]; then
+      grep "\->.*second" $FCM1_BUILD | grep -v Setup | grep -v Parse >> $TEST.fcm1_times
+    fi
+    grep -e '\[done\]' -e '\[FAIL\]' $FILE > $TEST.fcm2_times
+    $DIFF --side-by-side $TEST.fcm1_times $TEST.fcm2_times
+    rm $TEST.fcm1_times $TEST.fcm2_times
+  fi
+done
+
+exit 0
diff --git a/test/create_hpc_batch_script b/test/create_hpc_batch_script
new file mode 100755
index 0000000..ded90ef
--- /dev/null
+++ b/test/create_hpc_batch_script
@@ -0,0 +1,90 @@
+#!/bin/ksh
+
+cat <<EOF >$BATCH_SCRIPT
+#!/bin/ksh
+#
+# @ job_type = serial
+# @ class = serial
+# @ resources = ConsumableCpus(6) ConsumableMemory(1gb)
+# @ initialdir = $RUN_DIR_HPC
+# @ output = hpc_batch.stdout
+# @ error  = hpc_batch.stderr
+# @ queue
+
+. prg_12_1_0_9
+export PATH=$RUN_DIR_HPC/fcm/bin:\$PATH
+export DIFF="/opt/freeware/bin/diff"
+export DEBUG=$DEBUG
+
+. \$MY_BIN/$BATCH_DIRS_NAME
+let failed=0
+for TEST in \$TESTS_FCM1
+do
+  cd $RUN_DIR_HPC/\$TEST
+  echo "\$(date): Running \$TEST ..."
+  find src -exec touch {} \\;
+  fcm build -v 2 -j 6 >../\$TEST.build.stdout.1 2>../\$TEST.build.stderr.1
+  RC=\$?
+  if [[ \$RC != 0 ]]; then
+    echo "FAILED: \$TEST failed"
+    let failed=failed+1
+  else
+EOF
+
+if [[ $TYPE == control ]]; then
+  echo "    touch .tests.complete" >>$BATCH_SCRIPT
+else
+  cat <<EOF >>$BATCH_SCRIPT
+    export COMPARE_TIMES=true
+    cd $BASE_DIR_HPC
+    $MY_BIN/compare_results_fcm1 \$TEST
+    if [[ \$? != 0 ]]; then
+      let failed=failed+1
+    fi
+EOF
+fi
+
+cat <<EOF >>$BATCH_SCRIPT
+  fi
+done
+for TEST in \$TESTS_FCM2
+do
+  cd $RUN_DIR_HPC/\$TEST
+  echo "\$(date): Running \$TEST ..."
+  find extract -exec touch {} \\;
+  fcm make -j 6 >../\$TEST.make.stdout.1 2>../\$TEST.make.stderr.1
+  RC=\$?
+  if [[ \$RC != 0 ]]; then
+    echo "FAILED: \$TEST failed"
+    let failed=failed+1
+  else
+EOF
+
+if [[ $TYPE == control ]]; then
+  cat <<EOF >>$BATCH_SCRIPT
+    touch .tests.complete
+    cd ..
+    $MY_BIN/compare_times_fcm1-2 \$TEST
+EOF
+else
+  cat <<EOF >>$BATCH_SCRIPT
+    export COMPARE_TIMES=true
+    cd $BASE_DIR_HPC
+    $MY_BIN/compare_results_fcm2 \$TEST
+    if [[ \$? != 0 ]]; then
+      let failed=failed+1
+    fi
+EOF
+fi
+
+cat <<EOF >>$BATCH_SCRIPT
+  fi
+done
+
+echo "\$(date): HPC performance tests finished"
+if [[ \$failed == 0 ]]; then
+  echo "SUMMARY: All HPC performance tests succeeded"
+else
+  echo "SUMMARY: \$failed HPC performance tests failed"
+fi
+EOF
diff --git a/test/create_repos b/test/create_repos
new file mode 100755
index 0000000..21b6482
--- /dev/null
+++ b/test/create_repos
@@ -0,0 +1,197 @@
+#!/bin/ksh
+set -eu
+
+REPOS_FILES=$MY_BIN/repos
+
+echo "$(date): Creating repository ..."
+rm -rf $REPOS_DIR
+svnadmin create --fs-type fsfs $REPOS_DIR
+
+echo "$(date): Initial import ..."
+
+svn import -q $REPOS_FILES/trunk $REPOS_URL/trunk -m" "
+svn mkdir -q $REPOS_URL/branches -m" "
+svn mkdir -q $REPOS_URL/tags -m" "
+
+# Modify some files
+branch=modify_files_base
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+rm -rf $BASE_DIR/work
+svn co -q $REPOS_URL/branches/dev/Share/$branch $BASE_DIR/work
+cd $BASE_DIR/work
+perl -pi -e 's/IMPLICIT NONE/implicit none/' program/hello.F90
+perl -pi -e 's/Hello Earth/Hello Earthlings/' module/hello_constants.inc
+svn ci -m" "
+
+# Modify some files, one of which can be merged with modify_files_base branch
+branch=modify_files_merge1
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+perl -pi -e "s/this = 'Hello'/this = 'HELLO'/" program/hello.F90
+perl -pi -e 's/Hello Earth/Hello Earthlings/' subroutine/hello_c.c
+svn ci -m" "
+
+# Modify a file which can be merged with the modify_files_base & modify_files_merge1 branches
+branch=modify_files_merge2
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+perl -pi -e 's/PROGRAM/program/' program/hello.F90
+svn ci -m" "
+
+# Modify a file which clashes with modify_files_base branch
+branch=modify_files_clash
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+perl -pi -e 's/IMPLICIT NONE/implicit NONE/' program/hello.F90
+svn ci -m" "
+
+# Modify a subroutine without altering its interface
+branch=modify_subroutine
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+perl -pi -e 's/integer (common)/integer (com)/' subroutine/hello_sub.F90
+svn ci -m" "
+
+# Modify a subroutine and alter its interface
+branch=modify_subroutine_interface
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+perl -pi -e 's/integer_arg/int_arg/' subroutine/hello_sub.F90
+svn ci -m" "
+
+# Modify a pre-processing include file
+branch=modify_pp_include
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+perl -pi -e 's/:/: Message - /' subroutine/hello_sub.h
+svn ci -m" "
+
+# Add lines to a file to coincide with following branch
+branch=add_lines
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+cp $REPOS_FILES/add_subroutine/hello.F90.add_lines program/hello.F90
+svn ci -m" "
+
+# Add a new file
+branch=add_file
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+cp $REPOS_FILES/add_subroutine/hello.F90 program/hello.F90
+cp $REPOS_FILES/add_subroutine/hello_sub2.f90 subroutine
+svn add subroutine/hello_sub2.f90
+svn ci -m" "
+
+# Add a new directory
+branch=add_directory
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+cp $REPOS_FILES/add_subroutine/hello.F90 program/hello.F90
+mkdir subroutine2
+cp $REPOS_FILES/add_subroutine/hello_sub2.f90 subroutine2
+svn add subroutine2
+svn ci -m" "
+
+# Add a duplicate subroutine
+branch=add_duplicate
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+cp subroutine/hello_sub.F90 subroutine/hello_sub2.F90
+svn add subroutine/hello_sub2.F90
+svn ci -m" "
+
+# Delete a file
+branch=delete_file
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+svn rm subroutine/hello_c.c
+svn ci -m" "
+
+# Delete a CPP file
+branch=delete_pp_file
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+svn rm subroutine/hello_sub.F90
+svn ci -m" "
+
+# Delete a Fortran include file
+branch=delete_inc_file
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+svn rm module/hello_constants.inc
+svn ci -m" "
+
+# Delete a CPP include file
+branch=delete_ppinc_file
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+svn rm subroutine/hello_sub.h
+svn ci -m" "
+
+# Delete a directory
+branch=delete_directory
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+svn rm subroutine
+svn ci -m" "
+
+# Rename the executable
+branch=exe_rename
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+perl -pi -e 's/hello.exe/hello_world.exe/' script/hello.sh
+svn ci -m" "
+
+# Use a .f90 file as an include file
+branch=change_src_type
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+perl -pi -e 's/hello_constants.inc/hello_constants_inc.f90/g' module/hello_constants_dummy.inc
+svn mv module/hello_constants.inc module/hello_constants_inc.f90
+svn ci -m" "
+
+# Add a symbolic link
+branch=symbolic_link
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+cd subroutine
+ln -s hello_sub.F90 hello_sub2.F90
+svn add hello_sub2.F90
+cd $OLDPWD
+svn ci -m" "
+
+# Use space in a path name
+branch=space_in_name
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+svn mv blockdata/hello_blockdata.F90 "blockdata/hello blockdata.F90"
+svn mv --force blockdata "block data"
+svn ci -m" "
+
+# Create a cylc dependency which will fail
+branch=cyclic_dep_fail
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+cp $REPOS_FILES/cyclic_dependency/hello.F90 program/hello.F90
+cp $REPOS_FILES/cyclic_dependency/hello_constants.f90.fail module/hello_constants.f90
+svn ci -m" "
+
+# Create a cylc dependency which should be OK
+branch=cyclic_dep_ok
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+cp $REPOS_FILES/cyclic_dependency/hello.F90 program/hello.F90
+cp $REPOS_FILES/cyclic_dependency/hello_constants.f90.ok module/hello_constants.f90
+cp $REPOS_FILES/cyclic_dependency/hello_sub2.f90 subroutine
+svn add subroutine/hello_sub2.f90
+svn ci -m" "
+
+# Create a dependency which needs to be defined manually
+branch=f77_dep
+fcm bc -t SHARE --rev-flag NONE --non-interactive $branch $REPOS_URL at 1
+svn sw $REPOS_URL/branches/dev/Share/$branch
+perl -pi -e 's/INCLUDE /!INCLUDE /' program/hello.F90
+svn ci -m" "
+
+rm -rf $BASE_DIR/work
+echo "$(date): Finished"
diff --git a/test/get_hpc_results b/test/get_hpc_results
new file mode 100755
index 0000000..3fba9cd
--- /dev/null
+++ b/test/get_hpc_results
@@ -0,0 +1,34 @@
+#!/bin/ksh
+
+while getopts ":ch" opt
+do
+  case $opt in
+    c ) COPY=true ;;
+    h ) HELP=true ;;
+    \? ) echo "Invalid option"
+        HELP=true
+        break ;;
+  esac
+done
+if [[ $# != $(($OPTIND - 1)) ]]; then
+  echo "Invalid argument"
+  HELP=true
+fi
+
+if [[ $HELP == true ]]; then
+ echo 'Usage: get_hpc_results [options]'
+ echo 'Valid options:'
+ echo '-c'
+ echo '   Mirror the full test directory from the HPC'
+ echo '-h'
+ echo '   Print this help message'
+ exit 1
+fi
+
+export HPC=$(rose host-select -q hpc)
+ssh $HPC "cd working/fcm_test_suite; $MY_BIN/report_hpc_results" | tee $LOCALTEMP/fcm_test_suite/hpc.summary
+
+if [[ $COPY == true ]]; then
+  echo "Mirroring results from HPC ..."
+  rsync -a --delete --rsh="ssh" $HPC:fcm_test_suite/ $LOCALTEMP/fcm_test_suite/hpc_mirror
+fi
diff --git a/test/perform_test_fcm1 b/test/perform_test_fcm1
new file mode 100755
index 0000000..c2681ce
--- /dev/null
+++ b/test/perform_test_fcm1
@@ -0,0 +1,165 @@
+#!/bin/ksh
+set -u
+
+echo "$(date): Running $TEST ..."
+
+cfg_name=$TEST
+if [[ -a $MY_BIN/test_config/$TEST ]]; then
+ . $MY_BIN/test_config/$TEST
+fi
+
+export THIS_RUN_DIR=$RUN_DIR/$TEST
+rm -rf $THIS_RUN_DIR
+mkdir $THIS_RUN_DIR
+cd $THIS_RUN_DIR
+mirror=${mirror:-false}
+if [[ $mirror == remote ]]; then
+  export THIS_RUN_DIR_HPC=$RUN_DIR_HPC/$TEST
+fi
+
+NPROC=${NPROC:-1}
+let count=1
+for this_cfg in $cfg_name
+do
+  extract=$(eval "echo \${extract_$count:-}")
+  build=$(eval "echo \${build_$count:-}")
+  run=$(eval "echo \${run_$count:-}")
+  if [[ $DEBUG == true ]]; then
+    echo "Running extract ($count) ..."
+  fi
+  fcm extract -v 3 $REPOS_URL/trunk/cfg/$this_cfg.cfg >../$TEST.extract.stdout.$count 2>../$TEST.extract.stderr.$count
+  RC=$?
+  if [[ $extract == fail ]]; then
+    if [[ $RC == 0 ]]; then
+      echo "FAILED: $TEST ($count) extract did not fail"
+      exit 1
+    fi
+  elif [[ $extract == fail_known ]]; then
+    if [[ $RC == 0 ]]; then
+      echo "FAILED: $TEST ($count) extract did not fail as expected (known problem fixed?)"
+      exit 1
+    else
+      if [[ $DEBUG == true ]]; then
+        echo "Known problem: $TEST ($count) extract failed"
+      fi
+      break
+    fi
+  elif [[ $extract == succeed_known ]]; then
+    if [[ $RC == 0 ]]; then
+      if [[ $DEBUG == true ]]; then
+        echo "Known problem: $TEST ($count) extract did not fail"
+      fi
+      break
+    else
+      echo "FAILED: $TEST ($count) extract did not succeed as expected (known problem fixed?)"
+      exit 1
+    fi
+  else
+    if [[ $RC != 0 ]]; then
+      echo "FAILED: $TEST ($count) extract failed"
+      exit 1
+    else
+      if [[ $mirror == local ]]; then
+        cd ${THIS_RUN_DIR}_mirror
+      elif [[ $mirror == remote ]]; then
+        echo "$TEST" >> $BATCH_DIRS
+        break
+      fi
+      if [[ $DEBUG == true ]]; then
+        echo "Running build ($count) ..."
+      fi
+      export command_file=$RUN_DIR/$TEST.build.commands.$count
+      touch $command_file
+      fcm build -v 2 -j $NPROC >../$TEST.build.stdout.$count 2>../$TEST.build.stderr.$count
+      RC=$?
+      if [[ $build == fail ]]; then
+        if [[ $RC == 0 ]]; then
+          echo "FAILED: $TEST ($count) build did not fail"
+          exit 1
+        fi
+      elif [[ $build == fail_known ]]; then
+        if [[ $RC == 0 ]]; then
+          echo "FAILED: $TEST ($count) build did not fail as expected (known problem fixed?)"
+          exit 1
+        else
+          if [[ $DEBUG == true ]]; then
+            echo "Known problem: $TEST ($count) build failed"
+          fi
+          break
+        fi
+      elif [[ $build == succeed_known ]]; then
+        if [[ $RC == 0 ]]; then
+          if [[ $DEBUG == true ]]; then
+            echo "Known problem: $TEST ($count) build did not fail"
+          fi
+          break
+        else
+          echo "FAILED: $TEST ($count) build did not succeed as expected (known problem fixed?)"
+          exit 1
+        fi
+      else
+        if [[ $RC != 0 ]]; then
+          echo "FAILED: $TEST ($count) build failed"
+          exit 1
+        else
+          if [[ $run != no ]]; then
+            exe_name=hello.sh
+            env_file=fcm_env.sh
+            if [[ ! -a $env_file ]]; then
+              echo "FAILED: $TEST ($count) env file does not exist"
+              exit 1
+            else
+              . $env_file
+              if [[ $DEBUG == true ]]; then
+                echo "Running executable ($count) ..."
+              fi
+              $exe_name >../$TEST.exe.stdout.$count 2>../$TEST.exe.stderr.$count
+              RC=$?
+              if [[ $run == fail ]]; then
+                if [[ $RC == 0 ]]; then
+                  echo "FAILED: $TEST ($count) run did not fail"
+                  exit 1
+                fi
+              elif [[ $run == fail_known ]]; then
+                if [[ $RC == 0 ]]; then
+                  echo "FAILED: $TEST ($count) run did not fail as expected (known problem fixed?)"
+                  exit 1
+                else
+                  if [[ $DEBUG == true ]]; then
+                    echo "Known problem: $TEST ($count) run failed"
+                  fi
+                  break
+                fi
+              elif [[ $run == succeed_known ]]; then
+                if [[ $RC == 0 ]]; then
+                  if [[ $DEBUG == true ]]; then
+                    echo "Known problem: $TEST ($count) run did not fail"
+                  fi
+                  break
+                else
+                  echo "FAILED: $TEST ($count) run did not succeed as expected (known problem fixed?)"
+                  exit 1
+                fi
+              else
+                if [[ $RC != 0 ]]; then
+                  echo "FAILED: $TEST ($count) run failed"
+                  exit 1
+                fi
+              fi
+            fi
+          fi
+        fi
+      fi
+    fi
+  fi
+  let count=count+1
+done
+
+rm -rf $BASE_DIR/work
+
+if [[ $TYPE == control ]]; then
+  touch $THIS_RUN_DIR/.tests.complete
+else
+  cd $BASE_DIR
+  $MY_BIN/compare_results_fcm1 $TEST
+fi
diff --git a/test/perform_test_fcm2 b/test/perform_test_fcm2
new file mode 100755
index 0000000..49562d1
--- /dev/null
+++ b/test/perform_test_fcm2
@@ -0,0 +1,204 @@
+#!/bin/ksh
+set -u
+
+echo "$(date): Running $TEST ..."
+
+cfg_name=$TEST
+if [[ -a $MY_BIN/test_config/$TEST ]]; then
+ . $MY_BIN/test_config/$TEST
+fi
+
+export THIS_RUN_DIR=$RUN_DIR/$TEST
+rm -rf $THIS_RUN_DIR
+mkdir $THIS_RUN_DIR
+cd $THIS_RUN_DIR
+mirror=${mirror:-false}
+if [[ $mirror == remote ]]; then
+  export THIS_RUN_DIR_HPC=$RUN_DIR_HPC/$TEST
+fi
+
+NPROC=${NPROC:-1}
+let count=1
+for this_cfg in $cfg_name
+do
+  make=$(eval "echo \${make_$count:-}")
+  run=$(eval "echo \${run_$count:-}")
+  export command_file=$RUN_DIR/$TEST.build.commands.$count
+  touch $command_file
+  if [[ $DEBUG == true ]]; then
+    echo "Running make ($count) ..."
+  fi
+  fcm make -j $NPROC -f $REPOS_URL/trunk/cfg/$this_cfg.cfg >../$TEST.make.stdout.$count 2>../$TEST.make.stderr.$count
+  RC=$?
+  if [[ $make == fail ]]; then
+    if [[ $RC == 0 ]]; then
+      echo "FAILED: $TEST ($count) make did not fail"
+      exit 1
+    fi
+  elif [[ $make == fail_known ]]; then
+    if [[ $RC == 0 ]]; then
+      echo "FAILED: $TEST ($count) make did not fail as expected (known problem fixed?)"
+      exit 1
+    else
+      if [[ $DEBUG == true ]]; then
+        echo "Known problem: $TEST ($count) make failed"
+      fi
+      break
+    fi
+  elif [[ $make == succeed_known ]]; then
+    if [[ $RC == 0 ]]; then
+      if [[ $DEBUG == true ]]; then
+        echo "Known problem: $TEST ($count) make did not fail"
+      fi
+      break
+    else
+      echo "FAILED: $TEST ($count) make did not succeed as expected (known problem fixed?)"
+      exit 1
+    fi
+  else
+    if [[ $RC != 0 ]]; then
+      echo "FAILED: $TEST ($count) make failed"
+      exit 1
+    else
+      if [[ $mirror == remote ]]; then
+        echo "$TEST" >> $BATCH_DIRS
+        break
+      elif [[ $mirror == local ]]; then
+        cd ${THIS_RUN_DIR}_mirror
+        RC=$?
+        if [[ $RC != 0 ]]; then
+          echo "FAILED: $TEST ($count) cd to mirror failed"
+          exit 1
+        fi
+        if [[ $DEBUG == true ]]; then
+          echo "Running make after mirror ($count) ..."
+        fi
+        fcm make -j $NPROC >../$TEST.make2.stdout.$count 2>../$TEST.make2.stderr.$count
+        RC=$?
+        if [[ $RC != 0 ]]; then
+          echo "FAILED: $TEST ($count) make after mirror failed"
+          exit 1
+        fi
+      fi
+      if [[ $run != no ]]; then
+        exe_name=hello.sh
+        build_dir=$PWD/build/bin
+        if [[ ! -a $build_dir ]]; then
+          echo "FAILED: $TEST ($count) build directory does not exist"
+          exit 1
+        else
+          PATH=$build_dir:$PATH
+          if [[ $DEBUG == true ]]; then
+            echo "Running executable ($count) ..."
+          fi
+          $exe_name >../$TEST.exe.stdout.$count 2>../$TEST.exe.stderr.$count
+          RC=$?
+          if [[ $run == fail ]]; then
+            if [[ $RC == 0 ]]; then
+              echo "FAILED: $TEST ($count) run did not fail"
+              exit 1
+            fi
+          elif [[ $run == fail_known ]]; then
+            if [[ $RC == 0 ]]; then
+              echo "FAILED: $TEST ($count) run did not fail as expected (known problem fixed?)"
+              exit 1
+            else
+              if [[ $DEBUG == true ]]; then
+                echo "Known problem: $TEST ($count) run failed"
+              fi
+              break
+            fi
+          elif [[ $run == succeed_known ]]; then
+            if [[ $RC == 0 ]]; then
+              if [[ $DEBUG == true ]]; then
+                echo "Known problem: $TEST ($count) run did not fail"
+              fi
+              break
+            else
+              echo "FAILED: $TEST ($count) run did not succeed as expected (known problem fixed?)"
+              exit 1
+            fi
+          else
+            if [[ $RC != 0 ]]; then
+              echo "FAILED: $TEST ($count) run failed"
+              exit 1
+            else
+              build_dir=$PWD/build2/bin
+              if [[ -a $build_dir ]]; then
+                PATH=$build_dir:$PATH
+                if [[ $DEBUG == true ]]; then
+                  echo "Running executable from build2 ($count) ..."
+                fi
+                $exe_name >../$TEST.exe2.stdout.$count 2>../$TEST.exe2.stderr.$count
+                RC=$?
+                if [[ $RC != 0 ]]; then
+                  echo "FAILED: $TEST ($count) build2 run failed"
+                  exit 1
+                fi
+              fi
+            fi
+          fi
+        fi
+      fi
+    fi
+  fi
+  let count=count+1
+done
+
+rm -rf $BASE_DIR/work
+
+if [[ $TYPE == control ]]; then
+  touch $THIS_RUN_DIR/.tests.complete
+  compare_fcm1=${compare_fcm1:-true}
+  if [[ $compare_fcm1 == true ]]; then
+    cd $RUN_DIR
+    # Test that fcm2 triggers the same build commands as the equivalent from fcm1
+    if [[ -f $TEST.build.commands.1 ]]; then
+      for FILE in $(ls -1 $TEST.build.commands.*)
+      do
+        FCM1_FILE=fcm1_${FILE#fcm2_}
+        if [[ -f $FCM1_FILE ]]; then
+          if [[ $DEBUG == true ]]; then
+            echo "Comparing $FILE with $FCM1_FILE ..."
+          fi
+          cat $FCM1_FILE | grep -v wrap_ar | sed 's#wrap_ld#wrap_fc#' | sed 's# -c # #' | sed 's#__fcm__##' | sed 's#\.F#\.f#' | \
+             sed 's#/fcm1_[^ ]*/##g' | sed 's#-Iinc #-Iinclude #g' | sed 's# -Llib -Llib# -Llib#' | \
+             sed 's# -Llib -lmkmdblseq##' | sort > $TEST.fcm1.build.commands
+          cat $FILE | grep -v wrap_ar  | sed 's# -c # #' | sed 's#-oo/#-o #' | sed 's#-obin/#-o #' | sed 's# o/# #g' | \
+             sed 's#\.F#\.f#' | sed 's#/fcm2_[^ ]*/##g' | sed 's#\.\./\.\.##g' | sed 's#wrap_fc2 \(.*\.exe\)#wrap_fc \1#' | \
+             sort > $TEST.fcm2.build.commands
+          diff $TEST.fcm1.build.commands $TEST.fcm2.build.commands > $TEST.diff
+          if [[ $? != 0 ]]; then
+            echo "FAILED: $FILE - build commands differ from FCM1 case"
+            exit 1
+          else
+            rm $TEST.fcm1.build.commands $TEST.fcm2.build.commands $TEST.diff
+          fi
+        fi
+      done
+    fi
+    # Test that fcm2 run results match equivalent from fcm1
+    if [[ -f $TEST.exe.stdout.1 ]]; then
+      for FILE in $(ls -1 $TEST.exe.stdout.*)
+      do
+        FCM1_FILE=fcm1_${FILE#fcm2_}
+        if [[ -f $FCM1_FILE ]]; then
+          if [[ $DEBUG == true ]]; then
+            echo "Comparing $FILE with $FCM1_FILE ..."
+          fi
+          diff -q $FCM1_FILE $FILE
+          if [[ $? != 0 ]]; then
+            echo "FAILED: $FILE - contents differ from FCM1 case"
+            exit 1
+          fi
+        fi
+      done
+    fi
+    if [[ $COMPARE_TIMES == true ]]; then
+      $MY_BIN/compare_times_fcm1-2 $TEST
+    fi
+  fi
+else
+  cd $BASE_DIR
+  $MY_BIN/compare_results_fcm2 $TEST
+fi
diff --git a/test/report_hpc_results b/test/report_hpc_results
new file mode 100755
index 0000000..4abeca7
--- /dev/null
+++ b/test/report_hpc_results
@@ -0,0 +1,14 @@
+#!/bin/ksh
+
+if [[ -f control/hpc_batch.stderr ]]; then
+  echo "##### Control run stderr ############################################"
+  cat control/hpc_batch.stderr
+  echo "##### Control run stdout ############################################"
+  cat control/hpc_batch.stdout | sed -ne '1,/^SUMMARY/p'
+fi
+if [[ -f test/hpc_batch.stderr ]]; then
+  echo "##### Test run stderr ###############################################"
+  cat test/hpc_batch.stderr
+  echo "##### Test run stdout ###############################################"
+  cat test/hpc_batch.stdout | sed -ne '1,/^SUMMARY/p'
+fi
diff --git a/test/repos/add_subroutine/hello.F90 b/test/repos/add_subroutine/hello.F90
new file mode 100644
index 0000000..9785a76
--- /dev/null
+++ b/test/repos/add_subroutine/hello.F90
@@ -0,0 +1,26 @@
+PROGRAM Hello
+
+#if !defined(LOCAL_STRING)
+USE Hello_Constants, ONLY: hello_string
+#endif
+
+IMPLICIT NONE
+
+#if defined(LOCAL_STRING)
+CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Mother Earth!'
+#endif
+
+#if defined(CALL_HELLO_SUB)
+INCLUDE 'hello_sub.interface'
+#endif
+INCLUDE 'hello_sub2.interface'
+
+CHARACTER (LEN=*), PARAMETER :: this = 'Hello'
+
+WRITE (*, '(A)') this // ': ' // TRIM (hello_string)
+#if defined(CALL_HELLO_SUB)
+CALL Hello_Sub (HUGE(0))
+#endif
+CALL Hello_Sub2 ()
+
+END PROGRAM Hello
diff --git a/test/repos/add_subroutine/hello.F90.add_lines b/test/repos/add_subroutine/hello.F90.add_lines
new file mode 100644
index 0000000..672a7ee
--- /dev/null
+++ b/test/repos/add_subroutine/hello.F90.add_lines
@@ -0,0 +1,28 @@
+PROGRAM Hello
+
+#if !defined(LOCAL_STRING)
+USE Hello_Constants, ONLY: hello_string
+#endif
+
+IMPLICIT NONE
+
+#if defined(LOCAL_STRING)
+CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Mother Earth!'
+#endif
+
+INTEGER :: integer_arg = 1234
+
+#if defined(CALL_HELLO_SUB)
+INCLUDE 'hello_sub.interface'
+#endif
+
+! Just a comment line
+
+CHARACTER (LEN=*), PARAMETER :: this = 'Hello'
+
+WRITE (*, '(A)') this // ': ' // TRIM (hello_string)
+#if defined(CALL_HELLO_SUB)
+CALL Hello_Sub (integer_arg)
+#endif
+
+END PROGRAM Hello
diff --git a/test/repos/add_subroutine/hello_sub2.f90 b/test/repos/add_subroutine/hello_sub2.f90
new file mode 100644
index 0000000..8ebe19c
--- /dev/null
+++ b/test/repos/add_subroutine/hello_sub2.f90
@@ -0,0 +1,11 @@
+SUBROUTINE Hello_Sub2
+
+USE Hello_Constants, ONLY: hello_string
+
+IMPLICIT NONE
+
+CHARACTER (LEN=*), PARAMETER :: this = 'Hello_Sub2'
+
+WRITE (*, '(A)') this // ': ' // TRIM (hello_string)
+
+END SUBROUTINE Hello_Sub2
diff --git a/test/repos/cyclic_dependency/hello.F90 b/test/repos/cyclic_dependency/hello.F90
new file mode 100644
index 0000000..bcc2e27
--- /dev/null
+++ b/test/repos/cyclic_dependency/hello.F90
@@ -0,0 +1,14 @@
+PROGRAM Hello
+
+USE Hello_Constants, ONLY: hello_string, Hello_Sub_Wrapper
+
+IMPLICIT NONE
+
+INTEGER :: integer_arg = 1234
+
+CHARACTER (LEN=*), PARAMETER :: this = 'Hello'
+
+WRITE (*, '(A)') this // ': ' // TRIM (hello_string)
+CALL Hello_Sub_Wrapper (integer_arg)
+
+END PROGRAM Hello
diff --git a/test/repos/cyclic_dependency/hello_constants.f90.fail b/test/repos/cyclic_dependency/hello_constants.f90.fail
new file mode 100644
index 0000000..830077f
--- /dev/null
+++ b/test/repos/cyclic_dependency/hello_constants.f90.fail
@@ -0,0 +1,19 @@
+MODULE Hello_Constants
+
+INCLUDE 'hello_constants_dummy.inc'
+
+CONTAINS
+
+SUBROUTINE Hello_Sub_Wrapper (integer_arg)
+
+IMPLICIT NONE
+
+INTEGER :: integer_arg
+
+INCLUDE 'hello_sub.interface'
+
+CALL Hello_Sub (integer_arg)
+
+END SUBROUTINE Hello_Sub_Wrapper
+
+END MODULE Hello_Constants
diff --git a/test/repos/cyclic_dependency/hello_constants.f90.ok b/test/repos/cyclic_dependency/hello_constants.f90.ok
new file mode 100644
index 0000000..97e0ba3
--- /dev/null
+++ b/test/repos/cyclic_dependency/hello_constants.f90.ok
@@ -0,0 +1,19 @@
+MODULE Hello_Constants
+
+INCLUDE 'hello_constants_dummy.inc'
+
+CONTAINS
+
+SUBROUTINE Hello_Sub_Wrapper (integer_arg)
+
+IMPLICIT NONE
+
+INTEGER :: integer_arg
+
+INCLUDE 'hello_sub2.interface'
+
+CALL Hello_Sub2 (integer_arg)
+
+END SUBROUTINE Hello_Sub_Wrapper
+
+END MODULE Hello_Constants
diff --git a/test/repos/cyclic_dependency/hello_sub2.f90 b/test/repos/cyclic_dependency/hello_sub2.f90
new file mode 100644
index 0000000..57488c9
--- /dev/null
+++ b/test/repos/cyclic_dependency/hello_sub2.f90
@@ -0,0 +1,11 @@
+SUBROUTINE Hello_Sub2 (integer_arg)
+
+IMPLICIT NONE
+
+INTEGER :: integer_arg
+
+INCLUDE 'hello_sub.interface'
+
+CALL Hello_Sub (integer_arg)
+
+END SUBROUTINE Hello_Sub2
diff --git a/test/repos/trunk/blockdata/hello_blockdata.F90 b/test/repos/trunk/blockdata/hello_blockdata.F90
new file mode 100644
index 0000000..6ececec
--- /dev/null
+++ b/test/repos/trunk/blockdata/hello_blockdata.F90
@@ -0,0 +1,9 @@
+BLOCK DATA hello_blockdata
+INTEGER :: integer_common
+COMMON /general/integer_common
+#if defined(ODD)
+DATA integer_common/1357/
+#else
+DATA integer_common/2468/
+#endif
+END BLOCK DATA hello_blockdata
diff --git a/test/repos/trunk/cfg/fcm1_add_directory.cfg b/test/repos/trunk/cfg/fcm1_add_directory.cfg
new file mode 100644
index 0000000..341410e
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_add_directory.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/add_directory
diff --git a/test/repos/trunk/cfg/fcm1_add_directory_expsrc.cfg b/test/repos/trunk/cfg/fcm1_add_directory_expsrc.cfg
new file mode 100644
index 0000000..784de6b
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_add_directory_expsrc.cfg
@@ -0,0 +1,7 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/add_directory
+EXPSRC::test::branch1
diff --git a/test/repos/trunk/cfg/fcm1_add_file.cfg b/test/repos/trunk/cfg/fcm1_add_file.cfg
new file mode 100644
index 0000000..e333bf7
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_add_file.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/add_file
diff --git a/test/repos/trunk/cfg/fcm1_add_file_inherit.cfg b/test/repos/trunk/cfg/fcm1_add_file_inherit.cfg
new file mode 100644
index 0000000..f78386b
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_add_file_inherit.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/add_file
diff --git a/test/repos/trunk/cfg/fcm1_base.cfg b/test/repos/trunk/cfg/fcm1_base.cfg
new file mode 100644
index 0000000..e0e257c
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_base.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base_inc.cfg
+
+BLD::TARGET                             hello.sh test__namelist.etc
diff --git a/test/repos/trunk/cfg/fcm1_base_inc.cfg b/test/repos/trunk/cfg/fcm1_base_inc.cfg
new file mode 100644
index 0000000..7c2a5d2
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_base_inc.cfg
@@ -0,0 +1,23 @@
+DEST::ROOTDIR                           $PWD
+
+REPOS::test::base                       fcm:test_suite_tr
+EXPSRC::test::base
+
+BLD::TOOL::FC                           wrap_fc
+BLD::TOOL::FFLAGS                       -assume nosource_include
+
+BLD::TOOL::CC                           wrap_cc
+BLD::TOOL::CFLAGS                       -O3
+
+BLD::TOOL::FFLAGS::test/subroutine      %BLD::TOOL::FFLAGS -O3
+
+BLD::TOOL::LD                           wrap_ld
+BLD::TOOL::AR                           wrap_ar
+
+BLD::TOOL::FPP                          wrap_pp
+BLD::TOOL::FPPKEYS::test/subroutine/hello_sub HELLO_SUB
+BLD::TOOL::FPPKEYS::test/program/hello        CALL_HELLO_SUB
+BLD::PP::test/subroutine/hello_sub      true
+BLD::PP::test/program                   true
+
+BLD::BLOCKDATA                          hello_blockdata
diff --git a/test/repos/trunk/cfg/fcm1_branches_clash.cfg b/test/repos/trunk/cfg/fcm1_branches_clash.cfg
new file mode 100644
index 0000000..a25f37b
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_branches_clash.cfg
@@ -0,0 +1,7 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/modify_files_clash
diff --git a/test/repos/trunk/cfg/fcm1_branches_merge.cfg b/test/repos/trunk/cfg/fcm1_branches_merge.cfg
new file mode 100644
index 0000000..3244dc1
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_branches_merge.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/modify_files_merge1
+REPOS::test::branch3                    fcm:test_suite_br/dev/Share/modify_files_merge2
diff --git a/test/repos/trunk/cfg/fcm1_branches_merge_conflict_fail.cfg b/test/repos/trunk/cfg/fcm1_branches_merge_conflict_fail.cfg
new file mode 100644
index 0000000..fe0f200
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_branches_merge_conflict_fail.cfg
@@ -0,0 +1,10 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/modify_files_merge1
+REPOS::test::branch3                    fcm:test_suite_br/dev/Share/modify_files_merge2
+
+CONFLICT                                fail
diff --git a/test/repos/trunk/cfg/fcm1_branches_merge_conflict_override.cfg b/test/repos/trunk/cfg/fcm1_branches_merge_conflict_override.cfg
new file mode 100644
index 0000000..0f12d73
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_branches_merge_conflict_override.cfg
@@ -0,0 +1,10 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/modify_files_merge1
+REPOS::test::branch3                    fcm:test_suite_br/dev/Share/modify_files_merge2
+
+CONFLICT                                override
diff --git a/test/repos/trunk/cfg/fcm1_branches_merge_inherit.cfg b/test/repos/trunk/cfg/fcm1_branches_merge_inherit.cfg
new file mode 100644
index 0000000..dd53278
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_branches_merge_inherit.cfg
@@ -0,0 +1,10 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/modify_files_merge1
+REPOS::test::branch3                    fcm:test_suite_br/dev/Share/modify_files_merge2
diff --git a/test/repos/trunk/cfg/fcm1_branches_merge_inherit_wrong_include.cfg b/test/repos/trunk/cfg/fcm1_branches_merge_inherit_wrong_include.cfg
new file mode 100644
index 0000000..a9754c6
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_branches_merge_inherit_wrong_include.cfg
@@ -0,0 +1,12 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/modify_files_merge1
+REPOS::test::branch3                    fcm:test_suite_br/dev/Share/modify_files_merge2
+
+BLD::TOOL::FFLAGS::test/module          -O3
diff --git a/test/repos/trunk/cfg/fcm1_branches_merge_wcopies.cfg b/test/repos/trunk/cfg/fcm1_branches_merge_wcopies.cfg
new file mode 100644
index 0000000..85c9cdb
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_branches_merge_wcopies.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    $BASE_DIR/work/b1
+REPOS::test::branch2                    $BASE_DIR/work/b2
+REPOS::test::branch3                    $BASE_DIR/work/b3
diff --git a/test/repos/trunk/cfg/fcm1_branches_merge_wcopy.cfg b/test/repos/trunk/cfg/fcm1_branches_merge_wcopy.cfg
new file mode 100644
index 0000000..e632761
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_branches_merge_wcopy.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/modify_files_merge1
+REPOS::test::branch3                    $BASE_DIR/work
diff --git a/test/repos/trunk/cfg/fcm1_cflags.cfg b/test/repos/trunk/cfg/fcm1_cflags.cfg
new file mode 100644
index 0000000..606f6d2
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_cflags.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+BLD::TOOL::CFLAGS::test/subroutine      -O2 -g
diff --git a/test/repos/trunk/cfg/fcm1_change_src_type.cfg b/test/repos/trunk/cfg/fcm1_change_src_type.cfg
new file mode 100644
index 0000000..d71ac16
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_change_src_type.cfg
@@ -0,0 +1,7 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/change_src_type
+BLD::SRC_TYPE::test/module/hello_constants_inc.f90  FORTRAN::FORTRAN9X::INCLUDE
diff --git a/test/repos/trunk/cfg/fcm1_delete_directory.cfg b/test/repos/trunk/cfg/fcm1_delete_directory.cfg
new file mode 100644
index 0000000..0d33add
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_delete_directory.cfg
@@ -0,0 +1,7 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/delete_directory
diff --git a/test/repos/trunk/cfg/fcm1_delete_directory_inherit.cfg b/test/repos/trunk/cfg/fcm1_delete_directory_inherit.cfg
new file mode 100644
index 0000000..73cba1a
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_delete_directory_inherit.cfg
@@ -0,0 +1,9 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/delete_directory
diff --git a/test/repos/trunk/cfg/fcm1_delete_file.cfg b/test/repos/trunk/cfg/fcm1_delete_file.cfg
new file mode 100644
index 0000000..a67d987
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_delete_file.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/modify_files_merge1
+REPOS::test::branch3                    fcm:test_suite_br/dev/Share/delete_file
diff --git a/test/repos/trunk/cfg/fcm1_delete_file_inherit.cfg b/test/repos/trunk/cfg/fcm1_delete_file_inherit.cfg
new file mode 100644
index 0000000..c48ceb1
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_delete_file_inherit.cfg
@@ -0,0 +1,10 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/modify_files_merge1
+REPOS::test::branch3                    fcm:test_suite_br/dev/Share/delete_file
diff --git a/test/repos/trunk/cfg/fcm1_delete_inc_file.cfg b/test/repos/trunk/cfg/fcm1_delete_inc_file.cfg
new file mode 100644
index 0000000..8639845
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_delete_inc_file.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/delete_inc_file
diff --git a/test/repos/trunk/cfg/fcm1_delete_inc_file_inherit.cfg b/test/repos/trunk/cfg/fcm1_delete_inc_file_inherit.cfg
new file mode 100644
index 0000000..76b52df
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_delete_inc_file_inherit.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/delete_inc_file
diff --git a/test/repos/trunk/cfg/fcm1_delete_inc_file_inherit_force.cfg b/test/repos/trunk/cfg/fcm1_delete_inc_file_inherit_force.cfg
new file mode 100644
index 0000000..6ff2a3b
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_delete_inc_file_inherit_force.cfg
@@ -0,0 +1,10 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/delete_inc_file
+
+BLD::TOOL::FFLAGS::test/module          -assume nosource_include -O3
diff --git a/test/repos/trunk/cfg/fcm1_delete_pp_file.cfg b/test/repos/trunk/cfg/fcm1_delete_pp_file.cfg
new file mode 100644
index 0000000..711b899
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_delete_pp_file.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/modify_pp_include
+REPOS::test::branch3                    fcm:test_suite_br/dev/Share/delete_pp_file
diff --git a/test/repos/trunk/cfg/fcm1_delete_pp_file_inherit.cfg b/test/repos/trunk/cfg/fcm1_delete_pp_file_inherit.cfg
new file mode 100644
index 0000000..c5ebfdb
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_delete_pp_file_inherit.cfg
@@ -0,0 +1,10 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/modify_pp_include
+REPOS::test::branch3                    fcm:test_suite_br/dev/Share/delete_pp_file
diff --git a/test/repos/trunk/cfg/fcm1_delete_ppinc_file.cfg b/test/repos/trunk/cfg/fcm1_delete_ppinc_file.cfg
new file mode 100644
index 0000000..ddeb1db
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_delete_ppinc_file.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/delete_ppinc_file
diff --git a/test/repos/trunk/cfg/fcm1_delete_ppinc_file_inherit.cfg b/test/repos/trunk/cfg/fcm1_delete_ppinc_file_inherit.cfg
new file mode 100644
index 0000000..85762e2
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_delete_ppinc_file_inherit.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/delete_ppinc_file
diff --git a/test/repos/trunk/cfg/fcm1_delete_ppinc_file_inherit_force.cfg b/test/repos/trunk/cfg/fcm1_delete_ppinc_file_inherit_force.cfg
new file mode 100644
index 0000000..7e64e7a
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_delete_ppinc_file_inherit_force.cfg
@@ -0,0 +1,9 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/delete_ppinc_file
+BLD::TOOL::FPPKEYS::test/subroutine/hello_sub HELLO_SUB DUMMY
diff --git a/test/repos/trunk/cfg/fcm1_duplicate_target.cfg b/test/repos/trunk/cfg/fcm1_duplicate_target.cfg
new file mode 100644
index 0000000..fc5c04b
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_duplicate_target.cfg
@@ -0,0 +1,7 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/add_duplicate
+BLD::TOOL::FPPKEYS::test/subroutine/hello_sub2 HELLO_SUB
diff --git a/test/repos/trunk/cfg/fcm1_exclude_dependency.cfg b/test/repos/trunk/cfg/fcm1_exclude_dependency.cfg
new file mode 100644
index 0000000..5467cbd
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_exclude_dependency.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+BLD::EXCL_DEP                           USE::Hello_Constants
diff --git a/test/repos/trunk/cfg/fcm1_exe_permissions.cfg b/test/repos/trunk/cfg/fcm1_exe_permissions.cfg
new file mode 100644
index 0000000..6bc11cd
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_exe_permissions.cfg
@@ -0,0 +1,7 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    $BASE_DIR/work
+BLD::EXE_NAME::hello                    hello_world.exe
diff --git a/test/repos/trunk/cfg/fcm1_exe_rename.cfg b/test/repos/trunk/cfg/fcm1_exe_rename.cfg
new file mode 100644
index 0000000..59e7b55
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_exe_rename.cfg
@@ -0,0 +1,7 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/exe_rename
+BLD::EXE_NAME::hello                    hello_world.exe
diff --git a/test/repos/trunk/cfg/fcm1_fc.cfg b/test/repos/trunk/cfg/fcm1_fc.cfg
new file mode 100644
index 0000000..c5fc923
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_fc.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+BLD::TOOL::FC                           wrap_fc2
diff --git a/test/repos/trunk/cfg/fcm1_fflags1.cfg b/test/repos/trunk/cfg/fcm1_fflags1.cfg
new file mode 100644
index 0000000..2964591
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_fflags1.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+BLD::TOOL::FFLAGS::test                 %BLD::TOOL::FFLAGS -O2 -g
diff --git a/test/repos/trunk/cfg/fcm1_fflags2.cfg b/test/repos/trunk/cfg/fcm1_fflags2.cfg
new file mode 100644
index 0000000..c6861b7
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_fflags2.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+BLD::TOOL::FFLAGS::test/subroutine/hello_sub     -O2 -g
diff --git a/test/repos/trunk/cfg/fcm1_fflags_inherit.cfg b/test/repos/trunk/cfg/fcm1_fflags_inherit.cfg
new file mode 100644
index 0000000..ecc5adf
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_fflags_inherit.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+BLD::TOOL::FFLAGS::test/subroutine/hello_sub     -O2 -g
diff --git a/test/repos/trunk/cfg/fcm1_inc_devnull.cfg b/test/repos/trunk/cfg/fcm1_inc_devnull.cfg
new file mode 100644
index 0000000..4744078
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_inc_devnull.cfg
@@ -0,0 +1,9 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base_inc.cfg
+
+# The UM makes use of the following so we have to support it
+INC                                     /dev/null
+
+BLD::TARGET                             hello.sh test__namelist.etc
diff --git a/test/repos/trunk/cfg/fcm1_inherit_invalid_path.cfg b/test/repos/trunk/cfg/fcm1_inherit_invalid_path.cfg
new file mode 100644
index 0000000..30ed635
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_inherit_invalid_path.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     /invalid/path
diff --git a/test/repos/trunk/cfg/fcm1_inherit_target.cfg b/test/repos/trunk/cfg/fcm1_inherit_target.cfg
new file mode 100644
index 0000000..dd3ffb3
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_inherit_target.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+BLD::INHERIT::TARGET                    true
diff --git a/test/repos/trunk/cfg/fcm1_invalid_base_url.cfg b/test/repos/trunk/cfg/fcm1_invalid_base_url.cfg
new file mode 100644
index 0000000..087dd0e
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_invalid_base_url.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::base                       fcm:test_suite/invalid
diff --git a/test/repos/trunk/cfg/fcm1_invalid_branch_url.cfg b/test/repos/trunk/cfg/fcm1_invalid_branch_url.cfg
new file mode 100644
index 0000000..a200f0b
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_invalid_branch_url.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite/invalid
diff --git a/test/repos/trunk/cfg/fcm1_invalid_inc.cfg b/test/repos/trunk/cfg/fcm1_invalid_inc.cfg
new file mode 100644
index 0000000..178bfcd
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_invalid_inc.cfg
@@ -0,0 +1,4 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/invalid.cfg
diff --git a/test/repos/trunk/cfg/fcm1_invalid_namespace.cfg b/test/repos/trunk/cfg/fcm1_invalid_namespace.cfg
new file mode 100644
index 0000000..8c6461a
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_invalid_namespace.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+BLD::TOOL::FFLAGS::test/invalid         -O2
diff --git a/test/repos/trunk/cfg/fcm1_invalid_variable.cfg b/test/repos/trunk/cfg/fcm1_invalid_variable.cfg
new file mode 100644
index 0000000..c26ea3c
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_invalid_variable.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+BLD::TOOL::FFLAGS::test/subroutine/hello_sub    $INVALID
diff --git a/test/repos/trunk/cfg/fcm1_ld.cfg b/test/repos/trunk/cfg/fcm1_ld.cfg
new file mode 100644
index 0000000..20baa28
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_ld.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+BLD::TOOL::LD                           wrap_ld2
diff --git a/test/repos/trunk/cfg/fcm1_library.cfg b/test/repos/trunk/cfg/fcm1_library.cfg
new file mode 100644
index 0000000..4c8d152
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_library.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base_inc.cfg
+
+BLD::TARGET                             libtest.a
diff --git a/test/repos/trunk/cfg/fcm1_library_rename.cfg b/test/repos/trunk/cfg/fcm1_library_rename.cfg
new file mode 100644
index 0000000..849f62e
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_library_rename.cfg
@@ -0,0 +1,7 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base_inc.cfg
+
+BLD::LIB::test/module                   module
+BLD::TARGET                             libmodule.a
diff --git a/test/repos/trunk/cfg/fcm1_mirror.cfg b/test/repos/trunk/cfg/fcm1_mirror.cfg
new file mode 100644
index 0000000..0274742
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_mirror.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+RDEST::MACHINE                          localhost
+RDEST::REMOTE_SHELL                     ssh
+RDEST                                   ${THIS_RUN_DIR}_mirror
diff --git a/test/repos/trunk/cfg/fcm1_mirror_inherit.cfg b/test/repos/trunk/cfg/fcm1_mirror_inherit.cfg
new file mode 100644
index 0000000..61d329a
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_mirror_inherit.cfg
@@ -0,0 +1,12 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_mirror
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/modify_files_merge1
+REPOS::test::branch3                    fcm:test_suite_br/dev/Share/modify_files_merge2
+
+RDEST                                   ${THIS_RUN_DIR}_mirror
diff --git a/test/repos/trunk/cfg/fcm1_modify_subroutine_inherit.cfg b/test/repos/trunk/cfg/fcm1_modify_subroutine_inherit.cfg
new file mode 100644
index 0000000..2526b27
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_modify_subroutine_inherit.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_subroutine
diff --git a/test/repos/trunk/cfg/fcm1_modify_subroutine_interface_inherit.cfg b/test/repos/trunk/cfg/fcm1_modify_subroutine_interface_inherit.cfg
new file mode 100644
index 0000000..517bc9e
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_modify_subroutine_interface_inherit.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_subroutine_interface
diff --git a/test/repos/trunk/cfg/fcm1_multi_inherit.cfg b/test/repos/trunk/cfg/fcm1_multi_inherit.cfg
new file mode 100644
index 0000000..d0bfb81
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_multi_inherit.cfg
@@ -0,0 +1,9 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_fflags_inherit
+USE                                     $RUN_DIR/fcm1_modify_subroutine_inherit
+
+BLD::TOOL::FFLAGS::test/module         -O2 -g
diff --git a/test/repos/trunk/cfg/fcm1_no_dep.cfg b/test/repos/trunk/cfg/fcm1_no_dep.cfg
new file mode 100644
index 0000000..2ea4f3e
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_no_dep.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+BLD::NO_DEP                             true
diff --git a/test/repos/trunk/cfg/fcm1_ops.cfg b/test/repos/trunk/cfg/fcm1_ops.cfg
new file mode 100644
index 0000000..74bc1e5
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_ops.cfg
@@ -0,0 +1,211 @@
+# ------------------------------------------------------------------------------
+# File header
+# ------------------------------------------------------------------------------
+
+CFG::TYPE                                                                 ext
+CFG::VERSION                                                              1.0
+
+# ------------------------------------------------------------------------------
+# Destination
+# ------------------------------------------------------------------------------
+
+DEST                                                                      $PWD
+
+# ------------------------------------------------------------------------------
+# Build declarations
+# ------------------------------------------------------------------------------
+
+bld::excl_dep                                                             INC::mpif.h
+bld::excl_dep                                                             USE::F90_UNIX_IO
+bld::excl_dep                                                             USE::netcdf
+bld::excl_dep                                                             USE::YOMLUN
+bld::excl_dep                                                             USE::XLFUTILITY
+bld::exe_dep::OpsProg_BackErrCreate.exe                                   gcom
+bld::exe_dep::OpsProg_ExtractAndProcess.exe                               ops_admin::src::code::MetDB_BUFR_RETRIEVAL::source  ops_admin::src::code::MetDB_Bufr ops_admin::src::code::lapack ops_admin::src::code::blas gcom
+bld::exe_dep::OpsProg_MOPS.exe                                            ops_admin::src::code::MetDB_BUFR_RETRIEVAL::source  ops_admin::src::code::MetDB_Bufr gcom
+bld::exe_dep::OpsProg_SatRad_BiasCheck.exe                                gcom::build::gc
+bld::pp::gcom                                                             1
+bld::pp::ops::src::code::OpsMod_Altimeter::Ops_AltWriteNetCDF             1
+bld::pp::ops::src::code::OpsMod_OceanSound::Ops_OcnWriteNetCDF            1
+bld::pp::ops::src::code::OpsMod_Radar::Ops_WriteBackGrdOutput             1
+bld::pp::ops::src::code::OpsMod_Radar::Ops_WriteQCOutput                  1
+bld::pp::ops::src::code::OpsMod_Radar::Ops_WriteSoOutput                  1
+bld::pp::ops::src::code::OpsMod_RadarZ::Ops_ReadRadarRefl                 1
+bld::pp::ops::src::code::OpsMod_RadarZ::Ops_WriteRadarRTM                 1
+bld::pp::ops::src::code::OpsMod_RadarZ::Ops_WriteRadarRefl                1
+bld::pp::ops::src::code::OpsMod_SeaIce::Ops_SeaIceWriteNetCDF             1
+bld::pp::ops::src::code::OpsMod_SurfaceSST::Ops_SSTWriteNetCDF            1
+bld::pp::ops::src::code::OpsProg_ExtractAndProcess::Ops_ObStructureCreate 1
+bld::pp::ops::src::code::OpsProg_ExtractAndProcess::Ops_SetupControlInfo  1
+bld::pp::ops::src::code::Ops_RTTOV9::rttov9_parallel_ad                   1
+bld::pp::ops::src::code::Ops_RTTOV9::rttov9_parallel_direct               1
+bld::pp::ops::src::code::Ops_RTTOV9::rttov9_parallel_k                    1
+bld::pp::ops::src::code::Ops_RTTOV9::rttov9_parallel_tl                   1
+bld::target                                                               OpsScr_Build
+bld::tool::cc                                                             wrap_cc
+bld::tool::cflags                                                         
+bld::tool::cppkeys::gen::src::code::GenMod_Platform                       UNDERSCORE LOWERCASE C_LONG_LONG_INT FRL8
+bld::tool::cppkeys::gen::src::code::UM_Platform                           VAROPSVER C_LOW_U LINUX LITTLE_END C_LONG_LONG_INT FRL8
+bld::tool::cppkeys::ops::src::code::MetDB_ClientServer                    hpux DEBUG LL64 UNDERSCORE
+bld::tool::fc                                                             wrap_fc
+bld::tool::fflags                                                         -implicitnone -stand f95 -warn all -warn nointerfaces -i8 -r8 -i-static
+bld::tool::fflags::gcom                                                   -implicitnone -stand f95 -warn all -i8 -r8 -i-static -warn none -I$(OPSDIR)/mpi/mpich2-1.4-ukmo-v1/ifort-12/include
+bld::tool::fflags::ops::src::code::Ops_RTTOV9                             -implicitnone -stand f95 -warn all -warn nointerfaces -i-static -O3
+bld::tool::fppkeys::gcom                                                  GC_VERSION="'3.4'" GC_DESCRIP="'MPP'" GC_BUILD_DATE="'17285'" MPI_SRC MPILIB_32B PREC_64B GC__FORTERRUNIT=0 GC__FLUSHUNIT6 MPI_BSEND_BUFFER_SIZE=2560000
+bld::tool::fppkeys::gen::src::code::GenMod_Utilities::Gen_FlushUnit       USE_FLUSH
+bld::tool::fppkeys::gen::src::code::UM_COEX                               VAROPSVER
+bld::tool::fppkeys::gen::src::code::UM_Platform                           VAROPSVER
+bld::tool::fppkeys::ops::src::code::MetDB_GRIB                            SX6
+bld::tool::fppkeys::ops::src::code::OpsMod_Extract                        LITTLE_END
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9                            _RTTOV_TSTRAD_TEMP RTTOV_ARCH_VECTOR
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9::rttov9_parallel_ad        _RTTOV_TSTRAD_TEMP RTTOV_ARCH_VECTOR _RTTOV_PARALLEL_AD
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9::rttov9_parallel_direct    _RTTOV_TSTRAD_TEMP RTTOV_ARCH_VECTOR _RTTOV_PARALLEL_DIRECT
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9::rttov9_parallel_k         _RTTOV_TSTRAD_TEMP RTTOV_ARCH_VECTOR _RTTOV_PARALLEL_K
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9::rttov9_parallel_tl        _RTTOV_TSTRAD_TEMP RTTOV_ARCH_VECTOR _RTTOV_PARALLEL_TL
+bld::tool::fppkeys::ops_admin::src::code::MetDB_Bufr                      BPATH
+bld::tool::ld                                                             wrap_ld
+bld::tool::ldflags                                                        -i-static -L$(OPSDIR)/mpi/mpich2-1.4-ukmo-v1/ifort-12/lib -lmpich -lmpl -lpthread
+bld::tool::make                                                           gmake
+bld::tool::ar                                                             wrap_ar
+bld::tool::fpp                                                            wrap_pp
+
+# ------------------------------------------------------------------------------
+# Project and branches
+# ------------------------------------------------------------------------------
+
+REPOS::ops::base                                                          svn://fcm4/OPS_svn/OPS/trunk
+REVISION::ops::base                                                       19069
+SRC::ops::base                                                            src/code/MetDB_ClientServer
+SRC::ops::base                                                            src/code/MetDB_GRIB
+SRC::ops::base                                                            src/code/MetDB_GRIB2
+SRC::ops::base                                                            src/code/OpsMod_Aircraft
+SRC::ops::base                                                            src/code/OpsMod_Altimeter
+SRC::ops::base                                                            src/code/OpsMod_AssocData
+SRC::ops::base                                                            src/code/OpsMod_BackErrCreate
+SRC::ops::base                                                            src/code/OpsMod_BiasCorrect
+SRC::ops::base                                                            src/code/OpsMod_Bogus
+SRC::ops::base                                                            src/code/OpsMod_CXGenerate
+SRC::ops::base                                                            src/code/OpsMod_CXInfo
+SRC::ops::base                                                            src/code/OpsMod_Constants
+SRC::ops::base                                                            src/code/OpsMod_Control
+SRC::ops::base                                                            src/code/OpsMod_Extract
+SRC::ops::base                                                            src/code/OpsMod_GPSRO
+SRC::ops::base                                                            src/code/OpsMod_GeoCloud
+SRC::ops::base                                                            src/code/OpsMod_GeoIR
+SRC::ops::base                                                            src/code/OpsMod_GroundGPS
+SRC::ops::base                                                            src/code/OpsMod_HorizontalInterp
+SRC::ops::base                                                            src/code/OpsMod_Index
+SRC::ops::base                                                            src/code/OpsMod_Listing
+SRC::ops::base                                                            src/code/OpsMod_MFSST
+SRC::ops::base                                                            src/code/OpsMod_MOPS
+SRC::ops::base                                                            src/code/OpsMod_ModelColumnIO
+SRC::ops::base                                                            src/code/OpsMod_ModelIO
+SRC::ops::base                                                            src/code/OpsMod_ModelObInfo
+SRC::ops::base                                                            src/code/OpsMod_Monitor
+SRC::ops::base                                                            src/code/OpsMod_NSST100
+SRC::ops::base                                                            src/code/OpsMod_NetCDF
+SRC::ops::base                                                            src/code/OpsMod_ObsIO
+SRC::ops::base                                                            src/code/OpsMod_ObsInfo
+SRC::ops::base                                                            src/code/OpsMod_OceanSound
+SRC::ops::base                                                            src/code/OpsMod_Process
+SRC::ops::base                                                            src/code/OpsMod_QC
+SRC::ops::base                                                            src/code/OpsMod_RTTOV
+SRC::ops::base                                                            src/code/OpsMod_Radar
+SRC::ops::base                                                            src/code/OpsMod_RadarZ
+SRC::ops::base                                                            src/code/OpsMod_SBUV
+SRC::ops::base                                                            src/code/OpsMod_SSMI
+SRC::ops::base                                                            src/code/OpsMod_SatSST
+SRC::ops::base                                                            src/code/OpsMod_SatSound
+SRC::ops::base                                                            src/code/OpsMod_Satwind
+SRC::ops::base                                                            src/code/OpsMod_Scatwind
+SRC::ops::base                                                            src/code/OpsMod_SeaIce
+SRC::ops::base                                                            src/code/OpsMod_Sonde
+SRC::ops::base                                                            src/code/OpsMod_Sort
+SRC::ops::base                                                            src/code/OpsMod_StationList
+SRC::ops::base                                                            src/code/OpsMod_Stats
+SRC::ops::base                                                            src/code/OpsMod_Surface
+SRC::ops::base                                                            src/code/OpsMod_SurfaceSST
+SRC::ops::base                                                            src/code/OpsMod_TCBogus
+SRC::ops::base                                                            src/code/OpsMod_TURBO
+SRC::ops::base                                                            src/code/OpsMod_TropicalRain
+SRC::ops::base                                                            src/code/OpsMod_Utilities
+SRC::ops::base                                                            src/code/OpsMod_Varobs
+SRC::ops::base                                                            src/code/OpsMod_VerticalInterp
+SRC::ops::base                                                            src/code/OpsMod_VisControl
+SRC::ops::base                                                            src/code/OpsProg_BackErrCreate
+SRC::ops::base                                                            src/code/OpsProg_ExtractAndProcess
+SRC::ops::base                                                            src/code/OpsProg_KillRPC
+SRC::ops::base                                                            src/code/OpsProg_MOPS
+SRC::ops::base                                                            src/code/OpsProg_RTTOV9
+SRC::ops::base                                                            src/code/Ops_AIRS_1DVar
+SRC::ops::base                                                            src/code/Ops_AIRS_Utilities
+SRC::ops::base                                                            src/code/Ops_GPSRO_Info
+SRC::ops::base                                                            src/code/Ops_GPSRO_Process
+SRC::ops::base                                                            src/code/Ops_RTTOV7
+SRC::ops::base                                                            src/code/Ops_RTTOV7_RTTOVCLD
+SRC::ops::base                                                            src/code/Ops_RTTOV9
+SRC::ops::base                                                            src/code/Ops_SSMI_1DVar
+SRC::ops::base                                                            src/code/Ops_SatRad_Info
+SRC::ops::base                                                            src/code/Ops_SatRad_Process
+SRC::ops::base                                                            src/code/Ops_SatRad_SetUp
+SRC::ops::base                                                            src/code/Ops_SatRad_Stats
+SRC::ops::base                                                            src/code/Ops_SatRad_Utilities
+SRC::ops::base                                                            src/scripts/Ops_SatRad_Scripts
+SRC::ops::base                                                            src/scripts/Ops_Scripts
+
+REPOS::gen::base                                                          svn://fcm1/GEN_svn/GEN/trunk
+REVISION::gen::base                                                       3194
+SRC::gen::base                                                            src/code/GenMod_Constants
+SRC::gen::base                                                            src/code/GenMod_Control
+SRC::gen::base                                                            src/code/GenMod_FortranIO
+SRC::gen::base                                                            src/code/GenMod_GetEnv
+SRC::gen::base                                                            src/code/GenMod_ModelIO
+SRC::gen::base                                                            src/code/GenMod_Platform
+SRC::gen::base                                                            src/code/GenMod_Reporting
+SRC::gen::base                                                            src/code/GenMod_Trace
+SRC::gen::base                                                            src/code/GenMod_UMConstants
+SRC::gen::base                                                            src/code/GenMod_Utilities
+SRC::gen::base                                                            src/code/Reconfiguration
+SRC::gen::base                                                            src/code/UM_COEX
+SRC::gen::base                                                            src/code/UM_General
+SRC::gen::base                                                            src/code/UM_Platform
+
+REPOS::ops_admin::base                                                    svn://fcm4/OPS_svn/Admin/trunk
+REVISION::ops_admin::base                                                 19069
+SRC::ops_admin::base                                                      src/code/MetDB_BUFR_RETRIEVAL/apps/create_mdblseq
+SRC::ops_admin::base                                                      src/code/MetDB_BUFR_RETRIEVAL/source
+SRC::ops_admin::base                                                      src/code/MetDB_Bufr
+SRC::ops_admin::base                                                      src/code/blas
+SRC::ops_admin::base                                                      src/code/lapack
+
+REPOS::gcom::base                                                         svn://fcm2/UM_svn/GCOM/trunk
+REVISION::gcom::base                                                      17285
+SRC::gcom::base                                                           build/gc
+SRC::gcom::base                                                           build/gcg
+SRC::gcom::base                                                           build/include
+SRC::gcom::base                                                           build/mpl
+
+REPOS::um_admin::base                                                     svn://fcm2/UM_svn/Admin/trunk
+REVISION::um_admin::base                                                  11210
+SRC::um_admin::base                                                       utilities/IBM_signal_hander
+
+# ------------------------------------------------------------------------------
+# Extra stuff to build ENS code
+# ------------------------------------------------------------------------------
+
+repos::ens::base   fcm:ens_tr/forecast/code
+REVISION::ens::base                                                       1460
+src::ens::base     EnsProg_ETKF
+src::ens::base     EnsMod_Header
+src::ens::base     EnsMod_Obstore
+src::ens::base     EnsMod_Utilities
+src::ens::base     EnsMod_Varobs
+src::ens::base     EnsProg_TrimObstore
+
+bld::tool::fppkeys::ens                                                   IBM
+bld::tool::cppkeys::ens                                                   LOWERCASE
+bld::tool::ldflags::ens                                                   -i-static -L$(OPSDIR)/mpi/mpich2-1.4-ukmo-v1/ifort-12/lib -lmpich -lmpl -lpthread -llapack
+bld::pp::ens                                                              1
+bld::exe_dep::EnsProg_ETKF.exe                                            gcom
+bld::exe_dep::EnsProg_TrimObstore.exe                                     gcom
+bld::target                                                               EnsProg_ETKF.exe EnsProg_TrimObstore.exe
diff --git a/test/repos/trunk/cfg/fcm1_postproc_hpc.cfg b/test/repos/trunk/cfg/fcm1_postproc_hpc.cfg
new file mode 100644
index 0000000..8596543
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_postproc_hpc.cfg
@@ -0,0 +1,209 @@
+# ------------------------------------------------------------------------------
+# File header
+# ------------------------------------------------------------------------------
+
+CFG::TYPE                                                              ext
+CFG::VERSION                                                           1.0
+
+# ------------------------------------------------------------------------------
+# Destination
+# ------------------------------------------------------------------------------
+
+DEST                                                                   $PWD
+RDEST                                                                  $THIS_RUN_DIR_HPC
+RDEST::MACHINE                                                         $HPC
+
+# ------------------------------------------------------------------------------
+# Build declarations
+# ------------------------------------------------------------------------------
+
+bld::target         PPQYINTERP.ksh run_nae_pp.ksh run_qv_downscaling.ksh run_glo_pp.ksh run_EuroPP.ksh first_start.ksh PP4KOPER.ksh
+bld::infile_ext::cpp  C::SOURCE
+bld::tool::fc       xlf90_r
+bld::tool::ld       xlf90_r
+bld::tool::cc       xlC_r
+bld::tool::make     gmake
+bld::tool::cpp      xlC_r
+bld::tool::cppflags   -E -C
+bld::tool::fpp      cpp -E -P -traditional
+bld::excl_dep       USE::F90_UNIX_IO
+bld::excl_dep       H::gc_constants.h
+bld::excl_dep       H::gc_kinds.h
+bld::excl_dep       H::gc_options.h
+bld::tool::fflags   -c -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+bld::tool::fflags::rst  -c -qarch=pwr6 -qtune=pwr6 -O0 -qfullpath -qextname -qrealsize=8 -qintsize=8 
+bld::tool::fflags::ver  -c -qarch=pwr6 -qtune=pwr6 -O0 -qfullpath -qextname -qrealsize=8 -qintsize=8 
+bld::tool::fflags::ops  -c -qarch=pwr6 -qtune=pwr6 -O0 -qfullpath -qextname -qrealsize=8 -qintsize=8 
+bld::tool::cflags   -c -qarch=pwr6 -qtune=pwr6 -O0 -qhot
+bld::tool::ldflags  
+bld::tool::ldflags::pp::model_processing::format %bld::tool::ldflags -L/projects/um1/gcom/gcom3.2/meto_ibm_pwr6_serial/lib -lgcom
+bld::tool::ldflags::rst  %bld::tool::ldflags -L/projects/um1/gcom/gcom3.2/meto_ibm_pwr6_serial/lib -lgcom -bnoquiet -L/projects/um1/lib -lsig -L/usr/lib -lmass -lessl -qsmp
+bld::pp::pp::utilities  1
+bld::pp::pp::steps  1
+bld::pp::pp::get_metdb_obs::MetDB_source 1
+bld::pp::gen 1
+bld::pp::rst::src         1
+bld::pp::ver            1
+bld::tool::cppkeys::pp::get_metdb_obs::MetDB_source  LOWERCASE L64 UNDERSCORE
+bld::tool::cppkeys::gen  C_LOW_U FRL8 C_LONG_INT NEC VAROPSVER UTILIO UNDERSCORE LOWERCASE
+bld::tool::fppkeys::gen  C_LOW_U FRL8 C_LONG_INT NEC VAROPSVER UTILIO UNDERSCORE UPPERCASE
+bld::tool::fppkeys::rst::src  NEC
+bld::tool::fflags::gen               %bld::tool::fflags -qrealsize=8 -qintsize=8
+bld::tool::cflags::gen               %bld::tool::cflags -qrealsize=8 -qintsize=8
+bld::tool::fflags::gen::UM_Platform  %bld::tool::fflags::gen -qfixed=132
+bld::tool::fflags::gen::UM_Platform::IOERROR  %bld::tool::fflags::gen
+bld::tool::fflags::gen::UM_General   %bld::tool::fflags::gen -qfixed=132
+bld::tool::fflags::gen::UM_COEX      %bld::tool::fflags::gen -qfixed=132
+bld::tool::cflags::pp::get_metdb_obs::MetDB_source    
+bld::tool::fflags::pp::get_metdb_obs::get_metdb_obs  -c -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+bld::tool::fflags::pp::moses-pdm-rfm  %bld::tool::fflags -qstrict
+bld::tool::fflags::pp::moses-pdm-rfm::dlongrad  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::down_rad_calc  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::nimrod_extr_comp  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::read_ancil  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::dsolrad  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::nimrod_extr_wind  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::nimrod_hdr_read  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::ssdm  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::morloc  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::get_row_and_column  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::nimrod_idata_read  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::ssdm_var_generator  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::um_solpos  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::morangstrom  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::sun_angles  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::moses_cloud_cover  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::nimrod_3d_idata_read  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::q_vp_from_t_td  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::daynumber  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::nimrod_3dextr_comp  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::moses_qs_from_t  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::route_runoff  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::regrid_real  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::initialise_routing  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::routing  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::nextpoint  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::wavespeed  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::nearest_real  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::moses-pdm-rfm::sam  %bld::tool::fflags::pp::moses-pdm-rfm -qfixed=132
+bld::tool::fflags::pp::pressure_wind  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::pressure_wind::an_smear  %bld::tool::fflags
+bld::tool::fflags::pp::pressure_wind::gust_adjust  %bld::tool::fflags
+bld::tool::fflags::pp::pressure_wind::gust_analysis  %bld::tool::fflags
+bld::tool::fflags::pp::pressure_wind::pwindanal  %bld::tool::fflags
+bld::tool::fflags::pp::pressure_wind::bilin_mdi  %bld::tool::fflags
+bld::tool::fflags::pp::pressure_wind::convert_winduv  %bld::tool::fflags
+bld::tool::fflags::pp/precip_fcst  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp/precip_fcst/accmerge  %bld::tool::fflags
+bld::tool::fflags::pp/precip_fcst/object_motion  %bld::tool::fflags
+bld::tool::fflags::pp/precip_fcst/scale  %bld::tool::fflags
+bld::tool::fflags::pp/precip_fcst/wind_forecast_precip  %bld::tool::fflags
+bld::tool::fflags::pp::precip::get_surface_obs %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::precip::lightning_forecast %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::precip::lightning_merge %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::precip::metar_to_synop_weather %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::precip::read_adv_fc %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::precip::read_rad %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::model_processing::get_um_info  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::beammap_ascii_to_nimrod  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::ccitt  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::datetime_c_to_i_secs  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::def_head  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::domain_to_ng  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::european_observations_area  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::get_free_lun  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::icutout  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::ll_to_ng  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::ll_to_ps  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::locate_FCST_string  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::nearest  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::nearest_file  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::ng_to_ll  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::ng_to_ll_array  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::nimrod_i4read  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::nimrod_open2  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::nimrod_open_i4read  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::nimrod_open_i4write  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::nimrod_regrid  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::observations_area_metdb  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::ps_to_ll  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::regrid  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::round_cycle_string  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::subtract_time  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::time_diff  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::time_difference_prog  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::total_accum  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::trim  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::utilities::zpdate  %bld::tool::fflags -qfixed=132
+bld::tool::fflags::pp::verification  %bld::tool::fflags -qfixed=132
+bld::tool::cflags::pp::steps  -c -O0 -qarch=pwr6 -qtune=pwr6 -q64 -I/home/nwp/fr/ihab/fftw_opt/include/
+bld::tool::ld::pp::steps       xlC_r
+bld::tool::ldflags::pp::steps  -L/home/nwp/fr/ihab/fftw_opt/lib/ -lfftw3 -I/home/nwp/fr/ihab/fftw_opt/include/
+bld::tool::fflags::rst::src     -qarch=pwr6 -qtune=pwr6 -O0 -c -qfullpath -qextname -qrealsize=8 -qintsize=8  -qfixed=132
+bld::tool::fflags::rst::src::ReadFrcData -qarch=pwr6 -qtune=pwr6 -O0 -qstrict -c -qfullpath -qextname -qrealsize=8 -qintsize=8
+bld::tool::fflags::rst::src::profile -qarch=pwr6 -qtune=pwr6 -O0 -c -qfullpath -qextname -qrealsize=8 -qintsize=8  -qfixed=132 -qsmp=auto
+bld::tool::fflags::rst::src::MORST_main -qarch=pwr6 -qtune=pwr6 -O0 -c -qfullpath -qextname -qrealsize=8 -qintsize=8  -qfixed=132 -qsmp=omp
+bld::tool::fflags::rst::src::output_status -qarch=pwr6 -qtune=pwr6 -O0 -c -qfullpath -qextname
+
+# ------------------------------------------------------------------------------
+# Project and branches
+# ------------------------------------------------------------------------------
+
+repos::pp::trunk                    svn://fcm9/PostProc_svn/PostProc/trunk
+repos::ancil::trunk                 svn://fcm9/PostProc_svn/PostProcAncil/trunk
+repos::site_ext::trunk              svn://fcm9/PostProc_svn/SiteExtract/trunk
+repos::gen::code                    svn://fcm1/GEN_svn/GEN/trunk/src/code
+repos::rst::trunk                   svn://fcm9/PostProc_svn/RoadTemp/trunk
+repos::ver::code                    svn://fcm6/VER_svn/VER/trunk/src/code
+repos::ops::code                    svn://fcm4/OPS_svn/OPS/trunk/src/code
+revision::pp::trunk         2728
+revision::ancil::trunk      2524
+revision::site_ext::trunk   2481
+revision::gen::code         3015
+revision::rst::trunk        2416
+revision::ver::code         4739
+revision::ops::code        18088
+
+src::pp::trunk                      cloud
+src::pp::trunk                      CDP
+src::pp::trunk                      frasia
+src::pp::trunk                      get_metdb_obs
+src::pp::trunk                      get_metdb_obs/MetDB_source
+src::pp::trunk                      model_processing
+src::pp::trunk                      precip
+src::pp::trunk                      precip_fcst
+src::pp::trunk                      pressure_wind
+src::pp::trunk                      product_gen
+src::pp::trunk                      scripts
+src::pp::trunk                      utilities
+src::pp::trunk                      verification
+src::pp::trunk                      visibility
+src::pp::trunk                      moses-pdm-rfm
+src::pp::trunk                      steps
+src::pp::trunk                      SCW
+src::site_ext::trunk                FssMod_DMO
+src::gen::code  		    GenMod_UMConstants
+src::gen::code                      UM_Platform
+src::gen::code                      UM_General
+src::gen::code                      UM_COEX
+src::gen::code                      Reconfiguration
+src::gen::code                      GenMod_Constants
+src::gen::code                      GenMod_Control
+src::gen::code                      GenMod_FortranIO
+src::gen::code                      GenMod_GetEnv
+src::gen::code                      GenMod_Platform
+src::gen::code                      GenMod_Reporting
+src::gen::code                      GenMod_Trace
+src::gen::code                      GenMod_Utilities
+src::ancil::trunk                   .
+src::ancil::trunk                   ./archive
+src::ancil::trunk                   ./steps_det
+src::ancil::trunk                   ./steps_ens
+src::rst::trunk                     src
+src::rst::trunk                     scripts
+src::ver::code                      VerMod_FieldsIO
+src::ver::code                      VerMod_General
+src::ver::code                      VerMod_Grid
+src::ops::code                      OpsMod_MOPS
+src::ops::code                      OpsMod_ObsInfo
+src::ops::code                      OpsMod_Constants
diff --git a/test/repos/trunk/cfg/fcm1_pp_change_blockdata.cfg b/test/repos/trunk/cfg/fcm1_pp_change_blockdata.cfg
new file mode 100644
index 0000000..2c8a995
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_pp_change_blockdata.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+BLD::TOOL::FPPKEYS::test/blockdata      ODD 
diff --git a/test/repos/trunk/cfg/fcm1_pp_change_dependency.cfg b/test/repos/trunk/cfg/fcm1_pp_change_dependency.cfg
new file mode 100644
index 0000000..0c6e592
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_pp_change_dependency.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+BLD::TOOL::FPPKEYS::test/program/hello 
diff --git a/test/repos/trunk/cfg/fcm1_pp_change_include.cfg b/test/repos/trunk/cfg/fcm1_pp_change_include.cfg
new file mode 100644
index 0000000..00329fa
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_pp_change_include.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_pp_include
diff --git a/test/repos/trunk/cfg/fcm1_pp_change_include_inherit.cfg b/test/repos/trunk/cfg/fcm1_pp_change_include_inherit.cfg
new file mode 100644
index 0000000..058fc16
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_pp_change_include_inherit.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_pp_include
diff --git a/test/repos/trunk/cfg/fcm1_pp_empty_subroutine.cfg b/test/repos/trunk/cfg/fcm1_pp_empty_subroutine.cfg
new file mode 100644
index 0000000..6741f8a
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_pp_empty_subroutine.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+BLD::TOOL::FPPKEYS::test/subroutine/hello_sub 
diff --git a/test/repos/trunk/cfg/fcm1_pp_empty_subroutine_inherit.cfg b/test/repos/trunk/cfg/fcm1_pp_empty_subroutine_inherit.cfg
new file mode 100644
index 0000000..1ae9748
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_pp_empty_subroutine_inherit.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+BLD::TOOL::FPPKEYS::test/subroutine/hello_sub 
diff --git a/test/repos/trunk/cfg/fcm1_pp_empty_subroutine_inherit_force.cfg b/test/repos/trunk/cfg/fcm1_pp_empty_subroutine_inherit_force.cfg
new file mode 100644
index 0000000..ce55461
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_pp_empty_subroutine_inherit_force.cfg
@@ -0,0 +1,9 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_base
+
+BLD::TOOL::FPPKEYS::test/subroutine/hello_sub 
+BLD::TOOL::LD                           wrap_ld2
diff --git a/test/repos/trunk/cfg/fcm1_revmatch_false.cfg b/test/repos/trunk/cfg/fcm1_revmatch_false.cfg
new file mode 100644
index 0000000..1757ad1
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_revmatch_false.cfg
@@ -0,0 +1,13 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/modify_files_base
+REPOS::test::branch2                    fcm:test_suite_br/dev/Share/modify_files_merge1
+REPOS::test::branch3                    fcm:test_suite_br/dev/Share/modify_files_merge2
+
+REVISION::test::base                    21
+REVISION::test::branch1                 21
+REVISION::test::branch2                 21
+REVISION::test::branch3                 21
diff --git a/test/repos/trunk/cfg/fcm1_revmatch_true.cfg b/test/repos/trunk/cfg/fcm1_revmatch_true.cfg
new file mode 100644
index 0000000..ac18d80
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_revmatch_true.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_revmatch_false.cfg
+
+REVMATCH                                true
diff --git a/test/repos/trunk/cfg/fcm1_sps.cfg b/test/repos/trunk/cfg/fcm1_sps.cfg
new file mode 100644
index 0000000..efb82fd
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_sps.cfg
@@ -0,0 +1,129 @@
+# ------------------------------------------------------------------------------
+# File header
+# ------------------------------------------------------------------------------
+
+CFG::TYPE                                                                 ext
+CFG::VERSION                                                              1.0
+
+# ------------------------------------------------------------------------------
+# Destination
+# ------------------------------------------------------------------------------
+
+DEST                                                                      $PWD
+
+# ------------------------------------------------------------------------------
+# Build declarations
+# ------------------------------------------------------------------------------
+
+%FOPT -CB -traceback -u -convert big_endian 
+%SPS_LIBDIR /home/h04/cfsa/SPS/libraries/RHEL6
+bld::tool::fc      wrap_fc
+bld::tool::fflags  %FOPT -I%{SPS_LIBDIR}/grib_api/include
+bld::tool::cc      wrap_cc
+bld::tool::cflags  -Wall -O2 -DLOWERCASE -I%{SPS_LIBDIR}/hdf5/include
+bld::tool::ar      wrap_ar
+bld::tool::ld      wrap_ld
+bld::tool::ldflags -openmp -L%{SPS_LIBDIR}/hdf5/lib -lhdf5 -lhdf5_hl \
+                   -L%{SPS_LIBDIR}/bufr_ifort -lbufr \
+                   -L%{SPS_LIBDIR}/grib_ifort -lgrib_ifort \
+                   -L%{SPS_LIBDIR}/g2lib -lg2 \
+                   -L%{SPS_LIBDIR}/grib_api/lib -lgrib_api_f90 -lgrib_api \
+                   -L%{SPS_LIBDIR}/jasper/lib -ljasper \
+		   -L/usr/lib -llapack \
+                   -ljpeg -lpng
+bld::target   SpsScr_Install
+bld::target   sps__data__Sps_Fire.etc
+bld::target   sps__data__coeffs.etc
+bld::target   sps__data__palettes.etc
+bld::target   sps__data__products.etc
+bld::target   sps__data__sad.etc
+bld::target   sps__data__slotstore.etc
+bld::exe_name::h5admin  h5admin
+bld::exe_name::h5getatt h5getatt
+bld::exe_name::SpsProg_GetCoords sps_get_coords
+bld::excl_dep use::grib_api
+bld::tool::fflags::gen::src::code::GenMod_UMConstants             %FOPT -w -132
+bld::tool::cflags::sps::src::code::SpsMod_Image                   -Wall -DLOWERCASE -I%{SPS_LIBDIR}/hdf5/include
+bld::tool::fflags::sps::src::code::SpsMod_Utilities               %FOPT -Duse_f90_unix=''
+bld::tool::fflags::sps::src::code::SpsTask_HDFReader              %FOPT -auto -assume byterecl
+bld::tool::fflags::sps::src::code::SpsProg_ImageGrib              %FOPT -auto -assume byterecl
+bld::tool::fflags::sps::src::code::SpsProg_GlobalComposite        %FOPT -auto -assume byterecl
+bld::tool::fflags::sps::src::code::rttov10                        %FOPT -openmp
+bld::tool::fflags::sps::src::code::rttov10::main::rttov_locpat_k.F90 %FOPT
+
+# ------------------------------------------------------------------------------
+# Project and branches
+# ------------------------------------------------------------------------------
+
+repos::sps::base  fcm:sps_tr
+revision::sps::base   4964
+src::sps::base    src/code/SpsMod_AutosatFormat
+src::sps::base    src/code/SpsMod_Calibration
+src::sps::base    src/code/SpsMod_CloudContamination
+src::sps::base    src/code/SpsMod_CloudStoreTypes
+src::sps::base    src/code/SpsMod_Constants
+src::sps::base    src/code/SpsMod_Coordinates
+src::sps::base    src/code/SpsMod_FogParameters
+src::sps::base    src/code/SpsMod_GRIB
+src::sps::base    src/code/SpsMod_HDF
+src::sps::base    src/code/SpsMod_Image
+src::sps::base    src/code/SpsMod_InterpolateModelToPixel
+src::sps::base    src/code/SpsMod_LoadCloudMaskInfo
+src::sps::base    src/code/SpsMod_MPEFBufrDecode
+src::sps::base    src/code/SpsMod_NAME
+src::sps::base    src/code/SpsMod_NimrodFile
+src::sps::base    src/code/SpsMod_RTTOV
+src::sps::base    src/code/SpsMod_ScienceInterface
+src::sps::base    src/code/SpsMod_SdiSeg
+src::sps::base    src/code/SpsMod_Setup
+src::sps::base    src/code/SpsMod_SlotStoreGroups
+src::sps::base    src/code/SpsMod_Slotstore
+src::sps::base    src/code/SpsMod_Store
+src::sps::base    src/code/SpsMod_Utilities
+src::sps::base    src/code/SpsProg_ATDnetStrikes
+src::sps::base    src/code/SpsProg_BufrEncode_SEVIRI
+src::sps::base    src/code/SpsProg_Calibration
+src::sps::base    src/code/SpsProg_CloudProducts
+src::sps::base    src/code/SpsProg_Geometry
+src::sps::base    src/code/SpsProg_ImageGen
+src::sps::base    src/code/SpsProg_ImageGrib
+src::sps::base    src/code/SpsProg_MakeGRIB
+src::sps::base    src/code/SpsProg_ModelFieldsToHDF
+src::sps::base    src/code/SpsProg_MPEFBufrDecode
+src::sps::base    src/code/SpsProg_PreProcessCMAData
+src::sps::base    src/code/SpsProg_ProcessGOES_SA
+src::sps::base    src/code/SpsProg_RunRTTOV
+src::sps::base    src/code/SpsProg_GlobalComposite
+src::sps::base    src/code/SpsTask_Alpha
+src::sps::base    src/code/SpsTask_CalcParallaxErrors
+src::sps::base    src/code/SpsTask_CloudMask
+src::sps::base    src/code/SpsTask_CTHProcessing
+src::sps::base    src/code/SpsTask_Dust
+src::sps::base    src/code/SpsTask_Fog
+src::sps::base    src/code/SpsTask_HDFReader
+src::sps::base    src/code/SpsTask_MetOGII
+src::sps::base    src/code/SpsTask_PrecipIndex
+src::sps::base    src/code/SpsTask_SatPrecip
+src::sps::base    src/code/SpsTask_TauRe
+src::sps::base    src/code/SpsTask_VolcanicAsh
+src::sps::base    src/code/SpsTask_VolcanicSO2
+src::sps::base    src/code/Sps_MSGData
+src::sps::base    src/code/Sps_SlotstoreUtilities
+src::sps::base    src/code/Sps_Utils
+expsrc::sps::base src/code/rttov10
+src::sps::base    src/idl/Sps_Generic
+src::sps::base    src/idl/Sps_FireDetection
+src::sps::base    src/idl/Sps_MSGAOD
+src::sps::base    src/idl/Sps_MODISAOD
+src::sps::base    src/scripts/Sps_Perl
+src::sps::base    src/scripts/Sps_Scripts
+src::sps::base    src/scripts/Sps_Slotstore
+src::sps::base    src/scripts/Sps_Utils
+expsrc::sps::base src/scripts/sched
+expsrc::sps::base src/scripts/lib
+expsrc::sps::base data
+expsrc::sps::base control
+
+repos::gen::base  fcm:gen_tr
+revision::gen::base   3953
+src::gen::base    src/code/GenMod_UMConstants
diff --git a/test/repos/trunk/cfg/fcm1_suite.cfg b/test/repos/trunk/cfg/fcm1_suite.cfg
new file mode 100644
index 0000000..2337685
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_suite.cfg
@@ -0,0 +1,8 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+BLD::SRC::test                          $RUN_DIR/fcm1_base/bin
+
+BLD::TARGET                             hello.sh
diff --git a/test/repos/trunk/cfg/fcm1_symbolic_link.cfg b/test/repos/trunk/cfg/fcm1_symbolic_link.cfg
new file mode 100644
index 0000000..c5c4e9a
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_symbolic_link.cfg
@@ -0,0 +1,6 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+INC                                     $HERE/fcm1_base.cfg
+
+REPOS::test::branch1                    fcm:test_suite_br/dev/Share/symbolic_link
diff --git a/test/repos/trunk/cfg/fcm1_um.cfg b/test/repos/trunk/cfg/fcm1_um.cfg
new file mode 100644
index 0000000..9b10a10
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_um.cfg
@@ -0,0 +1,54 @@
+# ------------------------------------------------------------------------------
+# File header
+# ------------------------------------------------------------------------------
+
+CFG::TYPE                                                              ext
+CFG::VERSION                                                           1.0
+
+# ------------------------------------------------------------------------------
+# Destination
+# ------------------------------------------------------------------------------
+
+DEST                                                                   $PWD
+
+# ------------------------------------------------------------------------------
+# Build declarations
+# ------------------------------------------------------------------------------
+
+bld::blockdata                                                            blkdata.o
+bld::excl_dep                                                             USE::NetCDF
+bld::excl_dep                                                             INC::netcdf.inc
+bld::excl_dep                                                             INC::mpif.h
+bld::excl_dep                                                             USE::mpl
+bld::excl_dep                                                             USE::mod_prism_proto
+bld::excl_dep                                                             USE::mod_prism_grids_writing
+bld::excl_dep                                                             USE::mod_prism_def_partition_proto
+bld::excl_dep                                                             USE::mod_prism_put_proto
+bld::excl_dep                                                             USE::mod_prism_get_proto
+bld::excl_dep::UM::script                                                 EXE
+bld::exe_dep                                                              portio2a.o pio_data_conv.o pio_io_timer.o
+bld::exe_name::flumeMain                                                  um.exe
+bld::pp::UM                                                               1
+bld::target                                                               um.exe
+bld::tool::ar                                                             ar
+bld::tool::cc                                                             wrap_cc
+bld::tool::cpp                                                            wrap_mpicc
+bld::tool::cppflags                                                       -E
+bld::tool::cppkeys                                                        C_LONG_LONG_INT=c_long_long_int MPP=mpp C_LOW_U=c_low_u FRL8=frl8 LINUX=linux BUFRD_IO=bufrd_io LITTLE_END=little_end LINUX_INTEL_COMPILER=linux_intel_compiler CONTROL=control REPROD=reprod ATMOS=atmos GLOBAL=global A04_ALL=a04_all A01_3C=a01_3c A02_3C=a02_3c A03_8C=a03_8c A04_3D=a04_3d A05_4A=a05_4a A06_4A=a06_4a A08_7A=a08_7a A09_2A=a09_2a A10_2A=a10_2a A11_2A=a11_2a A12_2A=a12_2a A13_2A=a13_2a A14_1B=a14_1b A15_ [...]
+bld::tool::fc                                                             wrap_mpif90
+bld::tool::fflags                                                         -i8 -r8 -w -I /home/h01/frum/gcom/gcom4.1/linux_ifort_mpich2/inc -O0
+bld::tool::fppflags                                                       -E -P -traditional -I /home/h04/opsrc/ops0/mpi/mpich2-1.4-ukmo-v1/ifort-12/include
+bld::tool::fppkeys                                                        C_LONG_LONG_INT=c_long_long_int MPP=mpp C_LOW_U=c_low_u FRL8=frl8 LINUX=linux BUFRD_IO=bufrd_io LITTLE_END=little_end LINUX_INTEL_COMPILER=linux_intel_compiler CONTROL=control REPROD=reprod ATMOS=atmos GLOBAL=global A04_ALL=a04_all A01_3C=a01_3c A02_3C=a02_3c A03_8C=a03_8c A04_3D=a04_3d A05_4A=a05_4a A06_4A=a06_4a A08_7A=a08_7a A09_2A=a09_2a A10_2A=a10_2a A11_2A=a11_2a A12_2A=a12_2a A13_2A=a13_2a A14_1B=a14_1b A15_ [...]
+bld::tool::geninterface                                                   none
+bld::tool::ar                                                             wrap_ar
+bld::tool::ld                                                             wrap_mpif90
+bld::tool::ldflags                                                        -L/home/h01/frum/gcom/gcom4.1/linux_ifort_mpich2/lib -lgcom -Wl,--noinhibit-exec -Vaxlib
+bld::tool::fpp                                                            wrap_pp
+
+# ------------------------------------------------------------------------------
+# Project and branches
+# ------------------------------------------------------------------------------
+
+repos::UM::base        fcm:um-tr/src/
+version::UM::base      vn7.3
+expsrc::UM::base
diff --git a/test/repos/trunk/cfg/fcm1_um_hpc.cfg b/test/repos/trunk/cfg/fcm1_um_hpc.cfg
new file mode 100644
index 0000000..b718387
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_um_hpc.cfg
@@ -0,0 +1,60 @@
+# ------------------------------------------------------------------------------
+# File header
+# ------------------------------------------------------------------------------
+
+CFG::TYPE                                                              ext
+CFG::VERSION                                                           1.0
+
+# ------------------------------------------------------------------------------
+# Destination
+# ------------------------------------------------------------------------------
+
+DEST                                                                   $PWD
+RDEST                                                                  $THIS_RUN_DIR_HPC
+RDEST::MACHINE                                                         $HPC
+
+# ------------------------------------------------------------------------------
+# Build declarations
+# ------------------------------------------------------------------------------
+
+bld::blockdata                                                            blkdata.o
+bld::excl_dep                                                             USE::NetCDF
+bld::excl_dep                                                             INC::netcdf.inc
+bld::excl_dep                                                             INC::mpif.h
+bld::excl_dep                                                             USE::mpl
+bld::excl_dep                                                             USE::mod_prism_proto
+bld::excl_dep                                                             USE::mod_prism_grids_writing
+bld::excl_dep                                                             USE::mod_prism_def_partition_proto
+bld::excl_dep                                                             USE::mod_prism_put_proto
+bld::excl_dep                                                             USE::mod_prism_get_proto
+bld::excl_dep::UM::script                                                 EXE
+bld::exe_dep                                                              portio2a.o pio_data_conv.o pio_io_timer.o print_from_c.o
+bld::exe_name::flumeMain                                                  um.exe
+bld::pp::UM                                                               1
+bld::target                                                               um.exe
+bld::tool::ar                                                             ar
+bld::tool::cc                                                             xlc_r
+bld::tool::cpp                                                            xlc
+bld::tool::cppflags                                                       -E -C
+bld::tool::cppkeys                                                        C_LONG_INT=c_long_int MPP=mpp C_LOW_U=c_low_u FRL8=frl8 BUFRD_IO=bufrd_io VECTLIB=vectlib IBM=ibm CONTROL=control REPROD=reprod MPP=mpp ATMOS=atmos GLOBAL=global A04_ALL=a04_all A01_3C=a01_3c A02_3C=a02_3c A03_8C=a03_8c A04_3D=a04_3d A05_4A=a05_4a A06_4A=a06_4a A08_7A=a08_7a A09_2A=a09_2a A10_2A=a10_2a A11_2A=a11_2a A12_2A=a12_2a A13_2A=a13_2a A14_1B=a14_1b A15_1A=a15_1a A16_1A=a16_1a A17_2B=a17_2b A18_0A=a18_0a A1 [...]
+bld::tool::fc                                                             mpxlf90_r
+bld::tool::fflags                                                         -I/projects/um1/gcom/gcom3.3/meto_ibm_pwr6_mpp/inc -I/projects/um1/lib/netcdf3.20090102/include -qextname -qsuffix=f=f90 -qarch=pwr6 -qtune=pwr6 -qrealsize=8 -qintsize=8 -NS32768 -O0
+bld::tool::fflags::UM::atmosphere::dynamics_advection::eta_vert_weights_e -qextname -qsuffix=f=f90 -qarch=pwr6 -qtune=pwr6 -qrealsize=8 -qintsize=8  -O0 -NS32768
+bld::tool::fflags::UM::control::top_level::atm_step                       -qextname -qsuffix=f=f90 -qarch=pwr6 -qtune=pwr6 -qrealsize=8 -qintsize=8  -O0 -NS32768
+bld::tool::fflags::UM::control::top_level::u_model                        -qextname -qsuffix=f=f90 -qarch=pwr6 -qtune=pwr6 -qrealsize=8 -qintsize=8  -O0 -NS32768
+bld::tool::fpp                                                            cpp
+bld::tool::fppflags                                                       -E -P -traditional
+bld::tool::fppkeys                                                        C_LONG_INT=c_long_int MPP=mpp C_LOW_U=c_low_u FRL8=frl8 BUFRD_IO=bufrd_io VECTLIB=vectlib IBM=ibm CONTROL=control REPROD=reprod MPP=mpp ATMOS=atmos GLOBAL=global A04_ALL=a04_all A01_3C=a01_3c A02_3C=a02_3c A03_8C=a03_8c A04_3D=a04_3d A05_4A=a05_4a A06_4A=a06_4a A08_7A=a08_7a A09_2A=a09_2a A10_2A=a10_2a A11_2A=a11_2a A12_2A=a12_2a A13_2A=a13_2a A14_1B=a14_1b A15_1A=a15_1a A16_1A=a16_1a A17_2B=a17_2b A18_0A=a18_0a A1 [...]
+bld::tool::geninterface                                                   none
+bld::tool::ld                                                             mpxlf90_r
+bld::tool::ldflags                                                        -lmass -lmassvp6 -L/projects/um1/gcom/gcom3.3/meto_ibm_pwr6_mpp/lib -lgcom -L/projects/um1/lib -lgrib -lsig -L/projects/um1/lib/netcdf3.20090102/lib64 -lnetcdf 
+bld::tool::make                                                           gmake
+
+
+# ------------------------------------------------------------------------------
+# Project and branches
+# ------------------------------------------------------------------------------
+
+repos::UM::base        fcm:um-tr/src/
+version::UM::base      vn7.3
+expsrc::UM::base
diff --git a/test/repos/trunk/cfg/fcm1_um_inherit.cfg b/test/repos/trunk/cfg/fcm1_um_inherit.cfg
new file mode 100644
index 0000000..d926efc
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_um_inherit.cfg
@@ -0,0 +1,49 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+
+USE                                     $RUN_DIR/fcm1_um
+
+repos::UM::branch1     fcm:um_br/dev/Share/VN7.3_hg3_dust_443/src
+version::UM::branch1   11858
+expsrc::UM::branch1    /
+repos::UM::branch2     fcm:um_br/dev/Share/VN7.3_hg3_ccw_precip/src
+version::UM::branch2   11857
+expsrc::UM::branch2    /
+repos::UM::branch3     fcm:um_br/dev/hadco/VN7.3_HG3_porting_lsp_fixes/src
+version::UM::branch3   12029
+expsrc::UM::branch3    /
+repos::UM::branch4     fcm:um_br/dev/hadco/VN7.3_pc2_qcl_gt_tiny/src
+version::UM::branch4   12142
+expsrc::UM::branch4    /
+repos::UM::branch5     fcm:um_br/dev/hadas/VN7.3_w_CAPE_diag/src
+version::UM::branch5   12012
+expsrc::UM::branch5    /
+repos::UM::branch7     fcm:um_br/dev/frlk/VN7.3_BLLEVS_fixes/src
+version::UM::branch7   13938
+expsrc::UM::branch7    /
+repos::UM::branch8     fcm:um_br/dev/frwm/VN7.3_Cu_Diag_Low_LCL/src
+version::UM::branch8   12011
+expsrc::UM::branch8    /
+repos::UM::branch9     fcm:um_br/dev/frwm/VN7.3_LimitCnvParcPert/src
+version::UM::branch9   12041
+expsrc::UM::branch9    /
+repos::UM::branch10     fcm:um_br/dev/hadip/VN7.3_ilp_moose/src
+version::UM::branch10   13314
+expsrc::UM::branch10    /
+repos::UM::branch11     fcm:um_br/dev/hadco/VN7.3_temp_fix_solver/src
+version::UM::branch11   12740
+expsrc::UM::branch11    /
+repos::UM::branch12     fcm:um_br/dev/hadng/VN7.3_wetlands_rothc/src
+version::UM::branch12   12603
+expsrc::UM::branch12    /
+repos::UM::branch13     fcm:um_br/dev/frtg/VN7.3_reconf_extern_ancil/src
+version::UM::branch13   13063
+expsrc::UM::branch13    /
+repos::UM::branch15     fcm:um_br/dev/frma/VN7.3_rad_dev/src
+version::UM::branch15   12732
+expsrc::UM::branch15    /
+repos::UM::branch16     fcm:um_br/dev/frlk/VN7.3_melt_fix/src
+version::UM::branch16   13822
+expsrc::UM::branch16    /
diff --git a/test/repos/trunk/cfg/fcm1_um_inherit_hpc.cfg b/test/repos/trunk/cfg/fcm1_um_inherit_hpc.cfg
new file mode 100644
index 0000000..95c9364
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_um_inherit_hpc.cfg
@@ -0,0 +1,51 @@
+CFG::TYPE                               ext
+CFG::VERSION                            1.0
+
+DEST::ROOTDIR                           $PWD
+RDEST                                   $THIS_RUN_DIR_HPC
+RDEST::MACHINE                          $HPC
+
+USE                                     $RUN_DIR/fcm1_um_hpc
+
+repos::UM::branch1     fcm:um_br/dev/Share/VN7.3_hg3_dust_443/src
+version::UM::branch1   11858
+expsrc::UM::branch1    /
+repos::UM::branch2     fcm:um_br/dev/Share/VN7.3_hg3_ccw_precip/src
+version::UM::branch2   11857
+expsrc::UM::branch2    /
+repos::UM::branch3     fcm:um_br/dev/hadco/VN7.3_HG3_porting_lsp_fixes/src
+version::UM::branch3   12029
+expsrc::UM::branch3    /
+repos::UM::branch4     fcm:um_br/dev/hadco/VN7.3_pc2_qcl_gt_tiny/src
+version::UM::branch4   12142
+expsrc::UM::branch4    /
+repos::UM::branch5     fcm:um_br/dev/hadas/VN7.3_w_CAPE_diag/src
+version::UM::branch5   12012
+expsrc::UM::branch5    /
+repos::UM::branch7     fcm:um_br/dev/frlk/VN7.3_BLLEVS_fixes/src
+version::UM::branch7   13938
+expsrc::UM::branch7    /
+repos::UM::branch8     fcm:um_br/dev/frwm/VN7.3_Cu_Diag_Low_LCL/src
+version::UM::branch8   12011
+expsrc::UM::branch8    /
+repos::UM::branch9     fcm:um_br/dev/frwm/VN7.3_LimitCnvParcPert/src
+version::UM::branch9   12041
+expsrc::UM::branch9    /
+repos::UM::branch10     fcm:um_br/dev/hadip/VN7.3_ilp_moose/src
+version::UM::branch10   13314
+expsrc::UM::branch10    /
+repos::UM::branch11     fcm:um_br/dev/hadco/VN7.3_temp_fix_solver/src
+version::UM::branch11   12740
+expsrc::UM::branch11    /
+repos::UM::branch12     fcm:um_br/dev/hadng/VN7.3_wetlands_rothc/src
+version::UM::branch12   12603
+expsrc::UM::branch12    /
+repos::UM::branch13     fcm:um_br/dev/frtg/VN7.3_reconf_extern_ancil/src
+version::UM::branch13   13063
+expsrc::UM::branch13    /
+repos::UM::branch15     fcm:um_br/dev/frma/VN7.3_rad_dev/src
+version::UM::branch15   12732
+expsrc::UM::branch15    /
+repos::UM::branch16     fcm:um_br/dev/frlk/VN7.3_melt_fix/src
+version::UM::branch16   13822
+expsrc::UM::branch16    /
diff --git a/test/repos/trunk/cfg/fcm1_var.cfg b/test/repos/trunk/cfg/fcm1_var.cfg
new file mode 100644
index 0000000..3a543c7
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_var.cfg
@@ -0,0 +1,232 @@
+# ------------------------------------------------------------------------------
+# File header
+# ------------------------------------------------------------------------------
+
+CFG::TYPE                                                               ext
+CFG::VERSION                                                            1.0
+
+# ------------------------------------------------------------------------------
+# Destination
+# ------------------------------------------------------------------------------
+
+DEST                                                                    $PWD
+
+# ------------------------------------------------------------------------------
+# Build declarations
+# ------------------------------------------------------------------------------
+
+bld::excl_dep                                                           USE::F90_UNIX_IO
+bld::excl_dep                                                           USE::XLFUTILITY
+bld::excl_dep                                                           INC::mpif.h
+bld::exe_dep                                                            gcom varadmin::src::code::VarMod_Lapack varadmin::src::code::VarMod_Blas
+bld::pp::gcom                                                           1
+bld::pp::var::src::code::PF_MPP                                         1
+bld::target                                                             VarScr_HelpCompile
+bld::tool::cc                                                           wrap_cc
+bld::tool::cflags                                                       
+bld::tool::cppkeys                                                      
+bld::tool::cppkeys::gen::src::code::GenMod_Platform                     LOWERCASE  UNDERSCORE FRL8       C_LONG_LONG_INT
+bld::tool::cppkeys::gen::src::code::UM_Platform                         VAROPSVER       C_LOW_U         FRL8            C_LONG_LONG_INT LINUX           LITTLE_END
+bld::tool::fc                                                           wrap_fc
+bld::tool::fflags                                                       -implicitnone -integer_size 64 -real_size 64 -ftrapuv
+bld::tool::fflags::gcom                                                 -implicitnone -integer_size 64 -real_size 64 -ftrapuv -warn none
+bld::tool::fflags::gcom::build::mpl::mpl                                -implicitnone -integer_size 64 -real_size 64 -ftrapuv -warn none -I$(OPSDIR)/mpi/mpich2-1.4-ukmo-v1/ifort-12/include
+bld::tool::fflags::gen                                                  -implicitnone -integer_size 64 -real_size 64 -ftrapuv -warn noerrors
+bld::tool::fflags::ops::src::code::Ops_RTTOV9                           -implicitnone -integer_size 64 -real_size 64 -ftrapuv -warn none
+bld::tool::fflags::var::src::code::PF_Interpolation::Cubic_Lagrange_Adj -implicitnone -integer_size 64 -real_size 64 -ftrapuv -Wp,-P
+bld::tool::fflags::var::src::code::PF_MPP                               -implicitnone -integer_size 64 -real_size 64 -ftrapuv -warn noerrors
+bld::tool::fflags::var::src::code::VarProg_UMFileUtils                  -implicitnone -integer_size 64 -real_size 64
+bld::tool::fflags::varadmin::src::code::VarMod_Blas                     -implicitnone -integer_size 64 -real_size 64 -ftrapuv -warn none
+bld::tool::fflags::varadmin::src::code::VarMod_Lapack                   -implicitnone -integer_size 64 -real_size 64 -ftrapuv -warn none
+bld::tool::fppkeys                                                       IFORT_CDIRS
+bld::tool::fppkeys::gcom::build                                         GC_VERSION="'3.4+'" GC_BUILD_DATE="'15824'" PREC_64B GC__FLUSHUNIT6 GC__FORTERRUNIT=0 GC_DESCRIP="'MPP'" MPI_SRC MPILIB_32B
+bld::tool::fppkeys::gen::src::code::GenMod_Control                      GCOMHEADERS
+bld::tool::fppkeys::gen::src::code::GenMod_Utilities::Gen_FlushUnit     USE_FLUSH
+bld::tool::fppkeys::gen::src::code::UM_COEX                             VAROPSVER
+bld::tool::fppkeys::gen::src::code::UM_Platform                         VAROPSVER
+bld::tool::ldflags                                                      -L$(OPSDIR)/mpi/mpich2-1.4-ukmo-v1/ifort-12/lib -lmpich -lmpl -lpthread
+bld::tool::make                                                         gmake
+
+bld::pp::ops::src::code::Ops_RTTOV9::rttov9_parallel_direct            1
+bld::pp::ops::src::code::Ops_RTTOV9::rttov9_parallel_ad                1
+bld::pp::ops::src::code::Ops_RTTOV9::rttov9_parallel_tl                1
+bld::pp::ops::src::code::Ops_RTTOV9::rttov9_parallel_k                 1
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9::rttov9_parallel_ad     _RTTOV_PARALLEL_AD
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9::rttov9_parallel_direct _RTTOV_PARALLEL_DIRECT
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9::rttov9_parallel_k      _RTTOV_PARALLEL_K
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9::rttov9_parallel_tl     _RTTOV_PARALLEL_TL
+
+BLD::TOOL::LD                           wrap_ld
+BLD::TOOL::AR                           wrap_ar
+BLD::TOOL::FPP                          wrap_pp
+
+# ------------------------------------------------------------------------------
+# Project and branches
+# ------------------------------------------------------------------------------
+
+REPOS::var::base                                                       svn://fcm5/VAR_svn/VAR/trunk
+REVISION::var::base                                                    14844
+SRC::var::base                                                         src/code/NewDynamics
+SRC::var::base                                                         src/code/PFMod_Model
+SRC::var::base                                                         src/code/PFMod_SV
+SRC::var::base                                                         src/code/PF_Bdy_Layer
+SRC::var::base                                                         src/code/PF_Control
+SRC::var::base                                                         src/code/PF_EulerianAdv
+SRC::var::base                                                         src/code/PF_FirstGuessRhoW
+SRC::var::base                                                         src/code/PF_GcrSolver
+SRC::var::base                                                         src/code/PF_General
+SRC::var::base                                                         src/code/PF_Helmholtz
+SRC::var::base                                                         src/code/PF_IO
+SRC::var::base                                                         src/code/PF_Interpolation
+SRC::var::base                                                         src/code/PF_MPP
+SRC::var::base                                                         src/code/PF_MPPUtil
+SRC::var::base                                                         src/code/PF_MoistPhys
+SRC::var::base                                                         src/code/PF_SemiLagrangianTheta
+SRC::var::base                                                         src/code/PF_SemiLagrangianUV
+SRC::var::base                                                         src/code/PF_UVTransformation
+SRC::var::base                                                         src/code/PF_Update
+SRC::var::base                                                         src/code/PF_convec
+SRC::var::base                                                         src/code/SG_Interpolation
+SRC::var::base                                                         src/code/VarMod_ATOVS
+SRC::var::base                                                         src/code/VarMod_ATOVSRad
+SRC::var::base                                                         src/code/VarMod_Aircraft
+SRC::var::base                                                         src/code/VarMod_BalanceTerms
+SRC::var::base                                                         src/code/VarMod_BgPenAndGrad
+SRC::var::base                                                         src/code/VarMod_CovCheckTp
+SRC::var::base                                                         src/code/VarMod_CovCodes
+SRC::var::base                                                         src/code/VarMod_CovCond
+SRC::var::base                                                         src/code/VarMod_CovHorizontal
+SRC::var::base                                                         src/code/VarMod_CovOptions
+SRC::var::base                                                         src/code/VarMod_CovSample
+SRC::var::base                                                         src/code/VarMod_CovSpectral
+SRC::var::base                                                         src/code/VarMod_CovSpectralData
+SRC::var::base                                                         src/code/VarMod_CovStatsIO
+SRC::var::base                                                         src/code/VarMod_CovVariances
+SRC::var::base                                                         src/code/VarMod_CovVertical
+SRC::var::base                                                         src/code/VarMod_CovVerticalData
+SRC::var::base                                                         src/code/VarMod_DelSquaredFFT
+SRC::var::base                                                         src/code/VarMod_Diagnostics
+SRC::var::base                                                         src/code/VarMod_FieldOutput
+SRC::var::base                                                         src/code/VarMod_Fourier
+SRC::var::base                                                         src/code/VarMod_GPSRO
+SRC::var::base                                                         src/code/VarMod_GeneralOptions
+SRC::var::base                                                         src/code/VarMod_GroundGPS
+SRC::var::base                                                         src/code/VarMod_HorizontalInterp
+SRC::var::base                                                         src/code/VarMod_HorizontalInterp_Adj
+SRC::var::base                                                         src/code/VarMod_InterpColumns
+SRC::var::base                                                         src/code/VarMod_InterpColumns_Adj
+SRC::var::base                                                         src/code/VarMod_LS
+SRC::var::base                                                         src/code/VarMod_LTinterp
+SRC::var::base                                                         src/code/VarMod_MOPS
+SRC::var::base                                                         src/code/VarMod_MPP
+SRC::var::base                                                         src/code/VarMod_Minimise
+SRC::var::base                                                         src/code/VarMod_ModelIO
+SRC::var::base                                                         src/code/VarMod_ObsControl
+SRC::var::base                                                         src/code/VarMod_ObsIO
+SRC::var::base                                                         src/code/VarMod_ObsInfo
+SRC::var::base                                                         src/code/VarMod_ObsOptions
+SRC::var::base                                                         src/code/VarMod_ObsUtility
+SRC::var::base                                                         src/code/VarMod_ObsUtility_Adj
+SRC::var::base                                                         src/code/VarMod_PF
+SRC::var::base                                                         src/code/VarMod_PFInfo
+SRC::var::base                                                         src/code/VarMod_PF_Adj
+SRC::var::base                                                         src/code/VarMod_Platform
+SRC::var::base                                                         src/code/VarMod_Precip
+SRC::var::base                                                         src/code/VarMod_PseudoOb
+SRC::var::base                                                         src/code/VarMod_QC
+SRC::var::base                                                         src/code/VarMod_Radar
+SRC::var::base                                                         src/code/VarMod_SBUV
+SRC::var::base                                                         src/code/VarMod_SSMI
+SRC::var::base                                                         src/code/VarMod_SatRad
+SRC::var::base                                                         src/code/VarMod_Satwind
+SRC::var::base                                                         src/code/VarMod_Scatwind
+SRC::var::base                                                         src/code/VarMod_Sonde
+SRC::var::base                                                         src/code/VarMod_Stats
+SRC::var::base                                                         src/code/VarMod_Surface
+SRC::var::base                                                         src/code/VarMod_TestCovNL
+SRC::var::base                                                         src/code/VarMod_TotalPenAndGrad
+SRC::var::base                                                         src/code/VarMod_TpTransform
+SRC::var::base                                                         src/code/VarMod_TransSpecH
+SRC::var::base                                                         src/code/VarMod_TransformInfo
+SRC::var::base                                                         src/code/VarMod_Transform_g
+SRC::var::base                                                         src/code/VarMod_Transform_h
+SRC::var::base                                                         src/code/VarMod_Transform_p
+SRC::var::base                                                         src/code/VarMod_Transform_v
+SRC::var::base                                                         src/code/VarMod_Transforms
+SRC::var::base                                                         src/code/VarMod_Trig
+SRC::var::base                                                         src/code/VarMod_UpPF
+SRC::var::base                                                         src/code/VarMod_UpPF_Adj
+SRC::var::base                                                         src/code/VarMod_UpTransform
+SRC::var::base                                                         src/code/VarMod_UpTransform_Adj
+SRC::var::base                                                         src/code/VarMod_VerticalInterp
+SRC::var::base                                                         src/code/VarMod_VerticalInterp_Adj
+SRC::var::base                                                         src/code/VarMod_Vis
+SRC::var::base                                                         src/code/VarMod_Vp
+SRC::var::base                                                         src/code/VarProg_AnalysePF
+SRC::var::base                                                         src/code/VarProg_CovAccStats
+SRC::var::base                                                         src/code/VarProg_CovPFstats
+SRC::var::base                                                         src/code/VarProg_SV
+SRC::var::base                                                         src/code/VarProg_TestCov
+SRC::var::base                                                         src/code/VarProg_TestPFModel
+SRC::var::base                                                         src/code/VarProg_UMFileUtils
+SRC::var::base                                                         src/code/Var_DiffOperators
+SRC::var::base                                                         src/code/Var_General
+SRC::var::base                                                         src/code/Var_Initialization
+SRC::var::base                                                         src/code/Var_Jc
+SRC::var::base                                                         src/code/Var_LAPACK
+SRC::var::base                                                         src/scripts/Var_Scripts
+
+REPOS::ops::base                                                       svn://fcm4/OPS_svn/OPS/trunk
+REVISION::ops::base                                                    18341
+SRC::ops::base                                                         src/code/OpsMod_Constants
+SRC::ops::base                                                         src/code/OpsMod_Control
+SRC::ops::base                                                         src/code/OpsMod_GeoIR
+SRC::ops::base                                                         src/code/OpsMod_ObsInfo
+SRC::ops::base                                                         src/code/OpsMod_RTTOV
+SRC::ops::base                                                         src/code/OpsMod_Sort
+SRC::ops::base                                                         src/code/OpsMod_Utilities
+SRC::ops::base                                                         src/code/OpsMod_Varobs
+SRC::ops::base                                                         src/code/OpsMod_VerticalInterp
+SRC::ops::base                                                         src/code/OpsMod_VisControl
+SRC::ops::base                                                         src/code/OpsProg_RTTOV9
+SRC::ops::base                                                         src/code/Ops_AIRS_1DVar
+SRC::ops::base                                                         src/code/Ops_AIRS_Utilities
+SRC::ops::base                                                         src/code/Ops_RTTOV7
+SRC::ops::base                                                         src/code/Ops_RTTOV7_RTTOVCLD
+SRC::ops::base                                                         src/code/Ops_RTTOV9
+SRC::ops::base                                                         src/code/Ops_SatRad_Info
+SRC::ops::base                                                         src/code/Ops_SatRad_Process
+SRC::ops::base                                                         src/code/Ops_SatRad_SetUp
+SRC::ops::base                                                         src/code/Ops_SatRad_Utilities
+
+REPOS::gen::base                                                       svn://fcm1/GEN_svn/GEN/trunk
+REVISION::gen::base                                                    3073
+SRC::gen::base                                                         src/code/GenMod_Constants
+SRC::gen::base                                                         src/code/GenMod_Control
+SRC::gen::base                                                         src/code/GenMod_FortranIO
+SRC::gen::base                                                         src/code/GenMod_GetEnv
+SRC::gen::base                                                         src/code/GenMod_ModelIO
+SRC::gen::base                                                         src/code/GenMod_Platform
+SRC::gen::base                                                         src/code/GenMod_Reporting
+SRC::gen::base                                                         src/code/GenMod_Trace
+SRC::gen::base                                                         src/code/GenMod_UMConstants
+SRC::gen::base                                                         src/code/GenMod_Utilities
+SRC::gen::base                                                         src/code/Reconfiguration
+SRC::gen::base                                                         src/code/UM_COEX
+SRC::gen::base                                                         src/code/UM_General
+SRC::gen::base                                                         src/code/UM_Platform
+
+REPOS::da::base                                                        svn://fcm5/DA_svn/DA/trunk
+REVISION::da::base                                                     258
+
+REPOS::varadmin::base                                                  svn://fcm5/VAR_svn/Admin/trunk
+REVISION::varadmin::base                                               14851
+SRC::varadmin::base                                                    src/code/VarMod_Blas
+SRC::varadmin::base                                                    src/code/VarMod_Lapack
+
+REPOS::gcom::base                                                      svn://fcm2/UM_svn/GCOM/branches/dev/ibmjb/r12957_2194_ralltoalle_out_of_order
+REVISION::gcom::base                                                   15824
+SRC::gcom::base                                                        build/gc
+SRC::gcom::base                                                        build/gcg
+SRC::gcom::base                                                        build/include
+SRC::gcom::base                                                        build/mpl
diff --git a/test/repos/trunk/cfg/fcm1_var_hpc.cfg b/test/repos/trunk/cfg/fcm1_var_hpc.cfg
new file mode 100644
index 0000000..b7f648e
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm1_var_hpc.cfg
@@ -0,0 +1,238 @@
+# ------------------------------------------------------------------------------
+# File header
+# ------------------------------------------------------------------------------
+
+CFG::TYPE                                                              ext
+CFG::VERSION                                                           1.0
+
+# ------------------------------------------------------------------------------
+# Destination
+# ------------------------------------------------------------------------------
+
+DEST                                                                   $PWD
+RDEST                                                                  $THIS_RUN_DIR_HPC
+RDEST::MACHINE                                                         $HPC
+
+# ------------------------------------------------------------------------------
+# Build declarations
+# ------------------------------------------------------------------------------
+
+bld::excl_dep                                                          USE::F90_UNIX_IO
+bld::excl_dep                                                          USE::XLFUTILITY
+bld::excl_dep                                                          INC::mpif.h
+bld::exe_dep                                                           gcom varadmin::src::code::VarMod_Lapack varadmin::src::code::VarMod_Blas
+bld::pp::gcom                                                          1
+bld::target                                                            VarScr_HelpCompile
+bld::tool::cc                                                          xlc
+bld::tool::cppkeys                                                     LOWERCASE
+bld::tool::cppkeys::gen::src::code::GenMod_Platform                    LOWERCASE  FRL8       C_LONG_INT
+bld::tool::cppkeys::gen::src::code::UM_Platform                        VAROPSVER C_LOW     FRL8      C_LONG_INT
+bld::tool::fc                                                          mpxlf95_r
+bld::tool::fc_define                                                   -WF,-D
+bld::tool::fflags                                                      -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1
+bld::tool::fflags::var::src::code::PFMod_Model                         -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -NS1024
+bld::tool::fflags::gcom::build::gc                                     -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed
+bld::tool::fflags::gcom::build::gcg                                    -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed
+bld::tool::fflags::gcom::build::include                                -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed
+bld::tool::fflags::gen::src::code::GenMod_Reporting::GenMod_Reporting  -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -WF,-qfpp
+bld::tool::fflags::gen::src::code::GenMod_Utilities::Gen_FlushUnit     -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -WF,-qfpp
+bld::tool::fflags::gen::src::code::UM_COEX                             -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed -WF,-qfpp
+bld::tool::fflags::gen::src::code::UM_General                          -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed
+bld::tool::fflags::gen::src::code::UM_Platform                         -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed -WF,-qfpp
+bld::tool::fflags::gen::src::code::UM_Platform::IOERROR                -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed -WF,-qfpp -qfree=f90
+bld::tool::fflags::var::src::code::VarMod_CovVertical                  -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qnoessl
+bld::tool::fflags::var::src::code::VarMod_CovVerticalData              -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qnoessl
+bld::tool::fflags::varadmin::src::code::VarMod_Blas                    -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed -qrealsize=4 -qstrict
+bld::tool::fflags::varadmin::src::code::VarMod_Lapack                  -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed -qrealsize=4 -qstrict
+bld::tool::fppkeys                                                     
+bld::tool::fppkeys::gcom::build                                        GC_VERSION="'3.4+'" GC_BUILD_DATE="'15824'" PREC_64B GC__FLUSHUNIT6 GC__FORTERRUNIT=0 MPI_SRC MPILIB_32B GC_DESCRIP="'MPP'" MPI_BSEND_BUFFER_SIZE=10240000 IBM
+bld::tool::fppkeys::gen::src::code::GenMod_Control                     GCOMHEADERS
+bld::tool::fppkeys::gen::src::code::GenMod_Control::Gen_SetupControl   USE_CUSTOM_SIGNAL_HANDLER AIX
+bld::tool::fppkeys::gen::src::code::GenMod_Reporting::GenMod_Reporting GEN_LEN_ERROR_OUT=134
+bld::tool::fppkeys::gen::src::code::GenMod_Utilities::Gen_FlushUnit    USE_FLUSH AIX
+bld::tool::fppkeys::gen::src::code::UM_COEX                            VAROPSVER
+bld::tool::fppkeys::gen::src::code::UM_Platform                        VAROPSVER
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9                         RTTOV_ARCH_VECTOR
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9::rttov9_parkind1        DEFAULT_INTEGER_32BIT
+bld::tool::ldflags                                                     -L$(UMDIR)/lib -lsig -lessl -lmassvp6 -lmass
+bld::tool::make                                                        gmake
+
+bld::pp::ops::src::code::Ops_RTTOV9::rttov9_parallel_direct            1
+bld::pp::ops::src::code::Ops_RTTOV9::rttov9_parallel_ad                1
+bld::pp::ops::src::code::Ops_RTTOV9::rttov9_parallel_tl                1
+bld::pp::ops::src::code::Ops_RTTOV9::rttov9_parallel_k                 1
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9::rttov9_parallel_ad     _RTTOV_PARALLEL_AD
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9::rttov9_parallel_direct _RTTOV_PARALLEL_DIRECT
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9::rttov9_parallel_k      _RTTOV_PARALLEL_K
+bld::tool::fppkeys::ops::src::code::Ops_RTTOV9::rttov9_parallel_tl     _RTTOV_PARALLEL_TL
+
+# ------------------------------------------------------------------------------
+# Project and branches
+# ------------------------------------------------------------------------------
+
+REPOS::var::base                                                       svn://fcm5/VAR_svn/VAR/trunk
+REVISION::var::base                                                    14844
+SRC::var::base                                                         src/code/NewDynamics
+SRC::var::base                                                         src/code/PFMod_Model
+SRC::var::base                                                         src/code/PFMod_SV
+SRC::var::base                                                         src/code/PF_Bdy_Layer
+SRC::var::base                                                         src/code/PF_Control
+SRC::var::base                                                         src/code/PF_EulerianAdv
+SRC::var::base                                                         src/code/PF_FirstGuessRhoW
+SRC::var::base                                                         src/code/PF_GcrSolver
+SRC::var::base                                                         src/code/PF_General
+SRC::var::base                                                         src/code/PF_Helmholtz
+SRC::var::base                                                         src/code/PF_IO
+SRC::var::base                                                         src/code/PF_Interpolation
+SRC::var::base                                                         src/code/PF_MPP
+SRC::var::base                                                         src/code/PF_MPPUtil
+SRC::var::base                                                         src/code/PF_MoistPhys
+SRC::var::base                                                         src/code/PF_SemiLagrangianTheta
+SRC::var::base                                                         src/code/PF_SemiLagrangianUV
+SRC::var::base                                                         src/code/PF_UVTransformation
+SRC::var::base                                                         src/code/PF_Update
+SRC::var::base                                                         src/code/PF_convec
+SRC::var::base                                                         src/code/SG_Interpolation
+SRC::var::base                                                         src/code/VarMod_ATOVS
+SRC::var::base                                                         src/code/VarMod_ATOVSRad
+SRC::var::base                                                         src/code/VarMod_Aircraft
+SRC::var::base                                                         src/code/VarMod_BalanceTerms
+SRC::var::base                                                         src/code/VarMod_BgPenAndGrad
+SRC::var::base                                                         src/code/VarMod_CovCheckTp
+SRC::var::base                                                         src/code/VarMod_CovCodes
+SRC::var::base                                                         src/code/VarMod_CovCond
+SRC::var::base                                                         src/code/VarMod_CovHorizontal
+SRC::var::base                                                         src/code/VarMod_CovOptions
+SRC::var::base                                                         src/code/VarMod_CovSample
+SRC::var::base                                                         src/code/VarMod_CovSpectral
+SRC::var::base                                                         src/code/VarMod_CovSpectralData
+SRC::var::base                                                         src/code/VarMod_CovStatsIO
+SRC::var::base                                                         src/code/VarMod_CovVariances
+SRC::var::base                                                         src/code/VarMod_CovVertical
+SRC::var::base                                                         src/code/VarMod_CovVerticalData
+SRC::var::base                                                         src/code/VarMod_DelSquaredFFT
+SRC::var::base                                                         src/code/VarMod_Diagnostics
+SRC::var::base                                                         src/code/VarMod_FieldOutput
+SRC::var::base                                                         src/code/VarMod_Fourier
+SRC::var::base                                                         src/code/VarMod_GPSRO
+SRC::var::base                                                         src/code/VarMod_GeneralOptions
+SRC::var::base                                                         src/code/VarMod_GroundGPS
+SRC::var::base                                                         src/code/VarMod_HorizontalInterp
+SRC::var::base                                                         src/code/VarMod_HorizontalInterp_Adj
+SRC::var::base                                                         src/code/VarMod_InterpColumns
+SRC::var::base                                                         src/code/VarMod_InterpColumns_Adj
+SRC::var::base                                                         src/code/VarMod_LS
+SRC::var::base                                                         src/code/VarMod_LTinterp
+SRC::var::base                                                         src/code/VarMod_MOPS
+SRC::var::base                                                         src/code/VarMod_MPP
+SRC::var::base                                                         src/code/VarMod_Minimise
+SRC::var::base                                                         src/code/VarMod_ModelIO
+SRC::var::base                                                         src/code/VarMod_ObsControl
+SRC::var::base                                                         src/code/VarMod_ObsIO
+SRC::var::base                                                         src/code/VarMod_ObsInfo
+SRC::var::base                                                         src/code/VarMod_ObsOptions
+SRC::var::base                                                         src/code/VarMod_ObsUtility
+SRC::var::base                                                         src/code/VarMod_ObsUtility_Adj
+SRC::var::base                                                         src/code/VarMod_PF
+SRC::var::base                                                         src/code/VarMod_PFInfo
+SRC::var::base                                                         src/code/VarMod_PF_Adj
+SRC::var::base                                                         src/code/VarMod_Platform
+SRC::var::base                                                         src/code/VarMod_Precip
+SRC::var::base                                                         src/code/VarMod_PseudoOb
+SRC::var::base                                                         src/code/VarMod_QC
+SRC::var::base                                                         src/code/VarMod_Radar
+SRC::var::base                                                         src/code/VarMod_SBUV
+SRC::var::base                                                         src/code/VarMod_SSMI
+SRC::var::base                                                         src/code/VarMod_SatRad
+SRC::var::base                                                         src/code/VarMod_Satwind
+SRC::var::base                                                         src/code/VarMod_Scatwind
+SRC::var::base                                                         src/code/VarMod_Sonde
+SRC::var::base                                                         src/code/VarMod_Stats
+SRC::var::base                                                         src/code/VarMod_Surface
+SRC::var::base                                                         src/code/VarMod_TestCovNL
+SRC::var::base                                                         src/code/VarMod_TotalPenAndGrad
+SRC::var::base                                                         src/code/VarMod_TpTransform
+SRC::var::base                                                         src/code/VarMod_TransSpecH
+SRC::var::base                                                         src/code/VarMod_TransformInfo
+SRC::var::base                                                         src/code/VarMod_Transform_g
+SRC::var::base                                                         src/code/VarMod_Transform_h
+SRC::var::base                                                         src/code/VarMod_Transform_p
+SRC::var::base                                                         src/code/VarMod_Transform_v
+SRC::var::base                                                         src/code/VarMod_Transforms
+SRC::var::base                                                         src/code/VarMod_Trig
+SRC::var::base                                                         src/code/VarMod_UpPF
+SRC::var::base                                                         src/code/VarMod_UpPF_Adj
+SRC::var::base                                                         src/code/VarMod_UpTransform
+SRC::var::base                                                         src/code/VarMod_UpTransform_Adj
+SRC::var::base                                                         src/code/VarMod_VerticalInterp
+SRC::var::base                                                         src/code/VarMod_VerticalInterp_Adj
+SRC::var::base                                                         src/code/VarMod_Vis
+SRC::var::base                                                         src/code/VarMod_Vp
+SRC::var::base                                                         src/code/VarProg_AnalysePF
+SRC::var::base                                                         src/code/VarProg_CovAccStats
+SRC::var::base                                                         src/code/VarProg_CovPFstats
+SRC::var::base                                                         src/code/VarProg_SV
+SRC::var::base                                                         src/code/VarProg_TestCov
+SRC::var::base                                                         src/code/VarProg_TestPFModel
+SRC::var::base                                                         src/code/VarProg_UMFileUtils
+SRC::var::base                                                         src/code/Var_DiffOperators
+SRC::var::base                                                         src/code/Var_General
+SRC::var::base                                                         src/code/Var_Initialization
+SRC::var::base                                                         src/code/Var_Jc
+SRC::var::base                                                         src/code/Var_LAPACK
+SRC::var::base                                                         src/scripts/Var_Scripts
+
+REPOS::ops::base                                                       svn://fcm4/OPS_svn/OPS/trunk
+REVISION::ops::base                                                    18341
+SRC::ops::base                                                         src/code/OpsMod_Constants
+SRC::ops::base                                                         src/code/OpsMod_Control
+SRC::ops::base                                                         src/code/OpsMod_GeoIR
+SRC::ops::base                                                         src/code/OpsMod_ObsInfo
+SRC::ops::base                                                         src/code/OpsMod_RTTOV
+SRC::ops::base                                                         src/code/OpsMod_Sort
+SRC::ops::base                                                         src/code/OpsMod_Utilities
+SRC::ops::base                                                         src/code/OpsMod_Varobs
+SRC::ops::base                                                         src/code/OpsMod_VerticalInterp
+SRC::ops::base                                                         src/code/OpsMod_VisControl
+SRC::ops::base                                                         src/code/OpsProg_RTTOV9
+SRC::ops::base                                                         src/code/Ops_AIRS_1DVar
+SRC::ops::base                                                         src/code/Ops_AIRS_Utilities
+SRC::ops::base                                                         src/code/Ops_RTTOV7
+SRC::ops::base                                                         src/code/Ops_RTTOV7_RTTOVCLD
+SRC::ops::base                                                         src/code/Ops_RTTOV9
+SRC::ops::base                                                         src/code/Ops_SatRad_Info
+SRC::ops::base                                                         src/code/Ops_SatRad_Process
+SRC::ops::base                                                         src/code/Ops_SatRad_SetUp
+SRC::ops::base                                                         src/code/Ops_SatRad_Utilities
+
+REPOS::gen::base                                                       svn://fcm1/GEN_svn/GEN/trunk
+REVISION::gen::base                                                    3073
+SRC::gen::base                                                         src/code/GenMod_Constants
+SRC::gen::base                                                         src/code/GenMod_Control
+SRC::gen::base                                                         src/code/GenMod_FortranIO
+SRC::gen::base                                                         src/code/GenMod_GetEnv
+SRC::gen::base                                                         src/code/GenMod_ModelIO
+SRC::gen::base                                                         src/code/GenMod_Platform
+SRC::gen::base                                                         src/code/GenMod_Reporting
+SRC::gen::base                                                         src/code/GenMod_Trace
+SRC::gen::base                                                         src/code/GenMod_UMConstants
+SRC::gen::base                                                         src/code/GenMod_Utilities
+SRC::gen::base                                                         src/code/Reconfiguration
+SRC::gen::base                                                         src/code/UM_COEX
+SRC::gen::base                                                         src/code/UM_General
+SRC::gen::base                                                         src/code/UM_Platform
+
+REPOS::da::base                                                        svn://fcm5/DA_svn/DA/trunk
+REVISION::da::base                                                     258
+
+REPOS::varadmin::base                                                  svn://fcm5/VAR_svn/Admin/trunk
+REVISION::varadmin::base                                               14851
+SRC::varadmin::base                                                    src/code/VarMod_Blas
+SRC::varadmin::base                                                    src/code/VarMod_Lapack
+
+REPOS::gcom::base                                                      svn://fcm2/UM_svn/GCOM/branches/dev/ibmjb/r12957_2194_ralltoalle_out_of_order
+REVISION::gcom::base                                                   15824
+SRC::gcom::base                                                        build/gc
+SRC::gcom::base                                                        build/gcg
+SRC::gcom::base                                                        build/include
+SRC::gcom::base                                                        build/mpl
diff --git a/test/repos/trunk/cfg/fcm2_add_directory_expsrc.cfg b/test/repos/trunk/cfg/fcm2_add_directory_expsrc.cfg
new file mode 100644
index 0000000..298f275
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_add_directory_expsrc.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/add_directory
diff --git a/test/repos/trunk/cfg/fcm2_add_file.cfg b/test/repos/trunk/cfg/fcm2_add_file.cfg
new file mode 100644
index 0000000..3320192
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_add_file.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/add_file
diff --git a/test/repos/trunk/cfg/fcm2_add_file_inherit.cfg b/test/repos/trunk/cfg/fcm2_add_file_inherit.cfg
new file mode 100644
index 0000000..b13af23
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_add_file_inherit.cfg
@@ -0,0 +1,3 @@
+use = $RUN_DIR/fcm2_base
+
+extract.location{diff}[test_suite] = branches/dev/Share/add_file
diff --git a/test/repos/trunk/cfg/fcm2_base.cfg b/test/repos/trunk/cfg/fcm2_base.cfg
new file mode 100644
index 0000000..6454b65
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_base.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base_inc.cfg
+
+build.target = hello.sh test_suite/namelist/.etc
diff --git a/test/repos/trunk/cfg/fcm2_base_inc.cfg b/test/repos/trunk/cfg/fcm2_base_inc.cfg
new file mode 100644
index 0000000..6589f6c
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_base_inc.cfg
@@ -0,0 +1,6 @@
+steps = extract preprocess build
+
+extract.ns = test_suite
+extract.path-excl[test_suite] = cfg
+
+include = $HERE/fcm2_base_inc2.cfg
diff --git a/test/repos/trunk/cfg/fcm2_base_inc2.cfg b/test/repos/trunk/cfg/fcm2_base_inc2.cfg
new file mode 100644
index 0000000..e3a4925
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_base_inc2.cfg
@@ -0,0 +1,15 @@
+preprocess.ns-excl = / test_suite/subroutine/hello_c.c
+preprocess.ns-incl = test_suite/subroutine test_suite/program
+preprocess.prop{fpp} = wrap_pp
+preprocess.prop{fpp.defs}[test_suite/subroutine/hello_sub.F90] = HELLO_SUB
+preprocess.prop{fpp.defs}[test_suite/program/hello.F90] = CALL_HELLO_SUB
+
+build.prop{file-ext.script} = .pro
+build.prop{fc} = wrap_fc
+$fcflags{?} = -assume nosource_include
+build.prop{ fc.flags } = $fcflags
+build.prop{fc.flags}[ test_suite/subroutine ] = $fcflags -O3
+build.prop {cc} = wrap_cc
+build.prop{cc.flags}=-O3
+build.prop{ar} = wrap_ar
+build.prop{dep.o.special} [test_suite/program] = hello_blockdata.o
diff --git a/test/repos/trunk/cfg/fcm2_branches_clash.cfg b/test/repos/trunk/cfg/fcm2_branches_clash.cfg
new file mode 100644
index 0000000..750597b
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_branches_clash.cfg
@@ -0,0 +1,5 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = \
+    branches/dev/Share/modify_files_base \
+    branches/dev/Share/modify_files_clash
diff --git a/test/repos/trunk/cfg/fcm2_branches_merge.cfg b/test/repos/trunk/cfg/fcm2_branches_merge.cfg
new file mode 100644
index 0000000..fa18ec5
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_branches_merge.cfg
@@ -0,0 +1,6 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] =       \
+    branches/dev/Share/modify_files_base   \
+    branches/dev/Share/modify_files_merge1 \
+    branches/dev/Share/modify_files_merge2
diff --git a/test/repos/trunk/cfg/fcm2_branches_merge_duplicate.cfg b/test/repos/trunk/cfg/fcm2_branches_merge_duplicate.cfg
new file mode 100644
index 0000000..10f05f8
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_branches_merge_duplicate.cfg
@@ -0,0 +1,6 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = \
+    branches/dev/Share/add_file      \
+    branches/dev/Share/add_lines     \
+    branches/dev/Share/add_lines
diff --git a/test/repos/trunk/cfg/fcm2_branches_merge_inherit.cfg b/test/repos/trunk/cfg/fcm2_branches_merge_inherit.cfg
new file mode 100644
index 0000000..4862e5a
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_branches_merge_inherit.cfg
@@ -0,0 +1,6 @@
+use = $RUN_DIR/fcm2_base
+
+extract.location{diff}[test_suite] =       \
+    branches/dev/Share/modify_files_base   \
+    branches/dev/Share/modify_files_merge1 \
+    branches/dev/Share/modify_files_merge2
diff --git a/test/repos/trunk/cfg/fcm2_branches_merge_inherit_wrong_include.cfg b/test/repos/trunk/cfg/fcm2_branches_merge_inherit_wrong_include.cfg
new file mode 100644
index 0000000..8372e63
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_branches_merge_inherit_wrong_include.cfg
@@ -0,0 +1,8 @@
+use = $RUN_DIR/fcm2_base
+
+extract.location{diff}[test_suite] =       \
+    branches/dev/Share/modify_files_base   \
+    branches/dev/Share/modify_files_merge1 \
+    branches/dev/Share/modify_files_merge2
+
+build.prop{fc.flags}[test_suite/module] = -O3
diff --git a/test/repos/trunk/cfg/fcm2_branches_merge_wcopies.cfg b/test/repos/trunk/cfg/fcm2_branches_merge_wcopies.cfg
new file mode 100644
index 0000000..19b904d
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_branches_merge_wcopies.cfg
@@ -0,0 +1,6 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = \
+    $BASE_DIR/work/b1 \
+    $BASE_DIR/work/b2 \
+    $BASE_DIR/work/b3
diff --git a/test/repos/trunk/cfg/fcm2_branches_merge_wcopy.cfg b/test/repos/trunk/cfg/fcm2_branches_merge_wcopy.cfg
new file mode 100644
index 0000000..73b2231
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_branches_merge_wcopy.cfg
@@ -0,0 +1,6 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] =       \
+    branches/dev/Share/modify_files_base   \
+    branches/dev/Share/modify_files_merge1 \
+    $BASE_DIR/work
diff --git a/test/repos/trunk/cfg/fcm2_cflags.cfg b/test/repos/trunk/cfg/fcm2_cflags.cfg
new file mode 100644
index 0000000..e50e0c6
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_cflags.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+build.prop{cc.flags}[test_suite/subroutine] = -O2 -g
diff --git a/test/repos/trunk/cfg/fcm2_change_variable.cfg b/test/repos/trunk/cfg/fcm2_change_variable.cfg
new file mode 100644
index 0000000..1602432
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_change_variable.cfg
@@ -0,0 +1,4 @@
+include = $HERE/fcm2_base.cfg
+
+$fcflags = -assume nosource_include -g
+build.prop{fc.flags}[test_suite/subroutine/hello_sub.F90] = $fcflags -O2
diff --git a/test/repos/trunk/cfg/fcm2_cyclic_dep_fail.cfg b/test/repos/trunk/cfg/fcm2_cyclic_dep_fail.cfg
new file mode 100644
index 0000000..ab8bed3
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_cyclic_dep_fail.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/cyclic_dep_fail
diff --git a/test/repos/trunk/cfg/fcm2_cyclic_dep_ok.cfg b/test/repos/trunk/cfg/fcm2_cyclic_dep_ok.cfg
new file mode 100644
index 0000000..a640a6b
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_cyclic_dep_ok.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/cyclic_dep_ok
diff --git a/test/repos/trunk/cfg/fcm2_delete_directory.cfg b/test/repos/trunk/cfg/fcm2_delete_directory.cfg
new file mode 100644
index 0000000..5573ae4
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_delete_directory.cfg
@@ -0,0 +1,21 @@
+steps = extract preprocess build
+
+extract.ns = test_suite
+extract.path-excl[test_suite] = cfg
+extract.location{diff}[test_suite] =     \
+    branches/dev/Share/modify_files_base \
+    branches/dev/Share/delete_directory
+
+preprocess.ns-excl = / test_suite/subroutine/hello_c.c
+preprocess.ns-incl = test_suite/subroutine test_suite/program
+preprocess.prop{fpp} = wrap_pp
+
+build.prop{file-ext.script} = .pro
+build.prop{fc} = wrap_fc
+$fcflags{?} = -assume nosource_include
+build.prop{fc.flags} = $fcflags
+build.prop{cc} = wrap_cc
+build.prop{cc.flags} = -O3
+build.prop{ar} = wrap_ar
+build.prop{dep.o.special} = hello_blockdata.o
+build.target = hello.sh test_suite/namelist/.etc
diff --git a/test/repos/trunk/cfg/fcm2_delete_directory_inherit.cfg b/test/repos/trunk/cfg/fcm2_delete_directory_inherit.cfg
new file mode 100644
index 0000000..c307c77
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_delete_directory_inherit.cfg
@@ -0,0 +1,7 @@
+use = $RUN_DIR/fcm2_base
+
+preprocess.prop{fpp.defs}[test_suite/program/hello.F90] =
+
+extract.location{diff}[test_suite] =     \
+    branches/dev/Share/modify_files_base \
+    branches/dev/Share/delete_directory
diff --git a/test/repos/trunk/cfg/fcm2_delete_file.cfg b/test/repos/trunk/cfg/fcm2_delete_file.cfg
new file mode 100644
index 0000000..16d8969
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_delete_file.cfg
@@ -0,0 +1,6 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] =       \
+    branches/dev/Share/modify_files_base   \
+    branches/dev/Share/modify_files_merge1 \
+    branches/dev/Share/delete_file
diff --git a/test/repos/trunk/cfg/fcm2_delete_file_inherit.cfg b/test/repos/trunk/cfg/fcm2_delete_file_inherit.cfg
new file mode 100644
index 0000000..065cd02
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_delete_file_inherit.cfg
@@ -0,0 +1,6 @@
+use = $RUN_DIR/fcm2_base
+
+extract.location{diff}[test_suite] =       \
+    branches/dev/Share/modify_files_base   \
+    branches/dev/Share/modify_files_merge1 \
+    branches/dev/Share/delete_file
diff --git a/test/repos/trunk/cfg/fcm2_delete_inc_file.cfg b/test/repos/trunk/cfg/fcm2_delete_inc_file.cfg
new file mode 100644
index 0000000..cb3288d
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_delete_inc_file.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/delete_inc_file
diff --git a/test/repos/trunk/cfg/fcm2_delete_inc_file_inherit.cfg b/test/repos/trunk/cfg/fcm2_delete_inc_file_inherit.cfg
new file mode 100644
index 0000000..d0e50d9
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_delete_inc_file_inherit.cfg
@@ -0,0 +1,3 @@
+use = $RUN_DIR/fcm2_base
+
+extract.location{diff}[test_suite] = branches/dev/Share/delete_inc_file
diff --git a/test/repos/trunk/cfg/fcm2_delete_inc_file_inherit_force.cfg b/test/repos/trunk/cfg/fcm2_delete_inc_file_inherit_force.cfg
new file mode 100644
index 0000000..aec0417
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_delete_inc_file_inherit_force.cfg
@@ -0,0 +1,5 @@
+use = $RUN_DIR/fcm2_base
+
+extract.location{diff}[test_suite] = branches/dev/Share/delete_inc_file
+
+build.prop{fc.flags}[test_suite/module] = -assume nosource_include -O3
diff --git a/test/repos/trunk/cfg/fcm2_delete_pp_file.cfg b/test/repos/trunk/cfg/fcm2_delete_pp_file.cfg
new file mode 100644
index 0000000..c50db4c
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_delete_pp_file.cfg
@@ -0,0 +1,24 @@
+steps = extract preprocess build
+
+extract.ns = test_suite
+extract.path-excl[test_suite] = cfg
+
+extract.location{diff}[test_suite] =     \
+    branches/dev/Share/modify_files_base \
+    branches/dev/Share/modify_pp_include \
+    branches/dev/Share/delete_pp_file
+
+preprocess.ns-excl = / test_suite/subroutine/hello_c.c
+preprocess.ns-incl = test_suite/subroutine test_suite/program
+preprocess.prop{fpp} = wrap_pp
+preprocess.prop{fpp.defs}[test_suite/program/hello.F90] = CALL_HELLO_SUB
+
+build.target{task} = link
+build.prop{fc} = wrap_fc
+$fcflags{?} = -assume nosource_include
+build.prop{fc.flags} = $fcflags
+build.prop{fc.flags}[test_suite/subroutine] = $fcflags -O3
+build.prop{cc} = wrap_cc
+build.prop{cc.flags} = -O3
+build.prop{ar} = wrap_ar
+build.prop{dep.o.special} = hello_blockdata.o
diff --git a/test/repos/trunk/cfg/fcm2_delete_pp_file_inherit.cfg b/test/repos/trunk/cfg/fcm2_delete_pp_file_inherit.cfg
new file mode 100644
index 0000000..ef11840
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_delete_pp_file_inherit.cfg
@@ -0,0 +1,6 @@
+use = $RUN_DIR/fcm2_base
+
+extract.location{diff}[test_suite] =     \
+    branches/dev/Share/modify_files_base \
+    branches/dev/Share/modify_pp_include \
+    branches/dev/Share/delete_pp_file
diff --git a/test/repos/trunk/cfg/fcm2_delete_ppinc_file.cfg b/test/repos/trunk/cfg/fcm2_delete_ppinc_file.cfg
new file mode 100644
index 0000000..ff4b539
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_delete_ppinc_file.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/delete_ppinc_file
diff --git a/test/repos/trunk/cfg/fcm2_delete_ppinc_file_inherit.cfg b/test/repos/trunk/cfg/fcm2_delete_ppinc_file_inherit.cfg
new file mode 100644
index 0000000..53bce12
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_delete_ppinc_file_inherit.cfg
@@ -0,0 +1,3 @@
+use = $RUN_DIR/fcm2_base
+
+extract.location{diff}[test_suite] = branches/dev/Share/delete_ppinc_file
diff --git a/test/repos/trunk/cfg/fcm2_delete_ppinc_file_inherit_force.cfg b/test/repos/trunk/cfg/fcm2_delete_ppinc_file_inherit_force.cfg
new file mode 100644
index 0000000..a637c8e
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_delete_ppinc_file_inherit_force.cfg
@@ -0,0 +1,5 @@
+use = $RUN_DIR/fcm2_base
+
+extract.location{diff}[test_suite] = branches/dev/Share/delete_ppinc_file
+
+preprocess.prop{fpp.defs}[test_suite/subroutine/hello_sub.F90] = HELLO_SUB DUMMY
diff --git a/test/repos/trunk/cfg/fcm2_dep_o.cfg b/test/repos/trunk/cfg/fcm2_dep_o.cfg
new file mode 100644
index 0000000..1952e47
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_dep_o.cfg
@@ -0,0 +1,5 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/f77_dep
+
+build.prop{dep.o} [test_suite/program] = hello_sub.o
diff --git a/test/repos/trunk/cfg/fcm2_dep_o_all.cfg b/test/repos/trunk/cfg/fcm2_dep_o_all.cfg
new file mode 100644
index 0000000..8b0d5cd
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_dep_o_all.cfg
@@ -0,0 +1,5 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/f77_dep
+
+build.prop{dep.o} = hello_sub.o
diff --git a/test/repos/trunk/cfg/fcm2_dep_o_invalid.cfg b/test/repos/trunk/cfg/fcm2_dep_o_invalid.cfg
new file mode 100644
index 0000000..87b4110
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_dep_o_invalid.cfg
@@ -0,0 +1,5 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/f77_dep
+
+build.prop{dep.o} [test_suite/program] = invalid.o
diff --git a/test/repos/trunk/cfg/fcm2_duplicate_target.cfg b/test/repos/trunk/cfg/fcm2_duplicate_target.cfg
new file mode 100644
index 0000000..4264af6
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_duplicate_target.cfg
@@ -0,0 +1,4 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/add_duplicate
+preprocess.prop{fpp.defs}[test_suite/subroutine/hello_sub2.F90] = HELLO_SUB
diff --git a/test/repos/trunk/cfg/fcm2_exclude_dependency.cfg b/test/repos/trunk/cfg/fcm2_exclude_dependency.cfg
new file mode 100644
index 0000000..03b740c
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_exclude_dependency.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+build.prop{no-dep.f.module} = hello_constants
diff --git a/test/repos/trunk/cfg/fcm2_exe_permissions.cfg b/test/repos/trunk/cfg/fcm2_exe_permissions.cfg
new file mode 100644
index 0000000..6b19e58
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_exe_permissions.cfg
@@ -0,0 +1,6 @@
+include = $HERE/fcm2_base_inc.cfg
+
+build.target = hello.sh test_suite/namelist/.etc
+build.target-rename = hello.exe:hello_world.exe
+
+extract.location{diff}[test_suite] = $BASE_DIR/work
diff --git a/test/repos/trunk/cfg/fcm2_exe_rename.cfg b/test/repos/trunk/cfg/fcm2_exe_rename.cfg
new file mode 100644
index 0000000..c36191f
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_exe_rename.cfg
@@ -0,0 +1,6 @@
+include = $HERE/fcm2_base_inc.cfg
+
+build.target = hello.sh test_suite/namelist/.etc
+build.target-rename = hello.exe:hello_world.exe
+
+extract.location{diff}[test_suite] = branches/dev/Share/exe_rename
diff --git a/test/repos/trunk/cfg/fcm2_extract_path_excl_no_ns.cfg b/test/repos/trunk/cfg/fcm2_extract_path_excl_no_ns.cfg
new file mode 100644
index 0000000..d2f8c4d
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_extract_path_excl_no_ns.cfg
@@ -0,0 +1,9 @@
+steps = extract preprocess build
+
+extract.ns = test_suite
+extract.path-excl = cfg
+extract.path-incl[test_suite] = cfg/fcm2_base.cfg
+
+build.target = hello.sh test_suite/namelist/.etc
+
+include = $HERE/fcm2_base_inc2.cfg
diff --git a/test/repos/trunk/cfg/fcm2_fc.cfg b/test/repos/trunk/cfg/fcm2_fc.cfg
new file mode 100644
index 0000000..cd03f67
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_fc.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+build.prop{fc} = wrap_fc2
diff --git a/test/repos/trunk/cfg/fcm2_fflags1.cfg b/test/repos/trunk/cfg/fcm2_fflags1.cfg
new file mode 100644
index 0000000..5d630e2
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_fflags1.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+build.prop{fc.flags}[test_suite] = $fcflags -O2 -g
diff --git a/test/repos/trunk/cfg/fcm2_fflags2.cfg b/test/repos/trunk/cfg/fcm2_fflags2.cfg
new file mode 100644
index 0000000..4bc74a2
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_fflags2.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+build.prop{fc.flags}[test_suite/subroutine/hello_sub.F90] = -O2 -g
diff --git a/test/repos/trunk/cfg/fcm2_fflags_inherit.cfg b/test/repos/trunk/cfg/fcm2_fflags_inherit.cfg
new file mode 100644
index 0000000..58094b2
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_fflags_inherit.cfg
@@ -0,0 +1,3 @@
+use = $RUN_DIR/fcm2_base
+
+build.prop{fc.flags}[test_suite/subroutine/hello_sub.F90] = -O2 -g
diff --git a/test/repos/trunk/cfg/fcm2_flag-output.cfg b/test/repos/trunk/cfg/fcm2_flag-output.cfg
new file mode 100644
index 0000000..11dfd31
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_flag-output.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+build.prop{fc.flag-output} = -o %s
diff --git a/test/repos/trunk/cfg/fcm2_inc_devnull.cfg b/test/repos/trunk/cfg/fcm2_inc_devnull.cfg
new file mode 100644
index 0000000..4c8d4c7
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_inc_devnull.cfg
@@ -0,0 +1,6 @@
+include = $HERE/fcm2_base_inc.cfg
+
+# The UM makes use of the following so we have to support it
+include = /dev/null
+
+build.target = hello.sh test_suite/namelist/.etc
diff --git a/test/repos/trunk/cfg/fcm2_inherit_invalid_path.cfg b/test/repos/trunk/cfg/fcm2_inherit_invalid_path.cfg
new file mode 100644
index 0000000..eebf917
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_inherit_invalid_path.cfg
@@ -0,0 +1 @@
+use = /invalid/path
diff --git a/test/repos/trunk/cfg/fcm2_inherit_redefine_fail.cfg b/test/repos/trunk/cfg/fcm2_inherit_redefine_fail.cfg
new file mode 100644
index 0000000..dc033be
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_inherit_redefine_fail.cfg
@@ -0,0 +1,5 @@
+use = $RUN_DIR/fcm2_base
+
+include = $HERE/fcm2_base.cfg
+extract.path-excl[test_suite] = cfg namelist
+build.target = hello.sh
diff --git a/test/repos/trunk/cfg/fcm2_inherit_redefine_ok.cfg b/test/repos/trunk/cfg/fcm2_inherit_redefine_ok.cfg
new file mode 100644
index 0000000..761e715
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_inherit_redefine_ok.cfg
@@ -0,0 +1,3 @@
+use = $RUN_DIR/fcm2_base
+
+include = $HERE/fcm2_base.cfg
diff --git a/test/repos/trunk/cfg/fcm2_invalid_base_url.cfg b/test/repos/trunk/cfg/fcm2_invalid_base_url.cfg
new file mode 100644
index 0000000..742a2b2
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_invalid_base_url.cfg
@@ -0,0 +1,4 @@
+include = $HERE/fcm2_base.cfg
+steps = extract
+
+extract.location{primary}[test_suite] = fcm:test_suite/invalid
diff --git a/test/repos/trunk/cfg/fcm2_invalid_branch_url.cfg b/test/repos/trunk/cfg/fcm2_invalid_branch_url.cfg
new file mode 100644
index 0000000..9159101
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_invalid_branch_url.cfg
@@ -0,0 +1,4 @@
+include = $HERE/fcm2_base.cfg
+steps = extract
+
+extract.location{diff}[test_suite] = branches/dev/Share/invalid
diff --git a/test/repos/trunk/cfg/fcm2_invalid_branch_url2.cfg b/test/repos/trunk/cfg/fcm2_invalid_branch_url2.cfg
new file mode 100644
index 0000000..9e0cb95
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_invalid_branch_url2.cfg
@@ -0,0 +1,4 @@
+include = $HERE/fcm2_base.cfg
+steps = extract
+
+extract.location{diff}[test_suite] = /invalid/path
diff --git a/test/repos/trunk/cfg/fcm2_invalid_inc.cfg b/test/repos/trunk/cfg/fcm2_invalid_inc.cfg
new file mode 100644
index 0000000..2992dd3
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_invalid_inc.cfg
@@ -0,0 +1 @@
+include = $HERE/invalid.cfg
diff --git a/test/repos/trunk/cfg/fcm2_invalid_label.cfg b/test/repos/trunk/cfg/fcm2_invalid_label.cfg
new file mode 100644
index 0000000..7825698
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_invalid_label.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+build.prep{fc.flags}[test_suite/subroutine/hello_sub.f90] = -O2
diff --git a/test/repos/trunk/cfg/fcm2_invalid_modifier.cfg b/test/repos/trunk/cfg/fcm2_invalid_modifier.cfg
new file mode 100644
index 0000000..595f7cc
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_invalid_modifier.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+build.prop{fc.flegs}[test_suite/subroutine/hello_sub.f90] = -O2
diff --git a/test/repos/trunk/cfg/fcm2_invalid_modifiers.cfg b/test/repos/trunk/cfg/fcm2_invalid_modifiers.cfg
new file mode 100644
index 0000000..50d8219
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_invalid_modifiers.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+build.prop{fc.flags cc.flags}[test_suite/subroutine/hello_sub.f90] = -O2
diff --git a/test/repos/trunk/cfg/fcm2_invalid_namespace.cfg b/test/repos/trunk/cfg/fcm2_invalid_namespace.cfg
new file mode 100644
index 0000000..e71873c
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_invalid_namespace.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+build.prop{fc.flags}[test_suite/invalid] = -O2
diff --git a/test/repos/trunk/cfg/fcm2_invalid_namespace2.cfg b/test/repos/trunk/cfg/fcm2_invalid_namespace2.cfg
new file mode 100644
index 0000000..8d82253
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_invalid_namespace2.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+preprocess.ns-excl[test_suite] = /
diff --git a/test/repos/trunk/cfg/fcm2_invalid_target.cfg b/test/repos/trunk/cfg/fcm2_invalid_target.cfg
new file mode 100644
index 0000000..cad9008
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_invalid_target.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base_inc.cfg
+
+build.target = hello.sh invalid.sh
diff --git a/test/repos/trunk/cfg/fcm2_invalid_variable.cfg b/test/repos/trunk/cfg/fcm2_invalid_variable.cfg
new file mode 100644
index 0000000..1e0c272
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_invalid_variable.cfg
@@ -0,0 +1,5 @@
+include = $HERE/fcm2_base.cfg
+
+build.prop{fc.flags}[test_suite/subroutine/hello_sub.f90] = $INVALID
+
+$INVALID = too late
diff --git a/test/repos/trunk/cfg/fcm2_library.cfg b/test/repos/trunk/cfg/fcm2_library.cfg
new file mode 100644
index 0000000..494206c
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_library.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base_inc.cfg
+
+build.target = test_suite/libo.a
diff --git a/test/repos/trunk/cfg/fcm2_library_rename.cfg b/test/repos/trunk/cfg/fcm2_library_rename.cfg
new file mode 100644
index 0000000..e3ee0d4
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_library_rename.cfg
@@ -0,0 +1,4 @@
+include = $HERE/fcm2_base_inc.cfg
+
+build.target = mylib.a
+build.target-rename = test_suite/module/libo.a:mylib.a
diff --git a/test/repos/trunk/cfg/fcm2_mirror.cfg b/test/repos/trunk/cfg/fcm2_mirror.cfg
new file mode 100644
index 0000000..c1bb8c0
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_mirror.cfg
@@ -0,0 +1,6 @@
+include = $HERE/fcm2_base.cfg
+
+steps = extract mirror
+
+mirror.target = localhost:${THIS_RUN_DIR}_mirror
+mirror.prop{config-file.steps} =  preprocess build
diff --git a/test/repos/trunk/cfg/fcm2_mirror_after_pp.cfg b/test/repos/trunk/cfg/fcm2_mirror_after_pp.cfg
new file mode 100644
index 0000000..7b6cdb6
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_mirror_after_pp.cfg
@@ -0,0 +1,6 @@
+include = $HERE/fcm2_base.cfg
+
+steps = extract preprocess mirror
+
+mirror.target = localhost:${THIS_RUN_DIR}_mirror
+mirror.prop{config-file.steps} =  build
diff --git a/test/repos/trunk/cfg/fcm2_mirror_inherit.cfg b/test/repos/trunk/cfg/fcm2_mirror_inherit.cfg
new file mode 100644
index 0000000..7391d71
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_mirror_inherit.cfg
@@ -0,0 +1,8 @@
+use = $RUN_DIR/fcm2_mirror
+
+extract.location{diff}[test_suite] =       \
+    branches/dev/Share/modify_files_base   \
+    branches/dev/Share/modify_files_merge1 \
+    branches/dev/Share/modify_files_merge2
+
+mirror.target = localhost:${THIS_RUN_DIR}_mirror
diff --git a/test/repos/trunk/cfg/fcm2_mirror_inherit_fflags.cfg b/test/repos/trunk/cfg/fcm2_mirror_inherit_fflags.cfg
new file mode 100644
index 0000000..5b3d776
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_mirror_inherit_fflags.cfg
@@ -0,0 +1,6 @@
+use = $RUN_DIR/fcm2_mirror
+
+mirror.target = localhost:${THIS_RUN_DIR}_mirror
+
+include = $HERE/fcm2_base_inc2.cfg
+build.prop{fc.flags}[test_suite] = $fcflags -O2 -g
diff --git a/test/repos/trunk/cfg/fcm2_mirror_inherit_notarget.cfg b/test/repos/trunk/cfg/fcm2_mirror_inherit_notarget.cfg
new file mode 100644
index 0000000..7fd45ca
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_mirror_inherit_notarget.cfg
@@ -0,0 +1,6 @@
+use = $RUN_DIR/fcm2_mirror
+
+extract.location{diff}[test_suite] =       \
+    branches/dev/Share/modify_files_base   \
+    branches/dev/Share/modify_files_merge1 \
+    branches/dev/Share/modify_files_merge2
diff --git a/test/repos/trunk/cfg/fcm2_modify_subroutine_inherit.cfg b/test/repos/trunk/cfg/fcm2_modify_subroutine_inherit.cfg
new file mode 100644
index 0000000..ddc5110
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_modify_subroutine_inherit.cfg
@@ -0,0 +1,3 @@
+use = $RUN_DIR/fcm2_base
+
+extract.location{diff}[test_suite] = branches/dev/Share/modify_subroutine
diff --git a/test/repos/trunk/cfg/fcm2_modify_subroutine_interface_inherit.cfg b/test/repos/trunk/cfg/fcm2_modify_subroutine_interface_inherit.cfg
new file mode 100644
index 0000000..559fae6
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_modify_subroutine_interface_inherit.cfg
@@ -0,0 +1,3 @@
+use = $RUN_DIR/fcm2_base
+
+extract.location{diff}[test_suite] = branches/dev/Share/modify_subroutine_interface
diff --git a/test/repos/trunk/cfg/fcm2_multi_inherit.cfg b/test/repos/trunk/cfg/fcm2_multi_inherit.cfg
new file mode 100644
index 0000000..06b437c
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_multi_inherit.cfg
@@ -0,0 +1,4 @@
+use = $RUN_DIR/fcm2_fflags_inherit
+use = $RUN_DIR/fcm2_modify_subroutine_inherit
+
+build.prop{fc.flags}[test_suite/module] = -O2 -g
diff --git a/test/repos/trunk/cfg/fcm2_multiple_build.cfg b/test/repos/trunk/cfg/fcm2_multiple_build.cfg
new file mode 100644
index 0000000..ef1dedd
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_multiple_build.cfg
@@ -0,0 +1,16 @@
+include = $HERE/fcm2_base.cfg
+
+step.class[build2]      = build
+
+steps = extract preprocess build build2
+
+build2.prop{file-ext.script} = .pro
+build2.prop{fc} = wrap_fc
+build2.prop{fc.flags} = $fcflags
+build2.prop{fc.flags}[test_suite/subroutine] = $fcflags -O3
+build2.prop{cc} = wrap_cc
+build2.prop{cc.flags} = -O3
+build2.prop{ar} = wrap_ar
+build2.prop{dep.o.special}[test_suite/program] = hello_blockdata.o
+build2.prop{fc.defs}[test_suite/blockdata] = ODD
+build2.target = hello.sh
diff --git a/test/repos/trunk/cfg/fcm2_multiple_build_inherit.cfg b/test/repos/trunk/cfg/fcm2_multiple_build_inherit.cfg
new file mode 100644
index 0000000..d133685
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_multiple_build_inherit.cfg
@@ -0,0 +1,6 @@
+use = $RUN_DIR/fcm2_multiple_build
+
+extract.location{diff}[test_suite] =       \
+    branches/dev/Share/modify_files_base   \
+    branches/dev/Share/modify_files_merge1 \
+    branches/dev/Share/modify_files_merge2
diff --git a/test/repos/trunk/cfg/fcm2_multiple_pp-build.cfg b/test/repos/trunk/cfg/fcm2_multiple_pp-build.cfg
new file mode 100644
index 0000000..a237557
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_multiple_pp-build.cfg
@@ -0,0 +1,25 @@
+include = $HERE/fcm2_base.cfg
+
+step.class[preprocess2] = preprocess
+step.class[build2]      = build
+
+steps = extract preprocess build preprocess2 build2
+
+preprocess2.prop{no-step-source} = preprocess
+preprocess2.ns-excl = / test_suite/subroutine/hello_c.c
+preprocess2.ns-incl = test_suite/subroutine test_suite/program
+preprocess2.prop{fpp} = wrap_pp
+preprocess2.prop{fpp.defs}[test_suite/subroutine/hello_sub.F90] = HELLO_SUB
+preprocess2.prop{fpp.defs}[test_suite/program/hello.F90] = CALL_HELLO_SUB
+
+build2.prop{no-step-source} = preprocess
+build2.prop{file-ext.script} = .pro
+build2.prop{fc} = wrap_fc
+build2.prop{fc.flags} = $fcflags
+build2.prop{fc.flags}[test_suite/subroutine] = $fcflags -O3
+build2.prop{cc} = wrap_cc
+build2.prop{cc.flags} = -O3
+build2.prop{ar} = wrap_ar
+build2.prop{dep.o.special}[test_suite/program] = hello_blockdata.o
+build2.prop{fc.defs}[test_suite/blockdata] = ODD
+build2.target = hello.sh
diff --git a/test/repos/trunk/cfg/fcm2_multiple_pp-build_inherit.cfg b/test/repos/trunk/cfg/fcm2_multiple_pp-build_inherit.cfg
new file mode 100644
index 0000000..07e5a4a
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_multiple_pp-build_inherit.cfg
@@ -0,0 +1,6 @@
+use = $RUN_DIR/fcm2_multiple_pp-build
+
+extract.location{diff}[test_suite] =       \
+    branches/dev/Share/modify_files_base   \
+    branches/dev/Share/modify_files_merge1 \
+    branches/dev/Share/modify_files_merge2
diff --git a/test/repos/trunk/cfg/fcm2_no_dep.cfg b/test/repos/trunk/cfg/fcm2_no_dep.cfg
new file mode 100644
index 0000000..4657ee6
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_no_dep.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+build.prop{no-dep.bin} = *
diff --git a/test/repos/trunk/cfg/fcm2_ns-dep_o.cfg b/test/repos/trunk/cfg/fcm2_ns-dep_o.cfg
new file mode 100644
index 0000000..c2de1f8
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_ns-dep_o.cfg
@@ -0,0 +1,5 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/f77_dep
+
+build.prop{ns-dep.o} = test_suite
diff --git a/test/repos/trunk/cfg/fcm2_ns-dep_o_all.cfg b/test/repos/trunk/cfg/fcm2_ns-dep_o_all.cfg
new file mode 100644
index 0000000..1ce0686
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_ns-dep_o_all.cfg
@@ -0,0 +1,5 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/f77_dep
+
+build.prop{ns-dep.o} = /
diff --git a/test/repos/trunk/cfg/fcm2_ns-dep_o_file.cfg b/test/repos/trunk/cfg/fcm2_ns-dep_o_file.cfg
new file mode 100644
index 0000000..68dd26f
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_ns-dep_o_file.cfg
@@ -0,0 +1,5 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/f77_dep
+
+build.prop{ns-dep.o} = test_suite/subroutine/hello_sub.F90
diff --git a/test/repos/trunk/cfg/fcm2_ns-dep_o_invalid.cfg b/test/repos/trunk/cfg/fcm2_ns-dep_o_invalid.cfg
new file mode 100644
index 0000000..875790e
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_ns-dep_o_invalid.cfg
@@ -0,0 +1,5 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/f77_dep
+
+build.prop{ns-dep.o} = invalid test_suite
diff --git a/test/repos/trunk/cfg/fcm2_ops.cfg b/test/repos/trunk/cfg/fcm2_ops.cfg
new file mode 100644
index 0000000..6ea59c9
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_ops.cfg
@@ -0,0 +1,82 @@
+steps = extract preprocess build
+
+extract.ns = ens gcom gen ops ops_admin um_admin
+extract.location[ops] = trunk at 19069
+extract.path-root[ops] = src
+extract.path-excl[ops] = config scripts/Ops_SatRad_IDL code/OpsMod_ODB
+
+extract.location[gen] = trunk at 3194
+extract.path-root[gen] = src/code
+
+extract.location[ops_admin] = trunk at 19069
+extract.path-root[ops_admin] = src/code
+
+extract.location[um_admin] = trunk at 11210
+extract.path-root[um_admin] = utilities
+extract.path-excl[um_admin] = /
+extract.path-incl[um_admin] = IBM_signal_hander
+
+extract.location[gcom] = trunk at 17285
+extract.path-root[gcom] = build
+extract.path-excl[gcom] = configs ext_scripts
+
+extract.location[ens] = trunk at 1460
+extract.path-root[ens] = forecast/code
+extract.path-excl[ens] = ext.cfg ibm.ext.cfg lapack_eigen
+
+preprocess.ns-excl = ops gen ops_admin um_admin
+preprocess.ns-incl = var/code/PF_MPP \
+                   \ ops/code/OpsMod_Altimeter \
+                   \ ops/code/OpsMod_OceanSound \
+                   \ ops/code/OpsMod_Radar \
+                   \ ops/code/OpsMod_RadarZ \
+                   \ ops/code/OpsMod_SeaIce \
+                   \ ops/code/OpsMod_SurfaceSST \
+                   \ ops/code/OpsProg_ExtractAndProcess \
+                   \ ops/code/Ops_RTTOV9/rttov9_parallel_ad.F90 \
+                   \ ops/code/Ops_RTTOV9/rttov9_parallel_direct.F90 \
+                   \ ops/code/Ops_RTTOV9/rttov9_parallel_k.F90 \
+                   \ ops/code/Ops_RTTOV9/rttov9_parallel_tl.F90
+preprocess.prop{fpp} = wrap_pp
+preprocess.prop{fpp.defs}[gcom] = GC_VERSION="'3.4'" GC_DESCRIP="'MPP'" GC_BUILD_DATE="'17285'" MPI_SRC MPILIB_32B PREC_64B GC__FORTERRUNIT=0 GC__FLUSHUNIT6 MPI_BSEND_BUFFER_SIZE=2560000
+preprocess.prop{fpp.defs}[ens]  = IBM
+preprocess.prop{fpp.defs}[ops/code/Ops_RTTOV9/rttov9_parallel_ad.F90]     = _RTTOV_TSTRAD_TEMP RTTOV_ARCH_VECTOR _RTTOV_PARALLEL_AD
+preprocess.prop{fpp.defs}[ops/code/Ops_RTTOV9/rttov9_parallel_direct.F90] = _RTTOV_TSTRAD_TEMP RTTOV_ARCH_VECTOR _RTTOV_PARALLEL_DIRECT
+preprocess.prop{fpp.defs}[ops/code/Ops_RTTOV9/rttov9_parallel_k.F90]      = _RTTOV_TSTRAD_TEMP RTTOV_ARCH_VECTOR _RTTOV_PARALLEL_K
+preprocess.prop{fpp.defs}[ops/code/Ops_RTTOV9/rttov9_parallel_tl.F90]     = _RTTOV_TSTRAD_TEMP RTTOV_ARCH_VECTOR _RTTOV_PARALLEL_TL
+preprocess.prop{cpp.defs}[ens]  = LOWERCASE
+
+$OPSDIR{?} = /home/h04/opsrc/ops0
+$mpich2 = $OPSDIR/mpi/mpich2-1.4-ukmo-v1/ifort-12
+build.target = OpsScr_Build EnsProg_ETKF.exe EnsProg_TrimObstore.exe
+build.prop{file-ext.script} = .sh
+build.prop{cc} = wrap_cc
+build.prop{cc.defs}[gen/GenMod_Platform]         = UNDERSCORE LOWERCASE C_LONG_LONG_INT FRL8
+build.prop{cc.defs}[gen/UM_Platform]             = VAROPSVER C_LOW_U LINUX LITTLE_END C_LONG_LONG_INT FRL8
+build.prop{cc.defs}[ops/code/MetDB_ClientServer] = hpux DEBUG LL64 UNDERSCORE
+build.prop{fc} = wrap_fc
+build.prop{fc.flags}                      = -implicitnone -stand f95 -warn all -warn nointerfaces -i8 -r8 -i-static
+build.prop{fc.flags}[gcom]                = -implicitnone -stand f95 -warn all -i8 -r8 -i-static -warn none -I$mpich2/include
+build.prop{fc.flags}[ops/code/Ops_RTTOV9] = -implicitnone -stand f95 -warn all -warn nointerfaces -i-static -O3
+build.prop{fc.defs}[gen/GenMod_Utilities]    = USE_FLUSH
+build.prop{fc.defs}[gen/UM_COEX]             = VAROPSVER
+build.prop{fc.defs}[gen/UM_Platform]         = VAROPSVER
+build.prop{fc.defs}[ops/code/MetDB_GRIB]     = SX6
+build.prop{fc.defs}[ops/code/OpsMod_Extract] = LITTLE_END
+build.prop{fc.defs}[ops_admin/MetDB_Bufr]    = BPATH
+build.prop{fc.defs}[ops/code/Ops_RTTOV9]     = _RTTOV_TSTRAD_TEMP RTTOV_ARCH_VECTOR
+build.prop{fc.defs}[ops/code/Ops_RTTOV9/rttov9_parallel_ad.F90]     =
+build.prop{fc.defs}[ops/code/Ops_RTTOV9/rttov9_parallel_direct.F90] =
+build.prop{fc.defs}[ops/code/Ops_RTTOV9/rttov9_parallel_k.F90]      =
+build.prop{fc.defs}[ops/code/Ops_RTTOV9/rttov9_parallel_tl.F90]     =
+build.prop{fc.flags-ld}      = -i-static -L$mpich2/lib -lmpich -lmpl -lpthread
+build.prop{fc.flags-ld}[ens] = -i-static -L$mpich2/lib -lmpich -lmpl -lpthread -llapack
+build.prop{ns-dep.o}[ops/code/OpsProg_BackErrCreate]     = gcom
+build.prop{ns-dep.o}[ops/code/OpsProg_ExtractAndProcess] = ops_admin/MetDB_BUFR_RETRIEVAL/source ops_admin/MetDB_Bufr ops_admin/lapack ops_admin/blas gcom
+build.prop{ns-dep.o}[ops/code/OpsProg_MOPS]              = ops_admin/MetDB_BUFR_RETRIEVAL/source ops_admin/MetDB_Bufr gcom
+build.prop{ns-dep.o}[ops/code/OpsProg_KillRPC]           = gcom
+build.prop{ns-dep.o}[ops/code/Ops_SatRad_Stats]          = gcom
+build.prop{ns-dep.o}[ens/EnsProg_ETKF]                   = gcom
+build.prop{ns-dep.o}[ens/EnsProg_TrimObstore]            = gcom
+build.prop{no-dep.f.module} = f90_unix_io xlfutility netcdf yomlun
+build.prop{no-dep.include}  = mpif.h
diff --git a/test/repos/trunk/cfg/fcm2_postproc_hpc.cfg b/test/repos/trunk/cfg/fcm2_postproc_hpc.cfg
new file mode 100644
index 0000000..7bb1822
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_postproc_hpc.cfg
@@ -0,0 +1,171 @@
+steps = extract mirror
+
+mirror.target = $HPC:$THIS_RUN_DIR_HPC
+mirror.prop{config-file.steps} = preprocess build
+mirror.prop{rsync.flags} = -a --exclude='.*' --delete-excluded --timeout=900 --rsh='ssh -oBatchMode=yes' -v
+
+extract.ns = gen ops pp ppancil rst site_ext ver
+extract.location[pp] = trunk at 2728
+extract.path-excl[pp] = /
+extract.path-incl[pp] = CDP \
+                      \ SCW \
+                      \ cloud \
+                      \ frasia \
+                      \ get_metdb_obs \
+                      \ model_processing \
+                      \ moses-pdm-rfm \
+                      \ precip \
+                      \ precip_fcst \
+                      \ pressure_wind \
+                      \ product_gen \
+                      \ scripts \
+                      \ steps \
+                      \ utilities \
+                      \ verification \
+                      \ visibility
+
+extract.location[ppancil] = trunk at 2524
+extract.path-excl[ppancil] = web
+
+extract.location{primary}[site_ext] = svn://fcm9/PostProc_svn/SiteExtract
+extract.location[site_ext] = trunk at 2481
+extract.path-root[site_ext] = FssMod_DMO
+
+extract.location[gen] = trunk at 3015
+extract.path-root[gen] = src/code
+extract.path-excl[gen] = GenMod_ModelIO
+
+extract.location{primary}[rst] = svn://fcm9/PostProc_svn/RoadTemp
+extract.location[rst] = trunk at 2416
+extract.path-excl[rst] = /
+extract.path-incl[rst] = src scripts
+
+extract.location[ver] = trunk at 4739
+extract.path-root[ver] = src/code
+extract.path-excl[ver] = /
+extract.path-incl[ver] = VerMod_FieldsIO VerMod_General VerMod_Grid
+
+extract.location[ops] = trunk at 18088
+extract.path-root[ops] = src/code
+extract.path-excl[ops] = /
+extract.path-incl[ops] = OpsMod_MOPS OpsMod_ObsInfo OpsMod_Constants
+
+preprocess.ns-excl = pp ppancil site_ext rst ops
+preprocess.ns-incl = pp/utilities pp/steps pp/get_metdb_obs/MetDB_source rst/src
+preprocess.prop{cpp} = xlC_r
+preprocess.prop{cpp.flags} = -E -C
+preprocess.prop{cpp.defs}[pp/get_metdb_obs/MetDB_source] = LOWERCASE L64 UNDERSCORE
+preprocess.prop{cpp.defs}[gen] = C_LOW_U FRL8 C_LONG_INT NEC VAROPSVER UTILIO UNDERSCORE LOWERCASE
+preprocess.prop{fpp.flags} = -E -P -traditional
+preprocess.prop{fpp.defs}[gen] = C_LOW_U FRL8 C_LONG_INT NEC VAROPSVER UTILIO UNDERSCORE UPPERCASE
+preprocess.prop{fpp.defs}[rst] = NEC
+preprocess.prop{no-dep.include}  = gc_constants.h gc_kinds.h gc_options.h
+
+build.target = PPQYINTERP.ksh run_nae_pp.ksh run_qv_downscaling.ksh run_glo_pp.ksh run_EuroPP.ksh first_start.ksh PP4KOPER.ksh
+build.prop{cc} = xlC_r
+build.prop{fc} = xlf90_r
+build.prop{no-dep.f.module} = f90_unix_io
+build.prop{file-ext.script} = .ksh
+build.prop{dep.o}[pp/get_metdb_obs/get_metdb_obs.f90] = pout_i8.o
+build.prop{dep.o}[pp/precip/lightning_merge.f] = get_free_lun.o
+build.prop{dep.o}[pp/precip/mask_radar.f90] = nimrod_open.o nimrod_header.o
+build.prop{cc.flags-ld}[pp/steps]     = -L/home/nwp/fr/ihab/fftw_opt/lib/ -lfftw3 -I/home/nwp/fr/ihab/fftw_opt/include/
+build.prop{cc.flags}                  = -qarch=pwr6 -qtune=pwr6 -O0 -qhot
+build.prop{cc.flags}[gen]             = -qarch=pwr6 -qtune=pwr6 -O0 -qhot -qrealsize=8 -qintsize=8
+build.prop{cc.flags}[pp/get_metdb_obs/MetDB_source] =
+build.prop{cc.flags}[pp/steps]        = -O0 -qarch=pwr6 -qtune=pwr6 -q64 -I/home/nwp/fr/ihab/fftw_opt/include/
+build.prop{fc.flags-ld}[rst]          = -L/projects/um1/gcom/gcom3.2/meto_ibm_pwr6_serial/lib -lgcom -bnoquiet -L/projects/um1/lib -lsig -L/usr/lib -lmass -lessl -qsmp
+build.prop{fc.flags-ld}[pp/model_processing/format.f90] = -L/projects/um1/gcom/gcom3.2/meto_ibm_pwr6_serial/lib -lgcom
+build.prop{fc.flags}                  = -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+build.prop{fc.flags}[rst ver ops]     = -qarch=pwr6 -qtune=pwr6 -O0 -qfullpath -qextname -qrealsize=8 -qintsize=8
+build.prop{fc.flags}[gen]             = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qrealsize=8 -qintsize=8
+build.prop{fc.flags}[gen/UM_COEX]     = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qrealsize=8 -qintsize=8 -qfixed=132
+build.prop{fc.flags}[gen/UM_General]  = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qrealsize=8 -qintsize=8 -qfixed=132
+build.prop{fc.flags}[gen/UM_Platform] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qrealsize=8 -qintsize=8 -qfixed=132
+build.prop{fc.flags}[gen/UM_Platform/IOERROR.F90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qrealsize=8 -qintsize=8
+build.prop{fc.flags}[pp/get_metdb_obs/get_metdb_obs.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+build.prop{fc.flags}[pp/model_processing/get_um_info.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict
+build.prop{fc.flags}[pp/moses-pdm-rfm/daynumber.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/dlongrad.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/down_rad_calc.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/dsolrad.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/get_row_and_column.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/initialise_routing.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/morangstrom.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/morloc.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/moses_cloud_cover.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/moses_qs_from_t.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/nearest_real.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/nextpoint.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/nimrod_3d_idata_read.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/nimrod_3dextr_comp.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/nimrod_extr_comp.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/nimrod_extr_wind.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/nimrod_hdr_read.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/nimrod_idata_read.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/q_vp_from_t_td.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/read_ancil.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/regrid_real.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/route_runoff.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/routing.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/sam.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/ssdm.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/ssdm_var_generator.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/sun_angles.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/um_solpos.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/moses-pdm-rfm/wavespeed.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qstrict -qfixed=132
+build.prop{fc.flags}[pp/precip/get_surface_obs.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/precip/lightning_forecast.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/precip/lightning_merge.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/precip/metar_to_synop_weather.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/precip/read_adv_fc.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/precip/read_rad.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/precip_fcst] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/precip_fcst/accmerge.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+build.prop{fc.flags}[pp/precip_fcst/object_motion.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+build.prop{fc.flags}[pp/precip_fcst/scale.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+build.prop{fc.flags}[pp/precip_fcst/wind_forecast_precip.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+build.prop{fc.flags}[pp/pressure_wind] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/pressure_wind/an_smear.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+build.prop{fc.flags}[pp/pressure_wind/bilin_mdi.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+build.prop{fc.flags}[pp/pressure_wind/convert_winduv.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+build.prop{fc.flags}[pp/pressure_wind/gust_adjust.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+build.prop{fc.flags}[pp/pressure_wind/gust_analysis.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+build.prop{fc.flags}[pp/pressure_wind/pwindanal.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname
+build.prop{fc.flags}[pp/utilities/beammap_ascii_to_nimrod.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/ccitt.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/datetime_c_to_i_secs.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/def_head.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/domain_to_ng.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/european_observations_area.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/get_free_lun.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/icutout.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/ll_to_ng.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/ll_to_ps.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/locate_FCST_string.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/nearest.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/nearest_file.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/ng_to_ll.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/ng_to_ll_array.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/nimrod_i4read.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/nimrod_open2.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/nimrod_open_i4read.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/nimrod_open_i4write.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/nimrod_regrid.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/observations_area_metdb.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/ps_to_ll.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/regrid.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/round_cycle_string.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/subtract_time.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/time_diff.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/time_difference_prog.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/total_accum.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/trim.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/utilities/zpdate.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[pp/verification] = -qarch=pwr6 -qtune=pwr6 -O0 -qextname -qfixed=132
+build.prop{fc.flags}[rst/src] = -qarch=pwr6 -qtune=pwr6 -O0 -qfullpath -qextname -qrealsize=8 -qintsize=8  -qfixed=132
+build.prop{fc.flags}[rst/src/MORST_main.F] = -qarch=pwr6 -qtune=pwr6 -O0 -qfullpath -qextname -qrealsize=8 -qintsize=8 -qfixed=132 -qsmp=omp
+build.prop{fc.flags}[rst/src/ReadFrcData.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qstrict -qfullpath -qextname -qrealsize=8 -qintsize=8
+build.prop{fc.flags}[rst/src/output_status.f90] = -qarch=pwr6 -qtune=pwr6 -O0 -qfullpath -qextname
+build.prop{fc.flags}[rst/src/profile.f] = -qarch=pwr6 -qtune=pwr6 -O0 -qfullpath -qextname -qrealsize=8 -qintsize=8 -qfixed=132 -qsmp=auto
diff --git a/test/repos/trunk/cfg/fcm2_pp_change_blockdata.cfg b/test/repos/trunk/cfg/fcm2_pp_change_blockdata.cfg
new file mode 100644
index 0000000..d3c38ed
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_pp_change_blockdata.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+build.prop{fc.defs}[test_suite/blockdata] = ODD
diff --git a/test/repos/trunk/cfg/fcm2_pp_change_dependency.cfg b/test/repos/trunk/cfg/fcm2_pp_change_dependency.cfg
new file mode 100644
index 0000000..2712963
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_pp_change_dependency.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+preprocess.prop{fpp.defs}[test_suite/program/hello.F90] =
diff --git a/test/repos/trunk/cfg/fcm2_pp_change_include.cfg b/test/repos/trunk/cfg/fcm2_pp_change_include.cfg
new file mode 100644
index 0000000..4f9644c
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_pp_change_include.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/modify_pp_include
diff --git a/test/repos/trunk/cfg/fcm2_pp_change_include_inherit.cfg b/test/repos/trunk/cfg/fcm2_pp_change_include_inherit.cfg
new file mode 100644
index 0000000..a366279
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_pp_change_include_inherit.cfg
@@ -0,0 +1,3 @@
+use = $RUN_DIR/fcm2_base
+
+extract.location{diff}[test_suite] = branches/dev/Share/modify_pp_include
diff --git a/test/repos/trunk/cfg/fcm2_pp_empty_subroutine.cfg b/test/repos/trunk/cfg/fcm2_pp_empty_subroutine.cfg
new file mode 100644
index 0000000..5499b1c
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_pp_empty_subroutine.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+preprocess.prop{fpp.defs}[test_suite/subroutine/hello_sub.F90] =
diff --git a/test/repos/trunk/cfg/fcm2_pp_empty_subroutine_inherit.cfg b/test/repos/trunk/cfg/fcm2_pp_empty_subroutine_inherit.cfg
new file mode 100644
index 0000000..503fa5a
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_pp_empty_subroutine_inherit.cfg
@@ -0,0 +1,3 @@
+use = $RUN_DIR/fcm2_base
+
+preprocess.prop{fpp.defs}[test_suite/subroutine/hello_sub.F90] =
diff --git a/test/repos/trunk/cfg/fcm2_pp_empty_subroutine_inherit_force.cfg b/test/repos/trunk/cfg/fcm2_pp_empty_subroutine_inherit_force.cfg
new file mode 100644
index 0000000..59c2a3c
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_pp_empty_subroutine_inherit_force.cfg
@@ -0,0 +1,5 @@
+use = $RUN_DIR/fcm2_base
+
+preprocess.prop{fpp.defs}[test_suite/subroutine/hello_sub.F90] =
+
+build.prop{fc.flags}[test_suite/program] = -assume nosource_include -O2
diff --git a/test/repos/trunk/cfg/fcm2_revmatch_false.cfg b/test/repos/trunk/cfg/fcm2_revmatch_false.cfg
new file mode 100644
index 0000000..ed17bb0
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_revmatch_false.cfg
@@ -0,0 +1,7 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location[test_suite] = trunk at 21
+extract.location{diff}[test_suite] =          \
+    branches/dev/Share/modify_files_base at 21   \
+    branches/dev/Share/modify_files_merge1 at 21 \
+    branches/dev/Share/modify_files_merge2 at 21
diff --git a/test/repos/trunk/cfg/fcm2_single_file.cfg b/test/repos/trunk/cfg/fcm2_single_file.cfg
new file mode 100644
index 0000000..bb9270c
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_single_file.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+preprocess.prop{fpp.defs}[test_suite/program/hello.F90] = LOCAL_STRING
diff --git a/test/repos/trunk/cfg/fcm2_space_in_name.cfg b/test/repos/trunk/cfg/fcm2_space_in_name.cfg
new file mode 100644
index 0000000..4727f32
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_space_in_name.cfg
@@ -0,0 +1,5 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/space_in_name
+
+build.prop{fc.defs}["test_suite/block data/hello blockdata.F90"] = ODD
diff --git a/test/repos/trunk/cfg/fcm2_sps.cfg b/test/repos/trunk/cfg/fcm2_sps.cfg
new file mode 100644
index 0000000..b6f3bda
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_sps.cfg
@@ -0,0 +1,46 @@
+steps = extract build
+
+extract.ns = gen sps
+extract.location[sps] = trunk at 4964
+extract.path-excl[sps] = doc src/config src/simim \
+                         src/code/SpsMod_ModelIngest \
+                         src/code/SpsProg_ConvertFieldsfile \
+                         src/code/SpsTask_ConvectiveInitiation \
+                         src/code/old \
+                         src/scripts/Sps_System \
+                         src/scripts/old
+
+extract.location[gen] = trunk at 3953
+extract.path-root[gen] = src/code
+extract.path-excl[gen] = /
+extract.path-incl[gen] = GenMod_UMConstants
+
+build.target = SpsScr_Install sps/data/Sps_Fire/.etc sps/data/coeffs/.etc sps/data/palettes/.etc \
+               sps/data/products/.etc sps/data/sad/.etc sps/data/slotstore/.etc
+build.target-rename = h5admin.exe:h5admin h5getatt.exe:h5getatt SpsProg_GetCoords.exe:sps_get_coords
+build.prop{fc} = wrap_fc
+$FOPT = -CB -traceback -u -convert big_endian
+$SPS_LIBDIR = /home/h04/cfsa/SPS/libraries/RHEL6
+build.prop{fc.flags} = $FOPT -I${SPS_LIBDIR}/grib_api/include
+build.prop{fc.flags}[gen/GenMod_UMConstants] = $FOPT -w -132
+build.prop{fc.flags}[sps/src/code/SpsMod_Utilities]              = $FOPT -Duse_f90_unix=''
+build.prop{fc.flags}[sps/src/code/SpsTask_HDFReader]             = $FOPT -auto -assume byterecl
+build.prop{fc.flags}[sps/src/code/SpsProg_ImageGrib]             = $FOPT -auto -assume byterecl
+build.prop{fc.flags}[sps/src/code/SpsProg_GlobalComposite]       = $FOPT -auto -assume byterecl
+build.prop{fc.flags}[sps/src/code/rttov10]                       = $FOPT -openmp
+build.prop{fc.flags}[sps/src/code/rttov10/main/rttov_locpat_k.F90] = $FOPT
+build.prop{cc} = wrap_cc
+build.prop{cc.flags} = -Wall -O2 -DLOWERCASE -I${SPS_LIBDIR}/hdf5/include
+build.prop{cc.flags}[sps/src/code/SpsMod_Image]                  = -Wall -DLOWERCASE -I${SPS_LIBDIR}/hdf5/include
+build.prop{fc.flags-ld} = -openmp -L${SPS_LIBDIR}/hdf5/lib -lhdf5 -lhdf5_hl \
+                        \ -L${SPS_LIBDIR}/bufr_ifort -lbufr \
+                        \ -L${SPS_LIBDIR}/grib_ifort -lgrib_ifort \
+                        \ -L${SPS_LIBDIR}/g2lib -lg2 \
+                        \ -L${SPS_LIBDIR}/grib_api/lib -lgrib_api_f90 -lgrib_api \
+                        \ -L${SPS_LIBDIR}/jasper/lib -ljasper \
+		        \ -L/usr/lib -llapack \
+                        \ -ljpeg -lpng
+build.prop{dep.o}[sps/src/code/SpsMod_Store/SpsMod_Hdf5F90Support.c] = spsmod_storec.o
+build.prop{dep.include}[sps/src/code/SpsMod_Store/Sps_H5Fopen_auto.c] = Sps_H5Fopen_auto.h
+build.prop{no-dep.f.module} = grib_api
+build.prop{file-ext.script} = .ksh .pl .pm .pro
diff --git a/test/repos/trunk/cfg/fcm2_symbolic_link.cfg b/test/repos/trunk/cfg/fcm2_symbolic_link.cfg
new file mode 100644
index 0000000..237620b
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_symbolic_link.cfg
@@ -0,0 +1,3 @@
+include = $HERE/fcm2_base.cfg
+
+extract.location{diff}[test_suite] = branches/dev/Share/symbolic_link
diff --git a/test/repos/trunk/cfg/fcm2_um.cfg b/test/repos/trunk/cfg/fcm2_um.cfg
new file mode 100644
index 0000000..fb4b1b1
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_um.cfg
@@ -0,0 +1,36 @@
+steps = extract preprocess build
+
+extract.ns = um
+extract.location[um] = trunk at vn7.3
+extract.path-root[um] = src
+
+$keys_model = C_LONG_LONG_INT=c_long_long_int MPP=mpp C_LOW_U=c_low_u \
+    \ FRL8=frl8 LINUX=linux BUFRD_IO=bufrd_io LITTLE_END=little_end \
+    \ LINUX_INTEL_COMPILER=linux_intel_compiler CONTROL=control \
+    \ REPROD=reprod ATMOS=atmos GLOBAL=global A04_ALL=a04_all \
+    \ A01_3C=a01_3c A02_3C=a02_3c A03_8C=a03_8c A04_3D=a04_3d A05_4A=a05_4a \
+    \ A06_4A=a06_4a A08_7A=a08_7a A09_2A=a09_2a A10_2A=a10_2a A11_2A=a11_2a \
+    \ A12_2A=a12_2a A13_2A=a13_2a A14_1B=a14_1b A15_1A=a15_1a A16_1A=a16_1a \
+    \ A17_2B=a17_2b A18_0A=a18_0a A19_1A=a19_1a A25_0A=a25_0a A26_0A=a26_0a \
+    \ A30_1A=a30_1a A31_0A=a31_0a A32_1A=a32_1a A33_0A=a33_0a A34_0A=a34_0a \
+    \ A35_0A=a35_0a A38_0A=a38_0a A70_1C=a70_1c A71_1A=a71_1a C70_1A=c70_1a \
+    \ C72_0A=c72_0a C80_1A=c80_1a C82_1A=c82_1a C84_1A=c84_1a C92_2A=c92_2a \
+    \ C94_1A=c94_1a C95_2A=c95_2a C96_1C=c96_1c C97_3A=c97_3a
+preprocess.prop{fpp.defs} = $keys_model
+preprocess.prop{cpp.defs} = $keys_model
+preprocess.prop{fpp} = wrap_pp
+preprocess.prop{cpp} = wrap_mpicc
+preprocess.prop{cpp.flags} = -E
+preprocess.prop{fpp.flags} = -E -P -traditional -I /home/h04/opsrc/ops0/mpi/mpich2-1.4-ukmo-v1/ifort-12/include
+
+build.target  = um.exe
+build.target-rename  = flumeMain.exe:um.exe
+build.prop{cc} = wrap_cc
+build.prop{fc} = wrap_mpif90
+build.prop{fc.flags}      = -i8 -r8 -w -I /home/h01/frum/gcom/gcom4.1/linux_ifort_mpich2/inc -O0
+build.prop{fc.flags-ld}   = -L/home/h01/frum/gcom/gcom4.1/linux_ifort_mpich2/lib -lgcom -Wl,--noinhibit-exec -Vaxlib
+build.prop{dep.o.special} = blkdata.o
+build.prop{ns-dep.o} = um/control/c_code
+build.prop{no-dep.f.module} = netcdf mpl mod_prism_proto mod_prism_grids_writing mod_prism_def_partition_proto mod_prism_put_proto mod_prism_get_proto
+build.prop{no-dep.include}  = netcdf.inc mpif.h
+build.prop{no-dep.bin}[um/script] = *
diff --git a/test/repos/trunk/cfg/fcm2_um77.cfg b/test/repos/trunk/cfg/fcm2_um77.cfg
new file mode 100644
index 0000000..f8342d0
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_um77.cfg
@@ -0,0 +1,74 @@
+step.class[preprocess-recon]          = preprocess
+step.class[build-recon build-scripts] = build
+steps = extract build-scripts preprocess build preprocess-recon build-recon
+
+extract.ns = um
+extract.location[um] = trunk at vn7.7
+extract.path-root[um] = src
+extract.path-excl[um] = configs scm utility
+extract.path-incl[um] = utility/makebc utility/qxreconf
+
+build-scripts.prop{no-dep.bin} = *
+build-scripts.ns-excl = /
+build-scripts.ns-incl = um/script
+build-scripts.target = archfail autopp_tidyup getfile make_parexe.pl nextGenid \
+    OASIS3_ctl OASIS3_conf qscasedisp qscicerun qscicesetup qscombine \
+    qsexecute qsfinal qshistprint qshistreset qsmass qsmaster qsnemorun \
+    qsnemosetup NEMO_nl_ctl qspickup qsresubmit qsserver qssetup restartinfo \
+    submitchk UMScr_TopLevel qsmoose
+
+$keys_model = C_LONG_LONG_INT=c_long_long_int C_LOW_U=c_low_u \
+    \ FRL8=frl8 LINUX=linux BUFRD_IO=bufrd_io LITTLE_END=little_end \
+    \ LFS=lfs _LARGEFILE_SOURCE=_largefile_source _FILE_OFFSET_BITS=64 \
+    \ CONTROL=control ATMOS=atmos GLOBAL=global A04_ALL=a04_all \
+    \ A01_3C=a01_3c A02_3C=a02_3c A03_8C=a03_8c A04_3D=a04_3d A05_4A=a05_4a \
+    \ A06_4A=a06_4a A08_7A=a08_7a A09_2A=a09_2a A10_2A=a10_2a A11_0A=a11_0a \
+    \ A12_2A=a12_2a A13_2A=a13_2a A14_0A=a14_0a A15_1A=a15_1a A16_1A=a16_1a \
+    \ A17_0A=a17_0a A18_2A=a18_2a A19_1A=a19_1a A25_0A=a25_0a A26_0A=a26_0a \
+    \ A30_1A=a30_1a A31_0A=a31_0a A32_1A=a32_1a A33_0A=a33_0a A34_0A=a34_0a \
+    \ A35_0A=a35_0a A36_0A=a36_0a A37_0A=a37_0a A38_0A=a38_0a A39_0A=a39_0a \
+    \ A70_1C=a70_1c A71_1A=a71_1a C70_1A=c70_1a C72_0A=c72_0a C80_1A=c80_1a \
+    \ C82_1A=c82_1a C84_1A=c84_1a C92_2A=c92_2a C94_1A=c94_1a C95_2A=c95_2a \
+    \ C96_1C=c96_1c C97_3A=c97_3a
+preprocess.prop{fpp.defs} = $keys_model
+preprocess.prop{cpp.defs} = $keys_model
+preprocess.prop{fpp} = wrap_pp
+preprocess.prop{cpp} = wrap_mpicc
+preprocess.prop{cpp.flags} = -E
+preprocess.prop{fpp.flags} = -E -P -traditional -I /home/h04/opsrc/ops0/mpi/mpich2-1.4-ukmo-v1/ifort-12/include
+preprocess.ns-excl = um/script um/utility/qxreconf
+
+build.target  = um.exe
+build.target-rename  = flumeMain.exe:um.exe
+build.prop{cc} = wrap_cc
+build.prop{fc} = wrap_mpif90
+build.prop{fc.flags}      = -i8 -r8 -w -I /home/h01/frum/gcom/gcom4.1/linux_ifort_mpich2/inc -O0
+build.prop{fc.flags-ld}   = -L/home/h01/frum/gcom/gcom4.1/linux_ifort_mpich2/lib -lgcom -Vaxlib
+build.prop{dep.o.special} = blkdata.o
+build.prop{ns-dep.o} = um/control/c_code
+build.prop{no-dep.f.module} = mpl
+build.ns-excl = um/script um/utility/qxreconf
+
+$keys_recon = C_LONG_LONG_INT=c_long_long_int C_LOW_U=c_low_u \
+    \ FRL8=frl8 LINUX=linux BUFRD_IO=bufrd_io LITTLE_END=little_end \
+    \ LFS=lfs _LARGEFILE_SOURCE=_largefile_source _FILE_OFFSET_BITS=64 \
+    \ RECON=recon
+preprocess-recon.prop{no-step-source} = preprocess
+preprocess-recon.prop{fpp.defs} = $keys_recon
+preprocess-recon.prop{cpp.defs} = $keys_recon
+preprocess-recon.prop{fpp} = wrap_pp
+preprocess-recon.prop{cpp} = wrap_mpicc
+preprocess-recon.prop{cpp.flags} = -E
+preprocess-recon.prop{fpp.flags} = -E -P -traditional -I /home/h04/opsrc/ops0/mpi/mpich2-1.0.8p1-ukmo-v2/ifort-10/include
+preprocess-recon.ns-excl = um/script um/utility/makebc
+
+build-recon.prop{no-step-source} = preprocess
+build-recon.target        = qxreconf
+build-recon.target-rename = reconfigure.exe:qxreconf
+build-recon.prop{cc} = wrap_cc
+build-recon.prop{fc} = wrap_mpif90
+build-recon.prop{fc.flags}      = -i8 -r8 -w -I /home/h01/frum/gcom/gcom4.1/linux_ifort_mpich2/inc -O0
+build-recon.prop{fc.flags-ld}   = -L/home/h01/frum/gcom/gcom4.1/linux_ifort_mpich2/lib -lgcom -Vaxlib
+build-recon.prop{ns-dep.o} = um/control/c_code
+build-recon.prop{no-dep.f.module} = mpl
+build-recon.ns-excl = um/script um/utility/makebc
diff --git a/test/repos/trunk/cfg/fcm2_um77_hpc.cfg b/test/repos/trunk/cfg/fcm2_um77_hpc.cfg
new file mode 100644
index 0000000..3bc8ffc
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_um77_hpc.cfg
@@ -0,0 +1,89 @@
+step.class[preprocess-recon]          = preprocess
+step.class[build-recon build-scripts] = build
+steps = extract mirror
+
+mirror.target = $HPC:$THIS_RUN_DIR_HPC
+mirror.prop{config-file.steps} =  build-scripts preprocess build preprocess-recon build-recon
+
+extract.ns = um
+extract.location[um] = trunk at vn7.7
+extract.path-root[um] = src
+extract.path-excl[um] = configs scm utility
+extract.path-incl[um] = utility/makebc utility/qxreconf
+
+build-scripts.prop{no-dep.bin} = *
+build-scripts.ns-excl = /
+build-scripts.ns-incl = um/script
+build-scripts.target = archfail autopp_tidyup getfile make_parexe.pl nextGenid \
+    OASIS3_ctl OASIS3_conf qscasedisp qscicerun qscicesetup qscombine \
+    qsexecute qsfinal qshistprint qshistreset qsmass qsmaster qsnemorun \
+    qsnemosetup NEMO_nl_ctl qspickup qsresubmit qsserver qssetup restartinfo \
+    submitchk UMScr_TopLevel qsmoose
+
+$keys_model = C_LONG_INT=c_long_int C_LOW_U=c_low_u FRL8=frl8 \
+    \ BUFRD_IO=bufrd_io VECTLIB=vectlib IBM=ibm C98_1A=c98_1a MO_GRIB=mo_grib \
+    \ CONTROL=control ATMOS=atmos \
+    \ GLOBAL=global A04_ALL=a04_all A01_3C=a01_3c A02_3C=a02_3c \
+    \ A03_8C=a03_8c A04_3D=a04_3d A05_4A=a05_4a A06_4A=a06_4a A08_7A=a08_7a \
+    \ A09_2A=a09_2a A10_2A=a10_2a A11_0A=a11_0a A12_2A=a12_2a A13_2A=a13_2a \
+    \ A14_0A=a14_0a A15_1A=a15_1a A16_1A=a16_1a A17_0A=a17_0a A18_2A=a18_2a \
+    \ A19_1A=a19_1a A25_0A=a25_0a A26_0A=a26_0a A30_1A=a30_1a A31_0A=a31_0a \
+    \ A32_1A=a32_1a A33_0A=a33_0a A34_0A=a34_0a A35_0A=a35_0a A36_0A=a36_0a \
+    \ A37_0A=a37_0a A38_0A=a38_0a A39_0A=a39_0a A70_1C=a70_1c A71_1A=a71_1a \
+    \ C70_1A=c70_1a C72_0A=c72_0a C80_1A=c80_1a \
+    \ C82_1A=c82_1a C84_1A=c84_1a C92_2A=c92_2a C94_1A=c94_1a C95_2A=c95_2a \
+    \ C96_1C=c96_1c C97_3A=c97_3a
+preprocess.prop{fpp.defs} = $keys_model
+preprocess.prop{cpp.defs} = $keys_model
+preprocess.prop{fpp} = cpp
+preprocess.prop{cpp} = xlc
+preprocess.prop{cpp.flags} = -E -C
+preprocess.prop{fpp.flags} = -E -P -traditional
+preprocess.ns-excl = um/script um/utility/qxreconf
+
+build.target  = um.exe
+build.target-rename  = flumeMain.exe:um.exe
+build.prop{cc} = xlc_r
+build.prop{fc} = mpxlf90_r
+$flags_base_model = -qrealsize=8 -qintsize=8 -qextname -qsuffix=f=f90 \
+    \ -qarch=pwr6 -qtune=pwr6 -qxflag=p6div -NS32768 -g -O0 \
+    \ -qstrict -I/projects/um1/gcom/gcom3.6/meto_ibm_pwr6_mpp/inc \
+    \ -I/projects/um1/lib/netcdf3.20090102/include
+build.prop{fc.flags} = $flags_base_model 
+build.prop{fc.flags}[um/io_services] = $flags_base_model -qsmp=omp
+build.prop{fc.flags-ld} = -lmass -lmassvp6 -qsmp=omp \
+    \ -L/projects/um1/gcom/gcom3.6/meto_ibm_pwr6_mpp/lib -lgcom \
+    \ -L/projects/um1/lib/netcdf3.20090102/lib64 -lnetcdf \
+    \ -L/projects/um1/lib -lsig -lgrib
+build.prop{dep.o.special} = blkdata.o
+build.prop{ns-dep.o} = um/control/c_code
+build.prop{dep.o}[um/control/c_code] = print_from_c.o
+build.prop{no-dep.f.module} = mpl
+build.ns-excl = um/script um/utility/qxreconf
+
+$keys_recon = C_LONG_INT=c_long_int C_LOW_U=c_low_u FRL8=frl8 \
+    \ BUFRD_IO=bufrd_io VECTLIB=vectlib IBM=ibm C98_1A=c98_1a MO_GRIB=mo_grib \
+    \ RECON=recon
+preprocess-recon.prop{no-step-source} = preprocess
+preprocess-recon.prop{fpp.defs} = $keys_recon
+preprocess-recon.prop{cpp.defs} = $keys_recon
+preprocess-recon.prop{fpp} = cpp
+preprocess-recon.prop{cpp} = xlc
+preprocess-recon.prop{cpp.flags} = -E -C
+preprocess-recon.prop{fpp.flags} = -E -P -traditional
+preprocess-recon.ns-excl = um/script um/utility/makebc
+
+build-recon.prop{no-step-source} = preprocess
+build-recon.target        = qxreconf
+build-recon.target-rename = reconfigure.exe:qxreconf
+build-recon.prop{cc} = xlc_r
+build-recon.prop{fc} = mpxlf90_r
+build-recon.prop{fc.flags} = $flags_base_model 
+build-recon.prop{fc.flags-ld} = -lmass -lmassvp6 -qsmp=omp \
+    \ -L/projects/um1/gcom/gcom3.6/meto_ibm_pwr6_mpp/lib -lgcom \
+    \ -L/projects/um1/lib/netcdf3.20090102/lib64 -lnetcdf \
+    \ -L/projects/um1/lib -lsig -lgrib
+build-recon.prop{ns-dep.o} = um/control/c_code
+build-recon.prop{dep.o}[um/control/c_code] = print_from_c.o
+build-recon.prop{no-dep.f.module} = mpl
+build-recon.ns-excl = um/script um/utility/makebc
diff --git a/test/repos/trunk/cfg/fcm2_um77_inherit.cfg b/test/repos/trunk/cfg/fcm2_um77_inherit.cfg
new file mode 100644
index 0000000..fabdf74
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_um77_inherit.cfg
@@ -0,0 +1,15 @@
+use = $RUN_DIR/fcm2_um77
+
+extract.location{diff}[um] = \
+    branches/dev/frps/VN7.7_qpos_opt at 28576 \
+    branches/dev/frpe/VN7.7_extra_ts at 28576 \
+    branches/dev/hadco/VN7.7_incrCLO at 25591 \
+    branches/dev/frml/VN7.7_new_pmsl at 26275 \
+    branches/dev/frhi/VN7.7_bugfix at 27020 \
+    branches/dev/frgr/VN7.7_JULES_fixes at 26312 \
+    branches/dev/haddb/VN7.7_JULES_decpl at 27034 \
+    branches/dev/frme/VN7.7_cloud_scheme_bugfixes at 26531 \
+    branches/dev/frwm/VN7.7_simple_conv_diag at 26841 \
+    branches/dev/frjw/VN7.7_mphys_pert_sens at 26510 \
+    branches/dev/frjw/VN7.7_microphys_iterations_plumbing at 27035 \
+    branches/dev/haddb/VN7.7_CoastConvG at 27374
diff --git a/test/repos/trunk/cfg/fcm2_um77_inherit_hpc.cfg b/test/repos/trunk/cfg/fcm2_um77_inherit_hpc.cfg
new file mode 100644
index 0000000..8354b82
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_um77_inherit_hpc.cfg
@@ -0,0 +1,17 @@
+use = $RUN_DIR/fcm2_um77_hpc
+
+extract.location{diff}[um] = \
+    branches/dev/frps/VN7.7_qpos_opt at 28576 \
+    branches/dev/frpe/VN7.7_extra_ts at 28576 \
+    branches/dev/hadco/VN7.7_incrCLO at 25591 \
+    branches/dev/frml/VN7.7_new_pmsl at 26275 \
+    branches/dev/frhi/VN7.7_bugfix at 27020 \
+    branches/dev/frgr/VN7.7_JULES_fixes at 26312 \
+    branches/dev/haddb/VN7.7_JULES_decpl at 27034 \
+    branches/dev/frme/VN7.7_cloud_scheme_bugfixes at 26531 \
+    branches/dev/frwm/VN7.7_simple_conv_diag at 26841 \
+    branches/dev/frjw/VN7.7_mphys_pert_sens at 26510 \
+    branches/dev/frjw/VN7.7_microphys_iterations_plumbing at 27035 \
+    branches/dev/haddb/VN7.7_CoastConvG at 27374
+
+mirror.target = $HPC:$THIS_RUN_DIR_HPC
diff --git a/test/repos/trunk/cfg/fcm2_um_hpc.cfg b/test/repos/trunk/cfg/fcm2_um_hpc.cfg
new file mode 100644
index 0000000..5f98ed6
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_um_hpc.cfg
@@ -0,0 +1,42 @@
+steps = extract mirror
+
+mirror.target = $HPC:$THIS_RUN_DIR_HPC
+mirror.prop{config-file.steps} =  preprocess build
+
+extract.ns = um
+extract.location[um] = trunk at vn7.3
+extract.path-root[um] = src
+
+$keys_model = C_LONG_INT=c_long_int MPP=mpp C_LOW_U=c_low_u \
+    \ FRL8=frl8 BUFRD_IO=bufrd_io VECTLIB=vectlib IBM=ibm CONTROL=control \
+    \ REPROD=reprod MPP=mpp ATMOS=atmos GLOBAL=global A04_ALL=a04_all \
+    \ A01_3C=a01_3c A02_3C=a02_3c A03_8C=a03_8c A04_3D=a04_3d A05_4A=a05_4a \
+    \ A06_4A=a06_4a A08_7A=a08_7a A09_2A=a09_2a A10_2A=a10_2a A11_2A=a11_2a \
+    \ A12_2A=a12_2a A13_2A=a13_2a A14_1B=a14_1b A15_1A=a15_1a A16_1A=a16_1a \
+    \ A17_2B=a17_2b A18_0A=a18_0a A19_1A=a19_1a A25_0A=a25_0a A26_0A=a26_0a \
+    \ A30_1A=a30_1a A31_0A=a31_0a A32_1A=a32_1a A33_0A=a33_0a A34_0A=a34_0a \
+    \ A35_0A=a35_0a A38_0A=a38_0a A70_1C=a70_1c A71_1A=a71_1a C70_1A=c70_1a \
+    \ C72_0A=c72_0a C80_1A=c80_1a C82_1A=c82_1a C84_1A=c84_1a C92_2A=c92_2a \
+    \ C94_1A=c94_1a C95_2A=c95_2a C96_1C=c96_1c C97_3A=c97_3a
+preprocess.prop{fpp.defs} = $keys_model
+preprocess.prop{cpp.defs} = $keys_model
+preprocess.prop{fpp} = cpp
+preprocess.prop{cpp} = xlc
+preprocess.prop{cpp.flags} = -E -C
+preprocess.prop{fpp.flags} = -E -P -traditional
+
+build.target  = um.exe
+build.target-rename  = flumeMain.exe:um.exe
+build.prop{cc} = xlc_r
+build.prop{fc} = mpxlf90_r
+build.prop{fc.flags}      = -I/projects/um1/gcom/gcom3.3/meto_ibm_pwr6_mpp/inc -I/projects/um1/lib/netcdf3.20090102/include -qextname -qsuffix=f=f90 -qarch=pwr6 -qtune=pwr6 -qrealsize=8 -qintsize=8 -NS32768 -O0
+build.prop{fc.flags}[um/atmosphere/dynamics_advection/eta_vert_weights_e.F90] = -qextname -qsuffix=f=f90 -qarch=pwr6 -qtune=pwr6 -qrealsize=8 -qintsize=8 -O0 -NS32768
+build.prop{fc.flags}[um/control/top_level/atm_step.F90]                       = -qextname -qsuffix=f=f90 -qarch=pwr6 -qtune=pwr6 -qrealsize=8 -qintsize=8 -O0 -NS32768
+build.prop{fc.flags}[um/control/top_level/u_model.F90]                        = -qextname -qsuffix=f=f90 -qarch=pwr6 -qtune=pwr6 -qrealsize=8 -qintsize=8 -O0 -NS32768
+build.prop{fc.flags-ld}  = -lmass -lmassvp6 -L/projects/um1/gcom/gcom3.3/meto_ibm_pwr6_mpp/lib -lgcom -L/projects/um1/lib -lgrib -lsig -L/projects/um1/lib/netcdf3.20090102/lib64 -lnetcdf
+build.prop{dep.o.special} = blkdata.o
+build.prop{ns-dep.o} = um/control/c_code
+build.prop{dep.o}[um/control/c_code] = print_from_c.o
+build.prop{no-dep.f.module} = netcdf mpl mod_prism_proto mod_prism_grids_writing mod_prism_def_partition_proto mod_prism_put_proto mod_prism_get_proto
+build.prop{no-dep.include}  = netcdf.inc mpif.h
+build.prop{no-dep.bin}[um/script] = *
diff --git a/test/repos/trunk/cfg/fcm2_um_inherit.cfg b/test/repos/trunk/cfg/fcm2_um_inherit.cfg
new file mode 100644
index 0000000..9ecf20e
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_um_inherit.cfg
@@ -0,0 +1,17 @@
+use = $RUN_DIR/fcm2_um
+
+extract.location{diff}[um] = \
+    branches/dev/Share/VN7.3_hg3_dust_443 at 11858 \
+    branches/dev/Share/VN7.3_hg3_ccw_precip at 11857 \
+    branches/dev/hadco/VN7.3_HG3_porting_lsp_fixes at 12029 \
+    branches/dev/hadco/VN7.3_pc2_qcl_gt_tiny at 12142 \
+    branches/dev/hadas/VN7.3_w_CAPE_diag at 12012 \
+    branches/dev/frlk/VN7.3_BLLEVS_fixes at 13938 \
+    branches/dev/frwm/VN7.3_Cu_Diag_Low_LCL at 12011 \
+    branches/dev/frwm/VN7.3_LimitCnvParcPert at 12041 \
+    branches/dev/hadip/VN7.3_ilp_moose at 13314 \
+    branches/dev/hadco/VN7.3_temp_fix_solver at 12740 \
+    branches/dev/hadng/VN7.3_wetlands_rothc at 12603 \
+    branches/dev/frtg/VN7.3_reconf_extern_ancil at 13063 \
+    branches/dev/frma/VN7.3_rad_dev at 12732 \
+    branches/dev/frlk/VN7.3_melt_fix at 13822
diff --git a/test/repos/trunk/cfg/fcm2_um_inherit_hpc.cfg b/test/repos/trunk/cfg/fcm2_um_inherit_hpc.cfg
new file mode 100644
index 0000000..6a0e5fe
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_um_inherit_hpc.cfg
@@ -0,0 +1,19 @@
+use = $RUN_DIR/fcm2_um_hpc
+
+extract.location{diff}[um] = \
+    branches/dev/Share/VN7.3_hg3_dust_443 at 11858 \
+    branches/dev/Share/VN7.3_hg3_ccw_precip at 11857 \
+    branches/dev/hadco/VN7.3_HG3_porting_lsp_fixes at 12029 \
+    branches/dev/hadco/VN7.3_pc2_qcl_gt_tiny at 12142 \
+    branches/dev/hadas/VN7.3_w_CAPE_diag at 12012 \
+    branches/dev/frlk/VN7.3_BLLEVS_fixes at 13938 \
+    branches/dev/frwm/VN7.3_Cu_Diag_Low_LCL at 12011 \
+    branches/dev/frwm/VN7.3_LimitCnvParcPert at 12041 \
+    branches/dev/hadip/VN7.3_ilp_moose at 13314 \
+    branches/dev/hadco/VN7.3_temp_fix_solver at 12740 \
+    branches/dev/hadng/VN7.3_wetlands_rothc at 12603 \
+    branches/dev/frtg/VN7.3_reconf_extern_ancil at 13063 \
+    branches/dev/frma/VN7.3_rad_dev at 12732 \
+    branches/dev/frlk/VN7.3_melt_fix at 13822
+
+mirror.target = $HPC:$THIS_RUN_DIR_HPC
diff --git a/test/repos/trunk/cfg/fcm2_var.cfg b/test/repos/trunk/cfg/fcm2_var.cfg
new file mode 100644
index 0000000..21b0fa4
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_var.cfg
@@ -0,0 +1,80 @@
+steps = extract preprocess build
+
+extract.ns = gcom gen ops var var_admin
+extract.location[var] = trunk at 14844
+extract.path-root[var] = src
+extract.path-excl[var] = config scripts
+extract.path-incl[var] = scripts/Var_Scripts
+
+extract.location[ops] = trunk at 18341
+extract.path-root[ops] = src/code
+extract.path-excl[ops] = /
+extract.path-incl[ops] = OpsMod_Constants \
+                       \ OpsMod_Control OpsMod_GeoIR \
+                       \ OpsMod_ObsInfo \
+                       \ OpsMod_RTTOV \
+                       \ OpsMod_Sort \
+                       \ OpsMod_Utilities \
+                       \ OpsMod_Varobs \
+                       \ OpsMod_VerticalInterp \
+                       \ OpsMod_VisControl \
+                       \ OpsProg_RTTOV9 \
+                       \ Ops_AIRS_1DVar \
+                       \ Ops_AIRS_Utilities \
+                       \ Ops_RTTOV7 \
+                       \ Ops_RTTOV7_RTTOVCLD \
+                       \ Ops_RTTOV9 \
+                       \ Ops_SatRad_Info \
+                       \ Ops_SatRad_Process \
+                       \ Ops_SatRad_SetUp \
+                       \ Ops_SatRad_Utilities
+
+extract.location[gen] = trunk at 3073
+extract.path-root[gen] = src/code
+
+extract.location[var_admin] = trunk at 14851
+extract.path-root[var_admin] = src/code
+
+extract.location[gcom] = branches/dev/ibmjb/r12957_2194_ralltoalle_out_of_order at 15824
+extract.path-root[gcom] = build
+extract.path-excl[gcom] = configs ext_scripts
+
+preprocess.ns-excl = var ops gen var_admin
+preprocess.ns-incl = var/code/PF_MPP \
+                   \ ops/Ops_RTTOV9/rttov9_parallel_ad.F90 \
+                   \ ops/Ops_RTTOV9/rttov9_parallel_direct.F90 \
+                   \ ops/Ops_RTTOV9/rttov9_parallel_k.F90 \
+                   \ ops/Ops_RTTOV9/rttov9_parallel_tl.F90
+preprocess.prop{fpp} = wrap_pp
+preprocess.prop{fpp.defs} = IFORT_CDIRS
+preprocess.prop{fpp.defs}[gcom] = GC_VERSION="'3.4+'" GC_BUILD_DATE="'15824'" PREC_64B GC__FLUSHUNIT6 GC__FORTERRUNIT=0 GC_DESCRIP="'MPP'" MPI_SRC MPILIB_32B
+preprocess.prop{fpp.defs}[ops/Ops_RTTOV9/rttov9_parallel_ad.F90]     = _RTTOV_PARALLEL_AD
+preprocess.prop{fpp.defs}[ops/Ops_RTTOV9/rttov9_parallel_direct.F90] = _RTTOV_PARALLEL_DIRECT
+preprocess.prop{fpp.defs}[ops/Ops_RTTOV9/rttov9_parallel_k.F90]      = _RTTOV_PARALLEL_K
+preprocess.prop{fpp.defs}[ops/Ops_RTTOV9/rttov9_parallel_tl.F90]     = _RTTOV_PARALLEL_TL
+
+$OPSDIR{?} = /home/h04/opsrc/ops0
+$mpich2 = $OPSDIR/mpi/mpich2-1.4-ukmo-v1/ifort-12
+build.target = VarScr_HelpCompile
+build.prop{cc} = wrap_cc
+build.prop{cc.defs}[gen/GenMod_Platform] = LOWERCASE UNDERSCORE FRL8 C_LONG_LONG_INT
+build.prop{cc.defs}[gen/UM_Platform]     = VAROPSVER C_LOW_U FRL8 C_LONG_LONG_INT LINUX LITTLE_END
+build.prop{fc} = wrap_fc
+build.prop{fc.flags}                   = -implicitnone -integer_size 64 -real_size 64 -ftrapuv
+build.prop{fc.flags}[gcom]             = -implicitnone -integer_size 64 -real_size 64 -ftrapuv -warn none
+build.prop{fc.flags}[gcom/mpl/mpl.F90] = -implicitnone -integer_size 64 -real_size 64 -ftrapuv -warn none -I$mpich2/include
+build.prop{fc.flags}[gen]              = -implicitnone -integer_size 64 -real_size 64 -ftrapuv -warn noerrors
+build.prop{fc.flags}[ops/Ops_RTTOV9]   = -implicitnone -integer_size 64 -real_size 64 -ftrapuv -warn none
+build.prop{fc.flags}[var_admin]        = -implicitnone -integer_size 64 -real_size 64 -ftrapuv -warn none
+build.prop{fc.flags}[var/code/PF_Interpolation/Cubic_Lagrange_Adj.F90] = -implicitnone -integer_size 64 -real_size 64 -ftrapuv -Wp,-P
+build.prop{fc.flags}[var/code/PF_MPP] = -implicitnone -integer_size 64 -real_size 64 -ftrapuv -warn noerrors
+build.prop{fc.flags}[var/code/VarProg_UMFileUtils] = -implicitnone -integer_size 64 -real_size 64
+build.prop{fc.defs}[var ops gen var_admin] = IFORT_CDIRS
+build.prop{fc.defs}[var/code/PF_MPP] =
+build.prop{fc.defs}[gen/GenMod_Control] = GCOMHEADERS
+build.prop{fc.defs}[gen/GenMod_Utilities/Gen_FlushUnit.F90] = USE_FLUSH
+build.prop{fc.defs}[gen/UM_COEX gen/UM_Platform] = VAROPSVER
+build.prop{fc.flags-ld} = -L$mpich2/lib -lmpich -lmpl -lpthread
+build.prop{ns-dep.o}[var] = gcom var_admin/VarMod_Lapack var_admin/VarMod_Blas
+build.prop{no-dep.f.module} = f90_unix_io xlfutility
+build.prop{no-dep.include}  = mpif.h
diff --git a/test/repos/trunk/cfg/fcm2_var_hpc.cfg b/test/repos/trunk/cfg/fcm2_var_hpc.cfg
new file mode 100644
index 0000000..8148207
--- /dev/null
+++ b/test/repos/trunk/cfg/fcm2_var_hpc.cfg
@@ -0,0 +1,87 @@
+steps = extract mirror
+
+mirror.target = $HPC:$THIS_RUN_DIR_HPC
+mirror.prop{config-file.steps} =  preprocess build
+
+extract.ns = gcom gen ops var var_admin
+extract.location[var] = trunk at 14844
+extract.path-root[var] = src
+extract.path-excl[var] = config scripts
+extract.path-incl[var] = scripts/Var_Scripts
+
+extract.location[ops] = trunk at 18341
+extract.path-root[ops] = src/code
+extract.path-excl[ops] = /
+extract.path-incl[ops] = OpsMod_Constants \
+                       \ OpsMod_Control OpsMod_GeoIR \
+                       \ OpsMod_ObsInfo \
+                       \ OpsMod_RTTOV \
+                       \ OpsMod_Sort \
+                       \ OpsMod_Utilities \
+                       \ OpsMod_Varobs \
+                       \ OpsMod_VerticalInterp \
+                       \ OpsMod_VisControl \
+                       \ OpsProg_RTTOV9 \
+                       \ Ops_AIRS_1DVar \
+                       \ Ops_AIRS_Utilities \
+                       \ Ops_RTTOV7 \
+                       \ Ops_RTTOV7_RTTOVCLD \
+                       \ Ops_RTTOV9 \
+                       \ Ops_SatRad_Info \
+                       \ Ops_SatRad_Process \
+                       \ Ops_SatRad_SetUp \
+                       \ Ops_SatRad_Utilities
+
+extract.location[gen] = trunk at 3073
+extract.path-root[gen] = src/code
+
+extract.location[var_admin] = trunk at 14851
+extract.path-root[var_admin] = src/code
+
+extract.location[gcom] = branches/dev/ibmjb/r12957_2194_ralltoalle_out_of_order at 15824
+extract.path-root[gcom] = build
+extract.path-excl[gcom] = configs ext_scripts
+
+preprocess.ns-excl = var ops gen var_admin
+preprocess.ns-incl = ops/Ops_RTTOV9/rttov9_parallel_ad.F90 \
+                   \ ops/Ops_RTTOV9/rttov9_parallel_direct.F90 \
+                   \ ops/Ops_RTTOV9/rttov9_parallel_k.F90 \
+                   \ ops/Ops_RTTOV9/rttov9_parallel_tl.F90
+preprocess.prop{cpp.flags} = -C
+preprocess.prop{cpp.defs} = LOWERCASE
+preprocess.prop{fpp.defs}[gcom] = GC_VERSION="'3.4+'" GC_BUILD_DATE="'15824'" PREC_64B GC__FLUSHUNIT6 GC__FORTERRUNIT=0 MPI_SRC MPILIB_32B GC_DESCRIP="'MPP'" MPI_BSEND_BUFFER_SIZE=10240000 IBM
+preprocess.prop{fpp.defs}[ops/Ops_RTTOV9/rttov9_parallel_ad.F90]     = _RTTOV_PARALLEL_AD
+preprocess.prop{fpp.defs}[ops/Ops_RTTOV9/rttov9_parallel_direct.F90] = _RTTOV_PARALLEL_DIRECT
+preprocess.prop{fpp.defs}[ops/Ops_RTTOV9/rttov9_parallel_k.F90]      = _RTTOV_PARALLEL_K
+preprocess.prop{fpp.defs}[ops/Ops_RTTOV9/rttov9_parallel_tl.F90]     = _RTTOV_PARALLEL_TL
+
+build.target = VarScr_HelpCompile
+build.prop{cc} = xlc
+build.prop{cc.defs}[gen/GenMod_Platform] = LOWERCASE FRL8 C_LONG_INT
+build.prop{cc.defs}[gen/UM_Platform]     = VAROPSVER C_LOW FRL8 C_LONG_INT
+build.prop{fc} = mpxlf95_r
+build.prop{fc.flags}                       = -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1
+build.prop{fc.flags}[var/code/PFMod_Model] = -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -NS1024
+build.prop{fc.flags}[gcom/gc]              = -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed
+build.prop{fc.flags}[gcom/gcg]             = -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed
+build.prop{fc.flags}[gen/GenMod_Reporting/GenMod_Reporting.F90] = -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -WF,-qfpp
+build.prop{fc.flags}[gen/GenMod_Utilities/Gen_FlushUnit.F90]    = -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -WF,-qfpp
+build.prop{fc.flags}[gen/UM_COEX]                     = -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed -WF,-qfpp
+build.prop{fc.flags}[gen/UM_General]                  = -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed
+build.prop{fc.flags}[gen/UM_Platform]                 = -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed -WF,-qfpp
+build.prop{fc.flags}[gen/UM_Platform/IOERROR.F90]     = -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed -WF,-qfpp -qfree=f90
+build.prop{fc.flags}[var/code/VarMod_CovVertical]     = -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qnoessl
+build.prop{fc.flags}[var/code/VarMod_CovVerticalData] = -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qnoessl
+build.prop{fc.flags}[var_admin]                       = -O0 -qrealsize=8 -qintsize=8 -g -qfullpath -qessl -qarch=pwr6 -qtune=pwr6 -qmaxmem=-1 -qfixed -qrealsize=4 -qstrict
+build.prop{fc.flag-define} = -WF,-D%s
+build.prop{fc.defs}[gen/GenMod_Control]                        = GCOMHEADERS
+build.prop{fc.defs}[gen/GenMod_Control/Gen_SetupControl.F90]   = USE_CUSTOM_SIGNAL_HANDLER AIX
+build.prop{fc.defs}[gen/GenMod_Reporting/GenMod_Reporting.F90] = GEN_LEN_ERROR_OUT=134
+build.prop{fc.defs}[gen/GenMod_Utilities/Gen_FlushUnit.F90]    = USE_FLUSH AIX
+build.prop{fc.defs}[gen/UM_COEX gen/UM_Platform]               = VAROPSVER
+build.prop{fc.defs}[ops/Ops_RTTOV9]                            = RTTOV_ARCH_VECTOR
+build.prop{fc.defs}[ops/Ops_RTTOV9/rttov9_parkind1.F90]        = DEFAULT_INTEGER_32BIT
+build.prop{fc.flags-ld} = -L/projects/um1/lib -lsig -lessl -lmassvp6 -lmass
+build.prop{ns-dep.o}[var] = gcom var_admin/VarMod_Lapack var_admin/VarMod_Blas
+build.prop{no-dep.f.module} = f90_unix_io xlfutility
+build.prop{no-dep.include}  = mpif.h
diff --git a/test/repos/trunk/module/hello_constants.f90 b/test/repos/trunk/module/hello_constants.f90
new file mode 100644
index 0000000..b8237b9
--- /dev/null
+++ b/test/repos/trunk/module/hello_constants.f90
@@ -0,0 +1,5 @@
+MODULE Hello_Constants
+
+INCLUDE 'hello_constants_dummy.inc'
+
+END MODULE Hello_Constants
diff --git a/test/repos/trunk/module/hello_constants.inc b/test/repos/trunk/module/hello_constants.inc
new file mode 100644
index 0000000..ae26a9b
--- /dev/null
+++ b/test/repos/trunk/module/hello_constants.inc
@@ -0,0 +1 @@
+CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Earth!'
diff --git a/test/repos/trunk/module/hello_constants_dummy.inc b/test/repos/trunk/module/hello_constants_dummy.inc
new file mode 100644
index 0000000..06f117b
--- /dev/null
+++ b/test/repos/trunk/module/hello_constants_dummy.inc
@@ -0,0 +1 @@
+INCLUDE 'hello_constants.inc'
diff --git a/test/repos/trunk/namelist/namelist.NL b/test/repos/trunk/namelist/namelist.NL
new file mode 100644
index 0000000..8210d58
--- /dev/null
+++ b/test/repos/trunk/namelist/namelist.NL
@@ -0,0 +1,3 @@
+&TestNL
+  test = ,
+/
diff --git a/test/repos/trunk/pro/hello.pro b/test/repos/trunk/pro/hello.pro
new file mode 100644
index 0000000..bc880e8
--- /dev/null
+++ b/test/repos/trunk/pro/hello.pro
@@ -0,0 +1,2 @@
+PRO HELLO
+END
diff --git a/test/repos/trunk/pro/plot.pro b/test/repos/trunk/pro/plot.pro
new file mode 100644
index 0000000..5896f2b
--- /dev/null
+++ b/test/repos/trunk/pro/plot.pro
@@ -0,0 +1,3 @@
+PRO PLOT
+;  Calls    : hello.pro
+END
diff --git a/test/repos/trunk/program/hello.F90 b/test/repos/trunk/program/hello.F90
new file mode 100644
index 0000000..87f1a31
--- /dev/null
+++ b/test/repos/trunk/program/hello.F90
@@ -0,0 +1,26 @@
+PROGRAM Hello
+
+#if !defined(LOCAL_STRING)
+USE Hello_Constants, ONLY: hello_string
+#endif
+
+IMPLICIT NONE
+
+#if defined(LOCAL_STRING)
+CHARACTER (LEN=80), PARAMETER :: hello_string = 'Hello Mother Earth!'
+#endif
+
+INTEGER :: integer_arg = 1234
+
+#if defined(CALL_HELLO_SUB)
+INCLUDE 'hello_sub.interface'
+#endif
+
+CHARACTER (LEN=*), PARAMETER :: this = 'Hello'
+
+WRITE (*, '(A)') this // ': ' // TRIM (hello_string)
+#if defined(CALL_HELLO_SUB)
+CALL Hello_Sub (integer_arg)
+#endif
+
+END PROGRAM Hello
diff --git a/test/repos/trunk/script/hello.sh b/test/repos/trunk/script/hello.sh
new file mode 100755
index 0000000..3574568
--- /dev/null
+++ b/test/repos/trunk/script/hello.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/ksh
+# Calls : hello.exe
+# Calls : plot.pro
+
+hello.exe
diff --git a/test/repos/trunk/subroutine/hello_c.c b/test/repos/trunk/subroutine/hello_c.c
new file mode 100644
index 0000000..45ca182
--- /dev/null
+++ b/test/repos/trunk/subroutine/hello_c.c
@@ -0,0 +1,5 @@
+#include <stdio.h>
+
+void hello_c_ () {
+  printf ("%s\n", "Hello_C: Hello Earth!");
+}
diff --git a/test/repos/trunk/subroutine/hello_sub.F90 b/test/repos/trunk/subroutine/hello_sub.F90
new file mode 100644
index 0000000..b7d7da6
--- /dev/null
+++ b/test/repos/trunk/subroutine/hello_sub.F90
@@ -0,0 +1,24 @@
+#if defined(HELLO_SUB)
+SUBROUTINE Hello_Sub (integer_arg)
+
+USE Hello_Constants, ONLY: hello_string
+
+IMPLICIT NONE
+
+CHARACTER (LEN=*), PARAMETER :: this = 'Hello_Sub'
+INTEGER :: integer_arg
+INTEGER :: integer_common
+COMMON /general/integer_common
+
+! DEPENDS ON: hello_c.o
+EXTERNAL Hello_C
+
+#include "hello_sub_dummy.h"
+
+WRITE (*, '(A,I0)') this // ': integer (arg): ', integer_arg
+WRITE (*, '(A,I0)') this // ': integer (common): ', integer_common
+
+CALL Hello_C ()
+
+END SUBROUTINE Hello_Sub
+#endif
diff --git a/test/repos/trunk/subroutine/hello_sub.h b/test/repos/trunk/subroutine/hello_sub.h
new file mode 100644
index 0000000..36fd211
--- /dev/null
+++ b/test/repos/trunk/subroutine/hello_sub.h
@@ -0,0 +1 @@
+WRITE (*, '(A)') this // ': ' // TRIM (hello_string)
diff --git a/test/repos/trunk/subroutine/hello_sub_dummy.h b/test/repos/trunk/subroutine/hello_sub_dummy.h
new file mode 100644
index 0000000..591744b
--- /dev/null
+++ b/test/repos/trunk/subroutine/hello_sub_dummy.h
@@ -0,0 +1 @@
+#include "hello_sub.h"
diff --git a/test/run_tests b/test/run_tests
new file mode 100755
index 0000000..60cbbcc
--- /dev/null
+++ b/test/run_tests
@@ -0,0 +1,259 @@
+#!/bin/ksh
+set -u
+trap "echo Received signal ERR; exit 1"  ERR
+trap "echo Received signal TERM; exit 1" TERM
+
+export MY_BIN=$(cd $(dirname $0) && pwd)
+PATH=$MY_BIN:$PATH
+
+export DEBUG=false
+FCM1=true
+FCM2=true
+while getopts ":c:t:flradgh12" opt
+do
+  case $opt in
+    c ) CONTROL_URL=$OPTARG ;;
+    t ) TEST_URL=$OPTARG ;;
+    f ) FUNC_TESTS=true ;;
+    l ) LOCAL_TESTS=true ;;
+    r ) REMOTE_TESTS=true ;;
+    a ) FUNC_TESTS=true
+        LOCAL_TESTS=true
+        REMOTE_TESTS=true ;;
+    d ) DELETE=true ;;
+    g ) DEBUG=true ;;
+    h ) HELP=true ;;
+    1 ) FCM2=false ;;
+    2 ) FCM1=false ;;
+    \? ) echo "Invalid option"
+        HELP=true
+        break ;;
+  esac
+done
+if [[ $# != $(($OPTIND - 1)) ]]; then
+  echo "Invalid argument"
+  HELP=true
+fi
+
+if [[ ${HELP:-} == true ]]; then
+ echo 'Usage: run_tests [options]'
+ echo 'Valid options:'
+ echo '-c URL'
+ echo '   Generate the control results using the version of FCM at "URL"'
+ echo '-t URL'
+ echo '   Generate the test results using the version of FCM at "URL"'
+ echo '-f'
+ echo '   Perform the functional tests'
+ echo '-l'
+ echo '   Perform the local performance tests'
+ echo '-r'
+ echo '   Perform the remote performance tests'
+ echo '-a'
+ echo '   Perform all the tests (equivalent to -flr)'
+ echo '-d'
+ echo '   Remove any previous test results before starting'
+ echo '-g'
+ echo '   Output additional diagnostics'
+ echo '-h'
+ echo '   Print this help message'
+ echo '-1'
+ echo '   Only run the FCM1 tests'
+ echo '-2'
+ echo '   Only run the FCM2 tests'
+ exit 1
+fi
+
+if [[ -n "${CONTROL_URL:-}" ]]; then
+  TYPES="control"
+fi
+if [[ -n "${TEST_URL:-}" ]]; then
+  TYPES="${TYPES:-} test"
+fi
+if [[ -z "${TYPES:-}" ]]; then
+  echo "Either a control or a test URL must be specified"
+  exit 1
+fi
+
+if [[ ${REMOTE_TESTS:-} == true ]]; then
+  export HPC=$(rose host-select -q hpc)
+  export BASE_DIR_HPC=$(ssh $HPC 'echo $PWD')/working/fcm_test_suite
+fi
+
+export BASE_DIR=$LOCALTEMP/fcm_test_suite
+if [[ ${DELETE:-} == true ]]; then
+  if $DEBUG; then
+    echo "Removing any previous test directory ..."
+  fi
+  rm -rf $BASE_DIR
+  if [[ ${REMOTE_TESTS:-} == true ]]; then
+    ssh $HPC "rm -rf $BASE_DIR_HPC"
+  fi
+fi
+mkdir -p $BASE_DIR
+
+export REPOS_DIR=$BASE_DIR/test_svn
+export REPOS_URL="file://$REPOS_DIR"
+if [[ ! -d $REPOS_DIR ]]; then
+  echo "$(date): Creating test repository ..."
+  $MY_BIN/create_repos > $BASE_DIR/repos.stdout 2> $BASE_DIR/repos.stderr
+fi
+
+cp $MY_BIN/compare_*_fcm* $BASE_DIR
+PATH_BASE=$MY_BIN/wrapper_scripts:$PATH:~opsrc/ops0/mpi/mpich2-1.4-ukmo-v1/ifort-12/bin
+
+trap ""  ERR
+export TEST
+export TYPE
+for TYPE in $TYPES
+do
+  if [[ $TYPE == test ]]; then
+    URL=$TEST_URL
+  else
+    URL=$CONTROL_URL
+  fi
+  REV=$(git describe $URL) || exit $RC
+  echo "FCM version to be used: $REV"
+
+  export RUN_DIR=$BASE_DIR/$TYPE
+  rm -rf $RUN_DIR
+  mkdir $RUN_DIR
+
+  if $DEBUG; then
+    echo "Creating local copy of FCM ..."
+  fi
+  (cd $MY_BIN/.. && git archive --format=tar --prefix=fcm/ $REV) | (cd $RUN_DIR && tar xf -)
+  if [[ -a $RUN_DIR/fcm/etc/fcm.cfg ]]; then
+    echo "set::url::test_suite $REPOS_URL" >>$RUN_DIR/fcm/etc/fcm.cfg
+  else
+    echo "location{primary}[test_suite] = $REPOS_URL" >>$RUN_DIR/fcm/etc/fcm/keyword.cfg
+  fi
+  export PATH=$RUN_DIR/fcm/bin:$PATH_BASE
+
+  if [[ ${FUNC_TESTS:-} == true ]]; then
+    . $MY_BIN/tests_functional.list
+    export COMPARE_TIMES=false
+    let failed=0
+    if [[ $FCM1 == true ]]; then
+      for TEST in $TESTS_FCM1
+      do
+        $MY_BIN/perform_test_fcm1
+        if [[ $? != 0 ]]; then
+          let failed=failed+1
+        fi
+      done
+    fi
+    if [[ $FCM2 == true ]]; then
+      for TEST in $TESTS_FCM2
+      do
+        $MY_BIN/perform_test_fcm2
+        if [[ $? != 0 ]]; then
+          let failed=failed+1
+        fi
+      done
+    fi
+
+    echo "$(date): Functional tests finished"
+    if [[ $failed == 0 ]]; then
+      echo "SUMMARY: All functional tests succeeded"
+    else
+      echo "SUMMARY: $failed functional tests failed"
+    fi
+  fi
+
+  if [[ ${LOCAL_TESTS:-} == true ]]; then
+    . $MY_BIN/tests_perf_local.list
+    export COMPARE_TIMES=true
+    export run_1=no
+    export run_2=no
+    let failed=0
+    if [[ $FCM1 == true ]]; then
+      for TEST in $TESTS_FCM1
+      do
+        $MY_BIN/perform_test_fcm1
+        if [[ $? != 0 ]]; then
+          let failed=failed+1
+        fi
+      done
+    fi
+    if [[ $FCM2 == true ]]; then
+      for TEST in $TESTS_FCM2
+      do
+        $MY_BIN/perform_test_fcm2
+        if [[ $? != 0 ]]; then
+          let failed=failed+1
+        fi
+      done
+    fi
+    unset run_1 run_2
+
+    echo "$(date): Local performance tests finished"
+    if [[ $failed == 0 ]]; then
+      echo "SUMMARY: All local performance tests succeeded"
+    else
+      echo "SUMMARY: $failed local performance tests failed"
+    fi
+  fi
+
+  if [[ ${REMOTE_TESTS:-} == true ]]; then
+    if $DEBUG; then
+      echo "Copying files to HPC platform ..."
+    fi
+    export RUN_DIR_HPC=$BASE_DIR_HPC/$TYPE
+    ssh $HPC "rm -rf $RUN_DIR_HPC"
+    ssh $HPC "mkdir -p $RUN_DIR_HPC"
+    rsync -a --rsh="ssh" $RUN_DIR/fcm $HPC:$RUN_DIR_HPC
+    rsync -a --rsh="ssh" compare_*_fcm* report_hpc_results $HPC:$BASE_DIR_HPC
+
+    BATCH_SCRIPT_NAME=hpc_batch.sh
+    export BATCH_DIRS_NAME=hpc_dirs.sh
+    export BATCH_SCRIPT=$RUN_DIR/$BATCH_SCRIPT_NAME
+    $MY_BIN/create_hpc_batch_script
+    export BATCH_DIRS=$RUN_DIR/$BATCH_DIRS_NAME
+
+    . $MY_BIN/tests_perf_remote.list
+    export COMPARE_TIMES=true
+    export mirror=remote
+    let failed=0
+    echo 'TESTS_FCM1="' >$BATCH_DIRS
+    if [[ $FCM1 == true ]]; then
+      for TEST in $TESTS_FCM1
+      do
+        $MY_BIN/perform_test_fcm1
+        if [[ $? != 0 ]]; then
+          let failed=failed+1
+        else
+          SUBMIT_REMOTE=true
+        fi
+      done
+    fi
+    echo '"' >>$BATCH_DIRS
+    echo 'TESTS_FCM2="' >>$BATCH_DIRS
+    if [[ $FCM2 == true ]]; then
+      export NPROC=6
+      for TEST in $TESTS_FCM2
+      do
+        $MY_BIN/perform_test_fcm2
+        if [[ $? != 0 ]]; then
+          let failed=failed+1
+        else
+          SUBMIT_REMOTE=true
+        fi
+      done
+    fi
+    echo '"' >>$BATCH_DIRS
+    unset mirror NPROC
+
+    if [[ ${SUBMIT_REMOTE:-} == true ]]; then
+      echo "$(date): Submitting HPC build job ..."
+      rsync -a --rsh="ssh" $BATCH_SCRIPT $BATCH_DIRS $HPC:$RUN_DIR_HPC
+      ssh $HPC "llsubmit $RUN_DIR_HPC/$BATCH_SCRIPT_NAME"
+    fi
+
+    echo "$(date): HPC performance tests finished"
+    if [[ $failed == 0 ]]; then
+      echo "SUMMARY: All HPC performance tests succeeded"
+    else
+      echo "SUMMARY: $failed HPC performance tests failed"
+    fi
+  fi
+done
diff --git a/test/test_config/fcm1_add_directory b/test/test_config/fcm1_add_directory
new file mode 100755
index 0000000..06b4090
--- /dev/null
+++ b/test/test_config/fcm1_add_directory
@@ -0,0 +1,3 @@
+build_1=fail_known
+
+# This case fails since the extract system does not detect the new directory.
diff --git a/test/test_config/fcm1_add_directory_expsrc b/test/test_config/fcm1_add_directory_expsrc
new file mode 100755
index 0000000..64b9edb
--- /dev/null
+++ b/test/test_config/fcm1_add_directory_expsrc
@@ -0,0 +1,2 @@
+# Although this case works, the output from the extract system does not correctly
+# identify the file as added from branch1 (unlike in fcm1_add_file).
diff --git a/test/test_config/fcm1_branches_clash b/test/test_config/fcm1_branches_clash
new file mode 100755
index 0000000..c00e841
--- /dev/null
+++ b/test/test_config/fcm1_branches_clash
@@ -0,0 +1 @@
+extract_1=fail
diff --git a/test/test_config/fcm1_branches_merge_conflict_fail b/test/test_config/fcm1_branches_merge_conflict_fail
new file mode 100755
index 0000000..c00e841
--- /dev/null
+++ b/test/test_config/fcm1_branches_merge_conflict_fail
@@ -0,0 +1 @@
+extract_1=fail
diff --git a/test/test_config/fcm1_branches_merge_incremental b/test/test_config/fcm1_branches_merge_incremental
new file mode 100755
index 0000000..0cb29ab
--- /dev/null
+++ b/test/test_config/fcm1_branches_merge_incremental
@@ -0,0 +1 @@
+cfg_name="fcm1_base fcm1_branches_merge"
diff --git a/test/test_config/fcm1_branches_merge_inherit_wrong_include b/test/test_config/fcm1_branches_merge_inherit_wrong_include
new file mode 100755
index 0000000..e5257e8
--- /dev/null
+++ b/test/test_config/fcm1_branches_merge_inherit_wrong_include
@@ -0,0 +1,2 @@
+# The wrong include file gets used as demonstrated by the output from running the program.
+# See ticket:206
diff --git a/test/test_config/fcm1_branches_merge_wcopies b/test/test_config/fcm1_branches_merge_wcopies
new file mode 100755
index 0000000..92f75b3
--- /dev/null
+++ b/test/test_config/fcm1_branches_merge_wcopies
@@ -0,0 +1,4 @@
+mkdir -p $BASE_DIR/work
+svn co -q $REPOS_URL/branches/dev/Share/modify_files_base $BASE_DIR/work/b1
+svn co -q $REPOS_URL/branches/dev/Share/modify_files_merge1 $BASE_DIR/work/b2
+svn co -q $REPOS_URL/branches/dev/Share/modify_files_merge2 $BASE_DIR/work/b3
diff --git a/test/test_config/fcm1_branches_merge_wcopy b/test/test_config/fcm1_branches_merge_wcopy
new file mode 100755
index 0000000..48d6a7a
--- /dev/null
+++ b/test/test_config/fcm1_branches_merge_wcopy
@@ -0,0 +1 @@
+svn co -q $REPOS_URL/branches/dev/Share/modify_files_merge2 $BASE_DIR/work
diff --git a/test/test_config/fcm1_cflags_incremental b/test/test_config/fcm1_cflags_incremental
new file mode 100755
index 0000000..7ee8662
--- /dev/null
+++ b/test/test_config/fcm1_cflags_incremental
@@ -0,0 +1 @@
+cfg_name="fcm1_base fcm1_cflags fcm1_base"
diff --git a/test/test_config/fcm1_change_src_type_incremental b/test/test_config/fcm1_change_src_type_incremental
new file mode 100755
index 0000000..4ae350d
--- /dev/null
+++ b/test/test_config/fcm1_change_src_type_incremental
@@ -0,0 +1,5 @@
+cfg_name="fcm1_change_src_type fcm1_change_src_type"
+
+# This test was designed to demonstrate the problem raised in ticket:345.
+# The problem (now fixed) was that some files got rebuilt unnecessarily
+# in the incremental build.
diff --git a/test/test_config/fcm1_delete_directory b/test/test_config/fcm1_delete_directory
new file mode 100755
index 0000000..641513a
--- /dev/null
+++ b/test/test_config/fcm1_delete_directory
@@ -0,0 +1,3 @@
+extract_1=fail_known
+
+# This case fails since the extract system does not handle deleted directories.
diff --git a/test/test_config/fcm1_delete_directory_inherit b/test/test_config/fcm1_delete_directory_inherit
new file mode 100755
index 0000000..641513a
--- /dev/null
+++ b/test/test_config/fcm1_delete_directory_inherit
@@ -0,0 +1,3 @@
+extract_1=fail_known
+
+# This case fails since the extract system does not handle deleted directories.
diff --git a/test/test_config/fcm1_delete_file b/test/test_config/fcm1_delete_file
new file mode 100755
index 0000000..7e1fa29
--- /dev/null
+++ b/test/test_config/fcm1_delete_file
@@ -0,0 +1 @@
+build_1=fail
diff --git a/test/test_config/fcm1_delete_file_inherit b/test/test_config/fcm1_delete_file_inherit
new file mode 100755
index 0000000..7e1fa29
--- /dev/null
+++ b/test/test_config/fcm1_delete_file_inherit
@@ -0,0 +1 @@
+build_1=fail
diff --git a/test/test_config/fcm1_delete_inc_file b/test/test_config/fcm1_delete_inc_file
new file mode 100755
index 0000000..7e1fa29
--- /dev/null
+++ b/test/test_config/fcm1_delete_inc_file
@@ -0,0 +1 @@
+build_1=fail
diff --git a/test/test_config/fcm1_delete_inc_file_inherit b/test/test_config/fcm1_delete_inc_file_inherit
new file mode 100755
index 0000000..6f4f0ee
--- /dev/null
+++ b/test/test_config/fcm1_delete_inc_file_inherit
@@ -0,0 +1,4 @@
+build_1=succeed_known
+
+# The build succeeds when it should, in fact, fail.
+# See ticket:335
diff --git a/test/test_config/fcm1_delete_inc_file_inherit_force b/test/test_config/fcm1_delete_inc_file_inherit_force
new file mode 100755
index 0000000..6f4f0ee
--- /dev/null
+++ b/test/test_config/fcm1_delete_inc_file_inherit_force
@@ -0,0 +1,4 @@
+build_1=succeed_known
+
+# The build succeeds when it should, in fact, fail.
+# See ticket:335
diff --git a/test/test_config/fcm1_delete_pp_file b/test/test_config/fcm1_delete_pp_file
new file mode 100755
index 0000000..7e1fa29
--- /dev/null
+++ b/test/test_config/fcm1_delete_pp_file
@@ -0,0 +1 @@
+build_1=fail
diff --git a/test/test_config/fcm1_delete_pp_file_inherit b/test/test_config/fcm1_delete_pp_file_inherit
new file mode 100755
index 0000000..7e1fa29
--- /dev/null
+++ b/test/test_config/fcm1_delete_pp_file_inherit
@@ -0,0 +1 @@
+build_1=fail
diff --git a/test/test_config/fcm1_delete_ppinc_file b/test/test_config/fcm1_delete_ppinc_file
new file mode 100755
index 0000000..7e1fa29
--- /dev/null
+++ b/test/test_config/fcm1_delete_ppinc_file
@@ -0,0 +1 @@
+build_1=fail
diff --git a/test/test_config/fcm1_delete_ppinc_file_inherit b/test/test_config/fcm1_delete_ppinc_file_inherit
new file mode 100755
index 0000000..6f4f0ee
--- /dev/null
+++ b/test/test_config/fcm1_delete_ppinc_file_inherit
@@ -0,0 +1,4 @@
+build_1=succeed_known
+
+# The build succeeds when it should, in fact, fail.
+# See ticket:335
diff --git a/test/test_config/fcm1_delete_ppinc_file_inherit_force b/test/test_config/fcm1_delete_ppinc_file_inherit_force
new file mode 100755
index 0000000..6f4f0ee
--- /dev/null
+++ b/test/test_config/fcm1_delete_ppinc_file_inherit_force
@@ -0,0 +1,4 @@
+build_1=succeed_known
+
+# The build succeeds when it should, in fact, fail.
+# See ticket:335
diff --git a/test/test_config/fcm1_duplicate_target b/test/test_config/fcm1_duplicate_target
new file mode 100755
index 0000000..92a1bf3
--- /dev/null
+++ b/test/test_config/fcm1_duplicate_target
@@ -0,0 +1,4 @@
+build_1=succeed_known
+
+# The build succeeds when it should, in fact, fail.
+# See ticket:205
diff --git a/test/test_config/fcm1_exclude_dependency b/test/test_config/fcm1_exclude_dependency
new file mode 100755
index 0000000..7e1fa29
--- /dev/null
+++ b/test/test_config/fcm1_exclude_dependency
@@ -0,0 +1 @@
+build_1=fail
diff --git a/test/test_config/fcm1_exe_permissions b/test/test_config/fcm1_exe_permissions
new file mode 100755
index 0000000..8d98fae
--- /dev/null
+++ b/test/test_config/fcm1_exe_permissions
@@ -0,0 +1,3 @@
+svn co -q $REPOS_URL/branches/dev/Share/exe_rename $BASE_DIR/work
+chmod 644 $BASE_DIR/work/script/hello.sh
+run_1=fail
diff --git a/test/test_config/fcm1_exe_rename_incremental b/test/test_config/fcm1_exe_rename_incremental
new file mode 100755
index 0000000..7d5e5ef
--- /dev/null
+++ b/test/test_config/fcm1_exe_rename_incremental
@@ -0,0 +1 @@
+cfg_name="fcm1_base fcm1_exe_rename"
diff --git a/test/test_config/fcm1_fc_incremental b/test/test_config/fcm1_fc_incremental
new file mode 100755
index 0000000..5b1a7ba
--- /dev/null
+++ b/test/test_config/fcm1_fc_incremental
@@ -0,0 +1 @@
+cfg_name="fcm1_base fcm1_fc fcm1_base"
diff --git a/test/test_config/fcm1_fflags_incremental b/test/test_config/fcm1_fflags_incremental
new file mode 100755
index 0000000..85948ba
--- /dev/null
+++ b/test/test_config/fcm1_fflags_incremental
@@ -0,0 +1 @@
+cfg_name="fcm1_base fcm1_fflags1 fcm1_fflags2 fcm1_base"
diff --git a/test/test_config/fcm1_inc_devnull b/test/test_config/fcm1_inc_devnull
new file mode 100755
index 0000000..e66242c
--- /dev/null
+++ b/test/test_config/fcm1_inc_devnull
@@ -0,0 +1 @@
+run_1=no
diff --git a/test/test_config/fcm1_inherit_invalid_path b/test/test_config/fcm1_inherit_invalid_path
new file mode 100755
index 0000000..c88bb32
--- /dev/null
+++ b/test/test_config/fcm1_inherit_invalid_path
@@ -0,0 +1,3 @@
+extract_1=fail
+
+# Poor error message using FCM 1
diff --git a/test/test_config/fcm1_invalid_base_url b/test/test_config/fcm1_invalid_base_url
new file mode 100755
index 0000000..c00e841
--- /dev/null
+++ b/test/test_config/fcm1_invalid_base_url
@@ -0,0 +1 @@
+extract_1=fail
diff --git a/test/test_config/fcm1_invalid_branch_url b/test/test_config/fcm1_invalid_branch_url
new file mode 100755
index 0000000..c00e841
--- /dev/null
+++ b/test/test_config/fcm1_invalid_branch_url
@@ -0,0 +1 @@
+extract_1=fail
diff --git a/test/test_config/fcm1_invalid_inc b/test/test_config/fcm1_invalid_inc
new file mode 100755
index 0000000..c00e841
--- /dev/null
+++ b/test/test_config/fcm1_invalid_inc
@@ -0,0 +1 @@
+extract_1=fail
diff --git a/test/test_config/fcm1_invalid_namespace b/test/test_config/fcm1_invalid_namespace
new file mode 100755
index 0000000..7e1fa29
--- /dev/null
+++ b/test/test_config/fcm1_invalid_namespace
@@ -0,0 +1 @@
+build_1=fail
diff --git a/test/test_config/fcm1_invalid_variable b/test/test_config/fcm1_invalid_variable
new file mode 100755
index 0000000..7e1fa29
--- /dev/null
+++ b/test/test_config/fcm1_invalid_variable
@@ -0,0 +1 @@
+build_1=fail
diff --git a/test/test_config/fcm1_ld_incremental b/test/test_config/fcm1_ld_incremental
new file mode 100755
index 0000000..0ccf106
--- /dev/null
+++ b/test/test_config/fcm1_ld_incremental
@@ -0,0 +1 @@
+cfg_name="fcm1_base fcm1_ld fcm1_base"
diff --git a/test/test_config/fcm1_library b/test/test_config/fcm1_library
new file mode 100755
index 0000000..e66242c
--- /dev/null
+++ b/test/test_config/fcm1_library
@@ -0,0 +1 @@
+run_1=no
diff --git a/test/test_config/fcm1_library_rename b/test/test_config/fcm1_library_rename
new file mode 100755
index 0000000..e66242c
--- /dev/null
+++ b/test/test_config/fcm1_library_rename
@@ -0,0 +1 @@
+run_1=no
diff --git a/test/test_config/fcm1_mirror b/test/test_config/fcm1_mirror
new file mode 100755
index 0000000..830e04f
--- /dev/null
+++ b/test/test_config/fcm1_mirror
@@ -0,0 +1 @@
+mirror=local
diff --git a/test/test_config/fcm1_mirror_inherit b/test/test_config/fcm1_mirror_inherit
new file mode 100755
index 0000000..830e04f
--- /dev/null
+++ b/test/test_config/fcm1_mirror_inherit
@@ -0,0 +1 @@
+mirror=local
diff --git a/test/test_config/fcm1_no_dep b/test/test_config/fcm1_no_dep
new file mode 100755
index 0000000..66a8cf1
--- /dev/null
+++ b/test/test_config/fcm1_no_dep
@@ -0,0 +1 @@
+run_1=fail
diff --git a/test/test_config/fcm1_ops_parallel b/test/test_config/fcm1_ops_parallel
new file mode 100755
index 0000000..5f54981
--- /dev/null
+++ b/test/test_config/fcm1_ops_parallel
@@ -0,0 +1,2 @@
+cfg_name=fcm1_ops
+NPROC=6
diff --git a/test/test_config/fcm1_pp_change_include_inherit b/test/test_config/fcm1_pp_change_include_inherit
new file mode 100755
index 0000000..286a6c6
--- /dev/null
+++ b/test/test_config/fcm1_pp_change_include_inherit
@@ -0,0 +1,2 @@
+# The wrong include file gets used as demonstrated by the lack of anything being compiled.
+# See ticket:206
diff --git a/test/test_config/fcm1_pp_change_keys_incremental b/test/test_config/fcm1_pp_change_keys_incremental
new file mode 100755
index 0000000..9093515
--- /dev/null
+++ b/test/test_config/fcm1_pp_change_keys_incremental
@@ -0,0 +1 @@
+cfg_name="fcm1_pp_change_dependency fcm1_base fcm1_pp_change_blockdata"
diff --git a/test/test_config/fcm1_pp_empty_subroutine b/test/test_config/fcm1_pp_empty_subroutine
new file mode 100755
index 0000000..7e1fa29
--- /dev/null
+++ b/test/test_config/fcm1_pp_empty_subroutine
@@ -0,0 +1 @@
+build_1=fail
diff --git a/test/test_config/fcm1_pp_empty_subroutine_inherit b/test/test_config/fcm1_pp_empty_subroutine_inherit
new file mode 100755
index 0000000..605d5de
--- /dev/null
+++ b/test/test_config/fcm1_pp_empty_subroutine_inherit
@@ -0,0 +1,3 @@
+build_1=succeed_known
+
+# This case does not fail as expected (see #335)
diff --git a/test/test_config/fcm1_pp_empty_subroutine_inherit_force b/test/test_config/fcm1_pp_empty_subroutine_inherit_force
new file mode 100755
index 0000000..7f69c0a
--- /dev/null
+++ b/test/test_config/fcm1_pp_empty_subroutine_inherit_force
@@ -0,0 +1,5 @@
+build_1=fail
+
+# This case does fail as expected.
+# However, the failure is at the link stage whereas it ought to fail
+# due to a missing dependency (see #335)
diff --git a/test/test_config/fcm1_revmatch_true b/test/test_config/fcm1_revmatch_true
new file mode 100755
index 0000000..c00e841
--- /dev/null
+++ b/test/test_config/fcm1_revmatch_true
@@ -0,0 +1 @@
+extract_1=fail
diff --git a/test/test_config/fcm1_sps_parallel b/test/test_config/fcm1_sps_parallel
new file mode 100755
index 0000000..5230915
--- /dev/null
+++ b/test/test_config/fcm1_sps_parallel
@@ -0,0 +1,2 @@
+cfg_name=fcm1_sps
+NPROC=6
diff --git a/test/test_config/fcm1_um b/test/test_config/fcm1_um
new file mode 100755
index 0000000..73fd90d
--- /dev/null
+++ b/test/test_config/fcm1_um
@@ -0,0 +1,2 @@
+cfg_name="fcm1_um fcm1_um"
+NPROC=6
diff --git a/test/test_config/fcm1_um_inherit b/test/test_config/fcm1_um_inherit
new file mode 100755
index 0000000..6b9008c
--- /dev/null
+++ b/test/test_config/fcm1_um_inherit
@@ -0,0 +1,2 @@
+cfg_name="fcm1_um_inherit fcm1_um_inherit"
+NPROC=6
diff --git a/test/test_config/fcm1_var_parallel b/test/test_config/fcm1_var_parallel
new file mode 100755
index 0000000..e009b76
--- /dev/null
+++ b/test/test_config/fcm1_var_parallel
@@ -0,0 +1,2 @@
+cfg_name=fcm1_var
+NPROC=6
diff --git a/test/test_config/fcm2_branches_clash b/test/test_config/fcm2_branches_clash
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_branches_clash
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_branches_merge_incremental b/test/test_config/fcm2_branches_merge_incremental
new file mode 100755
index 0000000..bf5bef3
--- /dev/null
+++ b/test/test_config/fcm2_branches_merge_incremental
@@ -0,0 +1 @@
+cfg_name="fcm2_base fcm2_branches_merge"
diff --git a/test/test_config/fcm2_branches_merge_inherit_wrong_include b/test/test_config/fcm2_branches_merge_inherit_wrong_include
new file mode 100755
index 0000000..e5257e8
--- /dev/null
+++ b/test/test_config/fcm2_branches_merge_inherit_wrong_include
@@ -0,0 +1,2 @@
+# The wrong include file gets used as demonstrated by the output from running the program.
+# See ticket:206
diff --git a/test/test_config/fcm2_branches_merge_wcopies b/test/test_config/fcm2_branches_merge_wcopies
new file mode 100755
index 0000000..92f75b3
--- /dev/null
+++ b/test/test_config/fcm2_branches_merge_wcopies
@@ -0,0 +1,4 @@
+mkdir -p $BASE_DIR/work
+svn co -q $REPOS_URL/branches/dev/Share/modify_files_base $BASE_DIR/work/b1
+svn co -q $REPOS_URL/branches/dev/Share/modify_files_merge1 $BASE_DIR/work/b2
+svn co -q $REPOS_URL/branches/dev/Share/modify_files_merge2 $BASE_DIR/work/b3
diff --git a/test/test_config/fcm2_branches_merge_wcopy b/test/test_config/fcm2_branches_merge_wcopy
new file mode 100755
index 0000000..48d6a7a
--- /dev/null
+++ b/test/test_config/fcm2_branches_merge_wcopy
@@ -0,0 +1 @@
+svn co -q $REPOS_URL/branches/dev/Share/modify_files_merge2 $BASE_DIR/work
diff --git a/test/test_config/fcm2_cflags_incremental b/test/test_config/fcm2_cflags_incremental
new file mode 100755
index 0000000..e2acddb
--- /dev/null
+++ b/test/test_config/fcm2_cflags_incremental
@@ -0,0 +1 @@
+cfg_name="fcm2_base fcm2_cflags fcm2_base"
diff --git a/test/test_config/fcm2_cyclic_dep_fail b/test/test_config/fcm2_cyclic_dep_fail
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_cyclic_dep_fail
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_delete_file b/test/test_config/fcm2_delete_file
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_delete_file
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_delete_file_inherit b/test/test_config/fcm2_delete_file_inherit
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_delete_file_inherit
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_delete_inc_file b/test/test_config/fcm2_delete_inc_file
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_delete_inc_file
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_delete_inc_file_inherit b/test/test_config/fcm2_delete_inc_file_inherit
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_delete_inc_file_inherit
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_delete_inc_file_inherit_force b/test/test_config/fcm2_delete_inc_file_inherit_force
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_delete_inc_file_inherit_force
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_delete_pp_file b/test/test_config/fcm2_delete_pp_file
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_delete_pp_file
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_delete_pp_file_inherit b/test/test_config/fcm2_delete_pp_file_inherit
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_delete_pp_file_inherit
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_delete_ppinc_file b/test/test_config/fcm2_delete_ppinc_file
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_delete_ppinc_file
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_delete_ppinc_file_inherit b/test/test_config/fcm2_delete_ppinc_file_inherit
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_delete_ppinc_file_inherit
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_delete_ppinc_file_inherit_force b/test/test_config/fcm2_delete_ppinc_file_inherit_force
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_delete_ppinc_file_inherit_force
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_dep_o_invalid b/test/test_config/fcm2_dep_o_invalid
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_dep_o_invalid
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_duplicate_target b/test/test_config/fcm2_duplicate_target
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_duplicate_target
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_exclude_dependency b/test/test_config/fcm2_exclude_dependency
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_exclude_dependency
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_exe_permissions b/test/test_config/fcm2_exe_permissions
new file mode 100755
index 0000000..8d98fae
--- /dev/null
+++ b/test/test_config/fcm2_exe_permissions
@@ -0,0 +1,3 @@
+svn co -q $REPOS_URL/branches/dev/Share/exe_rename $BASE_DIR/work
+chmod 644 $BASE_DIR/work/script/hello.sh
+run_1=fail
diff --git a/test/test_config/fcm2_exe_rename_incremental b/test/test_config/fcm2_exe_rename_incremental
new file mode 100755
index 0000000..2af2b7e
--- /dev/null
+++ b/test/test_config/fcm2_exe_rename_incremental
@@ -0,0 +1 @@
+cfg_name="fcm2_base fcm2_exe_rename"
diff --git a/test/test_config/fcm2_fc_incremental b/test/test_config/fcm2_fc_incremental
new file mode 100755
index 0000000..e45691c
--- /dev/null
+++ b/test/test_config/fcm2_fc_incremental
@@ -0,0 +1 @@
+cfg_name="fcm2_base fcm2_fc fcm2_base"
diff --git a/test/test_config/fcm2_fflags_incremental b/test/test_config/fcm2_fflags_incremental
new file mode 100755
index 0000000..39caa04
--- /dev/null
+++ b/test/test_config/fcm2_fflags_incremental
@@ -0,0 +1,2 @@
+cfg_name="fcm2_base fcm2_fflags1 fcm2_fflags2 fcm2_base"
+compare_fcm1=false
diff --git a/test/test_config/fcm2_inc_devnull b/test/test_config/fcm2_inc_devnull
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_inc_devnull
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_inherit_invalid_path b/test/test_config/fcm2_inherit_invalid_path
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_inherit_invalid_path
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_inherit_redefine_fail b/test/test_config/fcm2_inherit_redefine_fail
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_inherit_redefine_fail
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_invalid_base_url b/test/test_config/fcm2_invalid_base_url
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_invalid_base_url
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_invalid_branch_url b/test/test_config/fcm2_invalid_branch_url
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_invalid_branch_url
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_invalid_branch_url2 b/test/test_config/fcm2_invalid_branch_url2
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_invalid_branch_url2
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_invalid_inc b/test/test_config/fcm2_invalid_inc
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_invalid_inc
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_invalid_label b/test/test_config/fcm2_invalid_label
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_invalid_label
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_invalid_modifier b/test/test_config/fcm2_invalid_modifier
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_invalid_modifier
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_invalid_modifiers b/test/test_config/fcm2_invalid_modifiers
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_invalid_modifiers
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_invalid_namespace b/test/test_config/fcm2_invalid_namespace
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_invalid_namespace
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_invalid_namespace2 b/test/test_config/fcm2_invalid_namespace2
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_invalid_namespace2
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_invalid_target b/test/test_config/fcm2_invalid_target
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_invalid_target
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_invalid_variable b/test/test_config/fcm2_invalid_variable
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_invalid_variable
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_library b/test/test_config/fcm2_library
new file mode 100755
index 0000000..1a91d42
--- /dev/null
+++ b/test/test_config/fcm2_library
@@ -0,0 +1,2 @@
+run_1=no
+compare_fcm1=false
diff --git a/test/test_config/fcm2_library_rename b/test/test_config/fcm2_library_rename
new file mode 100755
index 0000000..e66242c
--- /dev/null
+++ b/test/test_config/fcm2_library_rename
@@ -0,0 +1 @@
+run_1=no
diff --git a/test/test_config/fcm2_mirror b/test/test_config/fcm2_mirror
new file mode 100755
index 0000000..830e04f
--- /dev/null
+++ b/test/test_config/fcm2_mirror
@@ -0,0 +1 @@
+mirror=local
diff --git a/test/test_config/fcm2_mirror_after_pp b/test/test_config/fcm2_mirror_after_pp
new file mode 100755
index 0000000..9af7e0d
--- /dev/null
+++ b/test/test_config/fcm2_mirror_after_pp
@@ -0,0 +1,2 @@
+mirror=local
+make_1=fail
diff --git a/test/test_config/fcm2_mirror_inherit b/test/test_config/fcm2_mirror_inherit
new file mode 100755
index 0000000..830e04f
--- /dev/null
+++ b/test/test_config/fcm2_mirror_inherit
@@ -0,0 +1 @@
+mirror=local
diff --git a/test/test_config/fcm2_mirror_inherit_fflags b/test/test_config/fcm2_mirror_inherit_fflags
new file mode 100755
index 0000000..830e04f
--- /dev/null
+++ b/test/test_config/fcm2_mirror_inherit_fflags
@@ -0,0 +1 @@
+mirror=local
diff --git a/test/test_config/fcm2_mirror_inherit_notarget b/test/test_config/fcm2_mirror_inherit_notarget
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_mirror_inherit_notarget
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_modify_subroutine_inherit b/test/test_config/fcm2_modify_subroutine_inherit
new file mode 100755
index 0000000..5db5e06
--- /dev/null
+++ b/test/test_config/fcm2_modify_subroutine_inherit
@@ -0,0 +1 @@
+compare_fcm1=false
diff --git a/test/test_config/fcm2_multi_inherit b/test/test_config/fcm2_multi_inherit
new file mode 100755
index 0000000..5db5e06
--- /dev/null
+++ b/test/test_config/fcm2_multi_inherit
@@ -0,0 +1 @@
+compare_fcm1=false
diff --git a/test/test_config/fcm2_no_dep b/test/test_config/fcm2_no_dep
new file mode 100755
index 0000000..66a8cf1
--- /dev/null
+++ b/test/test_config/fcm2_no_dep
@@ -0,0 +1 @@
+run_1=fail
diff --git a/test/test_config/fcm2_ns-dep_o_invalid b/test/test_config/fcm2_ns-dep_o_invalid
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_ns-dep_o_invalid
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_ops_parallel b/test/test_config/fcm2_ops_parallel
new file mode 100755
index 0000000..70a5501
--- /dev/null
+++ b/test/test_config/fcm2_ops_parallel
@@ -0,0 +1,2 @@
+cfg_name=fcm2_ops
+NPROC=6
diff --git a/test/test_config/fcm2_override_variable b/test/test_config/fcm2_override_variable
new file mode 100755
index 0000000..7dad7b7
--- /dev/null
+++ b/test/test_config/fcm2_override_variable
@@ -0,0 +1,2 @@
+cfg_name="fcm2_base"
+export fcflags="-assume nosource_include -g"
diff --git a/test/test_config/fcm2_pp_change_include_inherit b/test/test_config/fcm2_pp_change_include_inherit
new file mode 100755
index 0000000..c15edb9
--- /dev/null
+++ b/test/test_config/fcm2_pp_change_include_inherit
@@ -0,0 +1,4 @@
+compare_fcm1=false
+
+# The wrong include file gets used as demonstrated by the lack of anything being compiled.
+# See ticket:206
diff --git a/test/test_config/fcm2_pp_change_keys_incremental b/test/test_config/fcm2_pp_change_keys_incremental
new file mode 100755
index 0000000..60991cc
--- /dev/null
+++ b/test/test_config/fcm2_pp_change_keys_incremental
@@ -0,0 +1 @@
+cfg_name="fcm2_pp_change_dependency fcm2_base fcm2_pp_change_blockdata"
diff --git a/test/test_config/fcm2_pp_empty_subroutine b/test/test_config/fcm2_pp_empty_subroutine
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_pp_empty_subroutine
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_pp_empty_subroutine_inherit b/test/test_config/fcm2_pp_empty_subroutine_inherit
new file mode 100755
index 0000000..6ab2ba3
--- /dev/null
+++ b/test/test_config/fcm2_pp_empty_subroutine_inherit
@@ -0,0 +1 @@
+make_1=fail
diff --git a/test/test_config/fcm2_pp_empty_subroutine_inherit_force b/test/test_config/fcm2_pp_empty_subroutine_inherit_force
new file mode 100755
index 0000000..0eadb74
--- /dev/null
+++ b/test/test_config/fcm2_pp_empty_subroutine_inherit_force
@@ -0,0 +1,2 @@
+make_1=fail
+compare_fcm1=false
diff --git a/test/test_config/fcm2_sps_parallel b/test/test_config/fcm2_sps_parallel
new file mode 100755
index 0000000..8a431f5
--- /dev/null
+++ b/test/test_config/fcm2_sps_parallel
@@ -0,0 +1,2 @@
+cfg_name=fcm2_sps
+NPROC=6
diff --git a/test/test_config/fcm2_um b/test/test_config/fcm2_um
new file mode 100755
index 0000000..8ad1be3
--- /dev/null
+++ b/test/test_config/fcm2_um
@@ -0,0 +1,2 @@
+cfg_name="fcm2_um fcm2_um"
+NPROC=6
diff --git a/test/test_config/fcm2_um77 b/test/test_config/fcm2_um77
new file mode 100755
index 0000000..f5d65e4
--- /dev/null
+++ b/test/test_config/fcm2_um77
@@ -0,0 +1,2 @@
+cfg_name="fcm2_um77 fcm2_um77"
+NPROC=6
diff --git a/test/test_config/fcm2_um77_inherit b/test/test_config/fcm2_um77_inherit
new file mode 100755
index 0000000..9ebe967
--- /dev/null
+++ b/test/test_config/fcm2_um77_inherit
@@ -0,0 +1,2 @@
+cfg_name="fcm2_um77_inherit fcm2_um77_inherit"
+NPROC=6
diff --git a/test/test_config/fcm2_um_inherit b/test/test_config/fcm2_um_inherit
new file mode 100755
index 0000000..2863cbb
--- /dev/null
+++ b/test/test_config/fcm2_um_inherit
@@ -0,0 +1,2 @@
+cfg_name="fcm2_um_inherit fcm2_um_inherit"
+NPROC=6
diff --git a/test/test_config/fcm2_var_parallel b/test/test_config/fcm2_var_parallel
new file mode 100755
index 0000000..0a15ac1
--- /dev/null
+++ b/test/test_config/fcm2_var_parallel
@@ -0,0 +1,2 @@
+cfg_name=fcm2_var
+NPROC=6
diff --git a/test/test_include/inc/fortran.inc b/test/test_include/inc/fortran.inc
new file mode 100644
index 0000000..5a1692c
--- /dev/null
+++ b/test/test_include/inc/fortran.inc
@@ -0,0 +1 @@
+WRITE (*, '(A)') 'PASSED: correct include file'
diff --git a/test/test_include/prog/fortran.inc b/test/test_include/prog/fortran.inc
new file mode 100644
index 0000000..b1d5733
--- /dev/null
+++ b/test/test_include/prog/fortran.inc
@@ -0,0 +1 @@
+WRITE (*, '(A)') 'FAILED: wrong include file'
diff --git a/test/test_include/prog/test_fortran_inc.f90 b/test/test_include/prog/test_fortran_inc.f90
new file mode 100644
index 0000000..0fc3e5b
--- /dev/null
+++ b/test/test_include/prog/test_fortran_inc.f90
@@ -0,0 +1,5 @@
+PROGRAM Test
+
+INCLUDE 'fortran.inc'
+
+END PROGRAM Test
diff --git a/test/test_include/prog/test_prepro_inc.F90 b/test/test_include/prog/test_prepro_inc.F90
new file mode 100644
index 0000000..606651f
--- /dev/null
+++ b/test/test_include/prog/test_prepro_inc.F90
@@ -0,0 +1,5 @@
+PROGRAM Test
+
+#include "fortran.inc"
+
+END PROGRAM Test
diff --git a/test/test_include/test.sh b/test/test_include/test.sh
new file mode 100755
index 0000000..69fd9da
--- /dev/null
+++ b/test/test_include/test.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+echo "### Fortran compiler tests"
+for fc in \
+  "ifort" \
+  "ifort -assume nosource_include" \
+  "gfortran" \
+  "gfortran -I-"
+do
+  echo
+  echo "Compiler: $fc"
+  echo "Fortran include test:"
+  $fc -o test.o -I$PWD/inc -c prog/test_fortran_inc.f90
+  $fc -o test.exe test.o
+  test.exe
+  rm test.exe test.o
+  echo "CPP include test:"
+  $fc -o test.o -I$PWD/inc -c prog/test_prepro_inc.F90
+  $fc -o test.exe test.o
+  test.exe
+  rm test.exe test.o
+done
+
+echo
+echo "### Preprocessor tests"
+fc=gfortran
+for cpp in \
+  "cpp -P -traditional" \
+  "cpp -P -traditional -I-"
+do
+  echo
+  echo "Pre-processor: $cpp"
+  $cpp -I$PWD/inc prog/test_prepro_inc.F90 >tmp.f90
+  $fc -o test.o -I$PWD/inc -c tmp.f90
+  $fc -o test.exe test.o
+  test.exe
+  rm test.exe test.o tmp.f90
+done
diff --git a/test/tests_functional.list b/test/tests_functional.list
new file mode 100755
index 0000000..7e79dba
--- /dev/null
+++ b/test/tests_functional.list
@@ -0,0 +1,158 @@
+TESTS_FCM1="
+fcm1_base
+fcm1_add_directory
+fcm1_add_directory_expsrc
+fcm1_add_file
+fcm1_add_file_inherit
+fcm1_branches_clash
+fcm1_branches_merge
+fcm1_branches_merge_conflict_fail
+fcm1_branches_merge_conflict_override
+fcm1_branches_merge_incremental
+fcm1_branches_merge_inherit
+fcm1_branches_merge_inherit_wrong_include
+fcm1_branches_merge_wcopy
+fcm1_branches_merge_wcopies
+fcm1_cflags_incremental
+fcm1_change_src_type_incremental
+fcm1_delete_directory
+fcm1_delete_directory_inherit
+fcm1_delete_file
+fcm1_delete_file_inherit
+fcm1_delete_inc_file
+fcm1_delete_inc_file_inherit
+fcm1_delete_inc_file_inherit_force
+fcm1_delete_pp_file
+fcm1_delete_pp_file_inherit
+fcm1_delete_ppinc_file
+fcm1_delete_ppinc_file_inherit
+fcm1_delete_ppinc_file_inherit_force
+fcm1_duplicate_target
+fcm1_exclude_dependency
+fcm1_exe_permissions
+fcm1_exe_rename
+fcm1_exe_rename_incremental
+fcm1_fc_incremental
+fcm1_fflags_incremental
+fcm1_fflags_inherit
+fcm1_inc_devnull
+fcm1_inherit_invalid_path
+fcm1_inherit_target
+fcm1_invalid_base_url
+fcm1_invalid_branch_url
+fcm1_invalid_inc
+fcm1_invalid_namespace
+fcm1_invalid_variable
+fcm1_ld_incremental
+fcm1_library
+fcm1_library_rename
+fcm1_mirror
+fcm1_mirror_inherit
+fcm1_modify_subroutine_inherit
+fcm1_modify_subroutine_interface_inherit
+fcm1_multi_inherit
+fcm1_no_dep
+fcm1_pp_change_include
+fcm1_pp_change_include_inherit
+fcm1_pp_change_keys_incremental
+fcm1_pp_empty_subroutine
+fcm1_pp_empty_subroutine_inherit
+fcm1_pp_empty_subroutine_inherit_force
+fcm1_revmatch_false
+fcm1_revmatch_true
+fcm1_suite
+fcm1_symbolic_link
+"
+
+TESTS_FCM2="
+fcm2_base
+fcm2_add_directory_expsrc
+fcm2_add_file
+fcm2_add_file_inherit
+fcm2_branches_clash
+fcm2_branches_merge
+fcm2_branches_merge_duplicate
+fcm2_branches_merge_incremental
+fcm2_branches_merge_inherit
+fcm2_branches_merge_inherit_wrong_include
+fcm2_branches_merge_wcopy
+fcm2_branches_merge_wcopies
+fcm2_cflags_incremental
+fcm2_change_variable
+fcm2_cyclic_dep_fail
+fcm2_cyclic_dep_ok
+fcm2_delete_directory
+fcm2_delete_directory_inherit
+fcm2_delete_file
+fcm2_delete_file_inherit
+fcm2_delete_inc_file
+fcm2_delete_inc_file_inherit
+fcm2_delete_inc_file_inherit_force
+fcm2_delete_pp_file
+fcm2_delete_pp_file_inherit
+fcm2_delete_ppinc_file
+fcm2_delete_ppinc_file_inherit
+fcm2_delete_ppinc_file_inherit_force
+fcm2_dep_o
+fcm2_dep_o_all
+fcm2_dep_o_invalid
+fcm2_duplicate_target
+fcm2_exclude_dependency
+fcm2_extract_path_excl_no_ns
+fcm2_exe_permissions
+fcm2_exe_rename
+fcm2_exe_rename_incremental
+fcm2_fc_incremental
+fcm2_fflags_incremental
+fcm2_fflags_inherit
+fcm2_flag-output
+fcm2_inc_devnull
+fcm2_inherit_invalid_path
+fcm2_inherit_redefine_fail
+fcm2_inherit_redefine_ok
+fcm2_invalid_base_url
+fcm2_invalid_branch_url
+fcm2_invalid_branch_url2
+fcm2_invalid_inc
+fcm2_invalid_label
+fcm2_invalid_modifier
+fcm2_invalid_modifiers
+fcm2_invalid_namespace
+fcm2_invalid_namespace2
+fcm2_invalid_target
+fcm2_invalid_variable
+fcm2_library
+fcm2_library_rename
+fcm2_mirror
+fcm2_mirror_after_pp
+fcm2_mirror_inherit
+fcm2_mirror_inherit_notarget
+fcm2_mirror_inherit_fflags
+fcm2_modify_subroutine_inherit
+fcm2_modify_subroutine_interface_inherit
+fcm2_multi_inherit
+fcm2_multiple_build
+fcm2_multiple_build_inherit
+fcm2_multiple_pp-build
+fcm2_multiple_pp-build_inherit
+fcm2_no_dep
+fcm2_ns-dep_o
+fcm2_ns-dep_o_all
+fcm2_ns-dep_o_file
+fcm2_ns-dep_o_invalid
+fcm2_override_variable
+fcm2_pp_change_include
+fcm2_pp_change_include_inherit
+fcm2_pp_change_keys_incremental
+fcm2_pp_empty_subroutine
+fcm2_pp_empty_subroutine_inherit
+fcm2_pp_empty_subroutine_inherit_force
+fcm2_revmatch_false
+fcm2_single_file
+fcm2_space_in_name
+fcm2_symbolic_link
+"
+
+# Remove any commented out tests
+TESTS_FCM1=$(echo $TESTS_FCM1 | sed 's/#[^ ]*//g')
+TESTS_FCM2=$(echo $TESTS_FCM2 | sed 's/#[^ ]*//g')
diff --git a/test/tests_perf_local.list b/test/tests_perf_local.list
new file mode 100755
index 0000000..f1fcfbf
--- /dev/null
+++ b/test/tests_perf_local.list
@@ -0,0 +1,23 @@
+TESTS_FCM1="
+fcm1_var
+fcm1_var_parallel
+fcm1_ops_parallel
+fcm1_sps_parallel
+fcm1_um
+fcm1_um_inherit
+"
+
+TESTS_FCM2="
+fcm2_var
+fcm2_var_parallel
+fcm2_ops_parallel
+fcm2_sps_parallel
+fcm2_um
+fcm2_um_inherit
+fcm2_um77
+fcm2_um77_inherit
+"
+
+# Remove any commented out tests
+TESTS_FCM1=$(echo $TESTS_FCM1 | sed 's/#[^ ]*//g')
+TESTS_FCM2=$(echo $TESTS_FCM2 | sed 's/#[^ ]*//g')
diff --git a/test/tests_perf_remote.list b/test/tests_perf_remote.list
new file mode 100755
index 0000000..dcd8e7a
--- /dev/null
+++ b/test/tests_perf_remote.list
@@ -0,0 +1,19 @@
+TESTS_FCM1="
+fcm1_var_hpc
+fcm1_postproc_hpc
+fcm1_um_hpc
+fcm1_um_inherit_hpc
+"
+
+TESTS_FCM2="
+fcm2_var_hpc
+fcm2_postproc_hpc
+fcm2_um_hpc
+fcm2_um_inherit_hpc
+fcm2_um77_hpc
+fcm2_um77_inherit_hpc
+"
+
+# Remove any commented out tests
+TESTS_FCM1=$(echo $TESTS_FCM1 | sed 's/#[^ ]*//g')
+TESTS_FCM2=$(echo $TESTS_FCM2 | sed 's/#[^ ]*//g')
diff --git a/test/wrapper_scripts/wrap_ar b/test/wrapper_scripts/wrap_ar
new file mode 100755
index 0000000..04cf798
--- /dev/null
+++ b/test/wrapper_scripts/wrap_ar
@@ -0,0 +1,2 @@
+echo "wrap_ar $@" | sed "s#$RUN_DIR##g" | sed "s#/var/tmp/[^/]*/lib#/lib#" >>$command_file
+ar  "$@"
diff --git a/test/wrapper_scripts/wrap_cc b/test/wrapper_scripts/wrap_cc
new file mode 100755
index 0000000..82898ed
--- /dev/null
+++ b/test/wrapper_scripts/wrap_cc
@@ -0,0 +1,2 @@
+echo "wrap_cc $@" | sed "s#$RUN_DIR##g" | sed "s#-I\./#-I#g" >>$command_file
+gcc  "$@"
diff --git a/test/wrapper_scripts/wrap_fc b/test/wrapper_scripts/wrap_fc
new file mode 100755
index 0000000..e77e4ff
--- /dev/null
+++ b/test/wrapper_scripts/wrap_fc
@@ -0,0 +1,2 @@
+echo "wrap_fc $@" | sed "s#$RUN_DIR##g" | sed "s#-I\./#-I#g" | sed "s#-L/var/tmp/[^ ]* #-Llib #" >>$command_file
+ifort  "$@"
diff --git a/test/wrapper_scripts/wrap_fc2 b/test/wrapper_scripts/wrap_fc2
new file mode 100755
index 0000000..4573377
--- /dev/null
+++ b/test/wrapper_scripts/wrap_fc2
@@ -0,0 +1,2 @@
+echo "wrap_fc2 $@" | sed "s#$RUN_DIR##g" | sed "s#-I\./#-I#g" | sed "s#-L/var/tmp/[^ ]* #-Llib #" >>$command_file
+ifort  "$@"
diff --git a/test/wrapper_scripts/wrap_ld b/test/wrapper_scripts/wrap_ld
new file mode 100755
index 0000000..585fbde
--- /dev/null
+++ b/test/wrapper_scripts/wrap_ld
@@ -0,0 +1,2 @@
+echo "wrap_ld $@" | sed "s#$RUN_DIR##g" >>$command_file
+ifort  "$@"
diff --git a/test/wrapper_scripts/wrap_ld2 b/test/wrapper_scripts/wrap_ld2
new file mode 100755
index 0000000..155c9b9
--- /dev/null
+++ b/test/wrapper_scripts/wrap_ld2
@@ -0,0 +1,2 @@
+echo "wrap_ld2 $@" | sed "s#$RUN_DIR##g" >>$command_file
+ifort  "$@"
diff --git a/test/wrapper_scripts/wrap_mpicc b/test/wrapper_scripts/wrap_mpicc
new file mode 100755
index 0000000..90330cb
--- /dev/null
+++ b/test/wrapper_scripts/wrap_mpicc
@@ -0,0 +1,2 @@
+echo "wrap_mpicc $@" | sed "s#$RUN_DIR##g" | sed "s#-I\./#-I#g" >>$command_file
+mpicc  "$@"
diff --git a/test/wrapper_scripts/wrap_mpif90 b/test/wrapper_scripts/wrap_mpif90
new file mode 100755
index 0000000..202c197
--- /dev/null
+++ b/test/wrapper_scripts/wrap_mpif90
@@ -0,0 +1,2 @@
+echo "wrap_mpif90 $@" | sed "s#$RUN_DIR##g" | sed "s#-I\./#-I#g" | sed "s#-L/var/tmp/[^ ]* #-Llib #" >>$command_file
+mpif90 "$@"
diff --git a/test/wrapper_scripts/wrap_pp b/test/wrapper_scripts/wrap_pp
new file mode 100755
index 0000000..0ece941
--- /dev/null
+++ b/test/wrapper_scripts/wrap_pp
@@ -0,0 +1,2 @@
+echo "wrap_pp $@" | sed "s#$RUN_DIR##g" | sed "s#-I\./#-I#g" >>$command_file
+cpp  "$@"
diff --git a/tutorial/README b/tutorial/README
new file mode 100644
index 0000000..a22e5b3
--- /dev/null
+++ b/tutorial/README
@@ -0,0 +1,14 @@
+This directory contains the files necessary to set up a Subversion repository
+for the FCM tutorial.
+
+For example (in bash/ksh):
+
+(shell)$ cd fcm-release/tutorial/ # to this directory
+(shell)$ ./fcm-tutorial-repos-create /path/to/tutorial/repos
+
+The repository should be configured to allow users write access. You may find
+it easiest to simply allow anonymous access.
+
+A Trac environment should be configured to be associated with the tutorial
+repository. You then need to allow users write access. You may find it easiest
+to set up a number of guest accounts for this purpose.
diff --git a/tutorial/fcm-tutorial-repos-create b/tutorial/fcm-tutorial-repos-create
new file mode 100755
index 0000000..93996d8
--- /dev/null
+++ b/tutorial/fcm-tutorial-repos-create
@@ -0,0 +1,80 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+set -eu
+if (($# < 1)); then
+    echo "Usage: $(basename $0) DEST" >&2
+    exit 1
+fi
+DEST=$1
+if [[ -e $DEST ]]; then
+    echo "$DEST: destination already exists." >&2
+    exit 1
+fi
+THIS_HOME=$(cd $(dirname $0) && pwd)
+WORK_DIR=
+function FINALLY() {
+    trap '' ERR
+    trap '' EXIT
+    cd ~
+    if [[ -n $WORK_DIR ]]; then
+        rm -rf $WORK_DIR
+    fi
+}
+#-------------------------------------------------------------------------------
+function rsyncs() {
+    rsync -a --exclude=".svn" --checksum "$@"
+}
+#-------------------------------------------------------------------------------
+
+WORK_DIR=$(mktemp -d)
+trap FINALLY ERR
+trap FINALLY EXIT
+cd $WORK_DIR
+
+svnadmin create repos
+REPOS_URL=file://$PWD/repos
+svn checkout -q $REPOS_URL working-copy
+mkdir -p working-copy/tutorial/{trunk,branches,tags}
+
+# r1
+rsyncs $THIS_HOME/trunk-r1/* working-copy/tutorial/trunk/
+svn add -q working-copy/tutorial
+svn commit -q -m'tutorial: initial import.' working-copy
+svn update -q working-copy
+
+# r2
+TRUNK_SRC=working-copy/tutorial/trunk/src
+svn move -q $TRUNK_SRC/module/hello_num.f90 $TRUNK_SRC/module/hello_number.f90
+sed -i 's/Hello World/Hello Earth/' $TRUNK_SRC/module/hello_constants.f90
+sed -i 's/Hello World/Hello Earth/' $TRUNK_SRC/subroutine/hello_c.c
+svn commit -q -m'tutorial: World=Earth, and correct module name.' working-copy
+svn update -q working-copy
+
+rsyncs $THIS_HOME/hooks/* repos/hooks/
+curl -o repos/hooks/svnperms.py \
+    http://svn.apache.org/repos/asf/subversion/trunk/tools/hook-scripts/svnperms.py
+chmod +x repos/hooks/svnperms.py
+cat >repos/hooks/svnperms.conf <<__SVNPERMS_CONF__
+[$(basename $DEST)]
+tutorial/branches/[^/]+/.* = *(add,remove,update)
+__SVNPERMS_CONF__
+mkdir -p $(dirname $DEST)
+svnadmin hotcopy repos $DEST
+echo "$DEST: tutorial repository created."
diff --git a/tutorial/hooks/pre-commit b/tutorial/hooks/pre-commit
new file mode 100755
index 0000000..0270e90
--- /dev/null
+++ b/tutorial/hooks/pre-commit
@@ -0,0 +1,7 @@
+#!/bin/bash
+set -eu
+REPOS=$1
+TXN=$2
+SCRIPT=$REPOS/hooks/svnperms.py
+CONFIG=$REPOS/hooks/svnperms.conf
+$SCRIPT -r $REPOS -t $TXN -f $CONFIG
diff --git a/tutorial/trunk-r1/doc/hello.html b/tutorial/trunk-r1/doc/hello.html
new file mode 100644
index 0000000..b779e01
--- /dev/null
+++ b/tutorial/trunk-r1/doc/hello.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
+
+<html>
+<head>
+  <title>Hello</title>
+  <meta name="author" content="FCM Team">
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+</head>
+
+<body>
+  <h1>Hello</h1>
+  <p>Hello from the FCM team!</p>
+</body>
+</html>
diff --git a/tutorial/trunk-r1/fcm-make.cfg b/tutorial/trunk-r1/fcm-make.cfg
new file mode 100644
index 0000000..4251ac0
--- /dev/null
+++ b/tutorial/trunk-r1/fcm-make.cfg
@@ -0,0 +1,5 @@
+steps = extract build
+extract.ns = tutorial
+extract.location[tutorial] = $HERE
+extract.path-root[tutorial] = src
+build.target{task} = link
diff --git a/tutorial/trunk-r1/src/module/hello_constants.f90 b/tutorial/trunk-r1/src/module/hello_constants.f90
new file mode 100644
index 0000000..785961a
--- /dev/null
+++ b/tutorial/trunk-r1/src/module/hello_constants.f90
@@ -0,0 +1,3 @@
+MODULE hello_constants
+CHARACTER(*), PARAMETER :: hello_string='Hello World!'
+END MODULE hello_constants
diff --git a/tutorial/trunk-r1/src/module/hello_num.f90 b/tutorial/trunk-r1/src/module/hello_num.f90
new file mode 100644
index 0000000..fc59ee8
--- /dev/null
+++ b/tutorial/trunk-r1/src/module/hello_num.f90
@@ -0,0 +1,17 @@
+MODULE hello_number
+
+IMPLICIT NONE
+
+PRIVATE
+INTEGER, PARAMETER :: i=0
+INTEGER, PARAMETER :: huge_number=HUGE(i)
+
+PUBLIC hello_huge_number
+
+CONTAINS
+SUBROUTINE hello_huge_number()
+CHARACTER(LEN=*), PARAMETER :: this='hello_huge_number'
+WRITE(*, '(A,I0)') this // ': maximum integer: ', huge_number
+END SUBROUTINE hello_huge_number
+
+END MODULE hello_number
diff --git a/tutorial/trunk-r1/src/program/hello.f90 b/tutorial/trunk-r1/src/program/hello.f90
new file mode 100644
index 0000000..c44289a
--- /dev/null
+++ b/tutorial/trunk-r1/src/program/hello.f90
@@ -0,0 +1,12 @@
+PROGRAM hello
+
+USE Hello_Constants, ONLY: hello_string
+
+IMPLICIT NONE
+INCLUDE 'hello_sub.interface'
+CHARACTER(*), PARAMETER :: this='hello'
+
+WRITE(*, '(A)') this // ': ' // TRIM(hello_string)
+CALL Hello_Sub()
+
+END PROGRAM hello
diff --git a/tutorial/trunk-r1/src/subroutine/hello_c.c b/tutorial/trunk-r1/src/subroutine/hello_c.c
new file mode 100644
index 0000000..711dd87
--- /dev/null
+++ b/tutorial/trunk-r1/src/subroutine/hello_c.c
@@ -0,0 +1,5 @@
+#include <stdio.h>
+
+void hello_c_() {
+    printf ("%s\n", "hello_c: Hello World!");
+}
diff --git a/tutorial/trunk-r1/src/subroutine/hello_sub.f90 b/tutorial/trunk-r1/src/subroutine/hello_sub.f90
new file mode 100644
index 0000000..ba122d7
--- /dev/null
+++ b/tutorial/trunk-r1/src/subroutine/hello_sub.f90
@@ -0,0 +1,15 @@
+SUBROUTINE hello_sub
+
+USE hello_constants, ONLY: hello_string
+USE hello_number, ONLY: hello_huge_number
+
+IMPLICIT NONE
+CHARACTER(*), PARAMETER :: this = 'hello_sub'
+! DEPENDS ON: hello_c.o
+EXTERNAL hello_c
+
+WRITE(*, '(A)') this // ': ' // TRIM(hello_string)
+CALL hello_huge_number()
+CALL hello_c()
+
+END SUBROUTINE hello_sub
diff --git a/usr/bin/fcm b/usr/bin/fcm
new file mode 100755
index 0000000..665112b
--- /dev/null
+++ b/usr/bin/fcm
@@ -0,0 +1,31 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-14 Met Office.
+#
+# This file is part of FCM, tools for managing and building source code.
+#
+# FCM is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# FCM is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FCM. If not, see <http://www.gnu.org/licenses/>.
+#-------------------------------------------------------------------------------
+# Wrapper for "fcm":
+# * Should be installed at a location like "/usr/bin".
+# * Modify the default value for FCM_HOME_ROOT to suit your site.
+#-------------------------------------------------------------------------------
+if [[ -z ${FCM_HOME:-} ]]; then
+    FCM_HOME_ROOT=${FCM_HOME_ROOT:-/opt}
+    FCM_HOME=$FCM_HOME_ROOT/fcm
+    if [[ -n ${FCM_VERSION:-} && -d $FCM_HOME_ROOT/fcm-$FCM_VERSION ]]; then
+        FCM_HOME=$FCM_HOME_ROOT/fcm-$FCM_VERSION
+    fi
+fi
+exec $FCM_HOME/bin/$(basename $0) "$@"

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/fcm.git



More information about the debian-science-commits mailing list