diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8d0ae06b614a8dbebd0c3c9b7a483ba596a600c9..49beaf83d423cd3a0e784b2af0bb8cebd2a745c7 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -134,7 +134,7 @@ variables:
   rules:
     - if:   $SKIP_TESTS != null
       when: never
-    - if:   $CI_COMMIT_MESSAGE =~ /skip-tests/
+    - if:   $CI_COMMIT_MESSAGE =~ /\[skip-tests\]/
       when: never
     - if:   $CI_PIPELINE_SOURCE == "merge_request_event"
       when: on_success
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index aeaf86356c9bd85834b13a825c8ecb20a2658fa8..b9fab5d092b766e33361a50ee96fb52c1f921953 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,6 +2,17 @@ This document contains the ``fslpy`` release history in reverse chronological
 order.
 
 
+3.15.0 (Under development)
+--------------------------
+
+
+Changed
+^^^^^^^
+
+
+* All metadata stored in GIfTI files is now copied by :class:`.GiftiMesh`
+  instances into their :class:`.Meta` store (!416).
+
 
 3.14.1 (Thursday 31st August 2023)
 ----------------------------------
diff --git a/fsl/data/cifti.py b/fsl/data/cifti.py
index ecd193eb9854b2c3198954852f8f9cfc76706bf0..08f67f3688a01a3cdaa2ab45ad2f647279a42eba 100644
--- a/fsl/data/cifti.py
+++ b/fsl/data/cifti.py
@@ -440,11 +440,12 @@ class BrainStructure(object):
         secondary_str = 'AnatomicalStructureSecondary'
         primary = "other"
         secondary = None
-        for meta in [gifti_obj] + gifti_obj.darrays:
-            if primary_str in meta.meta.metadata:
-                primary = meta.meta.metadata[primary_str]
-            if secondary_str in meta.meta.metadata:
-                secondary = meta.meta.metadata[secondary_str]
+
+        for obj in [gifti_obj] + gifti_obj.darrays:
+            if primary_str in obj.meta:
+                primary = obj.meta[primary_str]
+            if secondary_str in obj.meta:
+                secondary = obj.meta[secondary_str]
         anatomy = cls.from_string(primary, issurface=True)
         anatomy.secondary = None if secondary is None else secondary.lower()
         return anatomy
diff --git a/fsl/data/gifti.py b/fsl/data/gifti.py
index 077b7c4e14eea3c84064b45d9d69f9253a5ed742..0ff4c8dd5848e78dd7704a01d6e6db9474ffaca4 100644
--- a/fsl/data/gifti.py
+++ b/fsl/data/gifti.py
@@ -101,9 +101,24 @@ class GiftiMesh(fslmesh.Mesh):
 
         for i, v in enumerate(vertices):
             if i == 0: key = infile
-            else:      key = '{}_{}'.format(infile, i)
+            else:      key = f'{infile}_{i}'
             self.addVertices(v, key, select=(i == 0), fixWinding=fixWinding)
-        self.setMeta(infile, surfimg)
+        self.meta[infile] = surfimg
+
+        # Copy all metadata entries for the GIFTI image
+        for k, v in surfimg.meta.items():
+            self.meta[k] = v
+
+        # and also for each GIFTI data array - triangles
+        # are stored under "faces", and pointsets are
+        # stored under "vertices"/[0,1,2...] (as there may
+        # be multiple pointsets in a file)
+        self.meta['vertices'] = {}
+        for i, arr in enumerate(surfimg.darrays):
+            if arr.intent == constants.NIFTI_INTENT_POINTSET:
+                self.meta['vertices'][i] = dict(arr.meta)
+            elif arr.intent == constants.NIFTI_INTENT_TRIANGLE:
+                self.meta['faces'] = dict(arr.meta)
 
         if vdata is not None:
             self.addVertexData(infile, vdata)
@@ -130,7 +145,7 @@ class GiftiMesh(fslmesh.Mesh):
                     continue
 
                 self.addVertices(vertices[0], sfile, select=False)
-                self.setMeta(sfile, surfimg)
+                self.meta[sfile] = surfimg
 
 
     def loadVertices(self, infile, key=None, *args, **kwargs):
@@ -154,10 +169,10 @@ class GiftiMesh(fslmesh.Mesh):
 
         for i, v in enumerate(vertices):
             if i == 0: key = infile
-            else:      key = '{}_{}'.format(infile, i)
+            else:      key = f'{infile}_{i}'
             vertices[i] = self.addVertices(v, key, *args, **kwargs)
 
-        self.setMeta(infile, surfimg)
+        self.meta[infile] = surfimg
 
         return vertices
 
@@ -221,12 +236,12 @@ def loadGiftiMesh(filename):
     vdata     = [d for d in gimg.darrays if d.intent not in (pscode, tricode)]
 
     if len(triangles) != 1:
