Merge lp:~jelmer/bzr/feature-flags into lp:bzr

Proposed by Jelmer Vernooij
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: 6404
Proposed branch: lp:~jelmer/bzr/feature-flags
Merge into: lp:bzr
Prerequisite: lp:~jelmer/bzr/common-bzrdir-component
Diff against target: 900 lines (+399/-69)
14 files modified
bzrlib/branch.py (+17/-8)
bzrlib/bzrdir.py (+100/-28)
bzrlib/controldir.py (+1/-1)
bzrlib/errors.py (+29/-0)
bzrlib/repository.py (+10/-2)
bzrlib/tests/test_branch.py (+10/-2)
bzrlib/tests/test_bzrdir.py (+120/-0)
bzrlib/tests/test_repository.py (+38/-1)
bzrlib/tests/test_workingtree.py (+16/-1)
bzrlib/workingtree.py (+14/-5)
bzrlib/workingtree_3.py (+1/-1)
bzrlib/workingtree_4.py (+4/-4)
doc/developers/feature-flags.txt (+33/-16)
doc/en/release-notes/bzr-2.5.txt (+6/-0)
To merge this branch: bzr merge lp:~jelmer/bzr/feature-flags
Reviewer Review Type Date Requested Status
Vincent Ladeuil Approve
Review via email: mp+86712@code.launchpad.net

This proposal supersedes a proposal from 2011-12-07.

Commit message

Add support for feature flags in bzr formats.

Description of the change

Add support for feature flags.

For a background on feature flags, see doc/developers/feature-flags.txt.

This adds a common base class ("BzrFormat") for all classes that deal with bzr 'format' files. This includes all bzrdir implementations, and branch/repository/workingtree formats that can live in a .bzr meta dir. This base class handles serialization of the contents of the format file, and allows the setting of feature flags.

There currently is no way to set feature flags from the Repository/WorkingTree/Branch API (including locking, etc) I'm leaving that for another branch as this one is already big enough.

There is a very simple example of a plugin using this in lp:~jelmer/bzr-search/feature-flags.

To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal
Download full text (5.8 KiB)

Summary (spoiler alert): I have only minor concerns about the format. Most
of my remarks are more about interoperability with older bzr versions and
even how newer bzr versions will interact around the feature flags. So
basically, let's make sure we have a good format first. So I'm voting
NeedsInfo but nothing really wrong caught my eye.

Re-reading doc/developers/feature-flags.txt, a couple of typos:

   modified doc/developers/feature-flags.txt

=== modified file 'doc/developers/feature-flags.txt'
--- doc/developers/feature-flags.txt 2011-12-07 17:45:57 +0000
+++ doc/developers/feature-flags.txt 2011-12-14 14:56:20 +0000
@@ -95,7 +95,7 @@
 -----------

 Class methods will be added to ``BzrFormat`` to allow registering
-and deregistering the presence of particular features. This class is inherited
+and unregistering the presence of particular features. This class is inherited
 by ``BzrBranchFormat``, ``VersionedFileRepositoryFormat`` and
 ``InventoryWorkingTreeFormat``.

@@ -104,7 +104,7 @@

 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 BzrFormat to allow plugins, etc,
 to check whether a feature is present and adding new features:

Derigester is valid but we use unregister everywhere else ;)

Now, about the format:

  Bazaar repository format 2a (needs bzr 1.16 or later)
  optional feature git
  optional feature search
  optional feature tiplog
  required feature nested-trees

I'd rather use an existing one than a new one so, how about:

  Bazaar repository format 2a (needs bzr 1.16 or later)
  [features]
  optional = git,search,tiplog
  required = nested-trees

or

  Bazaar repository format 2a (needs bzr 1.16 or later)
  [features]
  git = optional
  search = optional
  tiplog = optional
  nested_trees = required # _ not - if that matters...

Even '[features]' is only needed if we plan to support something else than
features or leave open this possibility.

Note that it doesn't have to support all the fancy features of ConfigObj,
especially quoting (not even comments...).

But more importantly, I think a needed feature of this format is that old
bzr clients can safely ignore anything but the first line, but also get a
way to check that the file itself has not been corrupted.

This probably means some sort of final marker, a checksum or a robust
parser. Note that the later means we won't change the format of this
file. This is probably ok as we can still create new formats... but it's a
bit weird to introduce a limitation when trying to make the format open to
additions.

Your proposed implementation in bzr-search looks simple, that's great.

128 + Feature flags are supported as of bzr 2.5. Setting feature flags on formats
129 + will render them inaccessible to older versions of bzr.

Hmm. I'm not sure I correctly understand the implications here.

What do we need to backport to older (su...

Read more...

review: Needs Information
Revision history for this message
Jelmer Vernooij (jelmer) wrote : Posted in a previous version of this proposal
Download full text (9.7 KiB)

Am 14/12/11 17:23, schrieb Vincent Ladeuil:
> Review: Needs Information
>
> Summary (spoiler alert): I have only minor concerns about the format. Most
> of my remarks are more about interoperability with older bzr versions and
> even how newer bzr versions will interact around the feature flags. So
> basically, let's make sure we have a good format first. So I'm voting
> NeedsInfo but nothing really wrong caught my eye.
Cool. Thanks for taking the time to review this, much appreciated!
>
>
> Re-reading doc/developers/feature-flags.txt, a couple of typos:
>
> modified doc/developers/feature-flags.txt
>
>
> === modified file 'doc/developers/feature-flags.txt'
> --- doc/developers/feature-flags.txt 2011-12-07 17:45:57 +0000
> +++ doc/developers/feature-flags.txt 2011-12-14 14:56:20 +0000
> @@ -95,7 +95,7 @@
> -----------
>
> Class methods will be added to ``BzrFormat`` to allow registering
> -and deregistering the presence of particular features. This class is inherited
> +and unregistering the presence of particular features. This class is inherited
> by ``BzrBranchFormat``, ``VersionedFileRepositoryFormat`` and
> ``InventoryWorkingTreeFormat``.
>
> @@ -104,7 +104,7 @@
>
> 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 BzrFormat to allow plugins, etc,
> to check whether a feature is present and adding new features:
>
>
> Derigester is valid but we use unregister everywhere else ;)
Thanks, fixed.

> Now, about the format:
>
> Bazaar repository format 2a (needs bzr 1.16 or later)
> optional feature git
> optional feature search
> optional feature tiplog
> required feature nested-trees
>
> I'd rather use an existing one than a new one so, how about:
>
> Bazaar repository format 2a (needs bzr 1.16 or later)
> [features]
> optional = git,search,tiplog
> required = nested-trees
>
> or
>
> Bazaar repository format 2a (needs bzr 1.16 or later)
> [features]
> git = optional
> search = optional
> tiplog = optional
> nested_trees = required # _ not - if that matters...
>
> Even '[features]' is only needed if we plan to support something else than
> features or leave open this possibility.
>
> Note that it doesn't have to support all the fancy features of ConfigObj,
> especially quoting (not even comments...).
I'm not sure what using an ini-like format really gains us: the file
still isn't a standard ini-style file, because it has a custom first
line. It also makes parsing (and thus, backporting) much more complex.
One of the advantages of having one feature per line is that we can
allow whitespace in the feature information in the future, or allow
features to add parameters.

>
> But more importantly, I think a needed feature of this format is that old
> bzr clients can safely ignore anything but the first line, but also get a
> way to check that the file i...

Read more...

Revision history for this message
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal
Download full text (9.5 KiB)

> > Bazaar repository format 2a (needs bzr 1.16 or later)
> > [features]
> > git = optional
> > search = optional
> > tiplog = optional
> > nested_trees = required # _ not - if that matters...
> >
> > Even '[features]' is only needed if we plan to support something else than
> > features or leave open this possibility.
> >
> > Note that it doesn't have to support all the fancy features of ConfigObj,
> > especially quoting (not even comments...).

> I'm not sure what using an ini-like format really gains us: the file
> still isn't a standard ini-style file, because it has a custom first
> line.

