Rev 6404: (jelmer) Add support for feature flags in bzr formats. (Jelmer Vernooij) in file:///srv/pqm.bazaar-vcs.org/archives/thelove/bzr/%2Btrunk/

Patch Queue Manager pqm at pqm.ubuntu.com
Thu Dec 22 18:53:00 UTC 2011


At file:///srv/pqm.bazaar-vcs.org/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 6404 [merge]
revision-id: pqm at pqm.ubuntu.com-20111222185258-wgcba8590pbw5sf1
parent: pqm at pqm.ubuntu.com-20111222164440-bz1x4fw3k6aweupn
parent: jelmer at samba.org-20111222173020-qvr7v0cf5fs02z43
committer: Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Thu 2011-12-22 18:52:58 +0000
message:
  (jelmer) Add support for feature flags in bzr formats. (Jelmer Vernooij)
modified:
  bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
  bzrlib/bzrdir.py               bzrdir.py-20060131065624-156dfea39c4387cb
  bzrlib/controldir.py           controldir.py-20100802102926-hvtvh0uae5epuibp-1
  bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
  bzrlib/repository.py           rev_storage.py-20051111201905-119e9401e46257e3
  bzrlib/tests/test_branch.py    test_branch.py-20060116013032-97819aa07b8ab3b5
  bzrlib/tests/test_bzrdir.py    test_bzrdir.py-20060131065654-deba40eef51cf220
  bzrlib/tests/test_repository.py test_repository.py-20060131075918-65c555b881612f4d
  bzrlib/tests/test_workingtree.py testworkingtree.py-20051004024258-b88d0fe8f101d468
  bzrlib/workingtree.py          workingtree.py-20050511021032-29b6ec0a681e02e3
  bzrlib/workingtree_3.py        workingtree_3.py-20110503234428-nwa1nw7zfdd0hrw8-1
  bzrlib/workingtree_4.py        workingtree_4.py-20070208044105-5fgpc5j3ljlh5q6c-1
  doc/developers/feature-flags.txt featureflags.txt-20111014011252-hvtwde3cv5dusxnb-1
  doc/en/release-notes/bzr-2.5.txt bzr2.5.txt-20110708125756-587p0hpw7oke4h05-1
=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py	2011-12-21 20:43:29 +0000
+++ b/bzrlib/branch.py	2011-12-22 18:52:58 +0000
@@ -53,6 +53,7 @@
 import bzrlib.bzrdir
 
 from bzrlib import (
+    bzrdir,
     controldir,
     )
 from bzrlib.decorators import (
@@ -1587,7 +1588,7 @@
 
     Formats provide three things:
      * An initialization routine,
-     * a format string,
+     * a format description
      * an open routine.
 
     Formats are placed in an dict by their format string for reference
@@ -1999,13 +2000,13 @@
             self.revision_id)
 
 
-class BranchFormatMetadir(bzrdir.BzrDirMetaComponentFormat, BranchFormat):
+class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
     """Base class for branch formats that live in meta directories.
     """
 
     def __init__(self):
         BranchFormat.__init__(self)
-        bzrdir.BzrDirMetaComponentFormat.__init__(self)
+        bzrdir.BzrFormat.__init__(self)
 
     @classmethod
     def find_format(klass, controldir, name=None):
@@ -2049,7 +2050,7 @@
         control_files.create_lock()
         control_files.lock_write()
         try:
-            utf8_files += [('format', self.get_format_string())]
+            utf8_files += [('format', self.as_string())]
             for (filename, content) in utf8_files:
                 branch_transport.put_bytes(
                     filename, content,
@@ -2097,6 +2098,14 @@
     def supports_leaving_lock(self):
         return True
 
+    def check_support_status(self, allow_unsupported, recommend_upgrade=True,
+            basedir=None):
+        BranchFormat.check_support_status(self,
+            allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
+            basedir=basedir)
+        bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
+            recommend_upgrade=recommend_upgrade, basedir=basedir)
+
 
 class BzrBranchFormat5(BranchFormatMetadir):
     """Bzr branch format 5.
@@ -2304,7 +2313,7 @@
         branch_transport = a_bzrdir.get_branch_transport(self, name=name)
         branch_transport.put_bytes('location',
             target_branch.user_url)
-        branch_transport.put_bytes('format', self.get_format_string())
+        branch_transport.put_bytes('format', self.as_string())
         branch = self.open(
             a_bzrdir, name, _found=True,
             possible_transports=[target_branch.bzrdir.root_transport])
@@ -3218,7 +3227,7 @@
 
         # Copying done; now update target format
         new_branch._transport.put_bytes('format',
-            format.get_format_string(),
+            format.as_string(),
             mode=new_branch.bzrdir._get_file_mode())
 
         # Clean up old files
@@ -3237,7 +3246,7 @@
         format = BzrBranchFormat7()
         branch._set_config_location('stacked_on_location', '')
         # update target format
-        branch._transport.put_bytes('format', format.get_format_string())
+        branch._transport.put_bytes('format', format.as_string())
 
 
 class Converter7to8(object):
@@ -3247,7 +3256,7 @@
         format = BzrBranchFormat8()
         branch._transport.put_bytes('references', '')
         # update target format
-        branch._transport.put_bytes('format', format.get_format_string())
+        branch._transport.put_bytes('format', format.as_string())
 
 
 class InterBranch(InterObject):

=== modified file 'bzrlib/bzrdir.py'
--- a/bzrlib/bzrdir.py	2011-12-19 13:23:58 +0000
+++ b/bzrlib/bzrdir.py	2011-12-22 15:33:16 +0000
@@ -1102,7 +1102,7 @@
         return self.transport.clone(path)
 
 
-class BzrDirMetaComponentFormat(controldir.ControlComponentFormat):
+class BzrFormat(object):
     """Base class for all formats of things living in metadirs.
 
     This class manages the format string that is stored in the 'format'
@@ -1113,36 +1113,105 @@
     (i.e. different from .bzr/branch-format) derive from this class,
     as well as the relevant base class for their kind
     (BranchFormat, WorkingTreeFormat, RepositoryFormat).
+
+    Each format is identified by a "format" or "branch-format" file with a
+    single line containing the base format name and then an optional list of
+    feature flags.
+
+    Feature flags are supported as of bzr 2.5. Setting feature flags on formats
+    will render them inaccessible to older versions of bzr.
+
+    :ivar features: Dictionary mapping feature names to their necessity
     """
 
+    _present_features = set()
+
+    def __init__(self):
+        self.features = {}
+
+    @classmethod
+    def register_feature(cls, name):
+        """Register a feature as being present.
+
+        :param name: Name of the feature
+        """
+        if " " in name:
+            raise ValueError("spaces are not allowed in feature names")
+        if name in cls._present_features:
+            raise errors.FeatureAlreadyRegistered(name)
+        cls._present_features.add(name)
+
+    @classmethod
+    def unregister_feature(cls, name):
+        """Unregister a feature."""
+        cls._present_features.remove(name)
+
+    def check_support_status(self, allow_unsupported, recommend_upgrade=True,
+            basedir=None):
+        for name, necessity in self.features.iteritems():
+            if name in self._present_features:
+                continue
+            if necessity == "optional":
+                mutter("ignoring optional missing feature %s", name)
+                continue
+            elif necessity == "required":
+                raise errors.MissingFeature(name)
+            else:
+                mutter("treating unknown necessity as require for %s",
+                       name)
+                raise errors.MissingFeature(name)
+
     @classmethod
     def get_format_string(cls):
         """Return the ASCII format string that identifies this format."""
         raise NotImplementedError(cls.get_format_string)
 
     @classmethod
-    def from_string(cls, format_string):
-        if format_string != cls.get_format_string():
-            raise ValueError("Invalid format header %r" % format_string)
-        return cls()
+    def from_string(cls, text):
+        format_string = cls.get_format_string()
+        if not text.startswith(format_string):
+            raise AssertionError("Invalid format header %r for %r" % (text, cls))
+        lines = text[len(format_string):].splitlines()
+        ret = cls()
+        for lineno, line in enumerate(lines):
+            try:
+                (necessity, feature) = line.split(" ", 1)
+            except ValueError:
+                raise errors.ParseFormatError(format=cls, lineno=lineno+2,
+                    line=line, text=text)
+            ret.features[feature] = necessity
+        return ret
+
+    def as_string(self):
+        """Return the string representation of this format.
+        """
+        lines = [self.get_format_string()]
+        lines.extend([("%s %s\n" % (item[1], item[0])) for item in
+            self.features.iteritems()])
+        return "".join(lines)
 
     @classmethod
     def _find_format(klass, registry, kind, format_string):
         try:
-            cls = registry.get(format_string)
+            first_line = format_string[:format_string.index("\n")+1]
+        except ValueError:
+            first_line = format_string
+        try:
+            cls = registry.get(first_line)
         except KeyError:
-            raise errors.UnknownFormatError(format=format_string, kind=kind)
-        return cls
+            raise errors.UnknownFormatError(format=first_line, kind=kind)
+        return cls.from_string(format_string)
 
     def network_name(self):
         """A simple byte string uniquely identifying this format for RPC calls.
 
         Metadir branch formats use their format string.
         """
-        return self.get_format_string()
+        return self.as_string()
 
     def __eq__(self, other):
-        return (self.__class__ is other.__class__)
+        return (self.__class__ is other.__class__ and
+                self.features == other.features)
 
 
 class BzrProber(controldir.Prober):
@@ -1169,9 +1238,13 @@
         except errors.NoSuchFile:
             raise errors.NotBranchError(path=transport.base)
         try:
-            cls = klass.formats.get(format_string)
+            first_line = format_string[:format_string.index("\n")+1]
+        except ValueError:
+            first_line = format_string
+        try:
+            cls = klass.formats.get(first_line)
         except KeyError:
-            raise errors.UnknownFormatError(format=format_string, kind='bzrdir')
+            raise errors.UnknownFormatError(format=first_line, kind='bzrdir')
         return cls.from_string(format_string)
 
     @classmethod
@@ -1221,7 +1294,7 @@
         return set([RemoteBzrDirFormat()])
 
 
-class BzrDirFormat(controldir.ControlDirFormat):
+class BzrDirFormat(BzrFormat, controldir.ControlDirFormat):
     """ControlDirFormat base class for .bzr/ directories.
 
     Formats are placed in a dict by their format string for reference