-        raise ValueError('{}: GIFTI surface files must contain '
-                         'exactly one triangle array'.format(filename))
+        raise ValueError(f'{filename}: GIFTI surface files must '
+                         'contain exactly one triangle array')
 
     if len(pointsets) == 0:
-        raise ValueError('{}: GIFTI surface files must contain '
-                         'at least one pointset array'.format(filename))
+        raise ValueError(f'{filename}: GIFTI surface files must '
+                         'contain at least one pointset array')
 
     vertices = [ps.data for ps in pointsets]
     indices  = np.atleast_2d(triangles[0].data)
@@ -276,14 +291,14 @@ def prepareGiftiVertexData(darrays, filename=None):
     intents = {d.intent for d in darrays}
 
     if len(intents) != 1:
-        raise ValueError('{} contains multiple (or no) intents'
-                         ': {}'.format(filename, intents))
+        raise ValueError(f'{filename} contains multiple '
+                         f'(or no) intents: {intents}')
 
     intent = intents.pop()
 
     if intent in (constants.NIFTI_INTENT_POINTSET,
                   constants.NIFTI_INTENT_TRIANGLE):
-        raise ValueError('{} contains surface data'.format(filename))
+        raise ValueError(f'{filename} contains surface data')
 
     # Just a single array - return it as-is.
     # n.b. Storing (M, N) data in a single
@@ -298,8 +313,8 @@ def prepareGiftiVertexData(darrays, filename=None):
     vdata = [d.data for d in darrays]
 
     if any([len(d.shape) != 1 for d in vdata]):
-        raise ValueError('{} contains one or more non-vector '
-                         'darrays'.format(filename))
+        raise ValueError(f'{filename} contains one or '
+                         'more non-vector darrays')
 
     vdata = np.vstack(vdata).T
     vdata = vdata.reshape(vdata.shape[0], -1)
@@ -374,7 +389,7 @@ def relatedFiles(fname, ftypes=None):
 
     def searchhcp(match, ftype):
         prefix, space = match
-        template      = '{}.*.{}{}'.format(prefix, space, ftype)
+        template      = f'{prefix}.*.{space}{ftype}'
         return glob.glob(op.join(dirname, template))
 
     # BIDS style - extract all entities (kv
diff --git a/fsl/tests/__init__.py b/fsl/tests/__init__.py
index edd0f7b46b276ba03af3766973e0d5a9ec9b4a67..c96143037145baea9b2c0d177555120794580d28 100644
--- a/fsl/tests/__init__.py
+++ b/fsl/tests/__init__.py
@@ -147,6 +147,8 @@ def testdir(contents=None, suffix=""):
             shutil.rmtree(self.testdir)
 
     return ctx(contents)
+testdir.__test__ = False
+
 
 def make_dummy_files(paths):
     """Creates dummy files for all of the given paths. """
diff --git a/fsl/tests/test_meta.py b/fsl/tests/test_meta.py
index 791f1873b0b070e8b3a61c78aa0e8ae58917f541..a8221b2af6a15c35b36e474883a7a5b2ea8a8d04 100644
--- a/fsl/tests/test_meta.py
+++ b/fsl/tests/test_meta.py
@@ -19,6 +19,8 @@ def test_meta():
     for k, v in data.items():
         assert m.getMeta(k) == v
 
+    assert m.meta == data
+
     assert list(data.keys())   == list(m.metaKeys())
     assert list(data.values()) == list(m.metaValues())
     assert list(data.items())  == list(m.metaItems())
diff --git a/fsl/utils/meta.py b/fsl/utils/meta.py
index 30d18a567021f014f9ae41fcbcb381f43b47155d..2eccff01ea2cf36b070f1839f6f8a69494f14be4 100644
--- a/fsl/utils/meta.py
+++ b/fsl/utils/meta.py
@@ -7,12 +7,9 @@
 """This module provides the :class:`Meta` class. """
 
 
-import collections
-
-
-class Meta(object):
-    """The ``Meta`` class is intended to be used as a mixin for other classes. It
-    is simply a wrapper for a dictionary of key-value pairs.
+class Meta:
+    """The ``Meta`` class is intended to be used as a mixin for other classes.
+    It is simply a wrapper for a dictionary of key-value pairs.
 
     It has a handful of methods allowing you to add and access additional
     metadata associated with an object.
@@ -20,6 +17,7 @@ class Meta(object):
     .. autosummary::
        :nosignatures:
 
+       meta
        metaKeys
        metaValues
        metaItems
@@ -32,11 +30,17 @@ class Meta(object):
         """Initialises a ``Meta`` instance. """
 
         new        = super(Meta, cls).__new__(cls)
-        new.__meta = collections.OrderedDict()
+        new.__meta = {}
 
         return new
 
 
+    @property
+    def meta(self):
+        """Return a reference to the metadata dictionary. """
+        return self.__meta
+
+
     def metaKeys(self):
         """Returns the keys contained in the metadata dictionary
         (``dict.keys``).