That's trivial to skip.

> It also makes parsing (and thus, backporting) much more complex.

Quite the contrary, with the first line skipped we can use ConfigObj for
that. We should also get line numbers in error messages for ~free.

> One of the advantages of having one feature per line is that we can
> allow whitespace in the feature information in the future, or allow
> features to add parameters.

git = optional, version, pumpernickel

works fine too.

>
> >
> > But more importantly, I think a needed feature of this format is that old
> > bzr clients can safely ignore anything but the first line, but also get a
> > way to check that the file itself has not been corrupted.
>
> > This probably means some sort of final marker, a checksum or a robust
> > parser. Note that the later means we won't change the format of this
> > file. This is probably ok as we can still create new formats... but it's a
> > bit weird to introduce a limitation when trying to make the format open to
> > additions.
> They should be able to ignore any lines starting with "optional ", but
> should refuse to open formats when there are lines present that start
> with something else. This is so features can be mandatory, and we can
> prevent older bzr clients from opening objects that they can corrupt if
> they don't have a particular feature.

I think this is the core issue.

As long as features are optional the tree/branch/repo can still
inter-operate with users that doesn't have the bzr/plugin implementing the
feature.

As soon as a feature is mandatory, we are in the same situation as a format
bump.

From there, I'm very very tempted to say that mandatory features are not
that interesting since they raise almost the same issues as a format bump.

What about starting with just optional features and explore the fallouts ?
What are the requirements for plugins ? How do they install such a feature,
how to they produce the data they need when discovering a new
tree/branch/repo ? Is such data automatically propogated, always re-created
on demand or do the plugins need to set hooks for fetch ?

If we can implement features that are really optional and don't require any
upgrade for interop... they are extremely compelling !

>
> Adding support for ignoring all lines starting with "optional" in older
> versions of bzr should be a fairly small change.

Yup, but the goal is still the same: ignore anything that is not mandatory
while still making sure we have a valid content.

>
> We should always take a write lock before updating this file, and
> atomically replacing it....

Read more...

Revision history for this message
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal

> > I'm not sure what using an ini-like format really gains us: the file
> > still isn't a standard ini-style file, because it has a custom first
> > line.
>
> That's trivial to skip.

Hear, hear ! It's always trivial when somebody else does the work ;-D

So I gave it a try:

lp:~bzr/bzr/feature-flags which still pass the whole test suite with
--no-plugins (hung
bzrlib.tests.test_bzrdir.TestHTTPRedirections_pycurl.test_loop tracked down
to bzr-svn but I considered it irrelevant at this point).

Found that you used 'optional', 'required' and (more suprisingly)
'necessity'.

Overall, the experiment revealed that your design is sound and your tests
were quite cool to play around with a new implementation (and caught my noob
errors including forgetting to support a simple format file without any
support ;) !

>
> > It also makes parsing (and thus, backporting) much more complex.
>
> Quite the contrary, with the first line skipped we can use ConfigObj for
> that. We should also get line numbers in error messages for ~free.

But not that part (didn't try).

Running the whole test suite, it seems that
bzrlib.tests.test_workingtree.TestDefaultFormat.test_from_string needs a fix
even in your branch:

--- bzrlib/tests/test_workingtree.py 2011-12-11 03:49:52 +0000
+++ bzrlib/tests/test_workingtree.py 2011-12-16 12:34:48 +0000
@@ -146,10 +146,6 @@
         t.put_bytes('format', self.get_format_string())
         return 'A tree'

- @classmethod
- def from_string(cls, format_string):
- return cls()
-
     def is_supported(self):
         return False

Further feedback: setting a feature by appending a line to the format file
is brittle, better to add support at the BzrFormat level, but AIUI that's
out of scope for now. So only tests needs to do that which is fine so far.

Revision history for this message
Jelmer Vernooij (jelmer) wrote : Posted in a previous version of this proposal
Download full text (10.2 KiB)

Am 16/12/11 11:55, schrieb Vincent Ladeuil:
>>> Bazaar repository format 2a (needs bzr 1.16 or later)
>>> [features]
>>> git = optional
>>> search = optional
>>> tiplog = optional
>>> nested_trees = required # _ not - if that matters...
>>>
>>> Even '[features]' is only needed if we plan to support something else than
>>> features or leave open this possibility.
>>>
>>> Note that it doesn't have to support all the fancy features of ConfigObj,
>>> especially quoting (not even comments...).
>> I'm not sure what using an ini-like format really gains us: the file
>> still isn't a standard ini-style file, because it has a custom first
>> line.
> That's trivial to skip.
Sure, but it also reduces the amount of usefulness of it being an
ini-like file.
>
>> It also makes parsing (and thus, backporting) much more complex.
> Quite the contrary, with the first line skipped we can use ConfigObj for
> that. We should also get line numbers in error messages for ~free.
The current parser is per-line, counting lines shouldn't be an issue. :-)
>
>> One of the advantages of having one feature per line is that we can
>> allow whitespace in the feature information in the future, or allow
>> features to add parameters.
> git = optional, version, pumpernickel
>
> works fine too.
Sure, but what's the advantage of using an ini-like file in that case?

I'd much prefer a simple to parse format over a more complex one. It
should also have less object overhead, though I'm not sure how relevant
that is. And it would make backporting support for ignoring "optional "
lines to older formats a lot easier.

The situation would be different if we wanted users to be able to edit
this file, but they really shouldn't.

>>> But more importantly, I think a needed feature of this format is that old
>>> bzr clients can safely ignore anything but the first line, but also get a
>>> way to check that the file itself has not been corrupted.
>>> This probably means some sort of final marker, a checksum or a robust
>>> parser. Note that the later means we won't change the format of this
>>> file. This is probably ok as we can still create new formats... but it's a
>>> bit weird to introduce a limitation when trying to make the format open to
>>> additions.
>> They should be able to ignore any lines starting with "optional ", but
>> should refuse to open formats when there are lines present that start
>> with something else. This is so features can be mandatory, and we can
>> prevent older bzr clients from opening objects that they can corrupt if
>> they don't have a particular feature.
> I think this is the core issue.
>
> As long as features are optional the tree/branch/repo can still
> inter-operate with users that doesn't have the bzr/plugin implementing the
> feature.
>
> As soon as a feature is mandatory, we are in the same situation as a format
> bump.
>
> >From there, I'm very very tempted to say that mandatory features are not
> that interesting since they raise almost the same issues as a format bump.
>
> What about starting with just optional features and explore the fallouts ?
> What are the requirements for plugins ? How do they install such a feature,
> how to they ...

Revision history for this message
Jelmer Vernooij (jelmer) wrote : Posted in a previous version of this proposal

Am 16/12/11 13:43, schrieb Vincent Ladeuil:
>>> I'm not sure what using an ini-like format really gains us: the file
>>> still isn't a standard ini-style file, because it has a custom first
>>> line.
>> That's trivial to skip.
> Hear, hear ! It's always trivial when somebody else does the work ;-D
:-) Thanks again for thoroughly reviewing this. I appreciate it,
especially as this involves format changes that will affect us long from
now.
>
> So I gave it a try:
>
> lp:~bzr/bzr/feature-flags which still pass the whole test suite with
> --no-plugins (hung
> bzrlib.tests.test_bzrdir.TestHTTPRedirections_pycurl.test_loop tracked down
> to bzr-svn but I considered it irrelevant at this point).
Urgh - I should have a look at that.
>
> Found that you used 'optional', 'required' and (more suprisingly)
> 'necessity'.
Hmm, I guess using "necessity" is a bit confusing as that's the class
for things like "optional" and "required". Should I update
>
> Overall, the experiment revealed that your design is sound and your tests
> were quite cool to play around with a new implementation (and caught my noob
> errors including forgetting to support a simple format file without any
> support ;) !
>
>>> It also makes parsing (and thus, backporting) much more complex.
>> Quite the contrary, with the first line skipped we can use ConfigObj for
>> that. We should also get line numbers in error messages for ~free.
> But not that part (didn't try).
>
> Running the whole test suite, it seems that
> bzrlib.tests.test_workingtree.TestDefaultFormat.test_from_string needs a fix
> even in your branch:
>
> --- bzrlib/tests/test_workingtree.py 2011-12-11 03:49:52 +0000
> +++ bzrlib/tests/test_workingtree.py 2011-12-16 12:34:48 +0000
> @@ -146,10 +146,6 @@
> t.put_bytes('format', self.get_format_string())
> return 'A tree'
>
> - @classmethod
> - def from_string(cls, format_string):
> - return cls()
> -
> def is_supported(self):
> return False
Thanks!
>
> Further feedback: setting a feature by appending a line to the format file
> is brittle, better to add support at the BzrFormat level, but AIUI that's
> out of scope for now. So only tests needs to do that which is fine so far.
Yeah, I'd rather leave that for another branch since this one is already
big enough. I agree we need a dedicated API for it, and we shouldn't
encourage anybody to write to that file themselves.

Cheers,

Jelmer

Revision history for this message
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal
Download full text (6.3 KiB)

> > That's trivial to skip.

> Sure, but it also reduces the amount of usefulness of it being an ini-like
  file.

The point is not to make it an ini-like file but to address (end marker,
checksum or robust parser).

This is a robust parser.

It means that's the only thing to be backported to older supported bzr
releases to allow them to ignore the optional feature flags. They won't even
try to use the parsed content, they will ignore it, as long as it can be
parsed.

This means that corruptions will be detected.

> > that. We should also get line numbers in error messages for ~free.

> The current parser is per-line, counting lines shouldn't be an issue. :-)

I was talking about the new one, not yours ;)

