Merge lp:~jelmer/bzr/feature-flags into lp:bzr
- feature-flags
- Merge into bzr.dev
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 |
Related bugs: |
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/
This adds a common base class ("BzrFormat") for all classes that deal with bzr 'format' files. This includes all bzrdir implementations, and branch/
There currently is no way to set feature flags from the Repository/
There is a very simple example of a plugin using this in lp:~jelmer/bzr-search/feature-flags.
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal | # |
Jelmer Vernooij (jelmer) wrote : Posted in a previous version of this proposal | # |
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/
>
> modified doc/developers/
>
>
> === modified file 'doc/developers
> --- doc/developers/
> +++ doc/developers/
> @@ -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 ``BzrBranchForm
> ``InventoryWork
>
> @@ -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...
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal | # |
> > 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....
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.
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.
even in your branch:
--- bzrlib/
+++ bzrlib/
@@ -146,10 +146,6 @@
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.
Jelmer Vernooij (jelmer) wrote : Posted in a previous version of this proposal | # |
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 ...
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.
> 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.
> even in your branch:
>
> --- bzrlib/
> +++ bzrlib/
> @@ -146,10 +146,6 @@
> t.put_bytes(
> 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
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal | # |
> > 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/
> 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...
Jelmer Vernooij (jelmer) wrote : Posted in a previous version of this proposal | # |
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 ...
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal | # |
> 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...
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.
Vincent Ladeuil (vila) wrote : | # |
Thanks for your patience on IRC, I think the result was worth it.
Voting on the right proposal ;)
Jelmer Vernooij (jelmer) wrote : | # |
[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-
[10:51] <vila> comment posted
[10:52] <jelmer> vila: switch-
[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...
Jelmer Vernooij (jelmer) wrote : | # |
... for those reading the MP at home.
Thanks for your patience too Vincent. :)
Jelmer Vernooij (jelmer) wrote : | # |
sent to pqm by email
Jelmer Vernooij (jelmer) wrote : | # |
sent to pqm by email
Preview Diff
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 |
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' feature- flags.txt 2011-12-07 17:45:57 +0000 feature- flags.txt 2011-12-14 14:56:20 +0000
--- doc/developers/
+++ doc/developers/
@@ -95,7 +95,7 @@
-----------
Class methods will be added to ``BzrFormat`` to allow registering at``, ``VersionedFile RepositoryForma t`` and kingTreeFormat` `.
-and deregistering the presence of particular features. This class is inherited
+and unregistering the presence of particular features. This class is inherited
by ``BzrBranchForm
``InventoryWor
@@ -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...