@@ -1238,11 +1311,6 @@
     # _lock_class must be set in subclasses to the lock type, typ.
     # TransportLock or LockDir
 
-    @classmethod
-    def get_format_string(cls):
-        """Return the ASCII format string that identifies this format."""
-        raise NotImplementedError(cls.get_format_string)
-
     def initialize_on_transport(self, transport):
         """Initialize a new bzrdir in the base directory of a Transport."""
         try:
@@ -1433,11 +1501,20 @@
             compatible with whatever sub formats are supported by self.
         :return: None.
         """
+        other_format.features = dict(self.features)
 
     def supports_transport(self, transport):
         # bzr formats can be opened over all known transports
         return True
 
+    def check_support_status(self, allow_unsupported, recommend_upgrade=True,
+            basedir=None):
+        controldir.ControlDirFormat.check_support_status(self,
+            allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
+            basedir=basedir)
+        BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
+            recommend_upgrade=recommend_upgrade, basedir=basedir)
+
 
 class BzrDirMetaFormat1(BzrDirFormat):
     """Bzr meta control format 1
@@ -1459,6 +1536,7 @@
     colocated_branches = False
 
     def __init__(self):
+        BzrDirFormat.__init__(self)
         self._workingtree_format = None
         self._branch_format = None
         self._repository_format = None
@@ -1470,6 +1548,8 @@
             return False
         if other.workingtree_format != self.workingtree_format:
             return False
+        if other.features != self.features:
+            return False
         return True
 
     def __ne__(self, other):
@@ -1601,15 +1681,6 @@
         """See BzrDirFormat.get_format_description()."""
         return "Meta directory format 1"
 
-    @classmethod
-    def from_string(cls, format_string):
-        if format_string != cls.get_format_string():
-            raise ValueError("Invalid format string %r" % format_string)
-        return cls()
-
-    def network_name(self):
-        return self.get_format_string()
-
     def _open(self, transport):
         """See BzrDirFormat._open."""
         # Create a new format instance because otherwise initialisation of new
@@ -1644,6 +1715,7 @@
             compatible with whatever sub formats are supported by self.
         :return: None.
         """
+        super(BzrDirMetaFormat1, self)._supply_sub_formats_to(other_format)
         if getattr(self, '_repository_format', None) is not None:
             other_format.repository_format = self.repository_format
         if self._branch_format is not None:
@@ -1919,7 +1991,7 @@
         :return: A repository, is_new_flag (True if the repository was
             created).
         """
-        raise NotImplemented(RepositoryAcquisitionPolicy.acquire_repository)
+        raise NotImplementedError(RepositoryAcquisitionPolicy.acquire_repository)
 
 
 class CreateRepository(RepositoryAcquisitionPolicy):

=== modified file 'bzrlib/controldir.py'
--- a/bzrlib/controldir.py	2011-12-19 13:23:58 +0000
+++ b/bzrlib/controldir.py	2011-12-19 19:15:58 +0000
@@ -842,7 +842,7 @@
 
 
 class ControlComponentFormat(object):