> Sure, but what's the advantage of using an ini-like file in that case?

It means changes to the parser doesn't need to be backported and more
importantly deployed. We backport once, tell people to upgrade once and then
we can still change the data we need without changing the parser and more
importantly without requiring people to upgrade bzr more than once. And I
mean upgrading code not data, very important distinction.

> I'd much prefer a simple to parse format over a more complex one.

The point is that we can't be sure that the format is complete for now.

> It should also have less object overhead, though I'm not sure how relevant
> that is.

I fon't thnk it's relevant since older bzr will just ignore it.

> And it would make backporting support for ignoring "optional "
> lines to older formats a lot easier.

My point is that whatever data we need (present and future) can safely be
represented in a format for which the backport can be done now and once.

> The situation would be different if we wanted users to be able to edit
> this file, but they really shouldn't.

I din't even consider that, users should not edit format files, this is a
private bzr area only :)

> Of course, it would be nice if all features could be optional, but that
> isn't always the case - sometimes adding a feature renders a repository
> inaccessible to older versions of bzr.

That's the definition of a format bump. I think that allowing feature to
trigger a format bump is a Bad Idea.

> We should allow feature flags to represent that - by allowing features to
> be required. I want bzr 2.5 to be able to give a proper error if we add
> support for nested trees in the future, and start adding "required feature
> ..." lines.

What would that gain us ?

I fear that allowing required features will mean allowing (and therefore
supporting) *two* upgrade stories instead of focusing on a single one.

> The fact that plugins can add "required" features to a
> bzrdir/branch/repository/workingtree doesn't mean they actually have to do
> so. Where possible, they should make features optional.

I don't know yet how to articulate that clearly but my feeling is that it
would be better to allow required features only via format bumps (since
that's what they are really).

> We won't nag users into upgrading, and it would only affect users that
> actually use the features rather than everybody. If we can add support for
> ignoring "optional " lines to older release series o...

Read more...

Revision history for this message
Jelmer Vernooij (jelmer) wrote : Posted in a previous version of this proposal
Download full text (4.2 KiB)

Am 19/12/11 11:08, schrieb Vincent Ladeuil:
>>> That's trivial to skip.
>> Sure, but it also reduces the amount of usefulness of it being an ini-like
> file.
>
> The point is not to make it an ini-like file but to address (end marker,
> checksum or robust parser).
>
> This is a robust parser.
>
> It means that's the only thing to be backported to older supported bzr
> releases to allow them to ignore the optional feature flags. They won't even
> try to use the parsed content, they will ignore it, as long as it can be
> parsed.
>
> This means that corruptions will be detected.
I really don't see how this will protect against corruption - if we care
about that we should be using a checksum. As far as I can tell it only
protects against corruption that happens to render the file imparsable
to an ini-parser - there are lots of different kinds of corruption it
doesn't help against. The same is true for the current format.

>> Sure, but what's the advantage of using an ini-like file in that case?
> It means changes to the parser doesn't need to be backported and more
> importantly deployed. We backport once, tell people to upgrade once and then
> we can still change the data we need without changing the parser and more
> importantly without requiring people to upgrade bzr more than once. And I
> mean upgrading code not data, very important distinction.
It also means that we can't add anything else to the format file that
clients may not ignore.

The nice thing about the current format is that if we wanted to add
something else than feature flags, we could also allow optional and
required things for that.

Bzr branch format v1
optional feature search
required feature bar
optional index bar

will still work.

>
>> I'd much prefer a simple to parse format over a more complex one.
> The point is that we can't be sure that the format is complete for now.
Right, but that doesn't mean anything else in the file should optional.
It should be possible to add more to this file in the future and *still*
make it so the current version of bzr will refuse to open it. If we
parse the rest of the file as ini file then that means that we can't add
any mandatory stuff later.
>
>> It should also have less object overhead, though I'm not sure how relevant
>> that is.
> I fon't thnk it's relevant since older bzr will just ignore it.
They shouldn't simply ignore it - that would make it impossible to add
mandatory features.

>> Of course, it would be nice if all features could be optional, but that
>> isn't always the case - sometimes adding a feature renders a repository
>> inaccessible to older versions of bzr.
> That's the definition of a format bump. I think that allowing feature to
> trigger a format bump is a Bad Idea.
But that's exactly the main point of features. The goal is to allow
people to enable a particular feature without requiring a full upgrade,
being able to turn that feature off later or adding combinations of
features. It's a lot more granular than full format bumps.

Currently, we have full formats that combine a bunch of different
features. If we add support for nested trees and introduce a new format,
then repositories created with that new ...

Read more...

Revision history for this message
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal
Download full text (3.9 KiB)

> Am 19/12/11 11:08, schrieb Vincent Ladeuil:
> >>> That's trivial to skip.
> >> Sure, but it also reduces the amount of usefulness of it being an ini-like
> > file.
> >
> > The point is not to make it an ini-like file but to address (end marker,
> > checksum or robust parser).
> >
> > This is a robust parser.
> >
> > It means that's the only thing to be backported to older supported bzr
> > releases to allow them to ignore the optional feature flags. They won't even
> > try to use the parsed content, they will ignore it, as long as it can be
> > parsed.
> >
> > This means that corruptions will be detected.

> I really don't see how this will protect against corruption - if we care
> about that we should be using a checksum. As far as I can tell it only
> protects against corruption that happens to render the file imparsable to
> an ini-parser - there are lots of different kinds of corruption it doesn't
> help against.

I'm not aiming perfection but robustness, the point is not to detect all
corruptions but to not break if the corruption occurs in ignored data.

> The same is true for the current format.

Only for the data you've already specified. Each addition requires
backporting to previous releases and then deployment.

>
>
> >> Sure, but what's the advantage of using an ini-like file in that case?
> > It means changes to the parser doesn't need to be backported and more
> > importantly deployed. We backport once, tell people to upgrade once and then
> > we can still change the data we need without changing the parser and more
> > importantly without requiring people to upgrade bzr more than once. And I
> > mean upgrading code not data, very important distinction.

> It also means that we can't add anything else to the format file that
> clients may not ignore.

But I don't want to add data that clients may not ignore, that's the
definition of a format bump !

>
> The nice thing about the current format is that if we wanted to add
> something else than feature flags, we could also allow optional and
> required things for that.

>
> Bzr branch format v1
> optional feature search
> required feature bar
> optional index bar
>
> will still work.

You can add that and far more with an ini-like format.

The disagreement here is not about the format it's about whether we want to
turn supported formats into unsupported ones.

> >
> >> I'd much prefer a simple to parse format over a more complex one.
> > The point is that we can't be sure that the format is complete for now.

> Right, but that doesn't mean anything else in the file should optional.
> It should be possible to add more to this file in the future and *still*
> make it so the current version of bzr will refuse to open it. If we
> parse the rest of the file as ini file then that means that we can't add
> any mandatory stuff later.

That's exactly what I want. It's an issue separate from the format itself,
I'm *against* adding data in the format file if it triggers a format bump.

> > I fear that allowing required features will mean allowing (and therefore
> > supporting) *two* upgrade stories instead of focusing on a single one.

> Features aren't separate of formats, they are...

Read more...

Revision history for this message
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal

Thanks for your patience on IRC, I think the result was worth it.

review: Approve
Revision history for this message
Vincent Ladeuil (vila) wrote :

Thanks for your patience on IRC, I think the result was worth it.

Voting on the right proposal ;)

