Merge lp:~mbp/bzr/391411-reconfigure-stacked into lp:~bzr/bzr/trunk-old
- 391411-reconfigure-stacked
- Merge into trunk-old
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | not available | ||||
Proposed branch: | lp:~mbp/bzr/391411-reconfigure-stacked | ||||
Merge into: | lp:~bzr/bzr/trunk-old | ||||
Diff against target: | 656 lines | ||||
To merge this branch: | bzr merge lp:~mbp/bzr/391411-reconfigure-stacked | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ian Clatworthy | Needs Fixing | ||
Review via email: mp+8527@code.launchpad.net |
Commit message
Description of the change
Martin Pool (mbp) wrote : | # |
Ian Clatworthy (ian-clatworthy) wrote : | # |
Martin Pool wrote:
> Martin Pool has proposed merging lp:~mbp/bzr/391411-reconfigure-stacked into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> This adds reconfigure --stacked-on and --unstacked options, and fixes a bug in fetching data when unstacking that made it break in all but trivial cases of empty branches.
>
> I've included here part of the -Dunlock code because it made debugging this much easier.
>
> It also adds a RepositoryBase so that we can have at least some common code with RemoteRepository.
>
Thanks for working on this. It certainly fills in an important hole. I
suspect it needs another round of polish though before it's good enough
to land.
> + Option('unstacked',
> + help='Reconfigure a branch to be unstacked. This '
> + 'may requiring copying substantial substantial data into it.',
>
s/requiring/
substantial x 2
> + stacked_on=None,
> + unstacked=None):
> directory = bzrdir.
> + if stacked_on is not None:
>
I think we ought to guard against both --stacked-on and --unstacked
being both passed. (At the moment, we assume stacked-on is what the user
meant. If we want to keep that assumption, we need to document it in the
help at a minimum.) A blackbox test is required for this case too.
> -Dmerge Emit information for debugging merges.
> +-Dunlock Some errors during unlock are treated as warnings.
> -Dpack Emit information about pack operations.
>
Looks like a tabbing or whitespace issue here.
> + # block, so it's useful to have tho option not to generate a new error
>
s/tho/the/
> @@ -18,6 +18,7 @@
> # across to run on the server.
>
> import bz2
> +import warnings
>
> from bzrlib import (
> bencode,
> @@ -27,11 +28,13 @@
> debug,
> errors,
> graph,
> + lock,
> lockdir,
> repository,
> revision,
> revision as _mod_revision,
> symbol_versioning,
> + trace,
>
The new imports of warnings and trace are redundant here.
> + def has_same_
> + my_fb = self._fallback_
> + other_fb = other_repo.
> + if len(my_fb) != len(other_fb):
> + return False
> + for f, g in zip(my_fb, other_fb):
> + if not f.has_same_
> + return False
> + return True
>
This needs a docstring. It's also a public API (currently) so it needs
API tests written for it and/or needs to be made private. (BTW, I don't
think _fallback_
needs to be slightly more defensive as well.)
> +Be aware that when ``bzr reconfigure --unstacked`` is used, bzr will
> +to copy all the referenced data from the stacked-on repository into the
s/to copy/copy/
In summary, it's mainly small tweaks above but I suspect you want a few
more tests as well.
Ian C.
Martin Pool (mbp) wrote : | # |
2009/7/14 Ian Clatworthy <email address hidden>:
>> + 'may requiring copying substantial substantial data into it.',
Well, it's _really_ substantial :)
Thanks for the review, I'll do those tweaks.
--
Martin <http://
Robert Collins (lifeless) wrote : | # |
On Fri, 2009-07-10 at 08:55 +0000, Martin Pool wrote:
> @@ -662,6 +663,9 @@
> """
> if not self._format.
> raise errors.
> self.base)
> + # XXX: Changing from one fallback repository to another does
> not check
> + # that all the data you need is present in the new fallback.
> + # Possibly it should.
I think this would be better as a bug 'reconfigure --stacked-on-url does
not check new location has sufficient data'.
> - # in.
> - source_repository =
> self._get_
> - for revision_id in chain([
> - self.tags.
> - self.repository
> - find_ghosts=True)
...
I think it would be better to /try/ to copy the tags. That should be
easy to do (fetch the tip, then for each tag try and catch
NoSuchRevision).
> + self.repository
> + revision_
> + find_ghosts=True)
> + finally:
> + old_repository.
> + finally:
> + pb.finished()
>
> def _set_tags_
> """Mirror method for _get_tags_bytes.
...
> - def run(self, location=None, target_type=None, bind_to=None,
> force=False):
> + def run(self, location=None, target_type=None, bind_to=None,
> force=False,
> +... 'specified')
> elif target_type == 'branch':
> reconfiguration =
> reconfigure.
> elif target_type == 'tree':
Your changes to the reconfigure command add quite a few lines of logic,
but most of reconfigure is meant to be in the reconfigure module where
it is accessible to GUI's and so on. I think that this should be moved
there before landing, as otherwise the branch 'reduces clarity of
layering'.
> === modified file 'bzrlib/fetch.py'
> --- bzrlib/fetch.py 2009-06-17 17:57:15 +0000
> +++ bzrlib/fetch.py 2009-07-09 08:59:51 +0000
> @@ -59,11 +59,8 @@
> symbol_
> % "pb parameter to RepoFetcher.
> # and for simplicity it is in fact ignored
> - if to_repository.
> - # repository.fetch should be taking care of this case.
> - raise errors.
> - 'between two objects at the same location: '
> - '%r and %r' % (to_repository, from_repository))
> + # repository.fetch has the responsibility for
> short-circuiting
> + # attempts to copy between a repository and itself.
Is the above change needed? We added it when we refactored - if you're
removing this because you noticed it, thats fine. If you're removing it
because it was firing, thats a problem.
I don't think RepositoryBase is needed. Thats what Repository is.
-Rob
Martin Pool (mbp) wrote : | # |
2009/7/15 Robert Collins <email address hidden>:
> On Fri, 2009-07-10 at 08:55 +0000, Martin Pool wrote:
>
>> @@ -662,6 +663,9 @@
>> """
>> if not self._format.
>> raise errors.
>> self.base)
>> + # XXX: Changing from one fallback repository to another does
>> not check
>> + # that all the data you need is present in the new fallback.
>> + # Possibly it should.
>
> I think this would be better as a bug 'reconfigure --stacked-on-url does
> not check new location has sufficient data'.
Agree.
>> - # in.
>> - source_repository =
>> self._get_
>> - for revision_id in chain([
>> - self.tags.
>> - self.repository
>> - find_ghosts=True)
>
> ...
> I think it would be better to /try/ to copy the tags. That should be
> easy to do (fetch the tip, then for each tag try and catch
> NoSuchRevision).
I was a bit concerned this would make reconfigure be O(n_tags). I
suppose the common case is they will already have been copied and
we'll have enough of the graph cached to tell this quickly. It might
be nice if fetch could be give multiple revids and just tell you which
ones failed to be copied.
>
>> + self.repository
>> + revision_
>> + find_ghosts=True)
>> + finally:
>> + old_repository.
>> + finally:
>> + pb.finished()
>>
>> def _set_tags_
>> """Mirror method for _get_tags_bytes.
> ...
>> - def run(self, location=None, target_type=None, bind_to=None,
>> force=False):
>> + def run(self, location=None, target_type=None, bind_to=None,
>> force=False,
>> +... 'specified')
>> elif target_type == 'branch':
>> reconfiguration =
>> reconfigure.
>> elif target_type == 'tree':
>
> Your changes to the reconfigure command add quite a few lines of logic,
> but most of reconfigure is meant to be in the reconfigure module where
> it is accessible to GUI's and so on. I think that this should be moved
> there before landing, as otherwise the branch 'reduces clarity of
> layering'.
OK
>
>> === modified file 'bzrlib/fetch.py'
>> --- bzrlib/fetch.py 2009-06-17 17:57:15 +0000
>> +++ bzrlib/fetch.py 2009-07-09 08:59:51 +0000
>> @@ -59,11 +59,8 @@
>> symbol_
>> % "pb parameter to RepoFetcher.
>> # and for simplicity it is in fact ignored
>> - if to_repository.
>> - # repository.fetch should be taking care of this case.
>> - raise errors.
>> - 'between two objects at the same location: '
>> - '%r and %r' % (to_repository, from_repository))
>> + # repository.fetch has the r...
Martin Pool (mbp) wrote : | # |
2009/7/15 Martin Pool <email address hidden>:
>> I don't think RepositoryBase is needed. Thats what Repository is.
>
> It would be nice if Repository was actually the base. At the moment
> RemoteRepository does not inherit from it, and there seems to be some
> amount of copy-and-paste between them because of this. Perhaps that
> should be a separate bug and I should continue the copy/paste for now.
I may be wrong but I anticipate some trouble if I change this; I
recall Spiv talking for a while about whether it should be a subclass
or not...
--
Martin <http://
Robert Collins (lifeless) wrote : | # |
On Tue, 2009-07-14 at 23:48 +0000, Martin Pool wrote:
>
> > It would be nice if Repository was actually the base. At the moment
> > RemoteRepository does not inherit from it, and there seems to be
> some
> > amount of copy-and-paste between them because of this. Perhaps that
> > should be a separate bug and I should continue the copy/paste for
> now.
>
> I may be wrong but I anticipate some trouble if I change this; I
> recall Spiv talking for a while about whether it should be a subclass
> or not...
Spiv and I have repeatedly lamented them not being subclasses; however
we haven't had time to make it so. It may be easy.
-Rob
Ian Clatworthy (ian-clatworthy) wrote : | # |
See comments above from Robert and I.
Martin Pool (mbp) wrote : | # |
> Spiv and I have repeatedly lamented them not being subclasses; however
> we haven't had time to make it so. It may be easy.
https:/
Martin Pool (mbp) wrote : | # |
Also bug 401642 for checking when reconfiguring to a different fallback, and bug 401646 for copying data referenced by tags.
Martin Pool (mbp) wrote : | # |
> On Fri, 2009-07-10 at 08:55 +0000, Martin Pool wrote:
>
> > @@ -662,6 +663,9 @@
> > """
> > if not self._format.
> > raise errors.
> > self.base)
> > + # XXX: Changing from one fallback repository to another does
> > not check
> > + # that all the data you need is present in the new fallback.
> > + # Possibly it should.
>
> I think this would be better as a bug 'reconfigure --stacked-on-url does
> not check new location has sufficient data'.
>
> > - # in.
> > - source_repository =
> > self._get_
> > - for revision_id in chain([
> > - self.tags.
> > - self.repository
> > - find_ghosts=True)
>
> ...
> I think it would be better to /try/ to copy the tags. That should be
> easy to do (fetch the tip, then for each tag try and catch
> NoSuchRevision).
>
> > + self.repository
> > + revision_
> > + find_ghosts=True)
> > + finally:
> > + old_repository.
> > + finally:
> > + pb.finished()
> >
> > def _set_tags_
> > """Mirror method for _get_tags_bytes.
> ...
> > - def run(self, location=None, target_type=None, bind_to=None,
> > force=False):
> > + def run(self, location=None, target_type=None, bind_to=None,
> > force=False,
> > +... 'specified')
> > elif target_type == 'branch':
> > reconfiguration =
> > reconfigure.
> > elif target_type == 'tree':
>
> Your changes to the reconfigure command add quite a few lines of logic,
> but most of reconfigure is meant to be in the reconfigure module where
> it is accessible to GUI's and so on. I think that this should be moved
> there before landing, as otherwise the branch 'reduces clarity of
> layering'.
That's a very good point about putting this outside of builtins.py where it can be reused.
I have some qualms about putting it into class Reconfigure though; this seems like the kind of thing we don't have an entirely satisfactory style for. Perhaps in this case the different types of reconfiguration should be different subclasses, rather than a bunch of variables on the instance...
Martin Pool (mbp) wrote : | # |
> I have some qualms about putting it into class Reconfigure though; this seems
> like the kind of thing we don't have an entirely satisfactory style for.
> Perhaps in this case the different types of reconfiguration should be
> different subclasses, rather than a bunch of variables on the instance...
I've put it into new Reconfigure-like classes to do this, and having done the other tweaks will submit this.
Preview Diff
1 | === modified file 'NEWS' |
2 | --- NEWS 2009-07-23 17:00:27 +0000 |
3 | +++ NEWS 2009-07-24 03:35:23 +0000 |
4 | @@ -16,8 +16,12 @@ |
5 | New Features |
6 | ************ |
7 | |
8 | -* ``merge --interactive`` applies a user-selected portion of the merge. The UI |
9 | - is similar to ``shelve``. (Aaron Bentley) |
10 | +* ``bzr merge --interactive`` applies a user-selected portion of the |
11 | + merge. The UI is similar to ``shelve``. (Aaron Bentley) |
12 | + |
13 | +* ``bzr reconfigure`` now takes options ``--stacked-on URL`` and |
14 | + ``--unstacked`` to change stacking of a branch. |
15 | + (Martin Pool, #391411) |
16 | |
17 | Bug Fixes |
18 | ********* |
19 | @@ -172,7 +176,7 @@ |
20 | |
21 | * ``bzr push`` now aborts if uncommitted changes (including pending merges) |
22 | are present in the working tree (if one is present) and no revision is |
23 | - speified. The configuration option ``push_strict`` can be used to set the |
24 | + specified. The configuration option ``push_strict`` can be used to set the |
25 | default for this behavior. (Vincent Ladeuil, #284038, #322808, #65286) |
26 | |
27 | * ``bzr revno`` and ``bzr revision-info`` now have a ``--tree`` option to |
28 | |
29 | === modified file 'bzrlib/branch.py' |
30 | --- bzrlib/branch.py 2009-07-01 10:30:41 +0000 |
31 | +++ bzrlib/branch.py 2009-07-24 03:35:24 +0000 |
32 | @@ -35,6 +35,7 @@ |
33 | symbol_versioning, |
34 | transport, |
35 | tsort, |
36 | + ui, |
37 | urlutils, |
38 | ) |
39 | from bzrlib.config import BranchConfig, TransportConfig |
40 | @@ -662,6 +663,9 @@ |
41 | """ |
42 | if not self._format.supports_stacking(): |
43 | raise errors.UnstackableBranchFormat(self._format, self.base) |
44 | + # XXX: Changing from one fallback repository to another does not check |
45 | + # that all the data you need is present in the new fallback. |
46 | + # Possibly it should. |
47 | self._check_stackable_repo() |
48 | if not url: |
49 | try: |
50 | @@ -669,27 +673,67 @@ |
51 | except (errors.NotStacked, errors.UnstackableBranchFormat, |
52 | errors.UnstackableRepositoryFormat): |
53 | return |
54 | - url = '' |
55 | - # XXX: Lock correctness - should unlock our old repo if we were |
56 | - # locked. |
57 | - # repositories don't offer an interface to remove fallback |
58 | - # repositories today; take the conceptually simpler option and just |
59 | - # reopen it. |
60 | - self.repository = self.bzrdir.find_repository() |
61 | - self.repository.lock_write() |
62 | - # for every revision reference the branch has, ensure it is pulled |
63 | - # in. |
64 | - source_repository = self._get_fallback_repository(old_url) |
65 | - for revision_id in chain([self.last_revision()], |
66 | - self.tags.get_reverse_tag_dict()): |
67 | - self.repository.fetch(source_repository, revision_id, |
68 | - find_ghosts=True) |
69 | + self._unstack() |
70 | else: |
71 | self._activate_fallback_location(url) |
72 | # write this out after the repository is stacked to avoid setting a |
73 | # stacked config that doesn't work. |
74 | self._set_config_location('stacked_on_location', url) |
75 | |
76 | + def _unstack(self): |
77 | + """Change a branch to be unstacked, copying data as needed. |
78 | + |
79 | + Don't call this directly, use set_stacked_on_url(None). |
80 | + """ |
81 | + pb = ui.ui_factory.nested_progress_bar() |
82 | + try: |
83 | + pb.update("Unstacking") |
84 | + # The basic approach here is to fetch the tip of the branch, |
85 | + # including all available ghosts, from the existing stacked |
86 | + # repository into a new repository object without the fallbacks. |
87 | + # |
88 | + # XXX: See <https://launchpad.net/bugs/397286> - this may not be |
89 | + # correct for CHKMap repostiories |
90 | + old_repository = self.repository |
91 | + if len(old_repository._fallback_repositories) != 1: |
92 | + raise AssertionError("can't cope with fallback repositories " |
93 | + "of %r" % (self.repository,)) |
94 | + # unlock it, including unlocking the fallback |
95 | + old_repository.unlock() |
96 | + old_repository.lock_read() |
97 | + try: |
98 | + # Repositories don't offer an interface to remove fallback |
99 | + # repositories today; take the conceptually simpler option and just |
100 | + # reopen it. We reopen it starting from the URL so that we |
101 | + # get a separate connection for RemoteRepositories and can |
102 | + # stream from one of them to the other. This does mean doing |
103 | + # separate SSH connection setup, but unstacking is not a |
104 | + # common operation so it's tolerable. |
105 | + new_bzrdir = bzrdir.BzrDir.open(self.bzrdir.root_transport.base) |
106 | + new_repository = new_bzrdir.find_repository() |
107 | + self.repository = new_repository |
108 | + if self.repository._fallback_repositories: |
109 | + raise AssertionError("didn't expect %r to have " |
110 | + "fallback_repositories" |
111 | + % (self.repository,)) |
112 | + # this is not paired with an unlock because it's just restoring |
113 | + # the previous state; the lock's released when set_stacked_on_url |
114 | + # returns |
115 | + self.repository.lock_write() |
116 | + # XXX: If you unstack a branch while it has a working tree |
117 | + # with a pending merge, the pending-merged revisions will no |
118 | + # longer be present. You can (probably) revert and remerge. |
119 | + # |
120 | + # XXX: This only fetches up to the tip of the repository; it |
121 | + # doesn't bring across any tags. That's fairly consistent |
122 | + # with how branch works, but perhaps not ideal. |
123 | + self.repository.fetch(old_repository, |
124 | + revision_id=self.last_revision(), |
125 | + find_ghosts=True) |
126 | + finally: |
127 | + old_repository.unlock() |
128 | + finally: |
129 | + pb.finished() |
130 | |
131 | def _set_tags_bytes(self, bytes): |
132 | """Mirror method for _get_tags_bytes. |
133 | |
134 | === modified file 'bzrlib/builtins.py' |
135 | --- bzrlib/builtins.py 2009-07-19 04:47:49 +0000 |
136 | +++ bzrlib/builtins.py 2009-07-24 03:35:24 +0000 |
137 | @@ -5259,14 +5259,37 @@ |
138 | ), |
139 | Option('bind-to', help='Branch to bind checkout to.', type=str), |
140 | Option('force', |
141 | - help='Perform reconfiguration even if local changes' |
142 | - ' will be lost.') |
143 | + help='Perform reconfiguration even if local changes' |
144 | + ' will be lost.'), |
145 | + Option('stacked-on', |
146 | + help='Reconfigure a branch to be stacked on another branch.', |
147 | + type=unicode, |
148 | + ), |
149 | + Option('unstacked', |
150 | + help='Reconfigure a branch to be unstacked. This ' |
151 | + 'may require copying substantial data into it.', |
152 | + ), |
153 | ] |
154 | |
155 | - def run(self, location=None, target_type=None, bind_to=None, force=False): |
156 | + def run(self, location=None, target_type=None, bind_to=None, force=False, |
157 | + stacked_on=None, |
158 | + unstacked=None): |
159 | directory = bzrdir.BzrDir.open(location) |
160 | + if stacked_on and unstacked: |
161 | + raise BzrCommandError("Can't use both --stacked-on and --unstacked") |
162 | + elif stacked_on is not None: |
163 | + reconfigure.ReconfigureStackedOn().apply(directory, stacked_on) |
164 | + elif unstacked: |
165 | + reconfigure.ReconfigureUnstacked().apply(directory) |
166 | + # At the moment you can use --stacked-on and a different |
167 | + # reconfiguration shape at the same time; there seems no good reason |
168 | + # to ban it. |
169 | if target_type is None: |
170 | - raise errors.BzrCommandError('No target configuration specified') |
171 | + if stacked_on or unstacked: |
172 | + return |
173 | + else: |
174 | + raise errors.BzrCommandError('No target configuration ' |
175 | + 'specified') |
176 | elif target_type == 'branch': |
177 | reconfiguration = reconfigure.Reconfigure.to_branch(directory) |
178 | elif target_type == 'tree': |
179 | |
180 | === modified file 'bzrlib/bundle/serializer/v08.py' |
181 | --- bzrlib/bundle/serializer/v08.py 2009-05-06 05:36:28 +0000 |
182 | +++ bzrlib/bundle/serializer/v08.py 2009-07-24 03:35:25 +0000 |
183 | @@ -1,4 +1,4 @@ |
184 | -# Copyright (C) 2005, 2006 Canonical Ltd |
185 | +# Copyright (C) 2005, 2006, 2009 Canonical Ltd |
186 | # |
187 | # This program is free software; you can redistribute it and/or modify |
188 | # it under the terms of the GNU General Public License as published by |
189 | @@ -19,7 +19,10 @@ |
190 | |
191 | import os |
192 | |
193 | -from bzrlib import errors |
194 | +from bzrlib import ( |
195 | + errors, |
196 | + ui, |
197 | + ) |
198 | from bzrlib.bundle.serializer import (BundleSerializer, |
199 | _get_bundle_header, |
200 | ) |
201 | @@ -27,9 +30,7 @@ |
202 | from bzrlib.bundle.bundle_data import (RevisionInfo, BundleInfo, BundleTree) |
203 | from bzrlib.diff import internal_diff |
204 | from bzrlib.osutils import pathjoin |
205 | -from bzrlib.progress import DummyProgress |
206 | from bzrlib.revision import NULL_REVISION |
207 | -import bzrlib.ui |
208 | from bzrlib.testament import StrictTestament |
209 | from bzrlib.timestamp import ( |
210 | format_highres_date, |
211 | @@ -119,12 +120,11 @@ |
212 | source.lock_read() |
213 | try: |
214 | self._write_main_header() |
215 | - pb = DummyProgress() |
216 | + pb = ui.ui_factory.nested_progress_bar() |
217 | try: |
218 | self._write_revisions(pb) |
219 | finally: |
220 | - pass |
221 | - #pb.finished() |
222 | + pb.finished() |
223 | finally: |
224 | source.unlock() |
225 | |
226 | @@ -183,7 +183,7 @@ |
227 | |
228 | i_max = len(self.revision_ids) |
229 | for i, rev_id in enumerate(self.revision_ids): |
230 | - pb.update("Generating revsion data", i, i_max) |
231 | + pb.update("Generating revision data", i, i_max) |
232 | rev = self.source.get_revision(rev_id) |
233 | if rev_id == last_rev_id: |
234 | rev_tree = last_rev_tree |
235 | |
236 | === modified file 'bzrlib/fetch.py' |
237 | --- bzrlib/fetch.py 2009-06-17 17:57:15 +0000 |
238 | +++ bzrlib/fetch.py 2009-07-24 03:35:24 +0000 |
239 | @@ -59,11 +59,8 @@ |
240 | symbol_versioning.deprecated_in((1, 14, 0)) |
241 | % "pb parameter to RepoFetcher.__init__") |
242 | # and for simplicity it is in fact ignored |
243 | - if to_repository.has_same_location(from_repository): |
244 | - # repository.fetch should be taking care of this case. |
245 | - raise errors.BzrError('RepoFetcher run ' |
246 | - 'between two objects at the same location: ' |
247 | - '%r and %r' % (to_repository, from_repository)) |
248 | + # repository.fetch has the responsibility for short-circuiting |
249 | + # attempts to copy between a repository and itself. |
250 | self.to_repository = to_repository |
251 | self.from_repository = from_repository |
252 | self.sink = to_repository._get_sink() |
253 | |
254 | === modified file 'bzrlib/help_topics/en/debug-flags.txt' |
255 | --- bzrlib/help_topics/en/debug-flags.txt 2009-07-16 00:01:17 +0000 |
256 | +++ bzrlib/help_topics/en/debug-flags.txt 2009-07-24 03:35:25 +0000 |
257 | @@ -23,5 +23,6 @@ |
258 | -Dknit Trace knit operations. |
259 | -Dlock Trace when lockdir locks are taken or released. |
260 | -Dmerge Emit information for debugging merges. |
261 | +-Dunlock Some errors during unlock are treated as warnings. |
262 | -Dpack Emit information about pack operations. |
263 | -Dsftp Trace SFTP internals. |
264 | |
265 | === modified file 'bzrlib/lock.py' |
266 | --- bzrlib/lock.py 2009-07-07 09:00:59 +0000 |
267 | +++ bzrlib/lock.py 2009-07-24 03:35:24 +0000 |
268 | @@ -37,8 +37,10 @@ |
269 | import errno |
270 | import os |
271 | import sys |
272 | +import warnings |
273 | |
274 | from bzrlib import ( |
275 | + debug, |
276 | errors, |
277 | osutils, |
278 | trace, |
279 | @@ -86,6 +88,23 @@ |
280 | self.lock_url, self.details) |
281 | |
282 | |
283 | +def cant_unlock_not_held(locked_object): |
284 | + """An attempt to unlock failed because the object was not locked. |
285 | + |
286 | + This provides a policy point from which we can generate either a warning |
287 | + or an exception. |
288 | + """ |
289 | + # This is typically masking some other error and called from a finally |
290 | + # block, so it's useful to have the option not to generate a new error |
291 | + # here. You can use -Werror to make it fatal. It should possibly also |
292 | + # raise LockNotHeld. |
293 | + if 'unlock' in debug.debug_flags: |
294 | + warnings.warn("%r is already unlocked" % (locked_object,), |
295 | + stacklevel=3) |
296 | + else: |
297 | + raise errors.LockNotHeld(locked_object) |
298 | + |
299 | + |
300 | try: |
301 | import fcntl |
302 | have_fcntl = True |
303 | |
304 | === modified file 'bzrlib/lockable_files.py' |
305 | --- bzrlib/lockable_files.py 2009-03-25 04:20:12 +0000 |
306 | +++ bzrlib/lockable_files.py 2009-07-24 03:35:24 +0000 |
307 | @@ -24,6 +24,7 @@ |
308 | from bzrlib import ( |
309 | counted_lock, |
310 | errors, |
311 | + lock, |
312 | osutils, |
313 | transactions, |
314 | urlutils, |
315 | @@ -308,7 +309,7 @@ |
316 | |
317 | def unlock(self): |
318 | if not self._lock_mode: |
319 | - raise errors.LockNotHeld(self) |
320 | + return lock.cant_unlock_not_held(self) |
321 | if self._lock_warner.lock_count > 1: |
322 | self._lock_warner.lock_count -= 1 |
323 | else: |
324 | |
325 | === modified file 'bzrlib/lockdir.py' |
326 | --- bzrlib/lockdir.py 2009-05-08 15:39:11 +0000 |
327 | +++ bzrlib/lockdir.py 2009-07-24 03:35:24 +0000 |
328 | @@ -293,7 +293,7 @@ |
329 | self._fake_read_lock = False |
330 | return |
331 | if not self._lock_held: |
332 | - raise LockNotHeld(self) |
333 | + return lock.cant_unlock_not_held(self) |
334 | if self._locked_via_token: |
335 | self._locked_via_token = False |
336 | self._lock_held = False |
337 | |
338 | === modified file 'bzrlib/reconfigure.py' |
339 | --- bzrlib/reconfigure.py 2009-07-10 08:33:11 +0000 |
340 | +++ bzrlib/reconfigure.py 2009-07-24 03:35:24 +0000 |
341 | @@ -1,4 +1,4 @@ |
342 | -# Copyright (C) 2007 Canonical Ltd |
343 | +# Copyright (C) 2007, 2009 Canonical Ltd |
344 | # |
345 | # This program is free software; you can redistribute it and/or modify |
346 | # it under the terms of the GNU General Public License as published by |
347 | @@ -14,14 +14,62 @@ |
348 | # along with this program; if not, write to the Free Software |
349 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
350 | |
351 | -"""Reconfigure a bzrdir into a new tree/branch/repository layout""" |
352 | +"""Reconfigure a bzrdir into a new tree/branch/repository layout. |
353 | + |
354 | +Various types of reconfiguration operation are available either by |
355 | +constructing a class or using a factory method on Reconfigure. |
356 | +""" |
357 | + |
358 | |
359 | from bzrlib import ( |
360 | branch, |
361 | bzrdir, |
362 | errors, |
363 | + trace, |
364 | + ui, |
365 | + urlutils, |
366 | ) |
367 | |
368 | + |
369 | +# TODO: common base class for all reconfigure operations, making no |
370 | +# assumptions about what kind of change will be done. |
371 | + |
372 | + |
373 | +class ReconfigureStackedOn(object): |
374 | + """Reconfigures a branch to be stacked on another branch.""" |
375 | + |
376 | + def apply(self, bzrdir, stacked_on_url): |
377 | + branch = bzrdir.open_branch() |
378 | + # it may be a path relative to the cwd or a url; the branch wants |
379 | + # a path relative to itself... |
380 | + on_url = urlutils.relative_url(branch.base, |
381 | + urlutils.normalize_url(stacked_on_url)) |
382 | + branch.lock_write() |
383 | + try: |
384 | + branch.set_stacked_on_url(on_url) |
385 | + if not trace.is_quiet(): |
386 | + ui.ui_factory.note( |
387 | + "%s is now stacked on %s\n" |
388 | + % (branch.base, branch.get_stacked_on_url())) |
389 | + finally: |
390 | + branch.unlock() |
391 | + |
392 | + |
393 | +class ReconfigureUnstacked(object): |
394 | + |
395 | + def apply(self, bzrdir): |
396 | + branch = bzrdir.open_branch() |
397 | + branch.lock_write() |
398 | + try: |
399 | + branch.set_stacked_on_url(None) |
400 | + if not trace.is_quiet(): |
401 | + ui.ui_factory.note( |
402 | + "%s is now not stacked\n" |
403 | + % (branch.base,)) |
404 | + finally: |
405 | + branch.unlock() |
406 | + |
407 | + |
408 | class Reconfigure(object): |
409 | |
410 | def __init__(self, bzrdir, new_bound_location=None): |
411 | |
412 | === modified file 'bzrlib/remote.py' |
413 | --- bzrlib/remote.py 2009-07-06 09:47:35 +0000 |
414 | +++ bzrlib/remote.py 2009-07-24 03:35:24 +0000 |
415 | @@ -1,4 +1,4 @@ |
416 | -# Copyright (C) 2006, 2007, 2008 Canonical Ltd |
417 | +# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd |
418 | # |
419 | # This program is free software; you can redistribute it and/or modify |
420 | # it under the terms of the GNU General Public License as published by |
421 | @@ -14,9 +14,6 @@ |
422 | # along with this program; if not, write to the Free Software |
423 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
424 | |
425 | -# TODO: At some point, handle upgrades by just passing the whole request |
426 | -# across to run on the server. |
427 | - |
428 | import bz2 |
429 | |
430 | from bzrlib import ( |
431 | @@ -27,6 +24,7 @@ |
432 | debug, |
433 | errors, |
434 | graph, |
435 | + lock, |
436 | lockdir, |
437 | repository, |
438 | revision, |
439 | @@ -813,7 +811,23 @@ |
440 | result.add(_mod_revision.NULL_REVISION) |
441 | return result |
442 | |
443 | + def _has_same_fallbacks(self, other_repo): |
444 | + """Returns true if the repositories have the same fallbacks.""" |
445 | + # XXX: copied from Repository; it should be unified into a base class |
446 | + # <https://bugs.edge.launchpad.net/bzr/+bug/401622> |
447 | + my_fb = self._fallback_repositories |
448 | + other_fb = other_repo._fallback_repositories |
449 | + if len(my_fb) != len(other_fb): |
450 | + return False |
451 | + for f, g in zip(my_fb, other_fb): |
452 | + if not f.has_same_location(g): |
453 | + return False |
454 | + return True |
455 | + |
456 | def has_same_location(self, other): |
457 | + # TODO: Move to RepositoryBase and unify with the regular Repository |
458 | + # one; unfortunately the tests rely on slightly different behaviour at |
459 | + # present -- mbp 20090710 |
460 | return (self.__class__ is other.__class__ and |
461 | self.bzrdir.transport.base == other.bzrdir.transport.base) |
462 | |
463 | @@ -1025,7 +1039,7 @@ |
464 | |
465 | def unlock(self): |
466 | if not self._lock_count: |
467 | - raise errors.LockNotHeld(self) |
468 | + return lock.cant_unlock_not_held(self) |
469 | self._lock_count -= 1 |
470 | if self._lock_count > 0: |
471 | return |
472 | @@ -1230,7 +1244,9 @@ |
473 | raise errors.InternalBzrError( |
474 | "May not fetch while in a write group.") |
475 | # fast path same-url fetch operations |
476 | - if self.has_same_location(source) and fetch_spec is None: |
477 | + if (self.has_same_location(source) |
478 | + and fetch_spec is None |
479 | + and self._has_same_fallbacks(source)): |
480 | # check that last_revision is in 'from' and then return a |
481 | # no-operation. |
482 | if (revision_id is not None and |
483 | |
484 | === modified file 'bzrlib/repository.py' |
485 | --- bzrlib/repository.py 2009-07-02 23:10:53 +0000 |
486 | +++ bzrlib/repository.py 2009-07-24 03:35:25 +0000 |
487 | @@ -848,6 +848,7 @@ |
488 | ###################################################################### |
489 | # Repositories |
490 | |
491 | + |
492 | class Repository(object): |
493 | """Repository holding history for one or more branches. |
494 | |
495 | @@ -1183,8 +1184,25 @@ |
496 | self._inventory_entry_cache = fifo_cache.FIFOCache(10*1024) |
497 | |
498 | def __repr__(self): |
499 | - return '%s(%r)' % (self.__class__.__name__, |
500 | - self.base) |
501 | + if self._fallback_repositories: |
502 | + return '%s(%r, fallback_repositories=%r)' % ( |
503 | + self.__class__.__name__, |
504 | + self.base, |
505 | + self._fallback_repositories) |
506 | + else: |
507 | + return '%s(%r)' % (self.__class__.__name__, |
508 | + self.base) |
509 | + |
510 | + def _has_same_fallbacks(self, other_repo): |
511 | + """Returns true if the repositories have the same fallbacks.""" |
512 | + my_fb = self._fallback_repositories |
513 | + other_fb = other_repo._fallback_repositories |
514 | + if len(my_fb) != len(other_fb): |
515 | + return False |
516 | + for f, g in zip(my_fb, other_fb): |
517 | + if not f.has_same_location(g): |
518 | + return False |
519 | + return True |
520 | |
521 | def has_same_location(self, other): |
522 | """Returns a boolean indicating if this repository is at the same |
523 | @@ -1529,7 +1547,11 @@ |
524 | raise errors.InternalBzrError( |
525 | "May not fetch while in a write group.") |
526 | # fast path same-url fetch operations |
527 | - if self.has_same_location(source) and fetch_spec is None: |
528 | + # TODO: lift out to somewhere common with RemoteRepository |
529 | + # <https://bugs.edge.launchpad.net/bzr/+bug/401646> |
530 | + if (self.has_same_location(source) |
531 | + and fetch_spec is None |
532 | + and self._has_same_fallbacks(source)): |
533 | # check that last_revision is in 'from' and then return a |
534 | # no-operation. |
535 | if (revision_id is not None and |
536 | |
537 | === modified file 'bzrlib/tests/blackbox/test_reconfigure.py' |
538 | --- bzrlib/tests/blackbox/test_reconfigure.py 2009-05-07 05:08:46 +0000 |
539 | +++ bzrlib/tests/blackbox/test_reconfigure.py 2009-07-24 03:35:25 +0000 |
540 | @@ -1,4 +1,4 @@ |
541 | -# Copyright (C) 2007 Canonical Ltd |
542 | +# Copyright (C) 2007, 2009 Canonical Ltd |
543 | # |
544 | # This program is free software; you can redistribute it and/or modify |
545 | # it under the terms of the GNU General Public License as published by |
546 | @@ -20,6 +20,7 @@ |
547 | tests, |
548 | workingtree, |
549 | ) |
550 | +from bzrlib.branchbuilder import BranchBuilder |
551 | |
552 | |
553 | class TestReconfigure(tests.TestCaseWithTransport): |
554 | @@ -182,3 +183,59 @@ |
555 | |
556 | def test_lightweight_rich_root_pack_checkout_to_tree(self, format=None): |
557 | self.test_lightweight_format_checkout_to_tree('rich-root-pack') |
558 | + |
559 | + |
560 | +class TestReconfigureStacking(tests.TestCaseWithTransport): |
561 | + |
562 | + def test_reconfigure_stacking(self): |
563 | + """Test a fairly realistic scenario for stacking: |
564 | + |
565 | + * make a branch with some history |
566 | + * branch it |
567 | + * make the second branch stacked on the first |
568 | + * commit in the second |
569 | + * then make the second unstacked, so it has to fill in history from |
570 | + the original fallback lying underneath its original content |
571 | + |
572 | + See discussion in <https://bugs.edge.launchpad.net/bzr/+bug/391411> |
573 | + """ |
574 | + # there are also per_branch tests that exercise remote operation etc |
575 | + tree_1 = self.make_branch_and_tree('b1', format='2a') |
576 | + self.build_tree(['b1/foo']) |
577 | + tree_1.add(['foo']) |
578 | + tree_1.commit('add foo') |
579 | + branch_1 = tree_1.branch |
580 | + # now branch and commit again |
581 | + bzrdir_2 = tree_1.bzrdir.sprout('b2') |
582 | + tree_2 = bzrdir_2.open_workingtree() |
583 | + branch_2 = tree_2.branch |
584 | + # now reconfigure to be stacked |
585 | + out, err = self.run_bzr('reconfigure --stacked-on b1 b2') |
586 | + self.assertContainsRe(out, |
587 | + '^.*/b2/ is now stacked on ../b1\n$') |
588 | + self.assertEquals('', err) |
589 | + # can also give the absolute URL of the branch, and it gets stored |
590 | + # as a relative path if possible |
591 | + out, err = self.run_bzr('reconfigure --stacked-on %s b2' |
592 | + % (self.get_url('b1'),)) |
593 | + self.assertContainsRe(out, |
594 | + '^.*/b2/ is now stacked on ../b1\n$') |
595 | + self.assertEquals('', err) |
596 | + # It should be given a relative URL to the destination, if possible, |
597 | + # because that's most likely to work across different transports |
598 | + self.assertEquals(branch_2.get_stacked_on_url(), |
599 | + '../b1') |
600 | + # commit, and it should be stored into b2's repo |
601 | + self.build_tree_contents([('foo', 'new foo')]) |
602 | + tree_2.commit('update foo') |
603 | + # Now turn it off again |
604 | + out, err = self.run_bzr('reconfigure --unstacked b2') |
605 | + self.assertContainsRe(out, |
606 | + '^.*/b2/ is now not stacked\n$') |
607 | + self.assertEquals('', err) |
608 | + self.assertRaises(errors.NotStacked, |
609 | + branch_2.get_stacked_on_url) |
610 | + |
611 | + # XXX: Needs a test for reconfiguring stacking and shape at the same time; |
612 | + # no branch at location; stacked-on is not a branch; quiet mode. |
613 | + # -- mbp 20090706 |
614 | |
615 | === modified file 'bzrlib/tests/per_branch/test_stacking.py' |
616 | --- bzrlib/tests/per_branch/test_stacking.py 2009-07-10 05:49:34 +0000 |
617 | +++ bzrlib/tests/per_branch/test_stacking.py 2009-07-24 03:35:25 +0000 |
618 | @@ -184,11 +184,17 @@ |
619 | trunk_revid = trunk_tree.commit('revision on mainline') |
620 | # and make branch from it which is stacked |
621 | try: |
622 | - new_dir = trunk_tree.bzrdir.sprout('newbranch', stacked=True) |
623 | + new_dir = trunk_tree.bzrdir.sprout(self.get_url('newbranch'), |
624 | + stacked=True) |
625 | except unstackable_format_errors, e: |
626 | raise TestNotApplicable(e) |
627 | # stacked repository |
628 | self.assertRevisionNotInRepository('newbranch', trunk_revid) |
629 | + # TODO: we'd like to commit in the stacked repository; that requires |
630 | + # some care (maybe a BranchBuilder) if it's remote and has no |
631 | + # workingtree |
632 | + ##newbranch_revid = new_dir.open_workingtree().commit('revision in ' |
633 | + ##'newbranch') |
634 | # now when we unstack that should implicitly fetch, to make sure that |
635 | # the branch will still work |
636 | new_branch = new_dir.open_branch() |
637 | |
638 | === modified file 'doc/en/user-guide/stacked.txt' |
639 | --- doc/en/user-guide/stacked.txt 2008-07-17 06:00:08 +0000 |
640 | +++ doc/en/user-guide/stacked.txt 2009-07-24 03:35:25 +0000 |
641 | @@ -83,3 +83,15 @@ |
642 | stacked-on branch needs to be available for almost all operations. This is |
643 | not an issue when both branches are local or both branches are on the |
644 | same server. |
645 | + |
646 | + |
647 | +Changing branch stacking |
648 | +------------------------ |
649 | + |
650 | +Stacking of existing branches can be changed using the ``bzr reconfigure`` |
651 | +command to either stack on an existing branch, or to turn off stacking. |
652 | +Be aware that when ``bzr reconfigure --unstacked`` is used, bzr will |
653 | +copy all the referenced data from the stacked-on repository into the |
654 | +previously stacked repository. For large repositories this may take |
655 | +considerable time and may substantially increase the size of the |
656 | +repository. |
This adds reconfigure --stacked-on and --unstacked options, and fixes a bug in fetching data when unstacking that made it break in all but trivial cases of empty branches.
I've included here part of the -Dunlock code because it made debugging this much easier.
It also adds a RepositoryBase so that we can have at least some common code with RemoteRepository.