[fcm] 04/16: New upstream release fcm-2015.05.0

Alastair McKinstry mckinstry at moszumanska.debian.org
Tue Nov 1 12:16:27 UTC 2016


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

mckinstry pushed a commit to branch debian/master
in repository fcm.

commit 4220d53e4d5753fb6d7ca125244723d3c73d3242
Author: Alastair McKinstry <mckinstry at debian.org>
Date:   Fri Oct 23 16:50:48 2015 +0100

    New upstream release fcm-2015.05.0
---
 CHANGES.md                                         |  57 +++++++++++
 CONTRIBUTING.md                                    |   1 +
 doc/etc/fcm-version.js                             |   2 +-
 doc/user_guide/annex_cfg.html                      |  23 +++--
 doc/user_guide/command_ref.html                    |   2 +-
 doc/user_guide/make.html                           |  41 +++++---
 doc/user_guide/overview.html                       |   6 +-
 lib/FCM/Admin/System.pm                            |  91 ++++++++++++------
 lib/FCM/Admin/Users/LDAP.pm                        |   2 +-
 lib/FCM/Admin/Users/Passwd.pm                      |   2 +-
 lib/FCM/CLI/Parser.pm                              |   7 +-
 lib/FCM/CLI/fcm-branch-create.pod                  |   2 +-
 lib/FCM/Context/Make/Build.pm                      |   4 +-
 lib/FCM/System/Make/Build.pm                       | 107 ++++++++++++++-------
 lib/FCM/System/Make/Preprocess.pm                  |   6 +-
 lib/FCM/Util.pm                                    |  32 ++++--
 lib/FCM/Util/Locator.pm                            |  10 +-
 lib/FCM1/Cm.pm                                     |   5 +-
 sbin/post-commit-bg                                |  20 ++--
 sbin/pre-commit                                    |  17 +++-
 t/fcm-keyword-print/00-simple.t                    |   4 +-
 t/fcm-make/47-build-target-modifier-ns.t           |  93 ++++++++++++++++++
 .../47-build-target-modifier-ns/fcm-make.cfg       |   4 +
 .../src/greet/greet.f90                            |   6 ++
 .../47-build-target-modifier-ns/src/greet/world.nl |   3 +
 .../src/hello/hello.f90                            |   6 ++
 .../47-build-target-modifier-ns/src/hello/world.nl |   3 +
 t/fcm-make/48-build-sha1.t                         |  65 +++++++++++++
 t/fcm-make/48-build-sha1/fcm-make.cfg              |   5 +
 t/fcm-make/48-build-sha1/src/hello.f90             |   3 +
 t/svn-hooks/02-pre-commit.t                        |  12 ++-
 t/svn-hooks/03-post-commit-bg.t                    |  71 ++++++++++----
 32 files changed, 571 insertions(+), 141 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 485ca1a..9913a5b 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -5,6 +5,63 @@ for a full listing of issues for each release.
 
 --------------------------------------------------------------------------------
 