-    """A component that can live inside of a .bzr meta directory."""
+    """A component that can live inside of a control directory."""
 
     upgrade_recommended = False
 

=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py	2011-12-19 13:23:58 +0000
+++ b/bzrlib/errors.py	2011-12-22 15:33:16 +0000
@@ -763,6 +763,18 @@
         self.bzrdir = bzrdir_format
 
 
+class ParseFormatError(BzrError):
+
+    _fmt = "Parse error on line %(lineno)d of %(format)s format: %(line)s"
+
+    def __init__(self, format, lineno, line, text):
+        BzrError.__init__(self)
+        self.format = format
+        self.lineno = lineno
+        self.line = line
+        self.text = text
+
+
 class IncompatibleRepositories(BzrError):
     """Report an error that two repositories are not compatible.
 
@@ -3242,6 +3254,15 @@
         self.format = format
 
 
+class MissingFeature(BzrError):
+
+    _fmt = ("Missing feature %(feature)s not provided by this "
+            "version of Bazaar or any plugin.")
+
+    def __init__(self, feature):
+        self.feature = feature
+
+
 class PatchSyntax(BzrError):
     """Base class for patch syntax errors."""
 
@@ -3291,3 +3312,11 @@
         self.line_no = line_no
         self.orig_line = orig_line.rstrip('\n')
         self.patch_line = patch_line.rstrip('\n')
+
+
+class FeatureAlreadyRegistered(BzrError):
+
+    _fmt = 'The feature %(feature)s has already been registered.'
+
+    def __init__(self, feature):
+        self.feature = feature

=== modified file 'bzrlib/repository.py'
--- a/bzrlib/repository.py	2011-12-19 11:49:56 +0000
+++ b/bzrlib/repository.py	2011-12-19 19:15:58 +0000
@@ -1497,7 +1497,7 @@
             hook(params)
 
 
-class RepositoryFormatMetaDir(bzrdir.BzrDirMetaComponentFormat, RepositoryFormat):
+class RepositoryFormatMetaDir(bzrdir.BzrFormat, RepositoryFormat):
     """Common base class for the new repositories using the metadir layout."""
 
     rich_root_data = False
@@ -1514,7 +1514,7 @@
 
     def __init__(self):
         RepositoryFormat.__init__(self)
-        bzrdir.BzrDirMetaComponentFormat.__init__(self)
+        bzrdir.BzrFormat.__init__(self)
 
     def _create_control_files(self, a_bzrdir):
         """Create the required files and the initial control_files object."""
@@ -1559,6 +1559,14 @@
             raise errors.NoRepositoryPresent(a_bzrdir)
         return klass._find_format(format_registry, 'repository', format_string)
 
+    def check_support_status(self, allow_unsupported, recommend_upgrade=True,
+            basedir=None):
+        RepositoryFormat.check_support_status(self,
+            allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
+            basedir=basedir)
+        bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
+            recommend_upgrade=recommend_upgrade, basedir=basedir)
+
 
 # formats which have no format string are not discoverable or independently
 # creatable on disk, so are not registered in format_registry.  They're

=== modified file 'bzrlib/tests/test_branch.py'
--- a/bzrlib/tests/test_branch.py	2011-12-20 14:04:21 +0000
+++ b/bzrlib/tests/test_branch.py	2011-12-22 18:52:58 +0000
@@ -201,7 +201,7 @@
         self.assertIsInstance(
             SampleBranchFormat.from_string("Sample branch format."),
             SampleBranchFormat)
-        self.assertRaises(ValueError,
+        self.assertRaises(AssertionError,
             SampleBranchFormat.from_string, "Different branch format.")
 
     def test_find_format_not_branch(self):
@@ -217,6 +217,15 @@
                           _mod_branch.BranchFormatMetadir.find_format,
                           dir)
 
+    def test_find_format_with_features(self):
+        tree = self.make_branch_and_tree('.', format='2a')
+        tree.branch.control_transport.put_bytes('format',
+            tree.branch._format.get_format_string() +
+            "optional name\n")
+        found_format = _mod_branch.BranchFormatMetadir.find_format(tree.bzrdir)
+        self.assertIsInstance(found_format, _mod_branch.BranchFormatMetadir)
+        self.assertEquals(found_format.features.get("name"), "optional")
+
     def test_register_unregister_format(self):
         # Test the deprecated format registration functions
         format = SampleBranchFormat()
@@ -718,4 +727,3 @@
         f = StringIO()
         r.report(f)
         self.assertEqual("No revisions or tags to pull.\n", f.getvalue())
-

=== modified file 'bzrlib/tests/test_bzrdir.py'
--- a/bzrlib/tests/test_bzrdir.py	2011-12-07 14:03:01 +0000
+++ b/bzrlib/tests/test_bzrdir.py	2011-12-22 15:33:16 +0000
@@ -1025,6 +1025,17 @@
         self.assertNotEqual(otherdir2, mydir)
         self.assertFalse(otherdir2 == mydir)
 
+    def test_with_features(self):
+        tree = self.make_branch_and_tree('tree', format='2a')
+        tree.bzrdir.control_transport.put_bytes(
+            'branch-format',
+            tree.bzrdir._format.get_format_string() + "required bar\n")
+        self.assertRaises(errors.MissingFeature, bzrdir.BzrDir.open, 'tree')
+        bzrdir.BzrDirMetaFormat1.register_feature('bar')
+        self.addCleanup(bzrdir.BzrDirMetaFormat1.unregister_feature, 'bar')
+        dir = bzrdir.BzrDir.open('tree')
+        self.assertEquals("required", dir._format.features.get("bar"))
+
     def test_needs_conversion_different_working_tree(self):
         # meta1dirs need an conversion if any element is not the default.
         new_format = bzrdir.format_registry.make_bzrdir('dirstate')
@@ -1435,3 +1446,112 @@
         self.assertRaises(errors.BzrError, converter.convert, tree.bzrdir,
             None)
 
+
+class SampleBzrFormat(bzrdir.BzrFormat):
+
+    @classmethod
+    def get_format_string(cls):
+        return "First line\n"
+
+
+class TestBzrFormat(TestCase):
+    """Tests for BzrFormat."""
+
+    def test_as_string(self):
+        format = SampleBzrFormat()
+        format.features = {"foo": "required"}
+        self.assertEquals(format.as_string(),
+            "First line\n"
+            "required foo\n")
+        format.features["another"] = "optional"
+        self.assertEquals(format.as_string(),
+            "First line\n"
+            "required foo\n"
+            "optional another\n")
+
+    def test_network_name(self):
+        # The network string should include the feature info
+        format = SampleBzrFormat()
+        format.features = {"foo": "required"}
+        self.assertEquals(
+            "First line\nrequired foo\n",
+            format.network_name())
+
+    def test_from_string_no_features(self):
+        # No features
+        format = SampleBzrFormat.from_string(
+            "First line\n")
+        self.assertEquals({}, format.features)
+
+    def test_from_string_with_feature(self):
+        # Proper feature
+        format = SampleBzrFormat.from_string(
+            "First line\nrequired foo\n")
+        self.assertEquals("required", format.features.get("foo"))
+
+    def test_from_string_format_string_mismatch(self):
+        # The first line has to match the format string
+        self.assertRaises(AssertionError, SampleBzrFormat.from_string,
+            "Second line\nrequired foo\n")
+
+    def test_from_string_missing_space(self):
+        # At least one space is required in the feature lines
+        self.assertRaises(errors.ParseFormatError, SampleBzrFormat.from_string,
+            "First line\nfoo\n")
+
+    def test_from_string_with_spaces(self):
+        # Feature with spaces (in case we add stuff like this in the future)
+        format = SampleBzrFormat.from_string(
+            "First line\nrequired foo with spaces\n")
+        self.assertEquals("required", format.features.get("foo with spaces"))
+
+    def test_eq(self):
+        format1 = SampleBzrFormat()
+        format1.features = {"nested-trees": "optional"}
+        format2 = SampleBzrFormat()
+        format2.features = {"nested-trees": "optional"}
+        self.assertEquals(format1, format1)
+        self.assertEquals(format1, format2)
+        format3 = SampleBzrFormat()
+        self.assertNotEquals(format1, format3)
+
+    def test_check_support_status_optional(self):
+        # Optional, so silently ignore
+        format = SampleBzrFormat()
+        format.features = {"nested-trees": "optional"}
+        format.check_support_status(True)
+        self.addCleanup(SampleBzrFormat.unregister_feature, "nested-trees")
+        SampleBzrFormat.register_feature("nested-trees")
+        format.check_support_status(True)
+
+    def test_check_support_status_required(self):
+        # Optional, so trigger an exception
+        format = SampleBzrFormat()
+        format.features = {"nested-trees": "required"}
+        self.assertRaises(errors.MissingFeature, format.check_support_status,
+            True)
+        self.addCleanup(SampleBzrFormat.unregister_feature, "nested-trees")
+        SampleBzrFormat.register_feature("nested-trees")
+        format.check_support_status(True)
+
+    def test_check_support_status_unknown(self):
+        # treat unknown necessity as required
+        format = SampleBzrFormat()
+        format.features = {"nested-trees": "unknown"}
+        self.assertRaises(errors.MissingFeature, format.check_support_status,
+            True)
+        self.addCleanup(SampleBzrFormat.unregister_feature, "nested-trees")
+        SampleBzrFormat.register_feature("nested-trees")
+        format.check_support_status(True)
+
+    def test_feature_already_registered(self):
+        # a feature can only be registered once
+        self.addCleanup(SampleBzrFormat.unregister_feature, "nested-trees")
+        SampleBzrFormat.register_feature("nested-trees")
+        self.assertRaises(errors.FeatureAlreadyRegistered,
+            SampleBzrFormat.register_feature, "nested-trees")
+
+    def test_feature_with_space(self):
+        # spaces are not allowed in feature names
+        self.assertRaises(ValueError, SampleBzrFormat.register_feature,
+            "nested trees")

=== modified file 'bzrlib/tests/test_repository.py'
--- a/bzrlib/tests/test_repository.py	2011-12-14 14:09:47 +0000
+++ b/bzrlib/tests/test_repository.py	2011-12-22 16:21:25 +0000
@@ -153,7 +153,7 @@
             SampleRepositoryFormat.from_string(
                 "Sample .bzr repository format."),
             SampleRepositoryFormat)
-        self.assertRaises(ValueError,
+        self.assertRaises(AssertionError,
             SampleRepositoryFormat.from_string,
                 "Different .bzr repository format.")
 
@@ -164,6 +164,21 @@
                           repository.RepositoryFormatMetaDir.find_format,
                           dir)
 
+    def test_find_format_with_features(self):
+        tree = self.make_branch_and_tree('.', format='2a')
+        tree.branch.repository.control_transport.put_bytes('format',
+            tree.branch.repository._format.get_format_string() +
+            "necessity name\n")
+        found_format = repository.RepositoryFormatMetaDir.find_format(tree.bzrdir)
+        self.assertIsInstance(found_format, repository.RepositoryFormatMetaDir)
+        self.assertEquals(found_format.features.get("name"), "necessity")
+        self.assertRaises(errors.MissingFeature, found_format.check_support_status,
+            True)
+        self.addCleanup(repository.RepositoryFormatMetaDir.unregister_feature,
+            "name")
+        repository.RepositoryFormatMetaDir.register_feature("name")
+        found_format.check_support_status(True)
+
     def test_register_unregister_format(self):
         # Test deprecated format registration functions
         format = SampleRepositoryFormat()
@@ -1703,3 +1718,25 @@
         lazy = repository._LazyListJoin(['a'], ['b'])
         self.assertEqual("bzrlib.repository._LazyListJoin((['a'], ['b']))",
                          repr(lazy))
+
+
+class TestFeatures(tests.TestCaseWithTransport):
+
+    def test_open_with_present_feature(self):
+        self.addCleanup(
+            repository.RepositoryFormatMetaDir.unregister_feature,
+            "makes-cheese-sandwich")
+        repository.RepositoryFormatMetaDir.register_feature(
+            "makes-cheese-sandwich")
+        repo = self.make_repository('.')
+        repo.lock_write()
+        repo._format.features["makes-cheese-sandwich"] = "required"
+        repo._format.check_support_status(False)
+        repo.unlock()
+
+    def test_open_with_missing_required_feature(self):
+        repo = self.make_repository('.')
+        repo.lock_write()
+        repo._format.features["makes-cheese-sandwich"] = "required"
+        self.assertRaises(errors.MissingFeature,
+            repo._format.check_support_status, False)

=== modified file 'bzrlib/tests/test_workingtree.py'
--- a/bzrlib/tests/test_workingtree.py	2011-12-11 02:43:03 +0000
+++ b/bzrlib/tests/test_workingtree.py	2011-12-22 17:30:20 +0000
@@ -83,7 +83,7 @@
         self.assertIsInstance(
             SampleTreeFormat.from_string("Sample tree format."),
             SampleTreeFormat)
-        self.assertRaises(ValueError,
+        self.assertRaises(AssertionError,
             SampleTreeFormat.from_string, "Different format string.")
 
     def test_get_set_default_format_by_key(self):
@@ -243,6 +243,21 @@
             self.applyDeprecated(symbol_versioning.deprecated_in((2, 4, 0)),
                 workingtree.WorkingTreeFormat.get_formats))
 
+    def test_find_format_with_features(self):
+        tree = self.make_branch_and_tree('.', format='2a')
+        tree.control_transport.put_bytes('format',
+            tree._format.get_format_string() + "necessity name\n")
+        found_format = workingtree.WorkingTreeFormatMetaDir.find_format(
+            tree.bzrdir)
+        self.assertIsInstance(found_format, workingtree.WorkingTreeFormat)
+        self.assertEquals(found_format.features.get("name"), "necessity")
+        self.assertRaises(errors.MissingFeature, found_format.check_support_status,
+            True)
+        self.addCleanup(workingtree.WorkingTreeFormatMetaDir.unregister_feature,
+            "name")
+        workingtree.WorkingTreeFormatMetaDir.register_feature("name")
+        found_format.check_support_status(True)
+
 
 class TestWorkingTreeIterEntriesByDir_wSubtrees(TestCaseWithTransport):
 

=== modified file 'bzrlib/workingtree.py'
--- a/bzrlib/workingtree.py	2011-12-19 17:39:35 +0000
+++ b/bzrlib/workingtree.py	2011-12-19 19:15:58 +0000
@@ -47,7 +47,6 @@
 
 from bzrlib import (
     branch,
-    bzrdir,
     conflicts as _mod_conflicts,
     controldir,
     errors,
@@ -72,9 +71,11 @@
 
 # Explicitly import bzrlib.bzrdir so that the BzrProber
 # is guaranteed to be registered.
-import bzrlib.bzrdir
+from bzrlib import (
+    bzrdir,
+    symbol_versioning,
+    )
 
-from bzrlib import symbol_versioning
 from bzrlib.decorators import needs_read_lock, needs_write_lock
 from bzrlib.i18n import gettext
 from bzrlib.lock import LogicalLockResult
@@ -3133,12 +3134,12 @@
         return self._matchingbzrdir
 
 
-class WorkingTreeFormatMetaDir(bzrdir.BzrDirMetaComponentFormat, WorkingTreeFormat):
+class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
     """Base class for working trees that live in bzr meta directories."""
 
     def __init__(self):
         WorkingTreeFormat.__init__(self)
-        bzrdir.BzrDirMetaComponentFormat.__init__(self)
+        bzrdir.BzrFormat.__init__(self)
 
     @classmethod
     def find_format_string(klass, controldir):
@@ -3156,6 +3157,14 @@
         return klass._find_format(format_registry, 'working tree',
                 format_string)
 
+    def check_support_status(self, allow_unsupported, recommend_upgrade=True,
+            basedir=None):
+        WorkingTreeFormat.check_support_status(self,
+            allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
+            basedir=basedir)
+        bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
+            recommend_upgrade=recommend_upgrade, basedir=basedir)
+
 
 format_registry.register_lazy("Bazaar Working Tree Format 4 (bzr 0.15)\n",
     "bzrlib.workingtree_4", "WorkingTreeFormat4")

=== modified file 'bzrlib/workingtree_3.py'
--- a/bzrlib/workingtree_3.py	2011-12-19 13:23:58 +0000
+++ b/bzrlib/workingtree_3.py	2011-12-19 19:15:58 +0000
@@ -195,7 +195,7 @@
         control_files = self._open_control_files(a_bzrdir)
         control_files.create_lock()
         control_files.lock_write()
-        transport.put_bytes('format', self.get_format_string(),
+        transport.put_bytes('format', self.as_string(),
             mode=a_bzrdir._get_file_mode())
         if from_branch is not None:
             branch = from_branch

=== modified file 'bzrlib/workingtree_4.py'
--- a/bzrlib/workingtree_4.py	2011-12-19 17:39:35 +0000
+++ b/bzrlib/workingtree_4.py	2011-12-19 19:15:58 +0000
@@ -1478,7 +1478,7 @@
         control_files = self._open_control_files(a_bzrdir)
         control_files.create_lock()
         control_files.lock_write()
-        transport.put_bytes('format', self.get_format_string(),
+        transport.put_bytes('format', self.as_string(),
             mode=a_bzrdir._get_file_mode())
         if from_branch is not None:
             branch = from_branch
@@ -2260,7 +2260,7 @@
     def update_format(self, tree):
         """Change the format marker."""
         tree._transport.put_bytes('format',
-            self.target_format.get_format_string(),
+            self.target_format.as_string(),
             mode=tree.bzrdir._get_file_mode())
 
 
@@ -2283,7 +2283,7 @@
     def update_format(self, tree):
         """Change the format marker."""
         tree._transport.put_bytes('format',
-            self.target_format.get_format_string(),
+            self.target_format.as_string(),
             mode=tree.bzrdir._get_file_mode())
 
 
@@ -2312,5 +2312,5 @@
     def update_format(self, tree):
         """Change the format marker."""
         tree._transport.put_bytes('format',
-            self.target_format.get_format_string(),
+            self.target_format.as_string(),
             mode=tree.bzrdir._get_file_mode())

=== modified file 'doc/developers/feature-flags.txt'
--- a/doc/developers/feature-flags.txt	2011-10-24 19:21:00 +0000
+++ b/doc/developers/feature-flags.txt	2011-12-22 14:25:34 +0000
@@ -51,8 +51,8 @@
 Feature necessity
 -----------------
 
-The initial implementation will feature the following set of possible
-settings for feature "necessity". Any format necessity that can't
+The initial implementation will feature the following two possible
+settings for feature ``necessity``. Any format necessity that can't
 be understood should be interpreted as "required", and an appropriate
 warning printed.
 
@@ -62,8 +62,16 @@
       annotate cache)
  - required: read and write access is only possible if the feature
       is supported. Useful for things like nested trees.
- - write-required: read access is possible if the feature is not supported,
+
+In the future, we might add more values for necessity. Older
+versions of bzr treat unknown necessities as "required". Some likely
+candidates for new necessities that might be added in the future:
+
+ - read-optional: read access is possible if the feature is not supported,
       but write access requires it
+ - client-read-optional: directly writing to the object requires
+      the feature, but reading or writing through an intermediary (such as
+      a HPSS server) doesn't.
 
 Format changes
 --------------
@@ -94,23 +102,32 @@
 API Changes
 -----------
 
-Class methods will be added to ``BzrDirComponentFormat`` to allow registering
-and deregistering the presence of particular features. This class is inherited
-by ``BzrBranchFormat``, ``VersionedFileRepositoryFormat`` and
-``InventoryWorkingTreeFormat``.
-
- * BzrDirComponentFormat.register_feature(name)
- * BzrDirComponentFormat.unregister_feature(name)
-
-Upon opening, BzrDirComponentFormat will be responsible for checking that the
+Class methods will be added to ``BzrFormat`` to allow registering
+and unregistering the presence of particular features.
+
+ * BzrFormat.register_feature(name)
+ * BzrFormat.unregister_feature(name)
+
+The namespace for features is global. It is assumed
+that the plugin that provides the feature X provides that feature
+in all objects that it is relevant for. For example, if a plugin
+provides the ``nested-trees`` feature, it is assumed to support
+that in both working trees and repositories. If this is not the case,
+it should use a different feature name for the working tree support
+and the repository support.
+
+BzrFormat is inherited by ``BranchFormatMetaDir``, ``BzrDirFormat``,
+``RepositoryFormatMetaDir`` and ``WorkingTreeFormatMetaDir``.
+
+Upon opening, BzrFormat will be responsible for checking that the
 required features are present.  lock_write will raise an exception
-when there is an un unsupported mandatory feature required for write access.
+when there is an unsupported mandatory feature required for write access.
 
-Methods will also be added to BzrDirComponentFormat to allow plugins, etc,
+Methods will also be added to BzrFormat to allow plugins, etc,
 to check whether a feature is present and adding new features:
 
- * BzrDirComponentFormat.set_feature(name, necessity)
- * BzrDirComponentFormat.get_feature(name) -> necessity
+ * BzrFormat.features.set(name, necessity)
+ * BzrFormat.features.get(name) -> necessity
 
 See also
 --------

=== modified file 'doc/en/release-notes/bzr-2.5.txt'
--- a/doc/en/release-notes/bzr-2.5.txt	2011-12-21 21:31:03 +0000
+++ b/doc/en/release-notes/bzr-2.5.txt	2011-12-22 18:52:58 +0000
@@ -365,6 +365,12 @@
 * The registry of merge types has been moved to ``merge`` from ``option`` but
   ``merge.get_merge_type_registry`` remains as an accessor. (Martin Packman)
 
+* All bzr control directories, branch formats, repository formats and
+  working tree formats now support feature flags, which are
+  serialized in their respective format files. See
+  ``doc/developers/feature-flags.txt`` for details.
+  (Jelmer Vernooij)
+
 Testing
 *******
 




More information about the bazaar-commits mailing list