review: Approve
Revision history for this message
Jelmer Vernooij (jelmer) wrote :
Download full text (41.4 KiB)

[10:49] <jelmer> vila: let me know if you'd like to discus feature flags some more
[10:50] <jelmer> I've updated the docs
[10:51] <vila> I'll look into it, I was reviewing switch-possible-transports and there are still 2 test failures :-/
[10:51] <vila> comment posted
[10:52] <jelmer> vila: switch-possible-transports has two prerequisites, one of which isn't approved either.. so there are other branches I'm more interested in tbh :)
[10:53] <vila> yeah, well...
[11:01] <jelmer> vila: that's not to say I don't appreciate the review..
[11:05] <vila> so, if the feature name is global, _present_features should contain all features provided by the installed code base (ie. bzr and plugins) right ?
[11:06] <jelmer> yep
[11:06] <vila> hmm, I was about to ask for some help associated with the feature but that's useless for features that are not "installed"
[11:07] <vila> (thinking out aloud)
[11:08] <vila> lock_write raises for write acces, but what about read access ?
[11:09] <vila> if the format talks about feature, why does each line says feature ? Didn't we settle to put all features on the same line for each kind of necessity ? And consider all unknown necessities as required ?
[11:10] <jelmer> vila: All unknown necessities are considered required by the existing code
[11:10] <vila> (given that when a plugin provides a feature it's present even if the necessity s unknown)
[11:10] <jelmer> vila: I don't think we discussed the layout
[11:10] <vila> ha, misunderstanding then :)
[11:11] <jelmer> vila: I agree there are in concept two lists at the moment - the optional and the required features
[11:11] <vila> and optional additional lists for unknown necessities
[11:12] <vila> the core issues are still the same whatever format we chose :)
[11:12] <vila> I'm trying to agree with you on the simplest one that allows the most robust parser :)
[11:13] <vila> we're close to a simple scanner and scanners are more robust than parsers
[11:13] <jelmer> what's the difference?
[11:14] <vila> wow, many
[11:14] <vila> roughly a scanner doesn't need to take a context into account nor handle error recovery
[11:14] <vila> it sees a stream of text and split it into tokens
[11:15] <vila> a parser sees a stream of tokens and recognizes more complex constructs
[11:15] <jelmer> vila: right, I don't see how we can really avoid a parser?
[11:15] <jelmer> we'll need one in either case, even if it's very simple
[11:16] <vila> right, scanners are often called parsers even where there is no grammar to parse
[11:16] <jelmer> I think there is some really basic grammar here :)
[11:17] <vila> no, only tokens separated by spaces
[11:17] <vila> and newlines
[11:17] <jelmer> grammar exists over those tokens
[11:18] <vila> well, if you insist... but it's so trivial it doesn't need a parser
[11:18] <jelmer> we only allow "optional feature foo", not "foo bar bla"
[11:18] <vila> anyway, forget about the distinction
[11:18] <jelmer> sure
[11:18] <vila> I'd prefer: optional foo, bar
[11:18] <vila> required biz, bob
[11:19] <vila> 'feature' is just noise here
[11:19] <jelmer> vila: I've considered that as well, but it's less extendable in the future
[11:19] <jelmer> it doesn't allow adding ex...

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

... for those reading the MP at home.

Thanks for your patience too Vincent. :)

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