+## 2015.10.0 (2015-10-15)
+
+FCM release 29. Minor update.
+
+### Noteworthy Changes
+
+[#205](https://github.com/metomi/fcm/pull/205):
+fcm make: build: new setting `build.prop{checksum-method}=md5|sha1|...` allows
+users to choose MD5 or one of SHA algorithms in Perls' `Digest::SHA` to
+calculate the checksums of source and target files.
+
+[#204](https://github.com/metomi/fcm/pull/204):
+fcm branch-create: allow `--bob` as a synonym of `--branch-of-branch`.
+
+--------------------------------------------------------------------------------
+
+## 2015.09.0 (2015-09-28)
+
+FCM release 28. Minor update to admin utilities, nothing noteworthy for users.
+
+--------------------------------------------------------------------------------
+
+## 2015.08.0 (2015-08-19)
+
+FCM release 27. Minor update to admin utilities, nothing noteworthy for users.
+
+--------------------------------------------------------------------------------
+
+## 2015.07.0 (2015-07-09)
+
+FCM release 26.
+
+### Noteworthy Changes
+
+[#197](https://github.com/metomi/fcm/pull/197):
+fcm make: build: fix target select modifier.
+* The `category` modifier should now work.
+* The `ns` modifier never worked, and is removed.
+  Instead `task` and `category` selection can now be filtered by name-space.
+
+[#196](https://github.com/metomi/fcm/pull/196):
+pre/post commit: improve changeset size diagnostic
+* pre-commit: log (but don't email) >1MB transactions
+* post-commit: report pre-commit size threshold (normally 10MB) for any >1MB
+  changesets
+
+[#192](https://github.com/metomi/fcm/pull/192):
+Primary location keyword: trailing slashes in values of primary location
+keywords will now removed automatically.
+
+[#191](https://github.com/metomi/fcm/pull/191):
+fcm (branch-)diff --graphical: will now use the option
+`--config-option config:working-copy:exclusive-locking-clients=` to prevent the
+client from being locked.
+
+--------------------------------------------------------------------------------
+
 ## 2015.05.0 (2015-05-28)
 
 FCM release 25.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 214f9c6..d659346 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -30,6 +30,7 @@ below:
 * Matt Shin (Met Office, UK)
 * Matt Pryor (Met Office, UK)
 * Roddy Sharp (Met Office, UK)
+* Stuart Whitehouse (Met Office, UK)
 
 (All contributors are identifiable with email addresses in the version
 control logs or otherwise.)
diff --git a/doc/etc/fcm-version.js b/doc/etc/fcm-version.js
index bdab405..b4b3d48 100644
--- a/doc/etc/fcm-version.js
+++ b/doc/etc/fcm-version.js
@@ -1 +1 @@
-FCM.VERSION="2015.05.0";
+FCM.VERSION="2015.10.0";
diff --git a/doc/user_guide/annex_cfg.html b/doc/user_guide/annex_cfg.html
index a6c7afc..c51c0ee 100644
--- a/doc/user_guide/annex_cfg.html
+++ b/doc/user_guide/annex_cfg.html
@@ -897,20 +897,24 @@ build.ns-incl = foo bar/baz # include items in these name-spaces
 
     <dd>
       <p><dfn>description</dfn>: Selects targets to build according to their
-      keys, categories, name-spaces and tasks.</p>
+      keys, categories 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>
+      specified), <samp>category</samp> or <samp>task</samp></p>
+
+      <p><dfn>namespace</dfn>: Not allowed if modifier is <samp>key</samp>.
+      Optional if modifier is <samp>category</samp> or <samp>task</samp>. If
+      specified, setting only applies to given name-spaces. Otherwise, setting
+      applies to all name-spaces.</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{category}[etc/namelists etc/configs] = etc
+build.target{category}[src/hello src/greet src/world] = bin lib
+build.target{task}[src/public] = archive install
 build.target = egg.sh mince.py
 </pre>
     </dd>
@@ -1054,6 +1058,13 @@ build.prop{fc.libs}[myprog.exe] = netcdf grib
     <dd>The C linker will add each directory in this setting as a library
     search path.</dd>
 
+    <dt id="make.build.prop.checksum-method">checksum-method</dt>
+
+    <dd>The checksum method to use to determine if the content of a file is
+    changed or not. Either MD5 or one of the SHA algorithms supported by the
+    Perl module <a href="http://perldoc.perl.org/Digest/SHA.html">Digest::SHA</a>
+    can be used. (default=<samp>md5</samp>)</dd>
+
     <dt id="make.build.prop.cxx">cxx</dt>
 
     <dd>The C++ compiler and linker. (default=<samp>g++</samp>)</dd>
diff --git a/doc/user_guide/command_ref.html b/doc/user_guide/command_ref.html
index 185eb0f..e778862 100644
--- a/doc/user_guide/command_ref.html
+++ b/doc/user_guide/command_ref.html
@@ -220,7 +220,7 @@
 
     <dd>
       <dl>
-        <dt><code>--branch-of-branch</code></dt>
+        <dt><code>--branch-of-branch</code>, <code>--bob</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
diff --git a/doc/user_guide/make.html b/doc/user_guide/make.html
index 9d5fd4c..bc1403b 100644
--- a/doc/user_guide/make.html
+++ b/doc/user_guide/make.html
@@ -1742,16 +1742,20 @@ build.prop{fc.flags}[src/food/sausage.f90] = -O4
   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
+# Select targets matching these keys
 build.target = egg.bin ham.o bacon.sh
-# OR (
-#     those doing these tasks
+
+# Select all targets doing tasks "install" or "link"
 build.target{task} = install link
-#     AND in these categories
+
+# Select targets in name-space "foo" or "bar" doing tasks "link"
+build.target{task}[foo bar] = link
+
+# Select all targets in the "bin" category
 build.target{category} = bin
-#     AND in these name-spaces
-build.target{ns} = foo bar/baz
-# )
+
+# Select targets in name-space "foo" in the "etc" category
+build.target{category}[foo] = etc
 </pre>
 
   <p>There are times when an automatic target name is not what you want. In
@@ -1870,7 +1874,7 @@ build.prop{file-ext.bin} = .bin
   A target is considered out of date if:</p>
 
   <ul>
-    <li>the source file's MD5 checksum is changed.</li>
+    <li>the source file's checksum is changed.</li>
 
     <li>a required property is modified.</li>
 
@@ -1888,10 +1892,10 @@ build.prop{file-ext.bin} = .bin
     <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>
+    <li>the target does not exist or its checksum is changed.</li>
   </ul>
 
-  <p>If, after an update, the target's MD5 checksum is the same as before, the
+  <p>If, after an update, the target's 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
@@ -1909,6 +1913,18 @@ build.prop{file-ext.bin} = .bin
   re-link the executable. This allows incremental builds to be more
   efficient.</p>
 
+  <div class="well">
+    <p><strong><i class="icon-pencil"></i> Note - checksum algorithm</strong></p>
+
+    <p>By default, the MD5 algorithm is used to calculate the checksum. This is
+    normally good enough to detect whether a file is modified or not. If this is
+    insufficient for whatever reasons, you can tell the build system to use one
+    of the SHA algorithms supported by the Perl module <a href=
+    "http://perldoc.perl.org/Digest/SHA.html">Digest::SHA</a>, by setting the
+    value of <a href=
+    "annex_cfg.html#make.build.prop.checksum-method">build.prop{checksum-method}</a>.</p>
+  </div>
+
   <h3 id="build.inherit">Build Inheritance</h3>
 
   <p>If a previous build with a similar configuration exists in another
@@ -2193,8 +2209,10 @@ steps = extract preprocess build
 
 # ... some extract configuration
 
+# Switch off preprocessing for all
+preprocess.target{task} =
 # Only preprocess source files in these name-spaces
-preprocess.target{ns} = foo/bar egg/fried.F90
+preprocess.target{task}[foo/bar egg/fried.F90] = process
 # Specifies the macro definitions for the Fortran preprocessor
 preprocess.prop{fpp.defs} = THING=stuff HIGH=tall
 # Specifies the macro definitions for the C preprocessor
@@ -2268,7 +2286,6 @@ preprocess.prop{cpp.defs} = LOWER=lower UNDER LINUX
 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>
diff --git a/doc/user_guide/overview.html b/doc/user_guide/overview.html
index 9d9c1bc..11057e5 100644
--- a/doc/user_guide/overview.html
+++ b/doc/user_guide/overview.html
@@ -101,9 +101,9 @@
   <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>Efficient incremental build. Changes to the 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>
 
diff --git a/lib/FCM/Admin/System.pm b/lib/FCM/Admin/System.pm
index ec353d4..177ceb2 100644
--- a/lib/FCM/Admin/System.pm
+++ b/lib/FCM/Admin/System.pm
@@ -43,6 +43,7 @@ use FCM::Admin::Util qw{
 };
 use Fcntl qw{:mode}; # for S_IRGRP, S_IWGRP, S_IROTH, etc
 use File::Basename qw{basename dirname};
+use File::Compare qw{compare};
 use File::Find qw{find};
 use File::Spec::Functions qw{catfile rel2abs};
 use File::Temp qw{tempdir tempfile};
@@ -195,7 +196,7 @@ sub add_trac_environment {
     );
     $RUN->(
         "adding names and emails of users",
-        sub {manage_users_in_trac_db_of($project, {get_users()})},
+        sub {manage_users_in_trac_db_of($project, get_users())},
     );
     $RUN->(
         "updating configuration file",
@@ -549,12 +550,16 @@ sub get_projects_from_trac_live {
 # these IDs.
 sub get_users {
     my @only_users = @_;
+    my $name = $CONFIG->get_user_info_tool();
     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);
+    my $user_hash_ref = $USER_INFO_TOOL->get_users_info(@only_users);
+    if (!%{$user_hash_ref}) {
+        die("No user found via $name.\n");
+    }
+    return $user_hash_ref;
 }
 
 # ------------------------------------------------------------------------------
@@ -620,23 +625,6 @@ sub housekeep_svn_hook_logs {
 # 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});
@@ -655,9 +643,22 @@ sub install_svn_hook {
             ['TZ', 'UTC'],
         )
     );
+    my %can_clean;
+    my %norm_path_of = ();
+    _get_files_from(
+        catfile($CONFIG->get_fcm_home(), 'etc', 'svn-hooks'),
+        sub {
+            my ($base_name, $path) = @_;
+            if (index($base_name, q{.}) == 0 || -d $path) {
+                return;
+            }
+            $norm_path_of{$base_name} = $path;
+            $can_clean{$base_name} = $path;
+        },
+    );
     # Install hook scripts and associated files
-    for my $key (sort keys(%path_of)) {
-        my $hook_source = $path_of{$key};
+    for my $key (sort keys(%norm_path_of)) {
+        my $hook_source = $norm_path_of{$key};
         my $hook_dest = catfile($project->get_svn_live_hook_path(), $key);
         run_copy($hook_source, $hook_dest);
     }
@@ -676,14 +677,34 @@ sub install_svn_hook {
                     chmod((stat($dest))[2] | S_IRGRP | S_IROTH, $dest);
                 },
             );
-            $path_of{$name} = "^/$name";
+            $can_clean{$name} = "^/$name";
+        }
+    }
+    my %more_path_of = ();
+    _get_files_from(
+        catfile($CONFIG->get_fcm_site_home(), 'svn-hooks', $project->get_name()),
+        sub {
+            my ($base_name, $path) = @_;
+            if (index($base_name, q{.}) == 0 || -d $path) {
+                return;
+            }
+            $more_path_of{$base_name} = $path;
+            $can_clean{$base_name} = $path;
+        },
+    );
+    # Install hook scripts and associated files
+    for my $key (sort keys(%more_path_of)) {
+        my $hook_source = $more_path_of{$key};
+        my $hook_dest = catfile($project->get_svn_live_hook_path(), $key);
+        if (!-e $hook_dest || compare($hook_source, $hook_dest)) {
+            run_copy($hook_source, $hook_dest);
         }
     }
     # 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)})) {
+            if (!exists($can_clean{basename($path)})) {
                 run_rmtree($path);
             }
         }
@@ -980,15 +1001,21 @@ sub _chgrp_and_chmod {
     find(
         sub {
             my $file = $File::Find::name;
-            $RUNNER->run(
-                "changing group ownership for $file",
-                sub {return chown(-1, $gid, $file)},
-            );
+            my $old_gid = (stat($file))[5];
+            if ($old_gid != $gid) {
+                $RUNNER->run(
+                    "changing group ownership for $file",
+                    sub {return chown(-1, $gid, $file)},
+                );
+            }
+            my $old_mode = (stat($file))[2];
             my $mode = (stat($file))[2] | S_IRGRP | S_IWGRP;
-            $RUNNER->run(
-                "adding group write permission for $file",
-                sub {return chmod($mode, $file)},
-            );
+            if ($old_mode != $mode) {
+                $RUNNER->run(
+                    "adding group write permission for $file",
+                    sub {return chmod($mode, $file)},
+                );
+            }
         },
         $path,
     );
diff --git a/lib/FCM/Admin/Users/LDAP.pm b/lib/FCM/Admin/Users/LDAP.pm
index fdd5047..c51ac95 100644
--- a/lib/FCM/Admin/Users/LDAP.pm
+++ b/lib/FCM/Admin/Users/LDAP.pm
@@ -58,7 +58,7 @@ sub _get_users_info {
             });
         }
     }
-    return (wantarray() ? %user_of : \%user_of);
+    return \%user_of;
 }
 
 # Return a list of bad users.
diff --git a/lib/FCM/Admin/Users/Passwd.pm b/lib/FCM/Admin/Users/Passwd.pm
index 259f957..727d4e6 100644
--- a/lib/FCM/Admin/Users/Passwd.pm
+++ b/lib/FCM/Admin/Users/Passwd.pm
@@ -69,7 +69,7 @@ sub _get_users_info {
         });
     }
     endpwent();
