Merge lp:~spiv/bzr/fetch-all-tags-309682 into lp:bzr
- fetch-all-tags-309682
- Merge into bzr.dev
Status: | Merged |
---|---|
Merged at revision: | 5648 |
Proposed branch: | lp:~spiv/bzr/fetch-all-tags-309682 |
Merge into: | lp:bzr |
Prerequisite: | lp:~spiv/bzr/fetch-integration |
Diff against target: |
776 lines (+339/-98) 15 files modified
bzrlib/controldir.py (+58/-49) bzrlib/fetch.py (+93/-17) bzrlib/graph.py (+8/-6) bzrlib/remote.py (+3/-3) bzrlib/repofmt/knitrepo.py (+1/-1) bzrlib/repofmt/weaverepo.py (+1/-1) bzrlib/repository.py (+9/-9) bzrlib/tests/blackbox/test_branch.py (+33/-2) bzrlib/tests/per_branch/test_sprout.py (+19/-0) bzrlib/tests/per_controldir/test_controldir.py (+101/-1) bzrlib/tests/per_interrepository/test_interrepository.py (+1/-1) bzrlib/tests/test_bzrdir.py (+4/-0) bzrlib/tests/test_remote.py (+3/-3) doc/en/release-notes/bzr-2.3.txt (+0/-5) doc/en/release-notes/bzr-2.4.txt (+5/-0) |
To merge this branch: | bzr merge lp:~spiv/bzr/fetch-all-tags-309682 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John A Meinel | Needs Fixing | ||
Review via email: mp+42910@code.launchpad.net |
Commit message
'bzr branch' now fetches revisions referred to by tags.
Description of the change
This branch fixes <https:/
The main difficulty is the ugliness of sprout and to a lesser extent fetch. We don't want fetching the tags to regress our performance: i.e. no extra roundtrips for redundant locking/
The prerequisite branch addresses much of that, so that this patch on its own is fairly small. (The prerequisite branch is simply lp:~spiv/bzr/sprout-does-not-reopen-repo and lp:~spiv/bzr/fetch-spec-everything-not-in-other merged together. Both of those are in the review queue.)
I've taken the approach of moving much of the complexity out of the sprout method, into more focussed methods and objects which are hopefully easy to understand isolation.
Part of the problem is that the interface of sprout is pretty confusing, as demonstrated by the one test failure I've currently got in this branch: bzrlib.
That test fails because it verifies that bzrdir.sprout(url) from a bzrdir with a branch in a shared repo copies the entire repository. I think this behaviour is wrong, because it's likely to surprise callers. A quick check on IRC suggests that lifeless didn't expect this behaviour either. And quick skim of the bzrlib source suggests that at least 'bzr switch --create-branch' is encountering this behaviour without intending it. So I intend to remove or adjust the test for the new behaviour, but I thought I should get some reviewer feedback first.
More generally, the interaction between the various combinations of passing or not passing revision_id and source_branch and various configurations of source branches and repositories is too complex. It's not a friendly interface. Unfortunately adding consideration of tags into the mix is probably only making it messier. FWIW the behaviour now is:
* If a source branch is passed or found, only its tip and tags are fetched
* if revision_id is passed, then it overrides the tip
* otherwise if a revision_id is passed, then that is fetched
* otherwise the whole repository is fetched.
I think much of the mess stems from the weirdness of sprouting a bzrdir rather than a repository or branch directly. I'm starting to think we should remove ControlDir.sprout.
Finally, while this addresses the case reported in bug 309682 (bzr branch), I think a complete fix requires that pull, merge, update, etc also fetch tags. I have a follow-up branch for that.
Martin Pool (mbp) wrote : | # |
On 15 December 2010 09:56, John A Meinel <email address hidden> wrote:
> Review: Needs Fixing
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
>> I think much of the mess stems from the weirdness of sprouting a bzrdir rather than a repository or branch directly. I'm starting to think we should remove ControlDir.sprout.
>>
>
> I'm in favor of this. It will mess up our test suite a ton, because that
> has become the defacto method of creating a new branch. However using:
>
> tree.branch.
> tree.bzrdir.
>
> I think some of it stems from, do you want a WT in the target or not,
> etc, etc. I would be happier with something that you can compose. This
> is also cluttered because you can't just open the WT from a Branch, it
> will re-open the branch, etc. (Note that there was also bugs with
> branch.sprout() which didn't happen with bzrdir.sprout(), something with
> Trees and merge, and double-locking the WT, IIRC)
+1 to removing it.
Years ago, Robert and I had the idea it would be good to be able to
just clone any object as it currently exists. So for a bzrdir, that
would imply copying the whole repository (if any), plus branches, etc.
However I don't think that makes much sense give how things have now
evolved.
The term 'sprout' is intended to denote 'I'm making a new divergent
line of development' vs 'I'm copying exactly what's there'. At one
conceptual level there is a distinction. But we don't really expose
that in the UI; we don't use this name for it in the UI or user model;
and I'm not sure it makes sense to have as a separate method anyhow.
--
Martin
Andrew Bennetts (spiv) wrote : | # |
Martin Pool wrote:
> >> I think much of the mess stems from the weirdness of sprouting a
> >> bzrdir rather than a repository or branch directly. I'm starting
> >> to think we should remove ControlDir.sprout.
[...]
> +1 to removing it.
>
> Years ago, Robert and I had the idea it would be good to be able to
> just clone any object as it currently exists. So for a bzrdir, that
> would imply copying the whole repository (if any), plus branches, etc.
> However I don't think that makes much sense give how things have now
> evolved.
I agree that it doesn't make much sense now. It already produces
something that is not an exact clone, in hard to predict ways. e.g. it
will create a branch even if the source doesn't have one, and bzr-svn
chooses to make sprout from a SVN checkout produce a bzr-format bzrdir
rather than try to preserve format.
> The term 'sprout' is intended to denote 'I'm making a new divergent
> line of development' vs 'I'm copying exactly what's there'. At one
> conceptual level there is a distinction. But we don't really expose
> that in the UI; we don't use this name for it in the UI or user model;
> and I'm not sure it makes sense to have as a separate method anyhow.
I agree with this also.
Andrew Bennetts (spiv) wrote : | # |
John A Meinel wrote:
[...]
> I think some of it stems from, do you want a WT in the target or not,
> etc, etc. I would be happier with something that you can compose. This
> is also cluttered because you can't just open the WT from a Branch, it
> will re-open the branch, etc. (Note that there was also bugs with
> branch.sprout() which didn't happen with bzrdir.sprout(), something with
> Trees and merge, and double-locking the WT, IIRC)
Perhaps we need an object that explicitly composes together all the components
of a controldir? i.e. rather than getting a controldir that you can call
open_branch etc on, and but then to go from the branch to the working tree you
need to reopen the working tree, you grab all the objects at once.
Without thinking about it too hard, maybe something like:
bag_of_things = open_bag(location) # strawman name
branch = bag_of_
wt = bag_of_
[etc etc]
I'm not sure that's better, but I am sure the existing design isn't
great :)
The current API design makes it hard to avoid reopening the same object
over and over. Ideally the design would require you to go out of your way to
get two different in-memory objects representing the same on-disk
object, because that's usually not what you want.
> Things we want to keep in mind:
>
> a) In the ideal interface, we would have a way to copy any number of
> branches simultaneously into a target. This gives us a way to
> implement some form of Repository cloning that people would like.
> (Being able to grab the stack of a loom, or trunk+main features,
> etc.)
We at least have a workable way to do the fetch part of that operation,
but we certainly lack nice ways to find, open, copy or create sets of
branch objects.
[...]
> +class FetchSpecFactor
[...]
> ^- I really prefer calling them "source_
> "source_
> clear that this is a string rev_id, not a tuple key, not a Revision
> object, etc.
Good point, I'll make that change.
> However, shouldn't it be "source_
> To allow more flexibility? We've often been hampered by having a single
> revision_id to work with.
Hmm. Probably, although I'm inclined to delay complicating this further
until we have concrete use cases to drive the design. YAGNI, in other
words :)
I wouldn't be surprised to find at the point someone refactors to add
that sort of feature that the result turns out to be a deeper change.
> I see that you have "explicit_rev_ids = set()" which isn't documented.
Hmm, it's class-private variable really, I'll prefix it with an
underscore.
> Is there a reason to keep "source_
Yes. It's used by
<https:/
to support operations like “bzr pull -r 99”. That fetch should not pull
revno 100. So source_
overri...
Preview Diff
1 | === modified file 'bzrlib/controldir.py' |
2 | --- bzrlib/controldir.py 2011-01-26 19:34:58 +0000 |
3 | +++ bzrlib/controldir.py 2011-02-07 04:18:21 +0000 |
4 | @@ -29,6 +29,7 @@ |
5 | from bzrlib import ( |
6 | cleanup, |
7 | errors, |
8 | + fetch, |
9 | graph, |
10 | revision as _mod_revision, |
11 | transport as _mod_transport, |
12 | @@ -378,53 +379,41 @@ |
13 | accelerator_tree=None, hardlink=False, stacked=False, |
14 | source_branch=None, create_tree_if_local=True): |
15 | add_cleanup = op.add_cleanup |
16 | + fetch_spec_factory = fetch.FetchSpecFactory() |
17 | + if revision_id is not None: |
18 | + fetch_spec_factory.add_revision_ids([revision_id]) |
19 | + fetch_spec_factory.source_branch_stop_revision_id = revision_id |
20 | target_transport = _mod_transport.get_transport(url, |
21 | possible_transports) |
22 | target_transport.ensure_base() |
23 | cloning_format = self.cloning_metadir(stacked) |
24 | # Create/update the result branch |
25 | result = cloning_format.initialize_on_transport(target_transport) |
26 | + source_branch, source_repository = self._find_source_repo( |
27 | + add_cleanup, source_branch) |
28 | + fetch_spec_factory.source_branch = source_branch |
29 | # if a stacked branch wasn't requested, we don't create one |
30 | # even if the origin was stacked |
31 | - stacked_branch_url = None |
32 | - if source_branch is not None: |
33 | - add_cleanup(source_branch.lock_read().unlock) |
34 | - if stacked: |
35 | - stacked_branch_url = self.root_transport.base |
36 | - source_repository = source_branch.repository |
37 | + if stacked and source_branch is not None: |
38 | + stacked_branch_url = self.root_transport.base |
39 | else: |
40 | - try: |
41 | - source_branch = self.open_branch() |
42 | - source_repository = source_branch.repository |
43 | - if stacked: |
44 | - stacked_branch_url = self.root_transport.base |
45 | - except errors.NotBranchError: |
46 | - source_branch = None |
47 | - try: |
48 | - source_repository = self.open_repository() |
49 | - except errors.NoRepositoryPresent: |
50 | - source_repository = None |
51 | - else: |
52 | - add_cleanup(source_repository.lock_read().unlock) |
53 | - else: |
54 | - add_cleanup(source_branch.lock_read().unlock) |
55 | + stacked_branch_url = None |
56 | repository_policy = result.determine_repository_policy( |
57 | force_new_repo, stacked_branch_url, require_stacking=stacked) |
58 | result_repo, is_new_repo = repository_policy.acquire_repository() |
59 | add_cleanup(result_repo.lock_write().unlock) |
60 | - is_stacked = stacked or (len(result_repo._fallback_repositories) != 0) |
61 | - if is_new_repo and revision_id is not None and not is_stacked: |
62 | - fetch_spec = graph.PendingAncestryResult( |
63 | - [revision_id], source_repository) |
64 | + fetch_spec_factory.source_repo = source_repository |
65 | + fetch_spec_factory.target_repo = result_repo |
66 | + if stacked or (len(result_repo._fallback_repositories) != 0): |
67 | + target_repo_kind = fetch.TargetRepoKinds.STACKED |
68 | + elif is_new_repo: |
69 | + target_repo_kind = fetch.TargetRepoKinds.EMPTY |
70 | else: |
71 | - fetch_spec = None |
72 | + target_repo_kind = fetch.TargetRepoKinds.PREEXISTING |
73 | + fetch_spec_factory.target_repo_kind = target_repo_kind |
74 | if source_repository is not None: |
75 | - # Fetch while stacked to prevent unstacked fetch from |
76 | - # Branch.sprout. |
77 | - if fetch_spec is None: |
78 | - result_repo.fetch(source_repository, revision_id=revision_id) |
79 | - else: |
80 | - result_repo.fetch(source_repository, fetch_spec=fetch_spec) |
81 | + fetch_spec = fetch_spec_factory.make_fetch_spec() |
82 | + result_repo.fetch(source_repository, fetch_spec=fetch_spec) |
83 | |
84 | if source_branch is None: |
85 | # this is for sprouting a controldir without a branch; is that |
86 | @@ -455,34 +444,54 @@ |
87 | else: |
88 | wt = None |
89 | if recurse == 'down': |
90 | + basis = None |
91 | if wt is not None: |
92 | basis = wt.basis_tree() |
93 | - basis.lock_read() |
94 | - subtrees = basis.iter_references() |
95 | elif result_branch is not None: |
96 | basis = result_branch.basis_tree() |
97 | - basis.lock_read() |
98 | - subtrees = basis.iter_references() |
99 | elif source_branch is not None: |
100 | basis = source_branch.basis_tree() |
101 | - basis.lock_read() |
102 | + if basis is not None: |
103 | + add_cleanup(basis.lock_read().unlock) |
104 | subtrees = basis.iter_references() |
105 | else: |
106 | subtrees = [] |
107 | - basis = None |
108 | - try: |
109 | - for path, file_id in subtrees: |
110 | - target = urlutils.join(url, urlutils.escape(path)) |
111 | - sublocation = source_branch.reference_parent(file_id, path) |
112 | - sublocation.bzrdir.sprout(target, |
113 | - basis.get_reference_revision(file_id, path), |
114 | - force_new_repo=force_new_repo, recurse=recurse, |
115 | - stacked=stacked) |
116 | - finally: |
117 | - if basis is not None: |
118 | - basis.unlock() |
119 | + for path, file_id in subtrees: |
120 | + target = urlutils.join(url, urlutils.escape(path)) |
121 | + sublocation = source_branch.reference_parent(file_id, path) |
122 | + sublocation.bzrdir.sprout(target, |
123 | + basis.get_reference_revision(file_id, path), |
124 | + force_new_repo=force_new_repo, recurse=recurse, |
125 | + stacked=stacked) |
126 | return result |
127 | |
128 | + def _find_source_repo(self, add_cleanup, source_branch): |
129 | + """Find the source branch and repo for a sprout operation. |
130 | + |
131 | + This is helper intended for use by _sprout. |
132 | + |
133 | + :returns: (source_branch, source_repository). Either or both may be |
134 | + None. If not None, they will be read-locked (and their unlock(s) |
135 | + scheduled via the add_cleanup param). |
136 | + """ |
137 | + if source_branch is not None: |
138 | + add_cleanup(source_branch.lock_read().unlock) |
139 | + return source_branch, source_branch.repository |
140 | + try: |
141 | + source_branch = self.open_branch() |
142 | + source_repository = source_branch.repository |
143 | + except errors.NotBranchError: |
144 | + source_branch = None |
145 | + try: |
146 | + source_repository = self.open_repository() |
147 | + except errors.NoRepositoryPresent: |
148 | + source_repository = None |
149 | + else: |
150 | + add_cleanup(source_repository.lock_read().unlock) |
151 | + else: |
152 | + add_cleanup(source_branch.lock_read().unlock) |
153 | + return source_branch, source_repository |
154 | + |
155 | def push_branch(self, source, revision_id=None, overwrite=False, |
156 | remember=False, create_prefix=False): |
157 | """Push the source branch into this ControlDir.""" |
158 | |
159 | === modified file 'bzrlib/fetch.py' |
160 | --- bzrlib/fetch.py 2011-01-14 17:18:23 +0000 |
161 | +++ bzrlib/fetch.py 2011-02-07 04:18:21 +0000 |
162 | @@ -55,6 +55,8 @@ |
163 | |
164 | :param last_revision: If set, try to limit to the data this revision |
165 | references. |
166 | + :param fetch_spec: A SearchResult specifying which revisions to fetch. |
167 | + If set, this overrides last_revision. |
168 | :param find_ghosts: If True search the entire history for ghosts. |
169 | """ |
170 | # repository.fetch has the responsibility for short-circuiting |
171 | @@ -93,12 +95,12 @@ |
172 | pb.show_pct = pb.show_count = False |
173 | try: |
174 | pb.update("Finding revisions", 0, 2) |
175 | - search = self._revids_to_fetch() |
176 | - mutter('fetching: %s', search) |
177 | - if search.is_empty(): |
178 | + search_result = self._revids_to_fetch() |
179 | + mutter('fetching: %s', search_result) |
180 | + if search_result.is_empty(): |
181 | return |
182 | pb.update("Fetching revisions", 1, 2) |
183 | - self._fetch_everything_for_search(search) |
184 | + self._fetch_everything_for_search(search_result) |
185 | finally: |
186 | pb.finished() |
187 | |
188 | @@ -151,18 +153,9 @@ |
189 | install self._last_revision in self.to_repository. |
190 | |
191 | :returns: A SearchResult of some sort. (Possibly a |
192 | - PendingAncestryResult, EmptySearchResult, etc.) |
193 | + PendingAncestryResult, EmptySearchResult, etc.) |
194 | """ |
195 | - mutter("self._fetch_spec, self._last_revision: %r, %r", |
196 | - self._fetch_spec, self._last_revision) |
197 | - get_search_result = getattr(self._fetch_spec, 'get_search_result', None) |
198 | - if get_search_result is not None: |
199 | - mutter( |
200 | - 'resolving fetch_spec into search result: %s', self._fetch_spec) |
201 | - # This is EverythingNotInOther or a similar kind of fetch_spec. |
202 | - # Turn it into a search result. |
203 | - return get_search_result() |
204 | - elif self._fetch_spec is not None: |
205 | + if self._fetch_spec is not None: |
206 | # The fetch spec is already a concrete search result. |
207 | return self._fetch_spec |
208 | elif self._last_revision == NULL_REVISION: |
209 | @@ -172,11 +165,11 @@ |
210 | elif self._last_revision is not None: |
211 | return graph.NotInOtherForRevs(self.to_repository, |
212 | self.from_repository, [self._last_revision], |
213 | - find_ghosts=self.find_ghosts).get_search_result() |
214 | + find_ghosts=self.find_ghosts).execute() |
215 | else: # self._last_revision is None: |
216 | return graph.EverythingNotInOther(self.to_repository, |
217 | self.from_repository, |
218 | - find_ghosts=self.find_ghosts).get_search_result() |
219 | + find_ghosts=self.find_ghosts).execute() |
220 | |
221 | |
222 | class Inter1and2Helper(object): |
223 | @@ -339,3 +332,86 @@ |
224 | selected_ids.append(parent_id) |
225 | parent_keys = [(root_id, parent_id) for parent_id in selected_ids] |
226 | return parent_keys |
227 | + |
228 | + |
229 | +class TargetRepoKinds(object): |
230 | + """An enum-like set of constants. |
231 | + |
232 | + They are the possible values of FetchSpecFactory.target_repo_kinds. |
233 | + """ |
234 | + |
235 | + PREEXISTING = 'preexisting' |
236 | + STACKED = 'stacked' |
237 | + EMPTY = 'empty' |
238 | + |
239 | + |
240 | +class FetchSpecFactory(object): |
241 | + """A helper for building the best fetch spec for a sprout call. |
242 | + |
243 | + Factors that go into determining the sort of fetch to perform: |
244 | + * did the caller specify any revision IDs? |
245 | + * did the caller specify a source branch (need to fetch the tip + tags) |
246 | + * is there an existing target repo (don't need to refetch revs it |
247 | + already has) |
248 | + * target is stacked? (similar to pre-existing target repo: even if |
249 | + the target itself is new don't want to refetch existing revs) |
250 | + |
251 | + :ivar source_branch: the source branch if one specified, else None. |
252 | + :ivar source_branch_stop_revision_id: fetch up to this revision of |
253 | + source_branch, rather than its tip. |
254 | + :ivar source_repo: the source repository if one found, else None. |
255 | + :ivar target_repo: the target repository acquired by sprout. |
256 | + :ivar target_repo_kind: one of the TargetRepoKinds constants. |
257 | + """ |
258 | + |
259 | + def __init__(self): |
260 | + self._explicit_rev_ids = set() |
261 | + self.source_branch = None |
262 | + self.source_branch_stop_revision_id = None |
263 | + self.source_repo = None |
264 | + self.target_repo = None |
265 | + self.target_repo_kind = None |
266 | + |
267 | + def add_revision_ids(self, revision_ids): |
268 | + """Add revision_ids to the set of revision_ids to be fetched.""" |
269 | + self._explicit_rev_ids.update(revision_ids) |
270 | + |
271 | + def make_fetch_spec(self): |
272 | + """Build a SearchResult or PendingAncestryResult or etc.""" |
273 | + if self.target_repo_kind is None or self.source_repo is None: |
274 | + raise AssertionError( |
275 | + 'Incomplete FetchSpecFactory: %r' % (self.__dict__,)) |
276 | + if len(self._explicit_rev_ids) == 0 and self.source_branch is None: |
277 | + # Caller hasn't specified any revisions or source branch |
278 | + if self.target_repo_kind == TargetRepoKinds.EMPTY: |
279 | + return graph.EverythingResult(self.source_repo) |
280 | + else: |
281 | + # We want everything not already in the target (or target's |
282 | + # fallbacks). |
283 | + return graph.EverythingNotInOther( |
284 | + self.target_repo, self.source_repo).execute() |
285 | + heads_to_fetch = set(self._explicit_rev_ids) |
286 | + tags_to_fetch = set() |
287 | + if self.source_branch is not None: |
288 | + try: |
289 | + tags_to_fetch.update( |
290 | + self.source_branch.tags.get_reverse_tag_dict()) |
291 | + except errors.TagsNotSupported: |
292 | + pass |
293 | + if self.source_branch_stop_revision_id is not None: |
294 | + heads_to_fetch.add(self.source_branch_stop_revision_id) |
295 | + else: |
296 | + heads_to_fetch.add(self.source_branch.last_revision()) |
297 | + if self.target_repo_kind == TargetRepoKinds.EMPTY: |
298 | + # PendingAncestryResult does not raise errors if a requested head |
299 | + # is absent. Ideally it would support the |
300 | + # required_ids/if_present_ids distinction, but in practice |
301 | + # heads_to_fetch will almost certainly be present so this doesn't |
302 | + # matter much. |
303 | + all_heads = heads_to_fetch.union(tags_to_fetch) |
304 | + return graph.PendingAncestryResult(all_heads, self.source_repo) |
305 | + return graph.NotInOtherForRevs(self.target_repo, self.source_repo, |
306 | + required_ids=heads_to_fetch, if_present_ids=tags_to_fetch |
307 | + ).execute() |
308 | + |
309 | + |
310 | |
311 | === modified file 'bzrlib/graph.py' |
312 | --- bzrlib/graph.py 2011-01-14 17:18:23 +0000 |
313 | +++ bzrlib/graph.py 2011-02-07 04:18:21 +0000 |
314 | @@ -1576,14 +1576,15 @@ |
315 | |
316 | class AbstractSearch(object): |
317 | |
318 | - def get_search_result(self): |
319 | + def execute(self): |
320 | """Construct a network-ready search result from this search description. |
321 | |
322 | This may take some time to search repositories, etc. |
323 | |
324 | - :return: A search result. |
325 | + :return: A search result (an object that implements |
326 | + AbstractSearchResult's API). |
327 | """ |
328 | - raise NotImplementedError(self.get_search_result) |
329 | + raise NotImplementedError(self.execute) |
330 | |
331 | |
332 | class SearchResult(AbstractSearchResult): |
333 | @@ -1705,7 +1706,8 @@ |
334 | |
335 | def __repr__(self): |
336 | if len(self.heads) > 5: |
337 | - heads_repr = repr(list(self.heads)[:5] + ', ...]') |
338 | + heads_repr = repr(list(self.heads)[:5])[:-1] |
339 | + heads_repr += ', <%d more>...]' % (len(self.heads) - 5,) |
340 | else: |
341 | heads_repr = repr(self.heads) |
342 | return '<%s heads:%s repo:%r>' % ( |
343 | @@ -1813,7 +1815,7 @@ |
344 | self.from_repo = from_repo |
345 | self.find_ghosts = find_ghosts |
346 | |
347 | - def get_search_result(self): |
348 | + def execute(self): |
349 | return self.to_repo.search_missing_revision_ids( |
350 | self.from_repo, find_ghosts=self.find_ghosts) |
351 | |
352 | @@ -1853,7 +1855,7 @@ |
353 | self.__class__.__name__, self.from_repo, self.to_repo, |
354 | self.find_ghosts, reqd_revs_repr, ifp_revs_repr) |
355 | |
356 | - def get_search_result(self): |
357 | + def execute(self): |
358 | return self.to_repo.search_missing_revision_ids( |
359 | self.from_repo, revision_ids=self.required_ids, |
360 | if_present_ids=self.if_present_ids, find_ghosts=self.find_ghosts) |
361 | |
362 | === modified file 'bzrlib/remote.py' |
363 | --- bzrlib/remote.py 2011-01-14 17:18:23 +0000 |
364 | +++ bzrlib/remote.py 2011-02-07 04:18:21 +0000 |
365 | @@ -1360,7 +1360,7 @@ |
366 | if symbol_versioning.deprecated_passed(revision_id): |
367 | symbol_versioning.warn( |
368 | 'search_missing_revision_ids(revision_id=...) was ' |
369 | - 'deprecated in 2.3. Use revision_ids=[...] instead.', |
370 | + 'deprecated in 2.4. Use revision_ids=[...] instead.', |
371 | DeprecationWarning, stacklevel=2) |
372 | if revision_ids is not None: |
373 | raise AssertionError( |
374 | @@ -1991,11 +1991,11 @@ |
375 | if isinstance(search, graph.EverythingResult): |
376 | error_verb = e.error_from_smart_server.error_verb |
377 | if error_verb == 'BadSearch': |
378 | - # Pre-2.3 servers don't support this sort of search. |
379 | + # Pre-2.4 servers don't support this sort of search. |
380 | # XXX: perhaps falling back to VFS on BadSearch is a |
381 | # good idea in general? It might provide a little bit |
382 | # of protection against client-side bugs. |
383 | - medium._remember_remote_is_before((2, 3)) |
384 | + medium._remember_remote_is_before((2, 4)) |
385 | break |
386 | raise |
387 | else: |
388 | |
389 | === modified file 'bzrlib/repofmt/knitrepo.py' |
390 | --- bzrlib/repofmt/knitrepo.py 2010-12-21 06:10:11 +0000 |
391 | +++ bzrlib/repofmt/knitrepo.py 2011-02-07 04:18:21 +0000 |
392 | @@ -542,7 +542,7 @@ |
393 | if symbol_versioning.deprecated_passed(revision_id): |
394 | symbol_versioning.warn( |
395 | 'search_missing_revision_ids(revision_id=...) was ' |
396 | - 'deprecated in 2.3. Use revision_ids=[...] instead.', |
397 | + 'deprecated in 2.4. Use revision_ids=[...] instead.', |
398 | DeprecationWarning, stacklevel=2) |
399 | if revision_ids is not None: |
400 | raise AssertionError( |
401 | |
402 | === modified file 'bzrlib/repofmt/weaverepo.py' |
403 | --- bzrlib/repofmt/weaverepo.py 2011-01-14 17:18:23 +0000 |
404 | +++ bzrlib/repofmt/weaverepo.py 2011-02-07 04:18:21 +0000 |
405 | @@ -826,7 +826,7 @@ |
406 | if symbol_versioning.deprecated_passed(revision_id): |
407 | symbol_versioning.warn( |
408 | 'search_missing_revision_ids(revision_id=...) was ' |
409 | - 'deprecated in 2.3. Use revision_ids=[...] instead.', |
410 | + 'deprecated in 2.4. Use revision_ids=[...] instead.', |
411 | DeprecationWarning, stacklevel=2) |
412 | if revision_ids is not None: |
413 | raise AssertionError( |
414 | |
415 | === modified file 'bzrlib/repository.py' |
416 | --- bzrlib/repository.py 2011-01-19 23:00:12 +0000 |
417 | +++ bzrlib/repository.py 2011-02-07 04:18:21 +0000 |
418 | @@ -1608,7 +1608,7 @@ |
419 | if symbol_versioning.deprecated_passed(revision_id): |
420 | symbol_versioning.warn( |
421 | 'search_missing_revision_ids(revision_id=...) was ' |
422 | - 'deprecated in 2.3. Use revision_ids=[...] instead.', |
423 | + 'deprecated in 2.4. Use revision_ids=[...] instead.', |
424 | DeprecationWarning, stacklevel=3) |
425 | if revision_ids is not None: |
426 | raise AssertionError( |
427 | @@ -1785,9 +1785,6 @@ |
428 | not _mod_revision.is_null(revision_id)): |
429 | self.get_revision(revision_id) |
430 | return 0, [] |
431 | - # if there is no specific appropriate InterRepository, this will get |
432 | - # the InterRepository base class, which raises an |
433 | - # IncompatibleRepositories when asked to fetch. |
434 | inter = InterRepository.get(source, self) |
435 | return inter.fetch(revision_id=revision_id, pb=pb, |
436 | find_ghosts=find_ghosts, fetch_spec=fetch_spec) |
437 | @@ -3482,8 +3479,12 @@ |
438 | # them, make sure that they are present in the target. |
439 | # We don't care about other ghosts as we can't fetch them and |
440 | # haven't been asked to. |
441 | + mutter('reqd: %r if-present: %r -> ghosts: %r', revision_ids, |
442 | + if_present_ids, ghosts) |
443 | ghosts_to_check = set(revision_ids.intersection(ghosts)) |
444 | revs_to_get = set(next_revs).union(ghosts_to_check) |
445 | + mutter('ghosts_to_check: %r revs_to_get: %r searcher_exhausted: %r', |
446 | + ghosts_to_check, revs_to_get, searcher_exhausted) |
447 | if revs_to_get: |
448 | have_revs = set(target_graph.get_parent_map(revs_to_get)) |
449 | # we always have NULL_REVISION present. |
450 | @@ -3526,7 +3527,7 @@ |
451 | if symbol_versioning.deprecated_passed(revision_id): |
452 | symbol_versioning.warn( |
453 | 'search_missing_revision_ids(revision_id=...) was ' |
454 | - 'deprecated in 2.3. Use revision_ids=[...] instead.', |
455 | + 'deprecated in 2.4. Use revision_ids=[...] instead.', |
456 | DeprecationWarning, stacklevel=2) |
457 | if revision_ids is not None: |
458 | raise AssertionError( |
459 | @@ -3901,10 +3902,9 @@ |
460 | fetch_spec=None): |
461 | """See InterRepository.fetch().""" |
462 | if fetch_spec is not None: |
463 | - if (isinstance(fetch_spec, graph.NotInOtherForRevs) and |
464 | - len(fetch_spec.required_ids) == 1 and not |
465 | - fetch_spec.if_present_ids): |
466 | - revision_id = list(fetch_spec.required_ids)[0] |
467 | + if (isinstance(fetch_spec, graph.SearchResult) and |
468 | + len(fetch_spec.get_keys()) == 1): |
469 | + revision_id = list(fetch_spec.get_keys())[0] |
470 | del fetch_spec |
471 | else: |
472 | raise AssertionError("Not implemented yet...") |
473 | |
474 | === modified file 'bzrlib/tests/blackbox/test_branch.py' |
475 | --- bzrlib/tests/blackbox/test_branch.py 2011-01-10 22:20:12 +0000 |
476 | +++ bzrlib/tests/blackbox/test_branch.py 2011-02-07 04:18:21 +0000 |
477 | @@ -23,13 +23,11 @@ |
478 | branch, |
479 | bzrdir, |
480 | errors, |
481 | - repository, |
482 | revision as _mod_revision, |
483 | ) |
484 | from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1 |
485 | from bzrlib.tests import TestCaseWithTransport |
486 | from bzrlib.tests import ( |
487 | - KnownFailure, |
488 | HardlinkFeature, |
489 | test_server, |
490 | ) |
491 | @@ -270,6 +268,20 @@ |
492 | self.run_bzr('checkout --lightweight a b') |
493 | self.assertLength(2, calls) |
494 | |
495 | + def test_branch_fetches_all_tags(self): |
496 | + builder = self.make_branch_builder('source') |
497 | + builder.build_commit(message="Rev 1", rev_id='rev-1') |
498 | + builder.build_commit(message="Rev 2", rev_id='rev-2') |
499 | + source = builder.get_branch() |
500 | + source.tags.set_tag('tag-a', 'rev-2') |
501 | + source.set_last_revision_info(1, 'rev-1') |
502 | + # Now source has a tag not in its ancestry. Make a branch from it. |
503 | + self.run_bzr('branch source new-branch') |
504 | + new_branch = branch.Branch.open('new-branch') |
505 | + # The tag is present, and so is its revision. |
506 | + self.assertEqual('rev-2', new_branch.tags.lookup_tag('tag-a')) |
507 | + new_branch.repository.get_revision('rev-2') |
508 | + |
509 | |
510 | class TestBranchStacked(TestCaseWithTransport): |
511 | """Tests for branch --stacked""" |
512 | @@ -451,6 +463,25 @@ |
513 | # upwards without agreement from bzr's network support maintainers. |
514 | self.assertLength(14, self.hpss_calls) |
515 | |
516 | + def test_branch_from_branch_with_tags(self): |
517 | + self.setup_smart_server_with_call_log() |
518 | + builder = self.make_branch_builder('source') |
519 | + builder.build_commit(message="Rev 1", rev_id='rev-1') |
520 | + builder.build_commit(message="Rev 2", rev_id='rev-2') |
521 | + source = builder.get_branch() |
522 | + source.tags.set_tag('tag-a', 'rev-2') |
523 | + source.tags.set_tag('tag-missing', 'missing-rev') |
524 | + source.set_last_revision_info(1, 'rev-1') |
525 | + # Now source has a tag not in its ancestry. Make a branch from it. |
526 | + self.reset_smart_call_log() |
527 | + out, err = self.run_bzr(['branch', self.get_url('source'), 'target']) |
528 | + # This figure represent the amount of work to perform this use case. It |
529 | + # is entirely ok to reduce this number if a test fails due to rpc_count |
530 | + # being too low. If rpc_count increases, more network roundtrips have |
531 | + # become necessary for this use case. Please do not adjust this number |
532 | + # upwards without agreement from bzr's network support maintainers. |
533 | + self.assertLength(9, self.hpss_calls) |
534 | + |
535 | |
536 | class TestRemoteBranch(TestCaseWithSFTPServer): |
537 | |
538 | |
539 | === modified file 'bzrlib/tests/per_branch/test_sprout.py' |
540 | --- bzrlib/tests/per_branch/test_sprout.py 2011-01-13 05:36:39 +0000 |
541 | +++ bzrlib/tests/per_branch/test_sprout.py 2011-02-07 04:18:21 +0000 |
542 | @@ -111,6 +111,25 @@ |
543 | self.assertEqual((2, 'rev2-alt'), branch2.last_revision_info()) |
544 | self.assertEqual(['rev1', 'rev2-alt'], branch2.revision_history()) |
545 | |
546 | + def test_sprout_preserves_tags(self): |
547 | + """Sprout preserves tags, even tags of absent revisions.""" |
548 | + try: |
549 | + builder = self.make_branch_builder('source') |
550 | + except errors.UninitializableFormat: |
551 | + raise tests.TestSkipped('Uninitializable branch format') |
552 | + builder.build_commit(message="Rev 1", rev_id='rev-1') |
553 | + source = builder.get_branch() |
554 | + try: |
555 | + source.tags.set_tag('tag-a', 'missing-rev') |
556 | + except errors.TagsNotSupported: |
557 | + raise tests.TestNotApplicable( |
558 | + 'Branch format does not support tags.') |
559 | + # Now source has a tag pointing to an absent revision. Sprout it. |
560 | + target_bzrdir = self.make_repository('target').bzrdir |
561 | + new_branch = source.sprout(target_bzrdir) |
562 | + # The tag is present in the target |
563 | + self.assertEqual('missing-rev', new_branch.tags.lookup_tag('tag-a')) |
564 | + |
565 | def test_sprout_from_any_repo_revision(self): |
566 | """We should be able to sprout from any revision.""" |
567 | wt = self.make_branch_and_tree('source') |
568 | |
569 | === modified file 'bzrlib/tests/per_controldir/test_controldir.py' |
570 | --- bzrlib/tests/per_controldir/test_controldir.py 2011-01-27 14:27:18 +0000 |
571 | +++ bzrlib/tests/per_controldir/test_controldir.py 2011-02-07 04:18:21 +0000 |
572 | @@ -503,7 +503,10 @@ |
573 | self.assertNotEqual(dir.transport.base, target.transport.base) |
574 | self.assertNotEqual(dir.transport.base, shared_repo.bzrdir.transport.base) |
575 | branch = target.open_branch() |
576 | - self.assertTrue(branch.repository.has_revision('1')) |
577 | + # The sprouted bzrdir has a branch, so only revisions referenced by |
578 | + # that branch are copied, rather than the whole repository. It's an |
579 | + # empty branch, so none are copied. |
580 | + self.assertEqual([], branch.repository.all_revision_ids()) |
581 | if branch.bzrdir._format.supports_workingtrees: |
582 | self.assertTrue(branch.repository.make_working_trees()) |
583 | self.assertFalse(branch.repository.is_shared()) |
584 | @@ -669,6 +672,103 @@ |
585 | target = dir.sprout(self.get_url('target'), revision_id='1') |
586 | self.assertEqual('1', target.open_branch().last_revision()) |
587 | |
588 | + def test_sprout_bzrdir_branch_with_tags(self): |
589 | + # when sprouting a branch all revisions named in the tags are copied |
590 | + # too. |
591 | + builder = self.make_branch_builder('source') |
592 | + builder.build_commit(message="Rev 1", rev_id='rev-1') |
593 | + builder.build_commit(message="Rev 2", rev_id='rev-2') |
594 | + source = builder.get_branch() |
595 | + try: |
596 | + source.tags.set_tag('tag-a', 'rev-2') |
597 | + except errors.TagsNotSupported: |
598 | + raise TestNotApplicable('Branch format does not support tags.') |
599 | + source.set_last_revision_info(1, 'rev-1') |
600 | + # Now source has a tag not in its ancestry. Sprout its controldir. |
601 | + dir = source.bzrdir |
602 | + target = dir.sprout(self.get_url('target')) |
603 | + # The tag is present, and so is its revision. |
604 | + new_branch = target.open_branch() |
605 | + self.assertEqual('rev-2', new_branch.tags.lookup_tag('tag-a')) |
606 | + new_branch.repository.get_revision('rev-2') |
607 | + |
608 | + def test_sprout_bzrdir_branch_with_absent_tag(self): |
609 | + # tags referencing absent revisions are copied (and those absent |
610 | + # revisions do not prevent the sprout.) |
611 | + builder = self.make_branch_builder('source') |
612 | + builder.build_commit(message="Rev 1", rev_id='rev-1') |
613 | + source = builder.get_branch() |
614 | + try: |
615 | + source.tags.set_tag('tag-a', 'missing-rev') |
616 | + except errors.TagsNotSupported: |
617 | + raise TestNotApplicable('Branch format does not support tags.') |
618 | + # Now source has a tag pointing to an absent revision. Sprout its |
619 | + # controldir. |
620 | + dir = source.bzrdir |
621 | + target = dir.sprout(self.get_url('target')) |
622 | + # The tag is present in the target |
623 | + new_branch = target.open_branch() |
624 | + self.assertEqual('missing-rev', new_branch.tags.lookup_tag('tag-a')) |
625 | + |
626 | + def test_sprout_bzrdir_passing_source_branch_with_absent_tag(self): |
627 | + # tags referencing absent revisions are copied (and those absent |
628 | + # revisions do not prevent the sprout.) |
629 | + builder = self.make_branch_builder('source') |
630 | + builder.build_commit(message="Rev 1", rev_id='rev-1') |
631 | + source = builder.get_branch() |
632 | + try: |
633 | + source.tags.set_tag('tag-a', 'missing-rev') |
634 | + except errors.TagsNotSupported: |
635 | + raise TestNotApplicable('Branch format does not support tags.') |
636 | + # Now source has a tag pointing to an absent revision. Sprout its |
637 | + # controldir. |
638 | + dir = source.bzrdir |
639 | + target = dir.sprout(self.get_url('target'), source_branch=source) |
640 | + # The tag is present in the target |
641 | + new_branch = target.open_branch() |
642 | + self.assertEqual('missing-rev', new_branch.tags.lookup_tag('tag-a')) |
643 | + |
644 | + def test_sprout_bzrdir_passing_rev_not_source_branch_copies_tags(self): |
645 | + # dir.sprout(..., revision_id='rev1') copies rev1, and all the tags of |
646 | + # the branch at that bzrdir, the ancestry of all of those, but no other |
647 | + # revs (not even the tip of the source branch). |
648 | + builder = self.make_branch_builder('source') |
649 | + builder.build_commit(message="Base", rev_id='base-rev') |
650 | + # Make three parallel lines of ancestry off this base. |
651 | + source = builder.get_branch() |
652 | + builder.build_commit(message="Rev A1", rev_id='rev-a1') |
653 | + builder.build_commit(message="Rev A2", rev_id='rev-a2') |
654 | + builder.build_commit(message="Rev A3", rev_id='rev-a3') |
655 | + source.set_last_revision_info(1, 'base-rev') |
656 | + builder.build_commit(message="Rev B1", rev_id='rev-b1') |
657 | + builder.build_commit(message="Rev B2", rev_id='rev-b2') |
658 | + builder.build_commit(message="Rev B3", rev_id='rev-b3') |
659 | + source.set_last_revision_info(1, 'base-rev') |
660 | + builder.build_commit(message="Rev C1", rev_id='rev-c1') |
661 | + builder.build_commit(message="Rev C2", rev_id='rev-c2') |
662 | + builder.build_commit(message="Rev C3", rev_id='rev-c3') |
663 | + # Set the branch tip to A2 |
664 | + source.set_last_revision_info(3, 'rev-a2') |
665 | + try: |
666 | + # Create a tag for B2, and for an absent rev |
667 | + source.tags.set_tag('tag-non-ancestry', 'rev-b2') |
668 | + source.tags.set_tag('tag-absent', 'absent-rev') |
669 | + except errors.TagsNotSupported: |
670 | + raise TestNotApplicable('Branch format does not support tags.') |
671 | + # And ask sprout for C2 |
672 | + dir = source.bzrdir |
673 | + target = dir.sprout(self.get_url('target'), revision_id='rev-c2') |
674 | + # The tags are present |
675 | + new_branch = target.open_branch() |
676 | + self.assertEqual( |
677 | + {'tag-absent': 'absent-rev', 'tag-non-ancestry': 'rev-b2'}, |
678 | + new_branch.tags.get_tag_dict()) |
679 | + # And the revs for A2, B2 and C2's ancestries are present, but no |
680 | + # others. |
681 | + self.assertEqual( |
682 | + ['base-rev', 'rev-b1', 'rev-b2', 'rev-c1', 'rev-c2'], |
683 | + sorted(new_branch.repository.all_revision_ids())) |
684 | + |
685 | def test_sprout_bzrdir_tree_branch_reference(self): |
686 | # sprouting should create a repository if needed and a sprouted branch. |
687 | # the tree state should not be copied. |
688 | |
689 | === modified file 'bzrlib/tests/per_interrepository/test_interrepository.py' |
690 | --- bzrlib/tests/per_interrepository/test_interrepository.py 2011-01-14 17:18:23 +0000 |
691 | +++ bzrlib/tests/per_interrepository/test_interrepository.py 2011-02-07 04:18:21 +0000 |
692 | @@ -138,7 +138,7 @@ |
693 | find_ghosts=False) |
694 | self.callDeprecated( |
695 | ['search_missing_revision_ids(revision_id=...) was deprecated in ' |
696 | - '2.3. Use revision_ids=[...] instead.'], |
697 | + '2.4. Use revision_ids=[...] instead.'], |
698 | self.assertRaises, errors.NoSuchRevision, |
699 | repo_b.search_missing_revision_ids, repo_a, revision_id='pizza', |
700 | find_ghosts=False) |
701 | |
702 | === modified file 'bzrlib/tests/test_bzrdir.py' |
703 | --- bzrlib/tests/test_bzrdir.py 2011-01-27 14:27:18 +0000 |
704 | +++ bzrlib/tests/test_bzrdir.py 2011-02-07 04:18:21 +0000 |
705 | @@ -31,6 +31,7 @@ |
706 | help_topics, |
707 | lock, |
708 | repository, |
709 | + revision as _mod_revision, |
710 | osutils, |
711 | remote, |
712 | symbol_versioning, |
713 | @@ -1341,6 +1342,9 @@ |
714 | def copy_content_into(self, destination, revision_id=None): |
715 | self.calls.append('copy_content_into') |
716 | |
717 | + def last_revision(self): |
718 | + return _mod_revision.NULL_REVISION |
719 | + |
720 | def get_parent(self): |
721 | return self._parent |
722 | |
723 | |
724 | === modified file 'bzrlib/tests/test_remote.py' |
725 | --- bzrlib/tests/test_remote.py 2011-01-14 17:18:23 +0000 |
726 | +++ bzrlib/tests/test_remote.py 2011-02-07 04:18:21 +0000 |
727 | @@ -3211,15 +3211,15 @@ |
728 | override_existing=True) |
729 | |
730 | def test_fetch_everything_backwards_compat(self): |
731 | - """Can fetch with EverythingResult even with pre 2.3 servers. |
732 | + """Can fetch with EverythingResult even with pre 2.4 servers. |
733 | |
734 | - Pre-2.3 do not support 'everything' searches with the |
735 | + Pre-2.4 do not support 'everything' searches with the |
736 | Repository.get_stream_1.19 verb. |
737 | """ |
738 | verb_log = [] |
739 | class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19): |
740 | """A version of the Repository.get_stream_1.19 verb patched to |
741 | - reject 'everything' searches the way 2.2 and earlier do. |
742 | + reject 'everything' searches the way 2.3 and earlier do. |
743 | """ |
744 | def recreate_search(self, repository, search_bytes, discard_excess=False): |
745 | verb_log.append(search_bytes.split('\n', 1)[0]) |
746 | |
747 | === modified file 'doc/en/release-notes/bzr-2.3.txt' |
748 | --- doc/en/release-notes/bzr-2.3.txt 2011-02-03 05:13:46 +0000 |
749 | +++ doc/en/release-notes/bzr-2.3.txt 2011-02-07 04:18:21 +0000 |
750 | @@ -275,11 +275,6 @@ |
751 | crashes when encountering private bugs (they are just displayed as such). |
752 | (Vincent Ladeuil, #354985) |
753 | |
754 | -* The ``revision_id`` parameter of |
755 | - ``Repository.search_missing_revision_ids`` and |
756 | - ``InterRepository.search_missing_revision_ids`` is deprecated. It is |
757 | - replaced by the ``revision_ids`` parameter. (Andrew Bennetts) |
758 | - |
759 | Internals |
760 | ********* |
761 | |
762 | |
763 | === modified file 'doc/en/release-notes/bzr-2.4.txt' |
764 | --- doc/en/release-notes/bzr-2.4.txt 2011-02-03 05:46:08 +0000 |
765 | +++ doc/en/release-notes/bzr-2.4.txt 2011-02-07 04:18:21 +0000 |
766 | @@ -94,6 +94,11 @@ |
767 | * Added ``bzrlib.mergetools`` module with helper functions for working with |
768 | the list of external merge tools. (Gordon Tyler, #489915) |
769 | |
770 | +* The ``revision_id`` parameter of |
771 | + ``Repository.search_missing_revision_ids`` and |
772 | + ``InterRepository.search_missing_revision_ids`` is deprecated. It is |
773 | + replaced by the ``revision_ids`` parameter. (Andrew Bennetts) |
774 | + |
775 | Internals |
776 | ********* |
777 |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
> I think much of the mess stems from the weirdness of sprouting a bzrdir rather than a repository or branch directly. I'm starting to think we should remove ControlDir.sprout.
>
I'm in favor of this. It will mess up our test suite a ton, because that
has become the defacto method of creating a new branch. However using:
tree.branch. sprout( new_branch) makes a lot more sense to me than sprout( new_something)
tree.bzrdir.
I think some of it stems from, do you want a WT in the target or not,
etc, etc. I would be happier with something that you can compose. This
is also cluttered because you can't just open the WT from a Branch, it
will re-open the branch, etc. (Note that there was also bugs with
branch.sprout() which didn't happen with bzrdir.sprout(), something with
Trees and merge, and double-locking the WT, IIRC)
Things we want to keep in mind:
a) In the ideal interface, we would have a way to copy any number of
branches simultaneously into a target. This gives us a way to
implement some form of Repository cloning that people would like.
(Being able to grab the stack of a loom, or trunk+main features,
etc.)
b) I think we do like the ability to branch from a source, and preserve
the type. However, many extensions probably explicitly work around
this (bzr branch svn:// is not creating an svn branch, same for
hg/git/etc)
On 12/6/2010 11:28 PM, Andrew Bennetts wrote: /bugs.launchpad .net/bugs/ 309682 /bugs.launchpad .net/bzr/ +bug/309682>: bzr branch should copy revisions referred to in tags, not just the tip. unlocking/ locking again, no multiple calls to fetch for one sprout, no errors from bzr branch simply because a tag refers to a ghost, etc. At the same time we don't want to make the code any more complex and harder to understand than it already is! tests.per_ controldir. test_controldir .TestControlDir .test_sprout_ bzrdir_ repository_ branch_ only_source_ under_shared
> Andrew Bennetts has proposed merging lp:~spiv/bzr/fetch-all-tags-309682 into lp:bzr with lp:~spiv/bzr/fetch-integration as a prerequisite.
>
> Requested reviews:
> bzr-core (bzr-core)
> Related bugs:
> #309682 tags are copied but their revisions may not be
> https:/
>
>
> This branch fixes <https:/
>
> The main difficulty is the ugliness of sprout and to a lesser extent fetch. We don't want fetching the tags to regress our performance: i.e. no extra roundtrips for redundant locking/
>
> The prerequisite branch addresses much of that, so that this patch on its own is fairly small. (The prerequisite branch is simply lp:~spiv/bzr/sprout-does-not-reopen-repo and lp:~spiv/bzr/fetch-spec-everything-not-in-other merged together. Both of those are in the review queue.)
>
> I've taken the approach of moving much of the complexity out of the sprout method, into more focussed methods and objects which are hopefully easy to understand isolation.
>
> Part of the problem is that the interface of sprout is pretty confusing, as demonstrated by the one test failure I've currently got in this branch: bzrlib.
>
> That test fails because it verifies that bzrdir.sprout(url) from a bzrdir with a branch in a shared repo co...