sent to pqm by email

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/branch.py'
2--- bzrlib/branch.py 2011-12-21 20:43:29 +0000
3+++ bzrlib/branch.py 2011-12-22 17:33:44 +0000
4@@ -53,6 +53,7 @@
5 import bzrlib.bzrdir
6
7 from bzrlib import (
8+ bzrdir,
9 controldir,
10 )
11 from bzrlib.decorators import (
12@@ -1587,7 +1588,7 @@
13
14 Formats provide three things:
15 * An initialization routine,
16- * a format string,
17+ * a format description
18 * an open routine.
19
20 Formats are placed in an dict by their format string for reference
21@@ -1999,13 +2000,13 @@
22 self.revision_id)
23
24
25-class BranchFormatMetadir(bzrdir.BzrDirMetaComponentFormat, BranchFormat):
26+class BranchFormatMetadir(bzrdir.BzrFormat, BranchFormat):
27 """Base class for branch formats that live in meta directories.
28 """
29
30 def __init__(self):
31 BranchFormat.__init__(self)
32- bzrdir.BzrDirMetaComponentFormat.__init__(self)
33+ bzrdir.BzrFormat.__init__(self)
34
35 @classmethod
36 def find_format(klass, controldir, name=None):
37@@ -2049,7 +2050,7 @@
38 control_files.create_lock()
39 control_files.lock_write()
40 try:
41- utf8_files += [('format', self.get_format_string())]
42+ utf8_files += [('format', self.as_string())]
43 for (filename, content) in utf8_files:
44 branch_transport.put_bytes(
45 filename, content,
46@@ -2097,6 +2098,14 @@
47 def supports_leaving_lock(self):
48 return True
49
50+ def check_support_status(self, allow_unsupported, recommend_upgrade=True,
51+ basedir=None):
52+ BranchFormat.check_support_status(self,
53+ allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
54+ basedir=basedir)
55+ bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
56+ recommend_upgrade=recommend_upgrade, basedir=basedir)
57+
58
59 class BzrBranchFormat5(BranchFormatMetadir):
60 """Bzr branch format 5.
61@@ -2304,7 +2313,7 @@
62 branch_transport = a_bzrdir.get_branch_transport(self, name=name)
63 branch_transport.put_bytes('location',
64 target_branch.user_url)
65- branch_transport.put_bytes('format', self.get_format_string())
66+ branch_transport.put_bytes('format', self.as_string())
67 branch = self.open(
68 a_bzrdir, name, _found=True,
69 possible_transports=[target_branch.bzrdir.root_transport])
70@@ -3218,7 +3227,7 @@
71
72 # Copying done; now update target format
73 new_branch._transport.put_bytes('format',
74- format.get_format_string(),
75+ format.as_string(),
76 mode=new_branch.bzrdir._get_file_mode())
77
78 # Clean up old files
79@@ -3237,7 +3246,7 @@
80 format = BzrBranchFormat7()
81 branch._set_config_location('stacked_on_location', '')
82 # update target format
83- branch._transport.put_bytes('format', format.get_format_string())
84+ branch._transport.put_bytes('format', format.as_string())
85
86
87 class Converter7to8(object):
88@@ -3247,7 +3256,7 @@
89 format = BzrBranchFormat8()
90 branch._transport.put_bytes('references', '')
91 # update target format
92- branch._transport.put_bytes('format', format.get_format_string())
93+ branch._transport.put_bytes('format', format.as_string())
94
95
96 class InterBranch(InterObject):
97
98=== modified file 'bzrlib/bzrdir.py'
99--- bzrlib/bzrdir.py 2011-12-19 13:23:58 +0000
100+++ bzrlib/bzrdir.py 2011-12-22 17:33:44 +0000
101@@ -1102,7 +1102,7 @@
102 return self.transport.clone(path)
103
104
105-class BzrDirMetaComponentFormat(controldir.ControlComponentFormat):
106+class BzrFormat(object):
107 """Base class for all formats of things living in metadirs.
108
109 This class manages the format string that is stored in the 'format'
110@@ -1113,36 +1113,105 @@
111 (i.e. different from .bzr/branch-format) derive from this class,
112 as well as the relevant base class for their kind
113 (BranchFormat, WorkingTreeFormat, RepositoryFormat).
114+
115+ Each format is identified by a "format" or "branch-format" file with a
116+ single line containing the base format name and then an optional list of
117+ feature flags.
118+
119+ Feature flags are supported as of bzr 2.5. Setting feature flags on formats
120+ will render them inaccessible to older versions of bzr.
121+
122+ :ivar features: Dictionary mapping feature names to their necessity
123 """
124
125+ _present_features = set()
126+
127+ def __init__(self):
128+ self.features = {}
129+
130+ @classmethod
131+ def register_feature(cls, name):
132+ """Register a feature as being present.
133+
134+ :param name: Name of the feature
135+ """
136+ if " " in name:
137+ raise ValueError("spaces are not allowed in feature names")
138+ if name in cls._present_features:
139+ raise errors.FeatureAlreadyRegistered(name)
140+ cls._present_features.add(name)
141+
142+ @classmethod
143+ def unregister_feature(cls, name):
144+ """Unregister a feature."""
145+ cls._present_features.remove(name)
146+
147+ def check_support_status(self, allow_unsupported, recommend_upgrade=True,
148+ basedir=None):
149+ for name, necessity in self.features.iteritems():
150+ if name in self._present_features:
151+ continue
152+ if necessity == "optional":
153+ mutter("ignoring optional missing feature %s", name)
154+ continue
155+ elif necessity == "required":
156+ raise errors.MissingFeature(name)
157+ else:
158+ mutter("treating unknown necessity as require for %s",
159+ name)
160+ raise errors.MissingFeature(name)
161+
162 @classmethod
163 def get_format_string(cls):
164 """Return the ASCII format string that identifies this format."""
165 raise NotImplementedError(cls.get_format_string)
166
167 @classmethod
168- def from_string(cls, format_string):
169- if format_string != cls.get_format_string():
170- raise ValueError("Invalid format header %r" % format_string)
171- return cls()
172+ def from_string(cls, text):
173+ format_string = cls.get_format_string()
174+ if not text.startswith(format_string):
175+ raise AssertionError("Invalid format header %r for %r" % (text, cls))
176+ lines = text[len(format_string):].splitlines()
177+ ret = cls()
178+ for lineno, line in enumerate(lines):
179+ try:
180+ (necessity, feature) = line.split(" ", 1)
181+ except ValueError:
182+ raise errors.ParseFormatError(format=cls, lineno=lineno+2,
183+ line=line, text=text)
184+ ret.features[feature] = necessity
185+ return ret
186+
187+ def as_string(self):
188+ """Return the string representation of this format.
189+ """
190+ lines = [self.get_format_string()]
191+ lines.extend([("%s %s\n" % (item[1], item[0])) for item in
192+ self.features.iteritems()])
193+ return "".join(lines)
194
195 @classmethod
196 def _find_format(klass, registry, kind, format_string):
197 try:
198- cls = registry.get(format_string)
199+ first_line = format_string[:format_string.index("\n")+1]
200+ except ValueError:
201+ first_line = format_string
202+ try:
203+ cls = registry.get(first_line)
204 except KeyError:
205- raise errors.UnknownFormatError(format=format_string, kind=kind)
206- return cls
207+ raise errors.UnknownFormatError(format=first_line, kind=kind)
208+ return cls.from_string(format_string)
209
210 def network_name(self):
211 """A simple byte string uniquely identifying this format for RPC calls.
212
213 Metadir branch formats use their format string.
214 """
215- return self.get_format_string()
216+ return self.as_string()
217
218 def __eq__(self, other):
219- return (self.__class__ is other.__class__)
220+ return (self.__class__ is other.__class__ and
221+ self.features == other.features)
222
223
224 class BzrProber(controldir.Prober):
225@@ -1169,9 +1238,13 @@
226 except errors.NoSuchFile:
227 raise errors.NotBranchError(path=transport.base)
228 try:
229- cls = klass.formats.get(format_string)
230+ first_line = format_string[:format_string.index("\n")+1]
231+ except ValueError:
232+ first_line = format_string
233+ try:
234+ cls = klass.formats.get(first_line)
235 except KeyError:
236- raise errors.UnknownFormatError(format=format_string, kind='bzrdir')
237+ raise errors.UnknownFormatError(format=first_line, kind='bzrdir')
238 return cls.from_string(format_string)
239
240 @classmethod
241@@ -1221,7 +1294,7 @@
242 return set([RemoteBzrDirFormat()])
243
244
245-class BzrDirFormat(controldir.ControlDirFormat):
246+class BzrDirFormat(BzrFormat, controldir.ControlDirFormat):
247 """ControlDirFormat base class for .bzr/ directories.
248
249 Formats are placed in a dict by their format string for reference
250@@ -1238,11 +1311,6 @@
251 # _lock_class must be set in subclasses to the lock type, typ.
252 # TransportLock or LockDir
253
254- @classmethod
255- def get_format_string(cls):
256- """Return the ASCII format string that identifies this format."""
257- raise NotImplementedError(cls.get_format_string)
258-
259 def initialize_on_transport(self, transport):
260 """Initialize a new bzrdir in the base directory of a Transport."""
261 try:
262@@ -1433,11 +1501,20 @@
263 compatible with whatever sub formats are supported by self.
264 :return: None.
265 """
266+ other_format.features = dict(self.features)
267
268 def supports_transport(self, transport):
269 # bzr formats can be opened over all known transports
270 return True
271
272+ def check_support_status(self, allow_unsupported, recommend_upgrade=True,
273+ basedir=None):
274+ controldir.ControlDirFormat.check_support_status(self,
275+ allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
276+ basedir=basedir)
277+ BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
278+ recommend_upgrade=recommend_upgrade, basedir=basedir)
279+
280
281 class BzrDirMetaFormat1(BzrDirFormat):
282 """Bzr meta control format 1
283@@ -1459,6 +1536,7 @@
284 colocated_branches = False
285
286 def __init__(self):
287+ BzrDirFormat.__init__(self)
288 self._workingtree_format = None
289 self._branch_format = None
290 self._repository_format = None
291@@ -1470,6 +1548,8 @@
292 return False
293 if other.workingtree_format != self.workingtree_format:
294 return False
295+ if other.features != self.features:
296+ return False
297 return True
298
299 def __ne__(self, other):
300@@ -1601,15 +1681,6 @@
301 """See BzrDirFormat.get_format_description()."""
302 return "Meta directory format 1"
303
304- @classmethod
305- def from_string(cls, format_string):
306- if format_string != cls.get_format_string():
307- raise ValueError("Invalid format string %r" % format_string)
308- return cls()
309-
310- def network_name(self):
311- return self.get_format_string()
312-
313 def _open(self, transport):
314 """See BzrDirFormat._open."""
315 # Create a new format instance because otherwise initialisation of new
316@@ -1644,6 +1715,7 @@
317 compatible with whatever sub formats are supported by self.
318 :return: None.
319 """
320+ super(BzrDirMetaFormat1, self)._supply_sub_formats_to(other_format)
321 if getattr(self, '_repository_format', None) is not None:
322 other_format.repository_format = self.repository_format
323 if self._branch_format is not None:
324@@ -1919,7 +1991,7 @@
325 :return: A repository, is_new_flag (True if the repository was
326 created).
327 """
328- raise NotImplemented(RepositoryAcquisitionPolicy.acquire_repository)
329+ raise NotImplementedError(RepositoryAcquisitionPolicy.acquire_repository)
330
331
332 class CreateRepository(RepositoryAcquisitionPolicy):
333
334=== modified file 'bzrlib/controldir.py'
335--- bzrlib/controldir.py 2011-12-19 13:23:58 +0000
336+++ bzrlib/controldir.py 2011-12-22 17:33:44 +0000
337@@ -842,7 +842,7 @@
338
339
340 class ControlComponentFormat(object):
341- """A component that can live inside of a .bzr meta directory."""
342+ """A component that can live inside of a control directory."""
343
344 upgrade_recommended = False
345
346
347=== modified file 'bzrlib/errors.py'
348--- bzrlib/errors.py 2011-12-19 13:23:58 +0000
349+++ bzrlib/errors.py 2011-12-22 17:33:44 +0000
350@@ -763,6 +763,18 @@
351 self.bzrdir = bzrdir_format
352
353
354+class ParseFormatError(BzrError):
355+
356+ _fmt = "Parse error on line %(lineno)d of %(format)s format: %(line)s"
357+
358+ def __init__(self, format, lineno, line, text):
359+ BzrError.__init__(self)
360+ self.format = format
361+ self.lineno = lineno
362+ self.line = line
363+ self.text = text
364+
365+
366 class IncompatibleRepositories(BzrError):
367 """Report an error that two repositories are not compatible.
368
369@@ -3242,6 +3254,15 @@
370 self.format = format
371
372
373+class MissingFeature(BzrError):
374+
375+ _fmt = ("Missing feature %(feature)s not provided by this "
376+ "version of Bazaar or any plugin.")
377+
378+ def __init__(self, feature):
379+ self.feature = feature
380+
381+
382 class PatchSyntax(BzrError):
383 """Base class for patch syntax errors."""
384
385@@ -3291,3 +3312,11 @@
386 self.line_no = line_no
387 self.orig_line = orig_line.rstrip('\n')
388 self.patch_line = patch_line.rstrip('\n')
389+
390+
391+class FeatureAlreadyRegistered(BzrError):
392+
393+ _fmt = 'The feature %(feature)s has already been registered.'
394+
395+ def __init__(self, feature):
396+ self.feature = feature
397
398=== modified file 'bzrlib/repository.py'
399--- bzrlib/repository.py 2011-12-19 11:49:56 +0000
400+++ bzrlib/repository.py 2011-12-22 17:33:44 +0000
401@@ -1497,7 +1497,7 @@
402 hook(params)
403
404
405-class RepositoryFormatMetaDir(bzrdir.BzrDirMetaComponentFormat, RepositoryFormat):
406+class RepositoryFormatMetaDir(bzrdir.BzrFormat, RepositoryFormat):
407 """Common base class for the new repositories using the metadir layout."""
408
409 rich_root_data = False
410@@ -1514,7 +1514,7 @@
411
412 def __init__(self):
413 RepositoryFormat.__init__(self)
414- bzrdir.BzrDirMetaComponentFormat.__init__(self)
415+ bzrdir.BzrFormat.__init__(self)
416
417 def _create_control_files(self, a_bzrdir):
418 """Create the required files and the initial control_files object."""
419@@ -1559,6 +1559,14 @@
420 raise errors.NoRepositoryPresent(a_bzrdir)
421 return klass._find_format(format_registry, 'repository', format_string)
422
423+ def check_support_status(self, allow_unsupported, recommend_upgrade=True,
424+ basedir=None):
425+ RepositoryFormat.check_support_status(self,
426+ allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
427+ basedir=basedir)
428+ bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
429+ recommend_upgrade=recommend_upgrade, basedir=basedir)
430+
431
432 # formats which have no format string are not discoverable or independently
433 # creatable on disk, so are not registered in format_registry. They're
434
435=== modified file 'bzrlib/tests/test_branch.py'
436--- bzrlib/tests/test_branch.py 2011-12-20 14:04:21 +0000
437+++ bzrlib/tests/test_branch.py 2011-12-22 17:33:44 +0000
438@@ -201,7 +201,7 @@
439 self.assertIsInstance(
440 SampleBranchFormat.from_string("Sample branch format."),
441 SampleBranchFormat)
442- self.assertRaises(ValueError,
443+ self.assertRaises(AssertionError,
444 SampleBranchFormat.from_string, "Different branch format.")
445
446 def test_find_format_not_branch(self):
447@@ -217,6 +217,15 @@
448 _mod_branch.BranchFormatMetadir.find_format,
449 dir)
450
451+ def test_find_format_with_features(self):
452+ tree = self.make_branch_and_tree('.', format='2a')
453+ tree.branch.control_transport.put_bytes('format',
454+ tree.branch._format.get_format_string() +
455+ "optional name\n")
456+ found_format = _mod_branch.BranchFormatMetadir.find_format(tree.bzrdir)
457+ self.assertIsInstance(found_format, _mod_branch.BranchFormatMetadir)
458+ self.assertEquals(found_format.features.get("name"), "optional")
459+
460 def test_register_unregister_format(self):
461 # Test the deprecated format registration functions
462 format = SampleBranchFormat()
463@@ -718,4 +727,3 @@
464 f = StringIO()
465 r.report(f)
466 self.assertEqual("No revisions or tags to pull.\n", f.getvalue())
467-
468
469=== modified file 'bzrlib/tests/test_bzrdir.py'
470--- bzrlib/tests/test_bzrdir.py 2011-12-07 14:03:01 +0000
471+++ bzrlib/tests/test_bzrdir.py 2011-12-22 17:33:44 +0000
472@@ -1025,6 +1025,17 @@
473 self.assertNotEqual(otherdir2, mydir)
474 self.assertFalse(otherdir2 == mydir)
475
476+ def test_with_features(self):
477+ tree = self.make_branch_and_tree('tree', format='2a')
478+ tree.bzrdir.control_transport.put_bytes(
479+ 'branch-format',
480+ tree.bzrdir._format.get_format_string() + "required bar\n")
481+ self.assertRaises(errors.MissingFeature, bzrdir.BzrDir.open, 'tree')
482+ bzrdir.BzrDirMetaFormat1.register_feature('bar')
483+ self.addCleanup(bzrdir.BzrDirMetaFormat1.unregister_feature, 'bar')
484+ dir = bzrdir.BzrDir.open('tree')
485+ self.assertEquals("required", dir._format.features.get("bar"))
486+
487 def test_needs_conversion_different_working_tree(self):
488 # meta1dirs need an conversion if any element is not the default.
489 new_format = bzrdir.format_registry.make_bzrdir('dirstate')
490@@ -1435,3 +1446,112 @@
491 self.assertRaises(errors.BzrError, converter.convert, tree.bzrdir,
492 None)
493
494+
495+class SampleBzrFormat(bzrdir.BzrFormat):
496+
497+ @classmethod
498+ def get_format_string(cls):
499+ return "First line\n"
500+
501+
502+class TestBzrFormat(TestCase):
503+ """Tests for BzrFormat."""
504+
505+ def test_as_string(self):
506+ format = SampleBzrFormat()
507+ format.features = {"foo": "required"}
508+ self.assertEquals(format.as_string(),
509+ "First line\n"
510+ "required foo\n")
511+ format.features["another"] = "optional"
512+ self.assertEquals(format.as_string(),
513+ "First line\n"
514+ "required foo\n"
515+ "optional another\n")
516+
517+ def test_network_name(self):
518+ # The network string should include the feature info
519+ format = SampleBzrFormat()
520+ format.features = {"foo": "required"}
521+ self.assertEquals(
522+ "First line\nrequired foo\n",
523+ format.network_name())
524+
525+ def test_from_string_no_features(self):
526+ # No features
527+ format = SampleBzrFormat.from_string(
528+ "First line\n")
529+ self.assertEquals({}, format.features)
530+
531+ def test_from_string_with_feature(self):
532+ # Proper feature
533+ format = SampleBzrFormat.from_string(
534+ "First line\nrequired foo\n")
535+ self.assertEquals("required", format.features.get("foo"))
536+
537+ def test_from_string_format_string_mismatch(self):
538+ # The first line has to match the format string
539+ self.assertRaises(AssertionError, SampleBzrFormat.from_string,
540+ "Second line\nrequired foo\n")
541+
542+ def test_from_string_missing_space(self):
543+ # At least one space is required in the feature lines
544+ self.assertRaises(errors.ParseFormatError, SampleBzrFormat.from_string,
545+ "First line\nfoo\n")
546+
547+ def test_from_string_with_spaces(self):
548+ # Feature with spaces (in case we add stuff like this in the future)
549+ format = SampleBzrFormat.from_string(
550+ "First line\nrequired foo with spaces\n")
551+ self.assertEquals("required", format.features.get("foo with spaces"))
552+
553+ def test_eq(self):
554+ format1 = SampleBzrFormat()
555+ format1.features = {"nested-trees": "optional"}
556+ format2 = SampleBzrFormat()
557+ format2.features = {"nested-trees": "optional"}
558+ self.assertEquals(format1, format1)
559+ self.assertEquals(format1, format2)
560+ format3 = SampleBzrFormat()
561+ self.assertNotEquals(format1, format3)
562+
563+ def test_check_support_status_optional(self):
564+ # Optional, so silently ignore
565+ format = SampleBzrFormat()
566+ format.features = {"nested-trees": "optional"}
567+ format.check_support_status(True)
568+ self.addCleanup(SampleBzrFormat.unregister_feature, "nested-trees")
569+ SampleBzrFormat.register_feature("nested-trees")
570+ format.check_support_status(True)
571+
572+ def test_check_support_status_required(self):
573+ # Optional, so trigger an exception
574+ format = SampleBzrFormat()
575+ format.features = {"nested-trees": "required"}
576+ self.assertRaises(errors.MissingFeature, format.check_support_status,
577+ True)
578+ self.addCleanup(SampleBzrFormat.unregister_feature, "nested-trees")
579+ SampleBzrFormat.register_feature("nested-trees")
580+ format.check_support_status(True)
581+
582+ def test_check_support_status_unknown(self):
583+ # treat unknown necessity as required
584+ format = SampleBzrFormat()
585+ format.features = {"nested-trees": "unknown"}
586+ self.assertRaises(errors.MissingFeature, format.check_support_status,
587+ True)
588+ self.addCleanup(SampleBzrFormat.unregister_feature, "nested-trees")
589+ SampleBzrFormat.register_feature("nested-trees")
590+ format.check_support_status(True)
591+
592+ def test_feature_already_registered(self):
593+ # a feature can only be registered once
594+ self.addCleanup(SampleBzrFormat.unregister_feature, "nested-trees")
595+ SampleBzrFormat.register_feature("nested-trees")
596+ self.assertRaises(errors.FeatureAlreadyRegistered,
597+ SampleBzrFormat.register_feature, "nested-trees")
598+
599+ def test_feature_with_space(self):
600+ # spaces are not allowed in feature names
601+ self.assertRaises(ValueError, SampleBzrFormat.register_feature,
602+ "nested trees")
603
604=== modified file 'bzrlib/tests/test_repository.py'
605--- bzrlib/tests/test_repository.py 2011-12-14 14:09:47 +0000
606+++ bzrlib/tests/test_repository.py 2011-12-22 17:33:44 +0000
607@@ -153,7 +153,7 @@
608 SampleRepositoryFormat.from_string(
609 "Sample .bzr repository format."),
610 SampleRepositoryFormat)
611- self.assertRaises(ValueError,
612+ self.assertRaises(AssertionError,
613 SampleRepositoryFormat.from_string,
614 "Different .bzr repository format.")
615
616@@ -164,6 +164,21 @@
617 repository.RepositoryFormatMetaDir.find_format,
618 dir)
619
620+ def test_find_format_with_features(self):
621+ tree = self.make_branch_and_tree('.', format='2a')
622+ tree.branch.repository.control_transport.put_bytes('format',
623+ tree.branch.repository._format.get_format_string() +
624+ "necessity name\n")
625+ found_format = repository.RepositoryFormatMetaDir.find_format(tree.bzrdir)
626+ self.assertIsInstance(found_format, repository.RepositoryFormatMetaDir)
627+ self.assertEquals(found_format.features.get("name"), "necessity")
628+ self.assertRaises(errors.MissingFeature, found_format.check_support_status,
629+ True)
630+ self.addCleanup(repository.RepositoryFormatMetaDir.unregister_feature,
631+ "name")
632+ repository.RepositoryFormatMetaDir.register_feature("name")
633+ found_format.check_support_status(True)
634+
635 def test_register_unregister_format(self):
636 # Test deprecated format registration functions
637 format = SampleRepositoryFormat()
638@@ -1703,3 +1718,25 @@
639 lazy = repository._LazyListJoin(['a'], ['b'])
640 self.assertEqual("bzrlib.repository._LazyListJoin((['a'], ['b']))",
641 repr(lazy))
642+
643+
644+class TestFeatures(tests.TestCaseWithTransport):
645+
646+ def test_open_with_present_feature(self):
647+ self.addCleanup(
648+ repository.RepositoryFormatMetaDir.unregister_feature,
649+ "makes-cheese-sandwich")
650+ repository.RepositoryFormatMetaDir.register_feature(
651+ "makes-cheese-sandwich")
652+ repo = self.make_repository('.')
653+ repo.lock_write()
654+ repo._format.features["makes-cheese-sandwich"] = "required"
655+ repo._format.check_support_status(False)
656+ repo.unlock()
657+
658+ def test_open_with_missing_required_feature(self):
659+ repo = self.make_repository('.')
660+ repo.lock_write()
661+ repo._format.features["makes-cheese-sandwich"] = "required"
662+ self.assertRaises(errors.MissingFeature,
663+ repo._format.check_support_status, False)
664
665=== modified file 'bzrlib/tests/test_workingtree.py'
666--- bzrlib/tests/test_workingtree.py 2011-12-11 02:43:03 +0000
667+++ bzrlib/tests/test_workingtree.py 2011-12-22 17:33:44 +0000
668@@ -83,7 +83,7 @@
669 self.assertIsInstance(
670 SampleTreeFormat.from_string("Sample tree format."),
671 SampleTreeFormat)
672- self.assertRaises(ValueError,
673+ self.assertRaises(AssertionError,
674 SampleTreeFormat.from_string, "Different format string.")
675
676 def test_get_set_default_format_by_key(self):
677@@ -243,6 +243,21 @@
678 self.applyDeprecated(symbol_versioning.deprecated_in((2, 4, 0)),
679 workingtree.WorkingTreeFormat.get_formats))
680
681+ def test_find_format_with_features(self):
682+ tree = self.make_branch_and_tree('.', format='2a')
683+ tree.control_transport.put_bytes('format',
684+ tree._format.get_format_string() + "necessity name\n")
685+ found_format = workingtree.WorkingTreeFormatMetaDir.find_format(
686+ tree.bzrdir)
687+ self.assertIsInstance(found_format, workingtree.WorkingTreeFormat)
688+ self.assertEquals(found_format.features.get("name"), "necessity")
689+ self.assertRaises(errors.MissingFeature, found_format.check_support_status,
690+ True)
691+ self.addCleanup(workingtree.WorkingTreeFormatMetaDir.unregister_feature,
692+ "name")
693+ workingtree.WorkingTreeFormatMetaDir.register_feature("name")
694+ found_format.check_support_status(True)
695+
696
697 class TestWorkingTreeIterEntriesByDir_wSubtrees(TestCaseWithTransport):
698
699
700=== modified file 'bzrlib/workingtree.py'
701--- bzrlib/workingtree.py 2011-12-19 17:39:35 +0000
702+++ bzrlib/workingtree.py 2011-12-22 17:33:44 +0000
703@@ -47,7 +47,6 @@
704
705 from bzrlib import (
706 branch,
707- bzrdir,
708 conflicts as _mod_conflicts,
709 controldir,
710 errors,
711@@ -72,9 +71,11 @@
712
713 # Explicitly import bzrlib.bzrdir so that the BzrProber
714 # is guaranteed to be registered.
715-import bzrlib.bzrdir
716+from bzrlib import (
717+ bzrdir,
718+ symbol_versioning,
719+ )
720
721-from bzrlib import symbol_versioning
722 from bzrlib.decorators import needs_read_lock, needs_write_lock
723 from bzrlib.i18n import gettext
724 from bzrlib.lock import LogicalLockResult
725@@ -3133,12 +3134,12 @@
726 return self._matchingbzrdir
727
728
729-class WorkingTreeFormatMetaDir(bzrdir.BzrDirMetaComponentFormat, WorkingTreeFormat):
730+class WorkingTreeFormatMetaDir(bzrdir.BzrFormat, WorkingTreeFormat):
731 """Base class for working trees that live in bzr meta directories."""
732
733 def __init__(self):
734 WorkingTreeFormat.__init__(self)
735- bzrdir.BzrDirMetaComponentFormat.__init__(self)
736+ bzrdir.BzrFormat.__init__(self)
737
738 @classmethod
739 def find_format_string(klass, controldir):
740@@ -3156,6 +3157,14 @@
741 return klass._find_format(format_registry, 'working tree',
742 format_string)
743
744+ def check_support_status(self, allow_unsupported, recommend_upgrade=True,
745+ basedir=None):
746+ WorkingTreeFormat.check_support_status(self,
747+ allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
748+ basedir=basedir)
749+ bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
750+ recommend_upgrade=recommend_upgrade, basedir=basedir)
751+
752
753 format_registry.register_lazy("Bazaar Working Tree Format 4 (bzr 0.15)\n",
754 "bzrlib.workingtree_4", "WorkingTreeFormat4")
755
756=== modified file 'bzrlib/workingtree_3.py'
757--- bzrlib/workingtree_3.py 2011-12-19 13:23:58 +0000
758+++ bzrlib/workingtree_3.py 2011-12-22 17:33:44 +0000
759@@ -195,7 +195,7 @@
760 control_files = self._open_control_files(a_bzrdir)
761 control_files.create_lock()
762 control_files.lock_write()
763- transport.put_bytes('format', self.get_format_string(),
764+ transport.put_bytes('format', self.as_string(),
765 mode=a_bzrdir._get_file_mode())
766 if from_branch is not None:
767 branch = from_branch
768
769=== modified file 'bzrlib/workingtree_4.py'
770--- bzrlib/workingtree_4.py 2011-12-19 17:39:35 +0000
771+++ bzrlib/workingtree_4.py 2011-12-22 17:33:44 +0000
772@@ -1478,7 +1478,7 @@
773 control_files = self._open_control_files(a_bzrdir)
774 control_files.create_lock()
775 control_files.lock_write()
776- transport.put_bytes('format', self.get_format_string(),
777+ transport.put_bytes('format', self.as_string(),
778 mode=a_bzrdir._get_file_mode())
779 if from_branch is not None:
780 branch = from_branch
781@@ -2260,7 +2260,7 @@
782 def update_format(self, tree):
783 """Change the format marker."""
784 tree._transport.put_bytes('format',
785- self.target_format.get_format_string(),
786+ self.target_format.as_string(),
787 mode=tree.bzrdir._get_file_mode())
788
789
790@@ -2283,7 +2283,7 @@
791 def update_format(self, tree):
792 """Change the format marker."""
793 tree._transport.put_bytes('format',
794- self.target_format.get_format_string(),
795+ self.target_format.as_string(),
796 mode=tree.bzrdir._get_file_mode())
797
798
799@@ -2312,5 +2312,5 @@
800 def update_format(self, tree):
801 """Change the format marker."""
802 tree._transport.put_bytes('format',
803- self.target_format.get_format_string(),
804+ self.target_format.as_string(),
805 mode=tree.bzrdir._get_file_mode())
806
807=== modified file 'doc/developers/feature-flags.txt'
808--- doc/developers/feature-flags.txt 2011-10-24 19:21:00 +0000
809+++ doc/developers/feature-flags.txt 2011-12-22 17:33:44 +0000
810@@ -51,8 +51,8 @@
811 Feature necessity
812 -----------------
813
814-The initial implementation will feature the following set of possible
815-settings for feature "necessity". Any format necessity that can't
816+The initial implementation will feature the following two possible
817+settings for feature ``necessity``. Any format necessity that can't
818 be understood should be interpreted as "required", and an appropriate
819 warning printed.
820
821@@ -62,8 +62,16 @@
822 annotate cache)
823 - required: read and write access is only possible if the feature
824 is supported. Useful for things like nested trees.
825- - write-required: read access is possible if the feature is not supported,
826+
827+In the future, we might add more values for necessity. Older
828+versions of bzr treat unknown necessities as "required". Some likely
829+candidates for new necessities that might be added in the future:
830+
831+ - read-optional: read access is possible if the feature is not supported,
832 but write access requires it
833+ - client-read-optional: directly writing to the object requires
834+ the feature, but reading or writing through an intermediary (such as
835+ a HPSS server) doesn't.
836
837 Format changes
838 --------------
839@@ -94,23 +102,32 @@
840 API Changes
841 -----------
842
843-Class methods will be added to ``BzrDirComponentFormat`` to allow registering
844-and deregistering the presence of particular features. This class is inherited
845-by ``BzrBranchFormat``, ``VersionedFileRepositoryFormat`` and
846-``InventoryWorkingTreeFormat``.
847-
848- * BzrDirComponentFormat.register_feature(name)
849- * BzrDirComponentFormat.unregister_feature(name)
850-
851-Upon opening, BzrDirComponentFormat will be responsible for checking that the
852+Class methods will be added to ``BzrFormat`` to allow registering
853+and unregistering the presence of particular features.
854+
855+ * BzrFormat.register_feature(name)
856+ * BzrFormat.unregister_feature(name)
857+
858+The namespace for features is global. It is assumed
859+that the plugin that provides the feature X provides that feature
860+in all objects that it is relevant for. For example, if a plugin
861+provides the ``nested-trees`` feature, it is assumed to support
862+that in both working trees and repositories. If this is not the case,
863+it should use a different feature name for the working tree support
864+and the repository support.
865+
866+BzrFormat is inherited by ``BranchFormatMetaDir``, ``BzrDirFormat``,
867+``RepositoryFormatMetaDir`` and ``WorkingTreeFormatMetaDir``.
868+
869+Upon opening, BzrFormat will be responsible for checking that the
870 required features are present. lock_write will raise an exception
871-when there is an un unsupported mandatory feature required for write access.
872+when there is an unsupported mandatory feature required for write access.
873
874-Methods will also be added to BzrDirComponentFormat to allow plugins, etc,
875+Methods will also be added to BzrFormat to allow plugins, etc,
876 to check whether a feature is present and adding new features:
877
878- * BzrDirComponentFormat.set_feature(name, necessity)
879- * BzrDirComponentFormat.get_feature(name) -> necessity
880+ * BzrFormat.features.set(name, necessity)
881+ * BzrFormat.features.get(name) -> necessity
882
883 See also
884 --------
885
886=== modified file 'doc/en/release-notes/bzr-2.5.txt'
887--- doc/en/release-notes/bzr-2.5.txt 2011-12-21 21:31:03 +0000
888+++ doc/en/release-notes/bzr-2.5.txt 2011-12-22 17:33:44 +0000
889@@ -365,6 +365,12 @@
890 * The registry of merge types has been moved to ``merge`` from ``option`` but
891 ``merge.get_merge_type_registry`` remains as an accessor. (Martin Packman)
892
893+* All bzr control directories, branch formats, repository formats and
894+ working tree formats now support feature flags, which are
895+ serialized in their respective format files. See
896+ ``doc/developers/feature-flags.txt`` for details.
897+ (Jelmer Vernooij)
898+
899 Testing
900 *******
901