-    return (wantarray() ? %user_of : \%user_of);
+    return \%user_of;
 }
 
 # Gets a HASH of users matching @only_users using the POSIX password DB.
diff --git a/lib/FCM/CLI/Parser.pm b/lib/FCM/CLI/Parser.pm
index afab456..b4b9437 100644
--- a/lib/FCM/CLI/Parser.pm
+++ b/lib/FCM/CLI/Parser.pm
@@ -43,7 +43,7 @@ our %OPTION_OF = map {
     [['archive'            ,            ], ['a'], OPT_BOOL],
     [['auto-log'           ,            ], [   ], OPT_BOOL],
     [['branch'             ,            ], ['b'], OPT_BOOL],
-    [['branch-of-branch'   ,            ], [   ], OPT_BOOL],
+    [['branch-of-branch'   , 'bob'      ], [   ], OPT_BOOL],
     [['browser'            ,            ], ['b'], OPT_SCAL],
     [['check'              ,            ], ['c'], OPT_BOOL],
     [['clean'              ,            ], [   ], OPT_BOOL],
@@ -102,7 +102,10 @@ our %HOOK_BEFORE_FOR = (
     'delete' => _get_code_to_match($OPTION_OF{check}),
     'diff'   => sub {
         _get_code_to_replace(
-            $OPTION_OF{graphical}, [qw{--diff-cmd fcm_graphic_diff}]
+            $OPTION_OF{graphical}, [qw{
+                --config-option config:working-copy:exclusive-locking-clients=
+                --diff-cmd fcm_graphic_diff
+            }]
         )->(@_);
         _get_code_to_replace($OPTION_OF{summarize}, ['--summarize'])->(@_);
         _get_code_to_match($OPTION_OF{branch})->(@_);
diff --git a/lib/FCM/CLI/fcm-branch-create.pod b/lib/FCM/CLI/fcm-branch-create.pod
index 5cd802a..6326f71 100644
--- a/lib/FCM/CLI/fcm-branch-create.pod
+++ b/lib/FCM/CLI/fcm-branch-create.pod
@@ -24,7 +24,7 @@ working copy) must be URL under a standard FCM project.
 
 =over 4
 
-=item --branch-of-branch
+=item --branch-of-branch, --bob
 
 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.
diff --git a/lib/FCM/Context/Make/Build.pm b/lib/FCM/Context/Make/Build.pm
index d035a74..cdb78ea 100644
--- a/lib/FCM/Context/Make/Build.pm
+++ b/lib/FCM/Context/Make/Build.pm
@@ -237,7 +237,7 @@ following attributes:
 
 =item checksum
 
-The MD5 checksum of the source file.
+The checksum of the source file.
 
 =item deps
 
@@ -289,7 +289,7 @@ The target category, e.g. bin, etc, include, lib, o, src
 
 =item checksum
 
-The MD5 checksum of the target.
+The checksum of the target.
 
 =item deps
 
diff --git a/lib/FCM/System/Make/Build.pm b/lib/FCM/System/Make/Build.pm
index cbff538..201474b 100644
--- a/lib/FCM/System/Make/Build.pm
+++ b/lib/FCM/System/Make/Build.pm
@@ -60,7 +60,11 @@ our @FILE_TYPE_UTILS = (
 );
 
 # Default target selection
-our %TARGET_SELECT_BY = (task => {});
+our %TARGET_SELECT_BY = (
+    'category' => {},
+    'key'      => {},
+    'task'     => {},
+);
 
 # Configuration parser label to action map
 our %CONFIG_PARSER_OF = (
@@ -75,6 +79,7 @@ our %CONFIG_PARSER_OF = (
 our %PROP_OF = (
     #                               [default       , ns-ok]
     'archive-ok-target-category' => [q{include o}  , undef],
+    'checksum-method'            => [q{}           , undef],
     'ignore-missing-dep-ns'      => [q{}           , undef],
     'no-step-source'             => [q{}           , undef],
     'no-inherit-source'          => [q{}           , undef],
@@ -181,8 +186,16 @@ sub _config_parse_inherit_hook {
     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);
+    while (my ($key, $item_ref) = each(%{$i_ctx->get_target_select_by()})) {
+        while (my ($key2, $attr_set) = each(%{$item_ref})) {
+            if (ref($attr_set)) {
+                $ctx->get_target_select_by()->{$key}{$key2} = {%{$attr_set}};
+            }
+            else {
+                # Backward compat, $key2 is an $attr
+                $ctx->get_target_select_by()->{$key}{q{}}{$key2} = 1;
+            }
+        }
     }
     _config_parse_inherit_hook_prop($attrib_ref, $ctx, $i_ctx);
 }
@@ -211,15 +224,24 @@ sub _config_parse_source {
 sub _config_parse_target {
     my ($attrib_ref, $ctx, $entry) = @_;
     my %modifier_of = %{$entry->get_modifier_of()};
-    if (!keys(%modifier_of)) {
+    if (!(%modifier_of)) {
         %modifier_of = (key => 1);
     }
+    my @ns_list = map {$_ eq q{/} ? q{} : $_} @{$entry->get_ns_list()};
+    if (exists($modifier_of{'key'}) && grep {$_} @ns_list) {
+        return $E->throw($E->CONFIG_NS, $entry);
+    }
+    if (!@ns_list) {
+        @ns_list = (q{});
+    }
     while (my $name = each(%modifier_of)) {
-        if (!grep {$_ eq $name} qw{category key ns task}) {
+        if (!grep {$_ eq $name} qw{category key task}) {
             return $E->throw($E->CONFIG_MODIFIER, $entry);
         }
-        $ctx->get_target_select_by()->{$name}
-            = {map {$_ eq q{/} ? (q{} => 1) : ($_ => 1)} $entry->get_values()};
+        my %attr_set = map {($_ => 1)} $entry->get_values();
+        for my $ns (@ns_list) {
+            $ctx->get_target_select_by()->{$name}{$ns} = \%attr_set;
+        }
     }
 }
 
@@ -282,13 +304,20 @@ sub _config_unparse {
             : ()
         ),
         (   map {
-                FCM::Context::ConfigEntry->new({
-                    label       => $LABEL_OF{'target'},
-                    modifier_of => {$_ => 1},
-                    value       => _config_unparse_join(
-                        keys(%{$ctx->get_target_select_by()->{$_}}),
-                    ),
-                });
+                my $modifier = $_;
+                map {
+                    my $ns = $_;
+                    my @values = sort(keys(
+                        %{$ctx->get_target_select_by()->{$modifier}{$ns}}
+                    ));
+                    FCM::Context::ConfigEntry->new({
+                        label       => $LABEL_OF{'target'},
+                        modifier_of => {$modifier => 1},
+                        ns_list     => [$ns],
+                        value       => _config_unparse_join(@values),
+                    });
+                }
+                sort keys(%{$ctx->get_target_select_by()->{$modifier}});
             }
             sort keys(%{$ctx->get_target_select_by()})
         ),
@@ -526,13 +555,16 @@ sub _sources_type {
 sub _sources_analyse {
     my ($attrib_ref, $m_ctx, $ctx) = @_;
     my $timer = $UTIL->timer();
+    my $checksum_method = _prop($attrib_ref, 'checksum-method', $ctx);
     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->set_checksum(
+                $UTIL->file_checksum($source->get_path(), $checksum_method),
+            );
         }
     }
     # Source information
@@ -677,11 +709,12 @@ sub _targets_update {
         ,
     );
     # Performs targets update
+    my $checksum_method = _prop($attrib_ref, 'checksum-method', $ctx);
     my %stat_of = ();
     eval {
         my $n_jobs = $m_ctx->get_option_of('jobs');
         my $runner = $UTIL->task_runner(
-            sub {_target_update($attrib_ref, @_)},
+            sub {_target_update($attrib_ref, $checksum_method, @_)},
             $n_jobs,
         );
         eval {
@@ -740,7 +773,7 @@ sub _targets_update {
 
 # Updates a target.
 sub _target_update {
-    my ($attrib_ref, $target) = @_;
+    my ($attrib_ref, $checksum_method, $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 ($@) {
@@ -753,7 +786,7 @@ sub _target_update {
         return $E->throw($E->BUILD_TARGET, $target);
     }
     $target->set_status($target->ST_MODIFIED);
-    my $checksum = $UTIL->file_md5($target->get_path());
+    my $checksum = $UTIL->file_checksum($target->get_path(), $checksum_method);
     if ($target->get_checksum() && $checksum eq $target->get_checksum()) {
         $target->set_status($target->ST_UNCHANGED);
         if ($target->get_path_of_prev()) {
@@ -776,6 +809,7 @@ sub _targets_manager_funcs {
     my ($stack_ref, $state_hash_ref)
         = _targets_select($attrib_ref, $m_ctx, $ctx, \@targets);
 
+    my $checksum_method = _prop($attrib_ref, 'checksum-method', $ctx);
     my $get_action_ref = sub {
         STATE:
         while (my $state = pop(@{$stack_ref})) {
@@ -790,7 +824,7 @@ sub _targets_manager_funcs {
                     $stat_hash_ref, $ctx, $target, $state_hash_ref, $stack_ref,
                 );
             }
-            elsif (_target_check_ood($state, $state_hash_ref)) {
+            elsif (_target_check_ood($state, $state_hash_ref, $checksum_method)) {
                 _target_prep($state, $ctx);
                 $state->set_value($STATE->PENDING);
                 # Adds tasks that can be triggered by this task
@@ -1035,18 +1069,23 @@ sub _targets_select {
     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})
-                    )
+        ATTR_NAME:
+        for (
+            #$attr_name, $attr_func
+            ['key'     , sub {$_[0]->get_key()}],
+            ['category', sub {$_[0]->get_category()}],
+            ['task'    , sub {$_[0]->get_task()}],
         ) {
-            $target_set{$target->get_key()} = 1;
+            my ($attr_name, $attr_func) = @{$_};
+            for my $ns (sort keys(%{$select_by{$attr_name}})) {
+                my %attr_set = %{$select_by{$attr_name}->{$ns}};
+                if (    exists($attr_set{$attr_func->($target)})
+                    &&  (!$ns || $UTIL->ns_in_set($target->get_ns(), {$ns => 1}))
+                ) {
+                    $target_set{$target->get_key()} = 1;
+                    last ATTR_NAME;
+                }
+            }
         }
         if (exists($target_of{$target->get_key()})) {
             if (!exists($targets_of{$target->get_key()})) {
@@ -1250,8 +1289,8 @@ sub _targets_select {
     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 (exists($select_by{key}{q{}})) {
+        my @bad_keys = grep {!exists($state_of{$_})} keys(%{$select_by{key}{q{}}});
         if (@bad_keys) {
             return $E->throw($E->BUILD_TARGET_BAD, \@bad_keys);
         }
@@ -1355,7 +1394,7 @@ sub _target_check_failed_dep {
 
 # Returns true if $target is out of date.
 sub _target_check_ood {
-    my ($state, $state_hash_ref) = @_;
+    my ($state, $state_hash_ref, $checksum_method) = @_;
     my $target = $state->get_target();
     # Dependencies
     my $rc;
@@ -1390,7 +1429,7 @@ sub _target_check_ood {
     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->file_checksum($path_of_prev, $checksum_method) ne $checksum
         ||  $UTIL->hash_cmp($prop_hash_ref, $prop_of_prev_hash_ref)
     );
 }
diff --git a/lib/FCM/System/Make/Preprocess.pm b/lib/FCM/System/Make/Preprocess.pm
index aebec0e..1376602 100644
--- a/lib/FCM/System/Make/Preprocess.pm
+++ b/lib/FCM/System/Make/Preprocess.pm
@@ -27,7 +27,11 @@ use FCM::System::Make::Build::FileType::FPP;
 use FCM::System::Make::Build::FileType::H  ;
 
 # Default target selection
-our %TARGET_SELECT_BY = (task => {'process' => 1});
+our %TARGET_SELECT_BY = (
+    'category' => {},
+    'key'      => {},
+    'task'     => {q{} => {'process' => 1}},
+);
 
 # Classes for working with typed source files
 our @FILE_TYPE_UTILS = (
diff --git a/lib/FCM/Util.pm b/lib/FCM/Util.pm
index 17b6d4d..4fbeb22 100644
--- a/lib/FCM/Util.pm
+++ b/lib/FCM/Util.pm
@@ -24,6 +24,7 @@ package FCM::Util;
 use base qw{FCM::Class::CODE};
 
 use Digest::MD5;
+use Digest::SHA;
 use FCM::Context::Event;
 use FCM::Context::Locator;
 use FCM::Util::ConfigReader;
@@ -51,6 +52,7 @@ our %ACTION_OF = (
     config_reader        => _util_of_func('config_reader', 'main'),
     external_cfg_get     => \&_external_cfg_get,
     event                => \&_event,
+    file_checksum        => \&_file_checksum,
     file_ext             => \&_file_ext,
     file_head            => \&_file_head,
     file_load            => \&_file_load,
@@ -244,6 +246,20 @@ sub _event {
     }
 }
 
+# Returns the checksum of the content in a file system path.
+sub _file_checksum {
+    my ($attrib_ref, $path, $algorithm) = @_;
+    my $handle = _file_load_handle($attrib_ref, $path);
+    binmode($handle);
+    $algorithm ||= 'md5';
+    my $digest = $algorithm eq 'md5'
+        ? Digest::MD5->new() : Digest::SHA->new($algorithm);
+    $digest->addfile($handle);
+    my $checksum = $digest->hexdigest();
+    close($handle);
+    return $checksum;
+}
+
 # Returns the file extension of a file system path.
 sub _file_ext {
     my ($attrib_ref, $path) = @_;
@@ -287,13 +303,7 @@ sub _file_load_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;
+    _file_checksum($attrib_ref, $path, 'md5');
 }
 
 # Saves content to a file system path.
@@ -669,6 +679,12 @@ 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_checksum($path, $algorithm)
+
+Returns the checksum of $path. If $algorithm is not specified, the default
+algorithm to use is MD5. Otherwise, any algorithm supported by Perl's
+Digest::SHA module can be used.
+
 =item $u->file_ext($path)
 
 Returns file extension of $path. E.g.:
@@ -695,7 +711,7 @@ Returns a file handle for loading contents from $path.
 
 =item $u->file_md5($path)
 
-Returns the MD5 checksum of $path.
+Deprecated. Equivalent to $u->file_checksum($path, 'md5').
 
 =item $u->file_save($path, $content)
 
diff --git a/lib/FCM/Util/Locator.pm b/lib/FCM/Util/Locator.pm
index e8c98f4..fca808e 100644
--- a/lib/FCM/Util/Locator.pm
+++ b/lib/FCM/Util/Locator.pm
@@ -379,11 +379,12 @@ sub _kw_ctx_load_loc {
     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);
+        _as_normalised($attrib_ref, $locator);
+        my $entry = $attrib_ref->{kw_ctx}->add_entry(
+            $key, $locator->get_value(), {type => $locator->get_type()},
+        );
         for (@KEYWORD_IMPLIED_SUFFICES) {
             my ($value_suffix, $key_suffix_ref) = @{$_};
             my $locator = $ACTION_OF{cat}->($attrib_ref, $locator, $value_suffix);
@@ -396,6 +397,9 @@ sub _kw_ctx_load_loc {
             }
         }
     }
+    else {
+        $attrib_ref->{kw_ctx}->add_entry($key, $value, {type => $type});
+    }
 }
 
 # Loads the revision keyword context from a configuration entry.
diff --git a/lib/FCM1/Cm.pm b/lib/FCM1/Cm.pm
index 9ecb26f..00b77ed 100644
--- a/lib/FCM1/Cm.pm
+++ b/lib/FCM1/Cm.pm
@@ -286,7 +286,10 @@ sub cm_branch_diff {
     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->{graphical}  ? (qw{
+            --config-option config:working-copy:exclusive-locking-clients=
+            --diff-cmd fcm_graphic_diff
+        })
         : $option_ref->{'diff-cmd'} ? ('--diff-cmd', $option_ref->{'diff-cmd'})
         :                             ()
         ;
diff --git a/sbin/post-commit-bg b/sbin/post-commit-bg
index 8ea5d1c..1a248dc 100755
--- a/sbin/post-commit-bg
+++ b/sbin/post-commit-bg
@@ -100,14 +100,22 @@ main() {
     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"
+    local MB=1048576
+    local THRESHOLD=10
+    local SIZE_THRESHOLD_FILE="${REPOS}/hooks/pre-commit-size-threshold.conf"
+    if [[ -f "${SIZE_THRESHOLD_FILE}" && -r "${SIZE_THRESHOLD_FILE}" ]]; then
+        THRESHOLD=$(<"${SIZE_THRESHOLD_FILE}")
+    fi
+    local REV_FILE="${REPOS}/db/revs/$((${REV} / 1000))/${REV}"
+    local REV_FILE_SIZE=$(du -b -s "${REV_FILE}" | cut -f 1)
+    if ((${REV_FILE_SIZE} > ${THRESHOLD} * ${MB})); then
+        echo "REV_FILE_SIZE=${REV_FILE_SIZE} # >${THRESHOLD}MB"
+        RET_CODE=1
+    elif ((${REV_FILE_SIZE} > ${MB})); then
+        echo "REV_FILE_SIZE=${REV_FILE_SIZE} # >1MB <${THRESHOLD}MB"
         RET_CODE=1
     else
-        echo "REV_FILE_SIZE=$REV_FILE_SIZE # within $REV_FILE_SIZE_THRESHOLD"
+        echo "REV_FILE_SIZE=${REV_FILE_SIZE} # <1MB"
     fi
 
     # Install commit.conf and svnperms.conf, if necessary
diff --git a/sbin/pre-commit b/sbin/pre-commit
index dc561dc..568231d 100755
--- a/sbin/pre-commit
+++ b/sbin/pre-commit
@@ -48,13 +48,16 @@ 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"
+LOG_TXN="$REPOS/log/$THIS-$TXN.log"
 
-main() {
+begin() {
     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"
+}
+
+main() {
     local ADMIN_EMAIL=${FCM_SVN_HOOK_ADMIN_EMAIL:-Admin}
 
     # Check size.
@@ -71,6 +74,10 @@ main() {
         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
+    elif ((SIZE > MB)); then
+        # Log any changesets bigger than 1MB
+        SIZE=$(du -h "$TXN_FILE" | cut -f 1)
+        echo "$NAME@$TXN: changeset size ${SIZE}B exceeds 1MB." >&2
     fi
 
     # Check permission.
@@ -97,7 +104,9 @@ main() {
 }
 
 RET_CODE=0
-if ! main 1>"$LOG_TXN" 2>&1; then
+begin 1>"${LOG_TXN}" 2>&1
+LOG_TXN_SIZE="$(stat -c '%s' "${LOG_TXN}")"
+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
@@ -105,6 +114,8 @@ if ! main 1>"$LOG_TXN" 2>&1; then
     cat "$LOG_TXN"
     cat "$LOG_TXN" >&2
     RET_CODE=1
+elif [[ "${LOG_TXN_SIZE}" != "$(stat -c '%s' "${LOG_TXN}")" ]]; then
+    cat "$LOG_TXN"
 fi
 rm -f "$LOG_TXN"
 exit $RET_CODE
diff --git a/t/fcm-keyword-print/00-simple.t b/t/fcm-keyword-print/00-simple.t
index 0b70a01..23bbf03 100755
--- a/t/fcm-keyword-print/00-simple.t
+++ b/t/fcm-keyword-print/00-simple.t
@@ -25,8 +25,8 @@ 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}[daisy]=$URL/daisy/
+location{primary}[ivy]=$URL/ivy//
 location{primary}[holly]=$URL/holly
 __CFG__
 export FCM_CONF_PATH=$PWD/conf
diff --git a/t/fcm-make/47-build-target-modifier-ns.t b/t/fcm-make/47-build-target-modifier-ns.t
new file mode 100755
index 0000000..60987e5
--- /dev/null
+++ b/t/fcm-make/47-build-target-modifier-ns.t
@@ -0,0 +1,93 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-15 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.target{category} and build.target{task} with namespace.
+#-------------------------------------------------------------------------------
+. "$(dirname "$0")/test_header"
+#-------------------------------------------------------------------------------
+tests 12
+mkdir 'i0'
+cp -r "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}/"* 'i0'
+#-------------------------------------------------------------------------------
+TEST_KEY="${TEST_KEY_BASE}"
+run_pass "${TEST_KEY}" fcm make -C 'i0'
+
+grep -F 'build.target{category}' 'i0/.fcm-make/config-on-success.cfg' \
+    >'edited-config-on-success.cfg'
+file_cmp "${TEST_KEY}-edited-config-on-success.cfg" \
+    'edited-config-on-success.cfg' <<'__CFG__'
+build.target{category}[hello] = bin
+__CFG__
+
+find 'i0/build' -type f | sort >'find-i0-build.out'
+file_cmp "${TEST_KEY}-find-i0-build.out" 'find-i0-build.out' <<'__FIND__'
+i0/build/bin/hello
+i0/build/o/hello.o
+__FIND__
+
+"${PWD}/i0/build/bin/hello" <<<'&world_nl /' >'hello.out'
+file_cmp "${TEST_KEY}-hello.out" 'hello.out' <<<'Hello World'
+#-------------------------------------------------------------------------------
+TEST_KEY="${TEST_KEY_BASE}-inherit"
+run_pass "${TEST_KEY}" fcm make -C 'i1' \
+    "use=${PWD}/i0" \
+    'build.target{category}[greet] = etc'
+
+grep -F 'build.target{category}' 'i1/.fcm-make/config-on-success.cfg' \
+    >'edited-config-on-success.cfg'
+file_cmp "${TEST_KEY}-edited-config-on-success.cfg" \
+    'edited-config-on-success.cfg' <<'__CFG__'
+build.target{category}[greet] = etc
+build.target{category}[hello] = bin
+__CFG__
+
+find 'i1/build' -type f | sort >'find-i1-build.out'
+file_cmp "${TEST_KEY}-find-i1-build.out" 'find-i1-build.out' <<'__FIND__'
+i1/build/bin/hello
+i1/build/etc/greet/.etc
+i1/build/etc/greet/world.nl
+__FIND__
+
+"${PWD}/i1/build/bin/hello" <'i1/build/etc/greet/world.nl' >'hello.out'
+file_cmp "${TEST_KEY}-hello.out" 'hello.out' <<<'Hello Earth'
+#-------------------------------------------------------------------------------
+TEST_KEY="${TEST_KEY_BASE}-inherit-incr"
+touch 'before'
+run_pass "${TEST_KEY}" fcm make -C 'i1' \
+    "use=${PWD}/i0" \
+    'build.target{category}[greet] = bin etc'
+
+grep -F 'build.target{category}' 'i1/.fcm-make/config-on-success.cfg' \
+    >'edited-config-on-success.cfg'
+file_cmp "${TEST_KEY}-edited-config-on-success.cfg" \
+    'edited-config-on-success.cfg' <<'__CFG__'
+build.target{category}[greet] = bin etc
+build.target{category}[hello] = bin
+__CFG__
+
+find 'i1/build' -type f -newer 'before' | sort >'find-i1-build.out'
+file_cmp "${TEST_KEY}-find-i1-build.out" 'find-i1-build.out' <<'__FIND__'
+i1/build/bin/greet
+i1/build/o/greet.o
+__FIND__
+
+"${PWD}/i1/build/bin/greet" <'i1/build/etc/greet/world.nl' >'greet.out'
+file_cmp "${TEST_KEY}-greet.out" 'greet.out' <<<'Greet Earth'
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/47-build-target-modifier-ns/fcm-make.cfg b/t/fcm-make/47-build-target-modifier-ns/fcm-make.cfg
new file mode 100644
index 0000000..22b9e6c
--- /dev/null
+++ b/t/fcm-make/47-build-target-modifier-ns/fcm-make.cfg
@@ -0,0 +1,4 @@
+steps=build
+build.source=$HERE/src
+build.prop{file-ext.bin}=
+build.target{category}[hello] = bin
diff --git a/t/fcm-make/47-build-target-modifier-ns/src/greet/greet.f90 b/t/fcm-make/47-build-target-modifier-ns/src/greet/greet.f90
new file mode 100644
index 0000000..0f2cad4
--- /dev/null
+++ b/t/fcm-make/47-build-target-modifier-ns/src/greet/greet.f90
@@ -0,0 +1,6 @@
+program greet
+character(31) :: world = 'World'
+namelist /world_nl/ world
+read(*, nml=world_nl)
+write(*, '(a,1x,a)') 'Greet', trim(world)
+end program greet
diff --git a/t/fcm-make/47-build-target-modifier-ns/src/greet/world.nl b/t/fcm-make/47-build-target-modifier-ns/src/greet/world.nl
new file mode 100644
index 0000000..c20aea5
--- /dev/null
+++ b/t/fcm-make/47-build-target-modifier-ns/src/greet/world.nl
@@ -0,0 +1,3 @@
+&world_nl
+world='Earth',
+/
diff --git a/t/fcm-make/47-build-target-modifier-ns/src/hello/hello.f90 b/t/fcm-make/47-build-target-modifier-ns/src/hello/hello.f90
new file mode 100644
index 0000000..bfed897
--- /dev/null
+++ b/t/fcm-make/47-build-target-modifier-ns/src/hello/hello.f90
@@ -0,0 +1,6 @@
+program hello
+character(31) :: world = 'World'
+namelist /world_nl/ world
+read(*, nml=world_nl)
+write(*, '(a,1x,a)') 'Hello', trim(world)
+end program hello
diff --git a/t/fcm-make/47-build-target-modifier-ns/src/hello/world.nl b/t/fcm-make/47-build-target-modifier-ns/src/hello/world.nl
new file mode 100644
index 0000000..072e589
--- /dev/null
+++ b/t/fcm-make/47-build-target-modifier-ns/src/hello/world.nl
@@ -0,0 +1,3 @@
+&world_nl
+world='Jupiter',
+/
diff --git a/t/fcm-make/48-build-sha1.t b/t/fcm-make/48-build-sha1.t
new file mode 100755
index 0000000..8a13cca
--- /dev/null
+++ b/t/fcm-make/48-build-sha1.t
@@ -0,0 +1,65 @@
+#!/bin/bash
+#-------------------------------------------------------------------------------
+# (C) British Crown Copyright 2006-15 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, using SHA1 checksum to determine changes.
+#-------------------------------------------------------------------------------
+. "$(dirname "$0")/test_header"
+
+get-hello-checksum() {
+    PERL5LIB="${FCM_HOME}/lib" perl - <<'__PERL__'
+use strict;
+use warnings;
+use IO::Uncompress::Gunzip qw{gunzip};
+use Storable;
+gunzip('.fcm-make/ctx.gz', 'ctx');
+my $m_ctx = retrieve('ctx');
+printf(
+    "%s  %s\n",
+    $m_ctx->{'ctx_of'}{'build'}{'source_of'}{'hello.f90'}{'checksum'},
+    'src/hello.f90',
+);
+unlink('ctx')
+__PERL__
+}
+#-------------------------------------------------------------------------------
+tests 8
+cp -r "${TEST_SOURCE_DIR}/${TEST_KEY_BASE}/"* .
+#-------------------------------------------------------------------------------
+TEST_KEY="${TEST_KEY_BASE}"
+run_pass "${TEST_KEY}" fcm make
+run_pass "${TEST_KEY}-sha1sum-check" sha1sum -c - <<<"$(get-hello-checksum)"
+HELLO_O_MTIME="$(stat -c '%Y' 'build/o/hello.o')"
+#-------------------------------------------------------------------------------
+TEST_KEY="${TEST_KEY_BASE}-incr"
+run_pass "${TEST_KEY}" fcm make
+run_pass "${TEST_KEY}-sha1sum-check" sha1sum -c - <<<"$(get-hello-checksum)"
+HELLO_O_MTIME_INCR="$(stat -c '%Y' 'build/o/hello.o')"
+run_pass "${TEST_KEY}-mtime-of-hello.o" \
+    test "${HELLO_O_MTIME_INCR}" -eq "${HELLO_O_MTIME}"
+#-------------------------------------------------------------------------------
+TEST_KEY="${TEST_KEY_BASE}-incr-2"
+sed -i 's/Hello/Greet/' 'src/hello.f90'
+sleep 1  # In case computer is very fast, when everything can happen within 1s.
+run_pass "${TEST_KEY}" fcm make
+run_pass "${TEST_KEY}-sha1sum-check" sha1sum -c - <<<"$(get-hello-checksum)"
+HELLO_O_MTIME_INCR="$(stat -c '%Y' 'build/o/hello.o')"
+run_pass "${TEST_KEY}-mtime-of-hello.o" \
+    test "${HELLO_O_MTIME_INCR}" -gt "${HELLO_O_MTIME}"
+#-------------------------------------------------------------------------------
+exit 0
diff --git a/t/fcm-make/48-build-sha1/fcm-make.cfg b/t/fcm-make/48-build-sha1/fcm-make.cfg
new file mode 100644
index 0000000..08e9fcf
--- /dev/null
+++ b/t/fcm-make/48-build-sha1/fcm-make.cfg
@@ -0,0 +1,5 @@
+steps=build
+build.source=$HERE/src
+build.prop{checksum-method} = sha1
+build.prop{file-ext.bin}=
+build.target{task} = link
diff --git a/t/fcm-make/48-build-sha1/src/hello.f90 b/t/fcm-make/48-build-sha1/src/hello.f90
new file mode 100644
index 0000000..b43411d
--- /dev/null
+++ b/t/fcm-make/48-build-sha1/src/hello.f90
@@ -0,0 +1,3 @@
+program hello
+write(*, '(a)') 'Hello World!'
+end program hello
diff --git a/t/svn-hooks/02-pre-commit.t b/t/svn-hooks/02-pre-commit.t
index f62cb41..d81234e 100755
--- a/t/svn-hooks/02-pre-commit.t
+++ b/t/svn-hooks/02-pre-commit.t
@@ -180,9 +180,17 @@ 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"
+TXN="$(<'txn')"
 # 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
+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 1MB.
+__LOG__
+run_fail "$TEST_KEY.mail.out" test -e 'mail.out'
 #-------------------------------------------------------------------------------
 TEST_KEY="$TEST_KEY_BASE-custom-1" # block by custom script
 test_tidy
diff --git a/t/svn-hooks/03-post-commit-bg.t b/t/svn-hooks/03-post-commit-bg.t
index d63d9f9..444add0 100755
--- a/t/svn-hooks/03-post-commit-bg.t
+++ b/t/svn-hooks/03-post-commit-bg.t
@@ -29,15 +29,16 @@ test_tidy() {
         "$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
+        'file1' \
+        'file2' \
+        'file3' \
+        'file4' \
+        'file5' \
+        'svnperms.conf' \
+        'mail.out'
 }
 #-------------------------------------------------------------------------------
-tests 38
+tests 40
 #-------------------------------------------------------------------------------
 cp -p "$FCM_HOME/etc/svn-hooks/post-commit" "$REPOS_PATH/hooks/"
 sed -i "/set -eu/a\
@@ -58,7 +59,7 @@ svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip \\
     | (dd 'conv=fsync' "of=$PWD/svn-dumps/foo-$REV-tmp.gz" 2>/dev/null)
 * Dumped revision $REV.
 mv "$PWD/svn-dumps/foo-$REV-tmp.gz" "$PWD/svn-dumps/foo-$REV.gz"
-REV_FILE_SIZE=??? # within 1048576
+REV_FILE_SIZE=??? # <1MB
 RET_CODE=0
 __LOG__
 if [[ -n ${TRAC_ENV_PATH:-} ]] && ! $TRAC_RESYNC; then
@@ -95,7 +96,7 @@ svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip \\
     | (dd 'conv=fsync' "of=$PWD/svn-dumps/foo-$REV-tmp.gz" 2>/dev/null)
 * Dumped revision $REV.
 mv "$PWD/svn-dumps/foo-$REV-tmp.gz" "$PWD/svn-dumps/foo-$REV.gz"
-REV_FILE_SIZE=??? # within 1048576
+REV_FILE_SIZE=??? # <1MB
 svnlook cat $REPOS_PATH ${NAME} >$REPOS_PATH/hooks/${NAME}
 RET_CODE=0
 __LOG__
@@ -123,7 +124,7 @@ svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip \\
     | (dd 'conv=fsync' "of=$PWD/svn-dumps/foo-$REV-tmp.gz" 2>/dev/null)
 * Dumped revision $REV.
 mv "$PWD/svn-dumps/foo-$REV-tmp.gz" "$PWD/svn-dumps/foo-$REV.gz"
-REV_FILE_SIZE=??? # within 1048576
+REV_FILE_SIZE=??? # <1MB
 svnlook cat $REPOS_PATH ${NAME} >$REPOS_PATH/hooks/${NAME}
 RET_CODE=0
 __LOG__
@@ -145,7 +146,7 @@ svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip \\
     | (dd 'conv=fsync' "of=$PWD/svn-dumps/foo-$REV-tmp.gz" 2>/dev/null)
 * Dumped revision $REV.
 mv "$PWD/svn-dumps/foo-$REV-tmp.gz" "$PWD/svn-dumps/foo-$REV.gz"
-REV_FILE_SIZE=??? # within 1048576
+REV_FILE_SIZE=??? # <1MB
 rm -f $REPOS_PATH/hooks/${NAME}
 RET_CODE=0
 __LOG__
@@ -167,7 +168,7 @@ svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip \\
     | (dd 'conv=fsync' "of=$PWD/svn-dumps/foo-$REV-tmp.gz" 2>/dev/null)
 * Dumped revision $REV.
 mv "$PWD/svn-dumps/foo-$REV-tmp.gz" "$PWD/svn-dumps/foo-$REV.gz"
-REV_FILE_SIZE=??? # EXCEED 1048576
+REV_FILE_SIZE=??? # >1MB <10MB
 RET_CODE=1
 __LOG__
 date2datefmt mail.out \
@@ -180,19 +181,51 @@ svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip \\
     | (dd 'conv=fsync' "of=$PWD/svn-dumps/foo-$REV-tmp.gz" 2>/dev/null)
 * Dumped revision $REV.
 mv "$PWD/svn-dumps/foo-$REV-tmp.gz" "$PWD/svn-dumps/foo-$REV.gz"
-REV_FILE_SIZE=??? # EXCEED 1048576
+REV_FILE_SIZE=??? # >1MB <10MB
+RET_CODE=1
+__LOG__
+#-------------------------------------------------------------------------------
+TEST_KEY="$TEST_KEY_BASE-size-2"
+test_tidy
+perl -e 'map {print(rand())} 1..2097152' >'file3' # compress should be >10MB
+svn import --no-auth-cache -q -m"${TEST_KEY}" 'file3' "${REPOS_URL}/file3"
+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 \\
+    | (dd 'conv=fsync' "of=${PWD}/svn-dumps/foo-${REV}-tmp.gz" 2>/dev/null)
+* Dumped revision ${REV}.
+mv "${PWD}/svn-dumps/foo-${REV}-tmp.gz" "${PWD}/svn-dumps/foo-${REV}.gz"
+REV_FILE_SIZE=??? # >10MB
+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 \\
+    | (dd 'conv=fsync' "of=${PWD}/svn-dumps/foo-${REV}-tmp.gz" 2>/dev/null)
+* Dumped revision ${REV}.
+mv "${PWD}/svn-dumps/foo-${REV}-tmp.gz" "${PWD}/svn-dumps/foo-${REV}.gz"
+REV_FILE_SIZE=??? # >10MB
 RET_CODE=1
 __LOG__
 #-------------------------------------------------------------------------------
 TEST_KEY="$TEST_KEY_BASE-custom-1" # good custom
 test_tidy
-touch file3
+touch file4
 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"
+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"
@@ -205,7 +238,7 @@ svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip \\
     | (dd 'conv=fsync' "of=$PWD/svn-dumps/foo-$REV-tmp.gz" 2>/dev/null)
 * Dumped revision $REV.
 mv "$PWD/svn-dumps/foo-$REV-tmp.gz" "$PWD/svn-dumps/foo-$REV.gz"
-REV_FILE_SIZE=??? # within 1048576
+REV_FILE_SIZE=??? # <1MB
 $REPOS_PATH/hooks/post-commit-bg-custom $REPOS_PATH $REV $TXN
 $REPOS_PATH $REV $TXN
 RET_CODE=0
@@ -220,8 +253,8 @@ 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"
+touch file5
+svn import --no-auth-cache -q -m"$TEST_KEY" file5 "$REPOS_URL/file5"
 REV=$(<rev)
 TXN=$(<txn)
 poll 10 grep -q '^RET_CODE=' "$REPOS_PATH/log/post-commit.log"
@@ -234,7 +267,7 @@ svnadmin dump -r$REV --incremental --deltas $REPOS_PATH | gzip \\
     | (dd 'conv=fsync' "of=$PWD/svn-dumps/foo-$REV-tmp.gz" 2>/dev/null)
 * Dumped revision $REV.
 mv "$PWD/svn-dumps/foo-$REV-tmp.gz" "$PWD/svn-dumps/foo-$REV.gz"
-REV_FILE_SIZE=??? # within 1048576
+REV_FILE_SIZE=??? # <1MB
 $REPOS_PATH/hooks/post-commit-background-custom $REPOS_PATH $REV $TXN
 I have gone to the dark side.
 RET_CODE=1

-- 
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