Status: | Merged |
---|---|
Approved by: | Jelmer Vernooij |
Approved revision: | no longer in the source branch. |
Merge reported by: | The Breezy Bot |
Merged at revision: | not available |
Proposed branch: | lp:~jelmer/brz/trunk-3.1 |
Merge into: | lp:brz |
Diff against target: |
1889 lines (+542/-334) 25 files modified
breezy/branch.py (+42/-26) breezy/bzr/branch.py (+3/-1) breezy/bzr/bzrdir.py (+4/-2) breezy/bzr/knitpack_repo.py (+0/-6) breezy/bzr/remote.py (+2/-2) breezy/bzr/tag.py (+113/-0) breezy/controldir.py (+15/-8) breezy/git/branch.py (+152/-90) breezy/git/dir.py (+5/-4) breezy/git/interrepo.py (+12/-6) breezy/git/workingtree.py (+2/-2) breezy/plugins/propose/github.py (+6/-4) breezy/plugins/propose/gitlabs.py (+3/-3) breezy/plugins/propose/launchpad.py (+15/-11) breezy/plugins/weave_fmt/bzrdir.py (+2/-2) breezy/propose.py (+1/-1) breezy/tag.py (+80/-160) breezy/tests/per_branch/test_pull.py (+1/-1) breezy/tests/per_branch/test_tags.py (+15/-0) breezy/tests/per_controldir/test_push.py (+11/-0) breezy/tests/per_interbranch/test_pull.py (+21/-1) breezy/tests/per_interbranch/test_push.py (+20/-0) breezy/tests/test_foreign.py (+1/-1) breezy/tests/test_tag.py (+14/-1) breezy/workingtree.py (+2/-2) |
To merge this branch: | bzr merge lp:~jelmer/brz/trunk-3.1 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jelmer Vernooij | Approve | ||
Review via email: mp+379600@code.launchpad.net |
Commit message
Merge lp:brz/3.1.
Description of the change
Merge lp:brz/3.1.
To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) : | # |
review:
Approve
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'breezy/branch.py' |
2 | --- breezy/branch.py 2020-02-18 01:57:45 +0000 |
3 | +++ breezy/branch.py 2020-02-21 04:09:29 +0000 |
4 | @@ -1188,7 +1188,8 @@ |
5 | if revno < 1 or revno > self.revno(): |
6 | raise errors.InvalidRevisionNumber(revno) |
7 | |
8 | - def clone(self, to_controldir, revision_id=None, repository_policy=None): |
9 | + def clone(self, to_controldir, revision_id=None, repository_policy=None, |
10 | + tag_selector=None): |
11 | """Clone this branch into to_controldir preserving all semantic values. |
12 | |
13 | Most API users will want 'create_clone_on_transport', which creates a |
14 | @@ -1201,11 +1202,12 @@ |
15 | with self.lock_read(), result.lock_write(): |
16 | if repository_policy is not None: |
17 | repository_policy.configure_branch(result) |
18 | - self.copy_content_into(result, revision_id=revision_id) |
19 | + self.copy_content_into( |
20 | + result, revision_id=revision_id, tag_selector=tag_selector) |
21 | return result |
22 | |
23 | def sprout(self, to_controldir, revision_id=None, repository_policy=None, |
24 | - repository=None, lossy=False): |
25 | + repository=None, lossy=False, tag_selector=None): |
26 | """Create a new line of development from the branch, into to_controldir. |
27 | |
28 | to_controldir controls the branch format. |
29 | @@ -1222,7 +1224,8 @@ |
30 | with self.lock_read(), result.lock_write(): |
31 | if repository_policy is not None: |
32 | repository_policy.configure_branch(result) |
33 | - self.copy_content_into(result, revision_id=revision_id) |
34 | + self.copy_content_into( |
35 | + result, revision_id=revision_id, tag_selector=tag_selector) |
36 | master_url = self.get_bound_location() |
37 | if master_url is None: |
38 | result.set_parent(self.user_url) |
39 | @@ -1255,14 +1258,16 @@ |
40 | revno = 1 |
41 | destination.set_last_revision_info(revno, revision_id) |
42 | |
43 | - def copy_content_into(self, destination, revision_id=None): |
44 | + def copy_content_into(self, destination, revision_id=None, tag_selector=None): |
45 | """Copy the content of self into destination. |
46 | |
47 | revision_id: if not None, the revision history in the new branch will |
48 | be truncated to end with revision_id. |
49 | + tag_selector: Optional callback that receives a tag name |
50 | + and should return a boolean to indicate whether a tag should be copied |
51 | """ |
52 | return InterBranch.get(self, destination).copy_content_into( |
53 | - revision_id=revision_id) |
54 | + revision_id=revision_id, tag_selector=tag_selector) |
55 | |
56 | def update_references(self, target): |
57 | if not self._format.supports_reference_locations: |
58 | @@ -1307,7 +1312,8 @@ |
59 | |
60 | def create_clone_on_transport(self, to_transport, revision_id=None, |
61 | stacked_on=None, create_prefix=False, |
62 | - use_existing_dir=False, no_tree=None): |
63 | + use_existing_dir=False, no_tree=None, |
64 | + tag_selector=None): |
65 | """Create a clone of this branch and its bzrdir. |
66 | |
67 | :param to_transport: The transport to clone onto. |
68 | @@ -1327,7 +1333,7 @@ |
69 | dir_to = self.controldir.clone_on_transport( |
70 | to_transport, revision_id=revision_id, stacked_on=stacked_on, |
71 | create_prefix=create_prefix, use_existing_dir=use_existing_dir, |
72 | - no_tree=no_tree) |
73 | + no_tree=no_tree, tag_selector=tag_selector) |
74 | return dir_to.open_branch() |
75 | |
76 | def create_checkout(self, to_location, revision_id=None, |
77 | @@ -2054,7 +2060,7 @@ |
78 | raise NotImplementedError(klass._get_branch_formats_to_test) |
79 | |
80 | def pull(self, overwrite=False, stop_revision=None, |
81 | - possible_transports=None, local=False): |
82 | + possible_transports=None, local=False, tag_selector=None): |
83 | """Mirror source into target branch. |
84 | |
85 | The target branch is considered to be 'local', having low latency. |
86 | @@ -2064,18 +2070,21 @@ |
87 | raise NotImplementedError(self.pull) |
88 | |
89 | def push(self, overwrite=False, stop_revision=None, lossy=False, |
90 | - _override_hook_source_branch=None): |
91 | + _override_hook_source_branch=None, tag_selector=None): |
92 | """Mirror the source branch into the target branch. |
93 | |
94 | The source branch is considered to be 'local', having low latency. |
95 | """ |
96 | raise NotImplementedError(self.push) |
97 | |
98 | - def copy_content_into(self, revision_id=None): |
99 | + def copy_content_into(self, revision_id=None, tag_selector=None): |
100 | """Copy the content of source into target |
101 | |
102 | - revision_id: if not None, the revision history in the new branch will |
103 | - be truncated to end with revision_id. |
104 | + :param revision_id: |
105 | + if not None, the revision history in the new branch will |
106 | + be truncated to end with revision_id. |
107 | + :param tag_selector: Optional callback that can decide |
108 | + to copy or not copy tags. |
109 | """ |
110 | raise NotImplementedError(self.copy_content_into) |
111 | |
112 | @@ -2122,7 +2131,7 @@ |
113 | return format._custom_format |
114 | return format |
115 | |
116 | - def copy_content_into(self, revision_id=None): |
117 | + def copy_content_into(self, revision_id=None, tag_selector=None): |
118 | """Copy the content of source into target |
119 | |
120 | revision_id: if not None, the revision history in the new branch will |
121 | @@ -2139,7 +2148,7 @@ |
122 | if parent: |
123 | self.target.set_parent(parent) |
124 | if self.source._push_should_merge_tags(): |
125 | - self.source.tags.merge_to(self.target.tags) |
126 | + self.source.tags.merge_to(self.target.tags, selector=tag_selector) |
127 | |
128 | def fetch(self, stop_revision=None, limit=None, lossy=False): |
129 | if self.target.base == self.source.base: |
130 | @@ -2200,7 +2209,8 @@ |
131 | |
132 | def pull(self, overwrite=False, stop_revision=None, |
133 | possible_transports=None, run_hooks=True, |
134 | - _override_hook_target=None, local=False): |
135 | + _override_hook_target=None, local=False, |
136 | + tag_selector=None): |
137 | """Pull from source into self, updating my master if any. |
138 | |
139 | :param run_hooks: Private parameter - if false, this branch |
140 | @@ -2231,15 +2241,17 @@ |
141 | if master_branch: |
142 | # pull from source into master. |
143 | master_branch.pull( |
144 | - self.source, overwrite, stop_revision, run_hooks=False) |
145 | + self.source, overwrite, stop_revision, run_hooks=False, |
146 | + tag_selector=tag_selector) |
147 | return self._pull( |
148 | overwrite, stop_revision, _hook_master=master_branch, |
149 | run_hooks=run_hooks, |
150 | _override_hook_target=_override_hook_target, |
151 | - merge_tags_to_master=not source_is_master) |
152 | + merge_tags_to_master=not source_is_master, |
153 | + tag_selector=tag_selector) |
154 | |
155 | def push(self, overwrite=False, stop_revision=None, lossy=False, |
156 | - _override_hook_source_branch=None): |
157 | + _override_hook_source_branch=None, tag_selector=None): |
158 | """See InterBranch.push. |
159 | |
160 | This is the basic concrete implementation of push() |
161 | @@ -2271,18 +2283,21 @@ |
162 | with master_branch.lock_write(): |
163 | # push into the master from the source branch. |
164 | master_inter = InterBranch.get(self.source, master_branch) |
165 | - master_inter._basic_push(overwrite, stop_revision) |
166 | + master_inter._basic_push( |
167 | + overwrite, stop_revision, tag_selector=tag_selector) |
168 | # and push into the target branch from the source. Note |
169 | # that we push from the source branch again, because it's |
170 | # considered the highest bandwidth repository. |
171 | - result = self._basic_push(overwrite, stop_revision) |
172 | + result = self._basic_push( |
173 | + overwrite, stop_revision, tag_selector=tag_selector) |
174 | result.master_branch = master_branch |
175 | result.local_branch = self.target |
176 | _run_hooks() |
177 | else: |
178 | master_branch = None |
179 | # no master branch |
180 | - result = self._basic_push(overwrite, stop_revision) |
181 | + result = self._basic_push( |
182 | + overwrite, stop_revision, tag_selector=tag_selector) |
183 | # TODO: Why set master_branch and local_branch if there's no |
184 | # binding? Maybe cleaner to just leave them unset? -- mbp |
185 | # 20070504 |
186 | @@ -2291,7 +2306,7 @@ |
187 | _run_hooks() |
188 | return result |
189 | |
190 | - def _basic_push(self, overwrite, stop_revision): |
191 | + def _basic_push(self, overwrite, stop_revision, tag_selector=None): |
192 | """Basic implementation of push without bound branches or hooks. |
193 | |
194 | Must be called with source read locked and target write locked. |
195 | @@ -2310,7 +2325,7 @@ |
196 | if self.source._push_should_merge_tags(): |
197 | result.tag_updates, result.tag_conflicts = ( |
198 | self.source.tags.merge_to( |
199 | - self.target.tags, "tags" in overwrite)) |
200 | + self.target.tags, "tags" in overwrite, selector=tag_selector)) |
201 | self.update_references() |
202 | result.new_revno, result.new_revid = self.target.last_revision_info() |
203 | return result |
204 | @@ -2318,7 +2333,7 @@ |
205 | def _pull(self, overwrite=False, stop_revision=None, |
206 | possible_transports=None, _hook_master=None, run_hooks=True, |
207 | _override_hook_target=None, local=False, |
208 | - merge_tags_to_master=True): |
209 | + merge_tags_to_master=True, tag_selector=None): |
210 | """See Branch.pull. |
211 | |
212 | This function is the core worker, used by GenericInterBranch.pull to |
213 | @@ -2362,7 +2377,8 @@ |
214 | result.tag_updates, result.tag_conflicts = ( |
215 | self.source.tags.merge_to( |
216 | self.target.tags, "tags" in overwrite, |
217 | - ignore_master=not merge_tags_to_master)) |
218 | + ignore_master=not merge_tags_to_master, |
219 | + selector=tag_selector)) |
220 | self.update_references() |
221 | result.new_revno, result.new_revid = ( |
222 | self.target.last_revision_info()) |
223 | |
224 | === modified file 'breezy/bzr/branch.py' |
225 | --- breezy/bzr/branch.py 2020-02-18 01:57:45 +0000 |
226 | +++ breezy/bzr/branch.py 2020-02-21 04:09:29 +0000 |
227 | @@ -27,6 +27,8 @@ |
228 | lockdir, |
229 | rio, |
230 | shelf, |
231 | + ) |
232 | +from breezy.bzr import ( |
233 | tag as _mod_tag, |
234 | ) |
235 | """) |
236 | @@ -1022,7 +1024,7 @@ |
237 | def _make_reference_clone_function(format, a_branch): |
238 | """Create a clone() routine for a branch dynamically.""" |
239 | def clone(to_bzrdir, revision_id=None, |
240 | - repository_policy=None): |
241 | + repository_policy=None, tag_selector=None): |
242 | """See Branch.clone().""" |
243 | return format.initialize(to_bzrdir, target_branch=a_branch) |
244 | # cannot obey revision_id limits when cloning a reference ... |
245 | |
246 | === modified file 'breezy/bzr/bzrdir.py' |
247 | --- breezy/bzr/bzrdir.py 2020-02-18 01:57:45 +0000 |
248 | +++ breezy/bzr/bzrdir.py 2020-02-21 04:09:29 +0000 |
249 | @@ -141,7 +141,8 @@ |
250 | |
251 | def clone_on_transport(self, transport, revision_id=None, |
252 | force_new_repo=False, preserve_stacking=False, stacked_on=None, |
253 | - create_prefix=False, use_existing_dir=True, no_tree=False): |
254 | + create_prefix=False, use_existing_dir=True, no_tree=False, |
255 | + tag_selector=None): |
256 | """Clone this bzrdir and its contents to transport verbatim. |
257 | |
258 | :param transport: The transport for the location to produce the clone |
259 | @@ -233,7 +234,8 @@ |
260 | if local_branch is not None: |
261 | local_branch.clone( |
262 | result, revision_id=revision_id, |
263 | - repository_policy=repository_policy) |
264 | + repository_policy=repository_policy, |
265 | + tag_selector=tag_selector) |
266 | try: |
267 | # Cheaper to check if the target is not local, than to try making |
268 | # the tree and fail. |
269 | |
270 | === modified file 'breezy/bzr/knitpack_repo.py' |
271 | --- breezy/bzr/knitpack_repo.py 2020-02-18 01:57:45 +0000 |
272 | +++ breezy/bzr/knitpack_repo.py 2020-02-21 04:09:29 +0000 |
273 | @@ -264,8 +264,6 @@ |
274 | class RepositoryFormatKnitPack5(RepositoryFormatPack): |
275 | """Repository that supports external references to allow stacking. |
276 | |
277 | - New in release 1.6. |
278 | - |
279 | Supports external lookups, which results in non-truncated ghosts after |
280 | reconcile compared to pack-0.92 formats. |
281 | """ |
282 | @@ -303,8 +301,6 @@ |
283 | class RepositoryFormatKnitPack5RichRoot(RepositoryFormatPack): |
284 | """A repository with rich roots and stacking. |
285 | |
286 | - New in release 1.6.1. |
287 | - |
288 | Supports stacking on other repositories, allowing data to be accessed |
289 | without being stored locally. |
290 | """ |
291 | @@ -344,8 +340,6 @@ |
292 | class RepositoryFormatKnitPack5RichRootBroken(RepositoryFormatPack): |
293 | """A repository with rich roots and external references. |
294 | |
295 | - New in release 1.6. |
296 | - |
297 | Supports external lookups, which results in non-truncated ghosts after |
298 | reconcile compared to pack-0.92 formats. |
299 | |
300 | |
301 | === modified file 'breezy/bzr/remote.py' |
302 | --- breezy/bzr/remote.py 2020-02-18 01:57:45 +0000 |
303 | +++ breezy/bzr/remote.py 2020-02-21 04:09:29 +0000 |
304 | @@ -4019,12 +4019,12 @@ |
305 | source, overwrite=overwrite, stop_revision=stop_revision, |
306 | _override_hook_target=self, **kwargs) |
307 | |
308 | - def push(self, target, overwrite=False, stop_revision=None, lossy=False): |
309 | + def push(self, target, overwrite=False, stop_revision=None, lossy=False, tag_selector=None): |
310 | with self.lock_read(): |
311 | self._ensure_real() |
312 | return self._real_branch.push( |
313 | target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy, |
314 | - _override_hook_source_branch=self) |
315 | + _override_hook_source_branch=self, tag_selector=tag_selector) |
316 | |
317 | def peek_lock_mode(self): |
318 | return self._lock_mode |
319 | |
320 | === added file 'breezy/bzr/tag.py' |
321 | --- breezy/bzr/tag.py 1970-01-01 00:00:00 +0000 |
322 | +++ breezy/bzr/tag.py 2020-02-21 04:09:29 +0000 |
323 | @@ -0,0 +1,113 @@ |
324 | +# Copyright (C) 2005-2012 Canonical Ltd |
325 | +# Copyright (C) 2020 Breezy Developers |
326 | +# |
327 | +# This program is free software; you can redistribute it and/or modify |
328 | +# it under the terms of the GNU General Public License as published by |
329 | +# the Free Software Foundation; either version 2 of the License, or |
330 | +# (at your option) any later version. |
331 | +# |
332 | +# This program is distributed in the hope that it will be useful, |
333 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
334 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
335 | +# GNU General Public License for more details. |
336 | +# |
337 | +# You should have received a copy of the GNU General Public License |
338 | +# along with this program; if not, write to the Free Software |
339 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
340 | + |
341 | +from __future__ import absolute_import |
342 | + |
343 | +from ..tag import Tags |
344 | + |
345 | +from .. import ( |
346 | + bencode, |
347 | + errors, |
348 | + trace, |
349 | + ) |
350 | + |
351 | + |
352 | +class BasicTags(Tags): |
353 | + """Tag storage in an unversioned branch control file. |
354 | + """ |
355 | + |
356 | + def set_tag(self, tag_name, tag_target): |
357 | + """Add a tag definition to the branch. |
358 | + |
359 | + Behaviour if the tag is already present is not defined (yet). |
360 | + """ |
361 | + # all done with a write lock held, so this looks atomic |
362 | + with self.branch.lock_write(): |
363 | + master = self.branch.get_master_branch() |
364 | + if master is not None: |
365 | + master.tags.set_tag(tag_name, tag_target) |
366 | + td = self.get_tag_dict() |
367 | + td[tag_name] = tag_target |
368 | + self._set_tag_dict(td) |
369 | + |
370 | + def lookup_tag(self, tag_name): |
371 | + """Return the referent string of a tag""" |
372 | + td = self.get_tag_dict() |
373 | + try: |
374 | + return td[tag_name] |
375 | + except KeyError: |
376 | + raise errors.NoSuchTag(tag_name) |
377 | + |
378 | + def get_tag_dict(self): |
379 | + with self.branch.lock_read(): |
380 | + try: |
381 | + tag_content = self.branch._get_tags_bytes() |
382 | + except errors.NoSuchFile: |
383 | + # ugly, but only abentley should see this :) |
384 | + trace.warning('No branch/tags file in %s. ' |
385 | + 'This branch was probably created by bzr 0.15pre. ' |
386 | + 'Create an empty file to silence this message.' |
387 | + % (self.branch, )) |
388 | + return {} |
389 | + return self._deserialize_tag_dict(tag_content) |
390 | + |
391 | + def delete_tag(self, tag_name): |
392 | + """Delete a tag definition. |
393 | + """ |
394 | + with self.branch.lock_write(): |
395 | + d = self.get_tag_dict() |
396 | + try: |
397 | + del d[tag_name] |
398 | + except KeyError: |
399 | + raise errors.NoSuchTag(tag_name) |
400 | + master = self.branch.get_master_branch() |
401 | + if master is not None: |
402 | + try: |
403 | + master.tags.delete_tag(tag_name) |
404 | + except errors.NoSuchTag: |
405 | + pass |
406 | + self._set_tag_dict(d) |
407 | + |
408 | + def _set_tag_dict(self, new_dict): |
409 | + """Replace all tag definitions |
410 | + |
411 | + WARNING: Calling this on an unlocked branch will lock it, and will |
412 | + replace the tags without warning on conflicts. |
413 | + |
414 | + :param new_dict: Dictionary from tag name to target. |
415 | + """ |
416 | + return self.branch._set_tags_bytes(self._serialize_tag_dict(new_dict)) |
417 | + |
418 | + def _serialize_tag_dict(self, tag_dict): |
419 | + td = dict((k.encode('utf-8'), v) |
420 | + for k, v in tag_dict.items()) |
421 | + return bencode.bencode(td) |
422 | + |
423 | + def _deserialize_tag_dict(self, tag_content): |
424 | + """Convert the tag file into a dictionary of tags""" |
425 | + # was a special case to make initialization easy, an empty definition |
426 | + # is an empty dictionary |
427 | + if tag_content == b'': |
428 | + return {} |
429 | + try: |
430 | + r = {} |
431 | + for k, v in bencode.bdecode(tag_content).items(): |
432 | + r[k.decode('utf-8')] = v |
433 | + return r |
434 | + except ValueError as e: |
435 | + raise ValueError("failed to deserialize tag dictionary %r: %s" |
436 | + % (tag_content, e)) |
437 | |
438 | === modified file 'breezy/controldir.py' |
439 | --- breezy/controldir.py 2020-02-18 01:57:45 +0000 |
440 | +++ breezy/controldir.py 2020-02-21 04:09:29 +0000 |
441 | @@ -398,7 +398,8 @@ |
442 | raise NotImplementedError(self.sprout) |
443 | |
444 | def push_branch(self, source, revision_id=None, overwrite=False, |
445 | - remember=False, create_prefix=False, lossy=False): |
446 | + remember=False, create_prefix=False, lossy=False, |
447 | + tag_selector=None): |
448 | """Push the source branch into this ControlDir.""" |
449 | br_to = None |
450 | # If we can open a branch, use its direct repository, otherwise see |
451 | @@ -422,7 +423,9 @@ |
452 | # revision |
453 | revision_id = source.last_revision() |
454 | repository_to.fetch(source.repository, revision_id=revision_id) |
455 | - br_to = source.sprout(self, revision_id=revision_id, lossy=lossy) |
456 | + br_to = source.sprout( |
457 | + self, revision_id=revision_id, lossy=lossy, |
458 | + tag_selector=tag_selector) |
459 | if source.get_push_location() is None or remember: |
460 | # FIXME: Should be done only if we succeed ? -- vila 2012-01-18 |
461 | source.set_push_location(br_to.base) |
462 | @@ -442,17 +445,19 @@ |
463 | tree_to = self.open_workingtree() |
464 | except errors.NotLocalUrl: |
465 | push_result.branch_push_result = source.push( |
466 | - br_to, overwrite, stop_revision=revision_id, lossy=lossy) |
467 | + br_to, overwrite, stop_revision=revision_id, lossy=lossy, |
468 | + tag_selector=tag_selector) |
469 | push_result.workingtree_updated = False |
470 | except errors.NoWorkingTree: |
471 | push_result.branch_push_result = source.push( |
472 | - br_to, overwrite, stop_revision=revision_id, lossy=lossy) |
473 | + br_to, overwrite, stop_revision=revision_id, lossy=lossy, |
474 | + tag_selector=tag_selector) |
475 | push_result.workingtree_updated = None # Not applicable |
476 | else: |
477 | with tree_to.lock_write(): |
478 | push_result.branch_push_result = source.push( |
479 | tree_to.branch, overwrite, stop_revision=revision_id, |
480 | - lossy=lossy) |
481 | + lossy=lossy, tag_selector=tag_selector) |
482 | tree_to.update() |
483 | push_result.workingtree_updated = True |
484 | push_result.old_revno = push_result.branch_push_result.old_revno |
485 | @@ -491,7 +496,7 @@ |
486 | raise NotImplementedError(self.check_conversion_target) |
487 | |
488 | def clone(self, url, revision_id=None, force_new_repo=False, |
489 | - preserve_stacking=False): |
490 | + preserve_stacking=False, tag_selector=None): |
491 | """Clone this controldir and its contents to url verbatim. |
492 | |
493 | :param url: The url create the clone at. If url's last component does |
494 | @@ -507,11 +512,13 @@ |
495 | return self.clone_on_transport(_mod_transport.get_transport(url), |
496 | revision_id=revision_id, |
497 | force_new_repo=force_new_repo, |
498 | - preserve_stacking=preserve_stacking) |
499 | + preserve_stacking=preserve_stacking, |
500 | + tag_selector=tag_selector) |
501 | |
502 | def clone_on_transport(self, transport, revision_id=None, |
503 | force_new_repo=False, preserve_stacking=False, stacked_on=None, |
504 | - create_prefix=False, use_existing_dir=True, no_tree=False): |
505 | + create_prefix=False, use_existing_dir=True, no_tree=False, |
506 | + tag_selector=None): |
507 | """Clone this controldir and its contents to transport verbatim. |
508 | |
509 | :param transport: The transport for the location to produce the clone |
510 | |
511 | === modified file 'breezy/git/branch.py' |
512 | --- breezy/git/branch.py 2020-02-18 01:57:45 +0000 |
513 | +++ breezy/git/branch.py 2020-02-21 04:09:29 +0000 |
514 | @@ -41,7 +41,6 @@ |
515 | lock, |
516 | repository as _mod_repository, |
517 | revision, |
518 | - tag, |
519 | trace, |
520 | transport, |
521 | urlutils, |
522 | @@ -50,6 +49,10 @@ |
523 | from ..revision import ( |
524 | NULL_REVISION, |
525 | ) |
526 | +from ..tag import ( |
527 | + Tags, |
528 | + InterTags, |
529 | + ) |
530 | from ..trace import ( |
531 | is_quiet, |
532 | mutter, |
533 | @@ -111,44 +114,75 @@ |
534 | return self._lookup_revno(self.new_revid) |
535 | |
536 | |
537 | -class GitTags(tag.BasicTags): |
538 | - """Ref-based tag dictionary.""" |
539 | - |
540 | - def __init__(self, branch): |
541 | - self.branch = branch |
542 | - self.repository = branch.repository |
543 | - |
544 | - def _merge_to_remote_git(self, target_repo, source_tag_refs, |
545 | - overwrite=False): |
546 | +class InterTagsFromGitToRemoteGit(InterTags): |
547 | + |
548 | + @classmethod |
549 | + def is_compatible(klass, source, target): |
550 | + if not isinstance(source, GitTags): |
551 | + return False |
552 | + if not isinstance(target, GitTags): |
553 | + return False |
554 | + if getattr(target.branch.repository, "_git", None) is not None: |
555 | + return False |
556 | + return True |
557 | + |
558 | + def merge(self, overwrite=False, ignore_master=False, selector=None): |
559 | + if self.source.branch.repository.has_same_location(self.target.branch.repository): |
560 | + return {}, [] |
561 | updates = {} |
562 | conflicts = [] |
563 | + source_tag_refs = self.source.branch.get_tag_refs() |
564 | |
565 | def get_changed_refs(old_refs): |
566 | ret = dict(old_refs) |
567 | for ref_name, tag_name, peeled, unpeeled in ( |
568 | source_tag_refs.iteritems()): |
569 | + if selector and not selector(tag_name): |
570 | + continue |
571 | if old_refs.get(ref_name) == unpeeled: |
572 | pass |
573 | elif overwrite or ref_name not in old_refs: |
574 | ret[ref_name] = unpeeled |
575 | - updates[tag_name] = target_repo.lookup_foreign_revision_id( |
576 | + updates[tag_name] = self.target.branch.repository.lookup_foreign_revision_id( |
577 | peeled) |
578 | + self.target.branch._tag_refs = None |
579 | else: |
580 | conflicts.append( |
581 | (tag_name, |
582 | self.repository.lookup_foreign_revision_id(peeled), |
583 | - target_repo.lookup_foreign_revision_id( |
584 | + self.target.branch.repository.lookup_foreign_revision_id( |
585 | old_refs[ref_name]))) |
586 | return ret |
587 | - target_repo.controldir.send_pack( |
588 | + self.target.branch.repository.controldir.send_pack( |
589 | get_changed_refs, lambda have, want: []) |
590 | - return updates, conflicts |
591 | - |
592 | - def _merge_to_local_git(self, target_repo, source_tag_refs, |
593 | - overwrite=False): |
594 | + return updates, set(conflicts) |
595 | + |
596 | + |
597 | +class InterTagsFromGitToLocalGit(InterTags): |
598 | + |
599 | + @classmethod |
600 | + def is_compatible(klass, source, target): |
601 | + if not isinstance(source, GitTags): |
602 | + return False |
603 | + if not isinstance(target, GitTags): |
604 | + return False |
605 | + if getattr(target.branch.repository, "_git", None) is None: |
606 | + return False |
607 | + return True |
608 | + |
609 | + def merge(self, overwrite=False, ignore_master=False, selector=None): |
610 | + if self.source.branch.repository.has_same_location(self.target.branch.repository): |
611 | + return {}, [] |
612 | + |
613 | conflicts = [] |
614 | updates = {} |
615 | + source_tag_refs = self.source.branch.get_tag_refs() |
616 | + |
617 | + target_repo = self.target.branch.repository |
618 | + |
619 | for ref_name, tag_name, peeled, unpeeled in source_tag_refs: |
620 | + if selector and not selector(tag_name): |
621 | + continue |
622 | if target_repo._git.refs.get(ref_name) == unpeeled: |
623 | pass |
624 | elif overwrite or ref_name not in target_repo._git.refs: |
625 | @@ -164,9 +198,10 @@ |
626 | tag_name) |
627 | continue |
628 | target_repo._git.refs[ref_name] = unpeeled or peeled |
629 | + self.target.branch._tag_refs = None |
630 | else: |
631 | try: |
632 | - source_revid = self.repository.lookup_foreign_revision_id( |
633 | + source_revid = self.source.branch.repository.lookup_foreign_revision_id( |
634 | peeled) |
635 | target_revid = target_repo.lookup_foreign_revision_id( |
636 | target_repo._git.refs[ref_name]) |
637 | @@ -179,32 +214,54 @@ |
638 | tag_name) |
639 | continue |
640 | conflicts.append((tag_name, source_revid, target_revid)) |
641 | - return updates, conflicts |
642 | - |
643 | - def _merge_to_git(self, to_tags, source_tag_refs, overwrite=False): |
644 | - target_repo = to_tags.repository |
645 | - if self.repository.has_same_location(target_repo): |
646 | - return {}, [] |
647 | - try: |
648 | - if getattr(target_repo, "_git", None): |
649 | - return self._merge_to_local_git( |
650 | - target_repo, source_tag_refs, overwrite) |
651 | - else: |
652 | - return self._merge_to_remote_git( |
653 | - target_repo, source_tag_refs, overwrite) |
654 | - finally: |
655 | - to_tags.branch._tag_refs = None |
656 | - |
657 | - def _merge_to_non_git(self, to_tags, source_tag_refs, overwrite=False): |
658 | + return updates, set(conflicts) |
659 | + |
660 | + |
661 | +class InterTagsFromGitToNonGit(InterTags): |
662 | + |
663 | + @classmethod |
664 | + def is_compatible(klass, source, target): |
665 | + if not isinstance(source, GitTags): |
666 | + return False |
667 | + if isinstance(target, GitTags): |
668 | + return False |
669 | + return True |
670 | + |
671 | + def merge(self, overwrite=False, ignore_master=False, selector=None): |
672 | + """See Tags.merge_to.""" |
673 | + source_tag_refs = self.source.branch.get_tag_refs() |
674 | + if ignore_master: |
675 | + master = None |
676 | + else: |
677 | + master = self.target.branch.get_master_branch() |
678 | + with contextlib.ExitStack() as es: |
679 | + if master is not None: |
680 | + es.enter_context(master.lock_write()) |
681 | + updates, conflicts = self._merge_to( |
682 | + self.target, source_tag_refs, overwrite=overwrite, |
683 | + selector=selector) |
684 | + if master is not None: |
685 | + extra_updates, extra_conflicts = self._merge_to( |
686 | + master.tags, overwrite=overwrite, |
687 | + source_tag_refs=source_tag_refs, |
688 | + ignore_master=ignore_master, selector=selector) |
689 | + updates.update(extra_updates) |
690 | + conflicts.update(extra_conflicts) |
691 | + return updates, conflicts |
692 | + |
693 | + def _merge_to(self, to_tags, source_tag_refs, overwrite=False, |
694 | + selector=None): |
695 | unpeeled_map = defaultdict(set) |
696 | conflicts = [] |
697 | updates = {} |
698 | result = dict(to_tags.get_tag_dict()) |
699 | for ref_name, tag_name, peeled, unpeeled in source_tag_refs: |
700 | + if selector and not selector(tag_name): |
701 | + continue |
702 | if unpeeled is not None: |
703 | unpeeled_map[peeled].add(unpeeled) |
704 | try: |
705 | - bzr_revid = self.branch.lookup_foreign_revision_id(peeled) |
706 | + bzr_revid = self.source.branch.lookup_foreign_revision_id(peeled) |
707 | except NotCommitError: |
708 | continue |
709 | if result.get(tag_name) == bzr_revid: |
710 | @@ -219,36 +276,20 @@ |
711 | map_file = UnpeelMap.from_repository(to_tags.branch.repository) |
712 | map_file.update(unpeeled_map) |
713 | map_file.save_in_repository(to_tags.branch.repository) |
714 | - return updates, conflicts |
715 | - |
716 | - def merge_to(self, to_tags, overwrite=False, ignore_master=False, |
717 | - source_tag_refs=None): |
718 | - """See Tags.merge_to.""" |
719 | - if source_tag_refs is None: |
720 | - source_tag_refs = self.branch.get_tag_refs() |
721 | - if self == to_tags: |
722 | - return {}, [] |
723 | - if isinstance(to_tags, GitTags): |
724 | - return self._merge_to_git(to_tags, source_tag_refs, |
725 | - overwrite=overwrite) |
726 | - else: |
727 | - if ignore_master: |
728 | - master = None |
729 | - else: |
730 | - master = to_tags.branch.get_master_branch() |
731 | - with contextlib.ExitStack() as es: |
732 | - if master is not None: |
733 | - es.enter_context(master.lock_write()) |
734 | - updates, conflicts = self._merge_to_non_git( |
735 | - to_tags, source_tag_refs, overwrite=overwrite) |
736 | - if master is not None: |
737 | - extra_updates, extra_conflicts = self.merge_to( |
738 | - master.tags, overwrite=overwrite, |
739 | - source_tag_refs=source_tag_refs, |
740 | - ignore_master=ignore_master) |
741 | - updates.update(extra_updates) |
742 | - conflicts += extra_conflicts |
743 | - return updates, conflicts |
744 | + return updates, set(conflicts) |
745 | + |
746 | + |
747 | +InterTags.register_optimiser(InterTagsFromGitToRemoteGit) |
748 | +InterTags.register_optimiser(InterTagsFromGitToLocalGit) |
749 | +InterTags.register_optimiser(InterTagsFromGitToNonGit) |
750 | + |
751 | + |
752 | +class GitTags(Tags): |
753 | + """Ref-based tag dictionary.""" |
754 | + |
755 | + def __init__(self, branch): |
756 | + self.branch = branch |
757 | + self.repository = branch.repository |
758 | |
759 | def get_tag_dict(self): |
760 | ret = {} |
761 | @@ -262,6 +303,15 @@ |
762 | ret[tag_name] = bzr_revid |
763 | return ret |
764 | |
765 | + def lookup_tag(self, tag_name): |
766 | + """Return the referent string of a tag""" |
767 | + # TODO(jelmer): Replace with something more efficient for local tags. |
768 | + td = self.get_tag_dict() |
769 | + try: |
770 | + return td[tag_name] |
771 | + except KeyError: |
772 | + raise errors.NoSuchTag(tag_name) |
773 | + |
774 | |
775 | class LocalGitTagDict(GitTags): |
776 | """Dictionary with tags in a local repository.""" |
777 | @@ -612,9 +662,10 @@ |
778 | return revision.NULL_REVISION |
779 | return self.lookup_foreign_revision_id(self.head) |
780 | |
781 | - def _basic_push(self, target, overwrite=False, stop_revision=None): |
782 | + def _basic_push(self, target, overwrite=False, stop_revision=None, |
783 | + tag_selector=None): |
784 | return branch.InterBranch.get(self, target)._basic_push( |
785 | - overwrite, stop_revision) |
786 | + overwrite, stop_revision, tag_selector=tag_selector) |
787 | |
788 | def lookup_foreign_revision_id(self, foreign_revid): |
789 | try: |
790 | @@ -934,7 +985,7 @@ |
791 | stop_revision, fetch_tags=fetch_tags, limit=limit, lossy=lossy) |
792 | return _mod_repository.FetchResult() |
793 | |
794 | - def fetch_objects(self, stop_revision, fetch_tags, limit=None, lossy=False): |
795 | + def fetch_objects(self, stop_revision, fetch_tags, limit=None, lossy=False, tag_selector=None): |
796 | interrepo = self._get_interrepo(self.source, self.target) |
797 | if fetch_tags is None: |
798 | c = self.source.get_config_stack() |
799 | @@ -952,7 +1003,7 @@ |
800 | else: |
801 | self._last_revid = stop_revision |
802 | real = interrepo.get_determine_wants_revids( |
803 | - [self._last_revid], include_tags=fetch_tags) |
804 | + [self._last_revid], include_tags=fetch_tags, tag_selector=tag_selector) |
805 | return real(heads) |
806 | pack_hint, head, refs = interrepo.fetch_objects( |
807 | determine_wants, self.source.mapping, limit=limit, |
808 | @@ -962,8 +1013,8 @@ |
809 | self.target.repository.pack(hint=pack_hint) |
810 | return head, refs |
811 | |
812 | - def _update_revisions(self, stop_revision=None, overwrite=False): |
813 | - head, refs = self.fetch_objects(stop_revision, fetch_tags=None) |
814 | + def _update_revisions(self, stop_revision=None, overwrite=False, tag_selector=None): |
815 | + head, refs = self.fetch_objects(stop_revision, fetch_tags=None, tag_selector=tag_selector) |
816 | if overwrite: |
817 | prev_last_revid = None |
818 | else: |
819 | @@ -988,7 +1039,7 @@ |
820 | pass |
821 | |
822 | def _basic_pull(self, stop_revision, overwrite, run_hooks, |
823 | - _override_hook_target, _hook_master): |
824 | + _override_hook_target, _hook_master, tag_selector=None): |
825 | if overwrite is True: |
826 | overwrite = set(["history", "tags"]) |
827 | elif not overwrite: |
828 | @@ -1005,7 +1056,8 @@ |
829 | (result.old_revno, result.old_revid) = \ |
830 | self.target.last_revision_info() |
831 | result.new_git_head, remote_refs = self._update_revisions( |
832 | - stop_revision, overwrite=("history" in overwrite)) |
833 | + stop_revision, overwrite=("history" in overwrite), |
834 | + tag_selector=tag_selector) |
835 | tags_ret = self.source.tags.merge_to( |
836 | self.target.tags, ("tags" in overwrite), ignore_master=True) |
837 | if isinstance(tags_ret, tuple): |
838 | @@ -1028,7 +1080,7 @@ |
839 | |
840 | def pull(self, overwrite=False, stop_revision=None, |
841 | possible_transports=None, _hook_master=None, run_hooks=True, |
842 | - _override_hook_target=None, local=False): |
843 | + _override_hook_target=None, local=False, tag_selector=None): |
844 | """See Branch.pull. |
845 | |
846 | :param _hook_master: Private parameter - set the branch to |
847 | @@ -1066,9 +1118,10 @@ |
848 | master_branch = None |
849 | return self._basic_pull(stop_revision, overwrite, run_hooks, |
850 | _override_hook_target, |
851 | - _hook_master=master_branch) |
852 | + _hook_master=master_branch, |
853 | + tag_selector=tag_selector) |
854 | |
855 | - def _basic_push(self, overwrite, stop_revision): |
856 | + def _basic_push(self, overwrite, stop_revision, tag_selector=None): |
857 | if overwrite is True: |
858 | overwrite = set(["history", "tags"]) |
859 | elif not overwrite: |
860 | @@ -1078,9 +1131,11 @@ |
861 | result.target_branch = self.target |
862 | result.old_revno, result.old_revid = self.target.last_revision_info() |
863 | result.new_git_head, remote_refs = self._update_revisions( |
864 | - stop_revision, overwrite=("history" in overwrite)) |
865 | + stop_revision, overwrite=("history" in overwrite), |
866 | + tag_selector=tag_selector) |
867 | tags_ret = self.source.tags.merge_to( |
868 | - self.target.tags, "tags" in overwrite, ignore_master=True) |
869 | + self.target.tags, "tags" in overwrite, ignore_master=True, |
870 | + selector=tag_selector) |
871 | (result.tag_updates, result.tag_conflicts) = tags_ret |
872 | result.new_revno, result.new_revid = self.target.last_revision_info() |
873 | self.update_references(revid=result.new_revid) |
874 | @@ -1109,7 +1164,7 @@ |
875 | return (isinstance(source, LocalGitBranch) and |
876 | isinstance(target, RemoteGitBranch)) |
877 | |
878 | - def _basic_push(self, overwrite, stop_revision): |
879 | + def _basic_push(self, overwrite, stop_revision, tag_selector=None): |
880 | result = GitBranchPushResult() |
881 | result.source_branch = self.source |
882 | result.target_branch = self.target |
883 | @@ -1134,6 +1189,8 @@ |
884 | result.new_revid = stop_revision |
885 | for name, sha in ( |
886 | self.source.repository._git.refs.as_dict(b"refs/tags").items()): |
887 | + if tag_selector and not tag_selector(name): |
888 | + continue |
889 | if sha not in self.source.repository._git: |
890 | trace.mutter('Ignoring missing SHA: %s', sha) |
891 | continue |
892 | @@ -1173,7 +1230,7 @@ |
893 | interrepo.fetch_objects(determine_wants, limit=limit, lossy=lossy) |
894 | return _mod_repository.FetchResult() |
895 | |
896 | - def _basic_push(self, overwrite=False, stop_revision=None): |
897 | + def _basic_push(self, overwrite=False, stop_revision=None, tag_selector=None): |
898 | if overwrite is True: |
899 | overwrite = set(["history", "tags"]) |
900 | elif not overwrite: |
901 | @@ -1189,8 +1246,8 @@ |
902 | other_branch=self.source) |
903 | tags_ret = self.source.tags.merge_to( |
904 | self.target.tags, |
905 | - source_tag_refs=remote_refs_dict_to_tag_refs(refs), |
906 | - overwrite=("tags" in overwrite)) |
907 | + overwrite=("tags" in overwrite), |
908 | + selector=tag_selector) |
909 | if isinstance(tags_ret, tuple): |
910 | (result.tag_updates, result.tag_conflicts) = tags_ret |
911 | else: |
912 | @@ -1218,7 +1275,8 @@ |
913 | return result.refs, stop_revision |
914 | |
915 | def pull(self, stop_revision=None, overwrite=False, |
916 | - possible_transports=None, run_hooks=True, local=False): |
917 | + possible_transports=None, run_hooks=True, local=False, |
918 | + tag_selector=None): |
919 | # This type of branch can't be bound. |
920 | if local: |
921 | raise errors.LocalRequiresBoundBranch() |
922 | @@ -1239,7 +1297,7 @@ |
923 | other_branch=self.source) |
924 | tags_ret = self.source.tags.merge_to( |
925 | self.target.tags, overwrite=("tags" in overwrite), |
926 | - source_tag_refs=remote_refs_dict_to_tag_refs(refs)) |
927 | + selector=tag_selector) |
928 | if isinstance(tags_ret, tuple): |
929 | (result.tag_updates, result.tag_conflicts) = tags_ret |
930 | else: |
931 | @@ -1307,7 +1365,7 @@ |
932 | refs[ref] = (None, revid) |
933 | return refs, main_ref, (stop_revno, stop_revision) |
934 | |
935 | - def _update_refs(self, result, old_refs, new_refs, overwrite): |
936 | + def _update_refs(self, result, old_refs, new_refs, overwrite, tag_selector): |
937 | mutter("updating refs. old refs: %r, new refs: %r", |
938 | old_refs, new_refs) |
939 | result.tag_updates = {} |
940 | @@ -1346,6 +1404,8 @@ |
941 | except ValueError: |
942 | pass |
943 | else: |
944 | + if tag_selector and not tag_selector(tag_name): |
945 | + continue |
946 | result.tag_updates[tag_name] = revid |
947 | ret[ref] = (git_sha, revid) |
948 | else: |
949 | @@ -1381,7 +1441,8 @@ |
950 | for (old_revid, (new_sha, new_revid)) in revidmap.items()}) |
951 | |
952 | def pull(self, overwrite=False, stop_revision=None, local=False, |
953 | - possible_transports=None, run_hooks=True, _stop_revno=None): |
954 | + possible_transports=None, run_hooks=True, _stop_revno=None, |
955 | + tag_selector=None): |
956 | result = GitBranchPullResult() |
957 | result.source_branch = self.source |
958 | result.target_branch = self.target |
959 | @@ -1390,7 +1451,7 @@ |
960 | stop_revision, stop_revno=_stop_revno) |
961 | |
962 | def update_refs(old_refs): |
963 | - return self._update_refs(result, old_refs, new_refs, overwrite) |
964 | + return self._update_refs(result, old_refs, new_refs, overwrite, tag_selector) |
965 | try: |
966 | result.revidmap, old_refs, new_refs = ( |
967 | self.interrepo.fetch_refs(update_refs, lossy=False)) |
968 | @@ -1410,7 +1471,8 @@ |
969 | return result |
970 | |
971 | def push(self, overwrite=False, stop_revision=None, lossy=False, |
972 | - _override_hook_source_branch=None, _stop_revno=None): |
973 | + _override_hook_source_branch=None, _stop_revno=None, |
974 | + tag_selector=None): |
975 | result = GitBranchPushResult() |
976 | result.source_branch = self.source |
977 | result.target_branch = self.target |
978 | @@ -1421,7 +1483,7 @@ |
979 | stop_revision, stop_revno=_stop_revno) |
980 | |
981 | def update_refs(old_refs): |
982 | - return self._update_refs(result, old_refs, new_refs, overwrite) |
983 | + return self._update_refs(result, old_refs, new_refs, overwrite, tag_selector) |
984 | try: |
985 | result.revidmap, old_refs, new_refs = ( |
986 | self.interrepo.fetch_refs( |
987 | |
988 | === modified file 'breezy/git/dir.py' |
989 | --- breezy/git/dir.py 2020-02-18 01:57:45 +0000 |
990 | +++ breezy/git/dir.py 2020-02-21 04:09:29 +0000 |
991 | @@ -218,7 +218,8 @@ |
992 | def clone_on_transport(self, transport, revision_id=None, |
993 | force_new_repo=False, preserve_stacking=False, |
994 | stacked_on=None, create_prefix=False, |
995 | - use_existing_dir=True, no_tree=False): |
996 | + use_existing_dir=True, no_tree=False, |
997 | + tag_selector=None): |
998 | """See ControlDir.clone_on_transport.""" |
999 | from ..repository import InterRepository |
1000 | from .mapping import default_mapping |
1001 | @@ -240,7 +241,7 @@ |
1002 | interrepo = InterRepository.get(source_repo, target_repo) |
1003 | if revision_id is not None: |
1004 | determine_wants = interrepo.get_determine_wants_revids( |
1005 | - [revision_id], include_tags=True) |
1006 | + [revision_id], include_tags=True, tag_selector=tag_selector) |
1007 | else: |
1008 | determine_wants = interrepo.determine_wants_all |
1009 | (pack_hint, _, refs) = interrepo.fetch_objects(determine_wants, |
1010 | @@ -312,7 +313,7 @@ |
1011 | |
1012 | def push_branch(self, source, revision_id=None, overwrite=False, |
1013 | remember=False, create_prefix=False, lossy=False, |
1014 | - name=None): |
1015 | + name=None, tag_selector=None): |
1016 | """Push the source branch into this ControlDir.""" |
1017 | push_result = GitPushResult() |
1018 | push_result.workingtree_updated = None |
1019 | @@ -325,7 +326,7 @@ |
1020 | target = self.open_branch(name, nascent_ok=True) |
1021 | push_result.branch_push_result = source.push( |
1022 | target, overwrite=overwrite, stop_revision=revision_id, |
1023 | - lossy=lossy) |
1024 | + lossy=lossy, tag_selector=tag_selector) |
1025 | push_result.new_revid = push_result.branch_push_result.new_revid |
1026 | push_result.old_revid = push_result.branch_push_result.old_revid |
1027 | try: |
1028 | |
1029 | === modified file 'breezy/git/interrepo.py' |
1030 | --- breezy/git/interrepo.py 2020-02-18 01:57:45 +0000 |
1031 | +++ breezy/git/interrepo.py 2020-02-21 04:09:29 +0000 |
1032 | @@ -75,6 +75,7 @@ |
1033 | ) |
1034 | from .refs import ( |
1035 | is_tag, |
1036 | + ref_to_tag_name, |
1037 | ) |
1038 | from .repository import ( |
1039 | GitRepository, |
1040 | @@ -403,7 +404,7 @@ |
1041 | def _target_has_shas(self, shas): |
1042 | raise NotImplementedError(self._target_has_shas) |
1043 | |
1044 | - def get_determine_wants_heads(self, wants, include_tags=False): |
1045 | + def get_determine_wants_heads(self, wants, include_tags=False, tag_selector=None): |
1046 | wants = set(wants) |
1047 | |
1048 | def determine_wants(refs): |
1049 | @@ -416,7 +417,11 @@ |
1050 | for k, sha in refs.items(): |
1051 | if k.endswith(ANNOTATED_TAG_SUFFIX): |
1052 | continue |
1053 | - if not is_tag(k): |
1054 | + try: |
1055 | + tag_name = ref_to_tag_name(k) |
1056 | + except ValueError: |
1057 | + continue |
1058 | + if tag_selector and not tag_selector(tag_name): |
1059 | continue |
1060 | if sha == ZERO_SHA: |
1061 | continue |
1062 | @@ -502,14 +507,15 @@ |
1063 | """ |
1064 | raise NotImplementedError(self.fetch_objects) |
1065 | |
1066 | - def get_determine_wants_revids(self, revids, include_tags=False): |
1067 | + def get_determine_wants_revids(self, revids, include_tags=False, tag_selector=None): |
1068 | wants = set() |
1069 | for revid in set(revids): |
1070 | if self.target.has_revision(revid): |
1071 | continue |
1072 | git_sha, mapping = self.source.lookup_bzr_revision_id(revid) |
1073 | wants.add(git_sha) |
1074 | - return self.get_determine_wants_heads(wants, include_tags=include_tags) |
1075 | + return self.get_determine_wants_heads( |
1076 | + wants, include_tags=include_tags, tag_selector=tag_selector) |
1077 | |
1078 | def fetch(self, revision_id=None, find_ghosts=False, |
1079 | mapping=None, fetch_spec=None, include_tags=False, lossy=False): |
1080 | @@ -682,14 +688,14 @@ |
1081 | result.refs = wants_recorder.remote_refs |
1082 | return result |
1083 | |
1084 | - def get_determine_wants_revids(self, revids, include_tags=False): |
1085 | + def get_determine_wants_revids(self, revids, include_tags=False, tag_selector=None): |
1086 | wants = set() |
1087 | for revid in set(revids): |
1088 | if revid == NULL_REVISION: |
1089 | continue |
1090 | git_sha, mapping = self.source.lookup_bzr_revision_id(revid) |
1091 | wants.add(git_sha) |
1092 | - return self.get_determine_wants_heads(wants, include_tags=include_tags) |
1093 | + return self.get_determine_wants_heads(wants, include_tags=include_tags, tag_selector=tag_selector) |
1094 | |
1095 | def get_determine_wants_branches(self, branches, include_tags=False): |
1096 | def determine_wants(refs): |
1097 | |
1098 | === modified file 'breezy/git/workingtree.py' |
1099 | --- breezy/git/workingtree.py 2020-02-18 01:57:45 +0000 |
1100 | +++ breezy/git/workingtree.py 2020-02-21 04:09:29 +0000 |
1101 | @@ -1210,12 +1210,12 @@ |
1102 | |
1103 | def pull(self, source, overwrite=False, stop_revision=None, |
1104 | change_reporter=None, possible_transports=None, local=False, |
1105 | - show_base=False): |
1106 | + show_base=False, tag_selector=None): |
1107 | with self.lock_write(), source.lock_read(): |
1108 | old_revision = self.branch.last_revision() |
1109 | count = self.branch.pull(source, overwrite, stop_revision, |
1110 | possible_transports=possible_transports, |
1111 | - local=local) |
1112 | + local=local, tag_selector=tag_selector) |
1113 | self._update_git_tree( |
1114 | old_revision=old_revision, |
1115 | new_revision=self.branch.last_revision(), |
1116 | |
1117 | === modified file 'breezy/plugins/propose/github.py' |
1118 | --- breezy/plugins/propose/github.py 2020-02-18 01:57:45 +0000 |
1119 | +++ breezy/plugins/propose/github.py 2020-02-21 04:09:29 +0000 |
1120 | @@ -275,11 +275,12 @@ |
1121 | return json.loads(response.text) |
1122 | raise InvalidHttpResponse(path, response.text) |
1123 | |
1124 | - def _create_pull(self, path, title, head, base, body=None, labels=None, assignee=None): |
1125 | + def _create_pull(self, path, title, head, base, body=None, labels=None, assignee=None, draft=False): |
1126 | data = { |
1127 | 'title': title, |
1128 | 'head': head, |
1129 | 'base': base, |
1130 | + 'draft': draft, |
1131 | } |
1132 | if labels is not None: |
1133 | data['labels'] = labels |
1134 | @@ -373,7 +374,7 @@ |
1135 | |
1136 | def publish_derived(self, local_branch, base_branch, name, project=None, |
1137 | owner=None, revision_id=None, overwrite=False, |
1138 | - allow_lossy=True): |
1139 | + allow_lossy=True, tag_selector=None): |
1140 | base_owner, base_project, base_branch_name = parse_github_branch_url(base_branch) |
1141 | base_repo = self._get_repo(base_owner, base_project) |
1142 | if owner is None: |
1143 | @@ -393,13 +394,14 @@ |
1144 | try: |
1145 | push_result = remote_dir.push_branch( |
1146 | local_branch, revision_id=revision_id, overwrite=overwrite, |
1147 | - name=name) |
1148 | + name=name, tag_selector=tag_selector) |
1149 | except errors.NoRoundtrippingSupport: |
1150 | if not allow_lossy: |
1151 | raise |
1152 | push_result = remote_dir.push_branch( |
1153 | local_branch, revision_id=revision_id, |
1154 | - overwrite=overwrite, name=name, lossy=True) |
1155 | + overwrite=overwrite, name=name, lossy=True, |
1156 | + tag_selector=tag_selector) |
1157 | return push_result.target_branch, github_url_to_bzr_url( |
1158 | remote_repo['html_url'], name) |
1159 | |
1160 | |
1161 | === modified file 'breezy/plugins/propose/gitlabs.py' |
1162 | --- breezy/plugins/propose/gitlabs.py 2020-02-18 01:57:45 +0000 |
1163 | +++ breezy/plugins/propose/gitlabs.py 2020-02-21 04:09:29 +0000 |
1164 | @@ -413,7 +413,7 @@ |
1165 | |
1166 | def publish_derived(self, local_branch, base_branch, name, project=None, |
1167 | owner=None, revision_id=None, overwrite=False, |
1168 | - allow_lossy=True): |
1169 | + allow_lossy=True, tag_selector=None): |
1170 | (host, base_project, base_branch_name) = parse_gitlab_branch_url(base_branch) |
1171 | if owner is None: |
1172 | owner = self._get_logged_in_username() |
1173 | @@ -428,13 +428,13 @@ |
1174 | try: |
1175 | push_result = remote_dir.push_branch( |
1176 | local_branch, revision_id=revision_id, overwrite=overwrite, |
1177 | - name=name) |
1178 | + name=name, tag_selector=tag_selector) |
1179 | except errors.NoRoundtrippingSupport: |
1180 | if not allow_lossy: |
1181 | raise |
1182 | push_result = remote_dir.push_branch( |
1183 | local_branch, revision_id=revision_id, overwrite=overwrite, |
1184 | - name=name, lossy=True) |
1185 | + name=name, lossy=True, tag_selector=tag_selector) |
1186 | public_url = gitlab_url_to_bzr_url( |
1187 | target_project['http_url_to_repo'], name) |
1188 | return push_result.target_branch, public_url |
1189 | |
1190 | === modified file 'breezy/plugins/propose/launchpad.py' |
1191 | --- breezy/plugins/propose/launchpad.py 2020-02-18 01:57:45 +0000 |
1192 | +++ breezy/plugins/propose/launchpad.py 2020-02-21 04:09:29 +0000 |
1193 | @@ -258,7 +258,8 @@ |
1194 | return "~%s/%s" % (owner, project) |
1195 | |
1196 | def _publish_git(self, local_branch, base_path, name, owner, project=None, |
1197 | - revision_id=None, overwrite=False, allow_lossy=True): |
1198 | + revision_id=None, overwrite=False, allow_lossy=True, |
1199 | + tag_selector=None): |
1200 | to_path = self._get_derived_git_path(base_path, owner, project) |
1201 | to_transport = get_transport("git+ssh://git.launchpad.net/" + to_path) |
1202 | try: |
1203 | @@ -270,21 +271,23 @@ |
1204 | if dir_to is None: |
1205 | try: |
1206 | br_to = local_branch.create_clone_on_transport( |
1207 | - to_transport, revision_id=revision_id, name=name) |
1208 | + to_transport, revision_id=revision_id, name=name, |
1209 | + tag_selector=tag_selector) |
1210 | except errors.NoRoundtrippingSupport: |
1211 | br_to = local_branch.create_clone_on_transport( |
1212 | to_transport, revision_id=revision_id, name=name, |
1213 | - lossy=True) |
1214 | + lossy=True, tag_selector=tag_selector) |
1215 | else: |
1216 | try: |
1217 | dir_to = dir_to.push_branch( |
1218 | - local_branch, revision_id, overwrite=overwrite, name=name) |
1219 | + local_branch, revision_id, overwrite=overwrite, name=name, |
1220 | + tag_selector=tag_selector) |
1221 | except errors.NoRoundtrippingSupport: |
1222 | if not allow_lossy: |
1223 | raise |
1224 | dir_to = dir_to.push_branch( |
1225 | local_branch, revision_id, overwrite=overwrite, name=name, |
1226 | - lossy=True) |
1227 | + lossy=True, tag_selector=tag_selector) |
1228 | br_to = dir_to.target_branch |
1229 | return br_to, ( |
1230 | "https://git.launchpad.net/%s/+ref/%s" % (to_path, name)) |
1231 | @@ -310,7 +313,7 @@ |
1232 | |
1233 | def _publish_bzr(self, local_branch, base_branch, name, owner, |
1234 | project=None, revision_id=None, overwrite=False, |
1235 | - allow_lossy=True): |
1236 | + allow_lossy=True, tag_selector=None): |
1237 | to_path = self._get_derived_bzr_path(base_branch, name, owner, project) |
1238 | to_transport = get_transport("lp:" + to_path) |
1239 | try: |
1240 | @@ -321,10 +324,11 @@ |
1241 | |
1242 | if dir_to is None: |
1243 | br_to = local_branch.create_clone_on_transport( |
1244 | - to_transport, revision_id=revision_id) |
1245 | + to_transport, revision_id=revision_id, tag_selector=tag_selector) |
1246 | else: |
1247 | br_to = dir_to.push_branch( |
1248 | - local_branch, revision_id, overwrite=overwrite).target_branch |
1249 | + local_branch, revision_id, overwrite=overwrite, |
1250 | + tag_selector=tag_selector).target_branch |
1251 | return br_to, ("https://code.launchpad.net/" + to_path) |
1252 | |
1253 | def _split_url(self, url): |
1254 | @@ -341,7 +345,7 @@ |
1255 | |
1256 | def publish_derived(self, local_branch, base_branch, name, project=None, |
1257 | owner=None, revision_id=None, overwrite=False, |
1258 | - allow_lossy=True): |
1259 | + allow_lossy=True, tag_selector=None): |
1260 | """Publish a branch to the site, derived from base_branch. |
1261 | |
1262 | :param base_branch: branch to derive the new branch from |
1263 | @@ -360,12 +364,12 @@ |
1264 | return self._publish_bzr( |
1265 | local_branch, base_branch, name, project=project, owner=owner, |
1266 | revision_id=revision_id, overwrite=overwrite, |
1267 | - allow_lossy=allow_lossy) |
1268 | + allow_lossy=allow_lossy, tag_selector=tag_selector) |
1269 | elif base_vcs == 'git': |
1270 | return self._publish_git( |
1271 | local_branch, base_path, name, project=project, owner=owner, |
1272 | revision_id=revision_id, overwrite=overwrite, |
1273 | - allow_lossy=allow_lossy) |
1274 | + allow_lossy=allow_lossy, tag_selector=tag_selector) |
1275 | else: |
1276 | raise AssertionError('not a valid Launchpad URL') |
1277 | |
1278 | |
1279 | === modified file 'breezy/plugins/weave_fmt/bzrdir.py' |
1280 | --- breezy/plugins/weave_fmt/bzrdir.py 2020-02-18 01:57:45 +0000 |
1281 | +++ breezy/plugins/weave_fmt/bzrdir.py 2020-02-21 04:09:29 +0000 |
1282 | @@ -745,7 +745,7 @@ |
1283 | return self._format.__class__() |
1284 | |
1285 | def clone(self, url, revision_id=None, force_new_repo=False, |
1286 | - preserve_stacking=False): |
1287 | + preserve_stacking=False, tag_selector=None): |
1288 | """See ControlDir.clone(). |
1289 | |
1290 | force_new_repo has no effect, since this family of formats always |
1291 | @@ -757,7 +757,7 @@ |
1292 | result = self._format._initialize_for_clone(url) |
1293 | self.open_repository().clone(result, revision_id=revision_id) |
1294 | from_branch = self.open_branch() |
1295 | - from_branch.clone(result, revision_id=revision_id) |
1296 | + from_branch.clone(result, revision_id=revision_id, tag_selector=tag_selector) |
1297 | try: |
1298 | tree = self.open_workingtree() |
1299 | except errors.NotLocalUrl: |
1300 | |
1301 | === modified file 'breezy/propose.py' |
1302 | --- breezy/propose.py 2020-02-18 01:57:45 +0000 |
1303 | +++ breezy/propose.py 2020-02-21 04:09:29 +0000 |
1304 | @@ -237,7 +237,7 @@ |
1305 | |
1306 | def publish_derived(self, new_branch, base_branch, name, project=None, |
1307 | owner=None, revision_id=None, overwrite=False, |
1308 | - allow_lossy=True): |
1309 | + allow_lossy=True, tag_selector=None): |
1310 | """Publish a branch to the site, derived from base_branch. |
1311 | |
1312 | :param base_branch: branch to derive the new branch from |
1313 | |
1314 | === modified file 'breezy/tag.py' |
1315 | --- breezy/tag.py 2020-02-18 01:57:45 +0000 |
1316 | +++ breezy/tag.py 2020-02-21 04:09:29 +0000 |
1317 | @@ -24,29 +24,22 @@ |
1318 | |
1319 | from collections import defaultdict |
1320 | import contextlib |
1321 | +import itertools |
1322 | +import re |
1323 | +import sys |
1324 | |
1325 | # NOTE: I was going to call this tags.py, but vim seems to think all files |
1326 | # called tags* are ctags files... mbp 20070220. |
1327 | |
1328 | +from .inter import InterObject |
1329 | from .registry import Registry |
1330 | -from .lazy_import import lazy_import |
1331 | -lazy_import(globals(), """ |
1332 | -import itertools |
1333 | -import re |
1334 | -import sys |
1335 | - |
1336 | -from breezy import ( |
1337 | - bencode, |
1338 | - trace, |
1339 | - ) |
1340 | -""") |
1341 | |
1342 | from . import ( |
1343 | errors, |
1344 | ) |
1345 | |
1346 | |
1347 | -def _reconcile_tags(source_dict, dest_dict, overwrite): |
1348 | +def _reconcile_tags(source_dict, dest_dict, overwrite, selector): |
1349 | """Do a two-way merge of two tag dictionaries. |
1350 | |
1351 | * only in source => source value |
1352 | @@ -62,6 +55,8 @@ |
1353 | updates = {} |
1354 | result = dict(dest_dict) # copy |
1355 | for name, target in source_dict.items(): |
1356 | + if selector and not selector(name): |
1357 | + continue |
1358 | if result.get(name) == target: |
1359 | pass |
1360 | elif name not in result or overwrite: |
1361 | @@ -72,7 +67,7 @@ |
1362 | return result, updates, conflicts |
1363 | |
1364 | |
1365 | -class _Tags(object): |
1366 | +class Tags(object): |
1367 | |
1368 | def __init__(self, branch): |
1369 | self.branch = branch |
1370 | @@ -83,21 +78,39 @@ |
1371 | raise NotImplementedError(self.get_tag_dict) |
1372 | |
1373 | def get_reverse_tag_dict(self): |
1374 | - """Return a dictionary mapping revision ids to list of tags. |
1375 | - """ |
1376 | - raise NotImplementedError(self.get_reverse_tag_dict) |
1377 | - |
1378 | - def merge_to(self, to_tags, overwrite=False, ignore_master=False): |
1379 | - """Merge new tags from this tags container into another. |
1380 | - |
1381 | - :param to_tags: Tags container to merge into |
1382 | - :param overwrite: Whether to overwrite existing, divergent, tags. |
1383 | + """Returns a dict with revisions as keys |
1384 | + and a list of tags for that revision as value""" |
1385 | + d = self.get_tag_dict() |
1386 | + rev = defaultdict(set) |
1387 | + for key in d: |
1388 | + rev[d[key]].add(key) |
1389 | + return rev |
1390 | + |
1391 | + def merge_to(self, to_tags, overwrite=False, ignore_master=False, selector=None): |
1392 | + """Copy tags between repositories if necessary and possible. |
1393 | + |
1394 | + This method has common command-line behaviour about handling |
1395 | + error cases. |
1396 | + |
1397 | + All new definitions are copied across, except that tags that already |
1398 | + exist keep their existing definitions. |
1399 | + |
1400 | + :param to_tags: Branch to receive these tags |
1401 | + :param overwrite: Overwrite conflicting tags in the target branch |
1402 | :param ignore_master: Do not modify the tags in the target's master |
1403 | branch (if any). Default is false (so the master will be updated). |
1404 | - New in bzr 2.3. |
1405 | - :return: Tuple with tag updates as dictionary and tag conflicts |
1406 | + |
1407 | + :returns: Tuple with tag_updates and tag_conflicts. |
1408 | + tag_updates is a dictionary with new tags, None is used for |
1409 | + removed tags |
1410 | + tag_conflicts is a set of tags that conflicted, each of which is |
1411 | + (tagname, source_target, dest_target), or None if no copying was |
1412 | + done. |
1413 | """ |
1414 | - raise NotImplementedError(self.merge_to) |
1415 | + intertags = InterTags.get(self, to_tags) |
1416 | + return intertags.merge( |
1417 | + overwrite=overwrite, ignore_master=ignore_master, |
1418 | + selector=selector) |
1419 | |
1420 | def set_tag(self, tag_name, revision): |
1421 | """Set a tag. |
1422 | @@ -127,18 +140,21 @@ |
1423 | raise NotImplementedError(self.delete_tag) |
1424 | |
1425 | def rename_revisions(self, rename_map): |
1426 | - """Replace revision ids according to a rename map. |
1427 | + """Rename revisions in this tags dictionary. |
1428 | |
1429 | - :param rename_map: Dictionary mapping old revision ids to |
1430 | - new revision ids. |
1431 | + :param rename_map: Dictionary mapping old revids to new revids |
1432 | """ |
1433 | - raise NotImplementedError(self.rename_revisions) |
1434 | + reverse_tags = self.get_reverse_tag_dict() |
1435 | + for revid, names in reverse_tags.items(): |
1436 | + if revid in rename_map: |
1437 | + for name in names: |
1438 | + self.set_tag(name, rename_map[revid]) |
1439 | |
1440 | def has_tag(self, tag_name): |
1441 | return tag_name in self.get_tag_dict() |
1442 | |
1443 | |
1444 | -class DisabledTags(_Tags): |
1445 | +class DisabledTags(Tags): |
1446 | """Tag storage that refuses to store anything. |
1447 | |
1448 | This is used by older formats that can't store tags. |
1449 | @@ -153,7 +169,7 @@ |
1450 | lookup_tag = _not_supported |
1451 | delete_tag = _not_supported |
1452 | |
1453 | - def merge_to(self, to_tags, overwrite=False, ignore_master=False): |
1454 | + def merge_to(self, to_tags, overwrite=False, ignore_master=False, selector=None): |
1455 | # we never have anything to copy |
1456 | return {}, [] |
1457 | |
1458 | @@ -166,102 +182,19 @@ |
1459 | return {} |
1460 | |
1461 | |
1462 | -class BasicTags(_Tags): |
1463 | - """Tag storage in an unversioned branch control file. |
1464 | +class InterTags(InterObject): |
1465 | + """Operations between sets of tags. |
1466 | """ |
1467 | |
1468 | - def set_tag(self, tag_name, tag_target): |
1469 | - """Add a tag definition to the branch. |
1470 | - |
1471 | - Behaviour if the tag is already present is not defined (yet). |
1472 | - """ |
1473 | - # all done with a write lock held, so this looks atomic |
1474 | - with self.branch.lock_write(): |
1475 | - master = self.branch.get_master_branch() |
1476 | - if master is not None: |
1477 | - master.tags.set_tag(tag_name, tag_target) |
1478 | - td = self.get_tag_dict() |
1479 | - td[tag_name] = tag_target |
1480 | - self._set_tag_dict(td) |
1481 | - |
1482 | - def lookup_tag(self, tag_name): |
1483 | - """Return the referent string of a tag""" |
1484 | - td = self.get_tag_dict() |
1485 | - try: |
1486 | - return td[tag_name] |
1487 | - except KeyError: |
1488 | - raise errors.NoSuchTag(tag_name) |
1489 | - |
1490 | - def get_tag_dict(self): |
1491 | - with self.branch.lock_read(): |
1492 | - try: |
1493 | - tag_content = self.branch._get_tags_bytes() |
1494 | - except errors.NoSuchFile: |
1495 | - # ugly, but only abentley should see this :) |
1496 | - trace.warning('No branch/tags file in %s. ' |
1497 | - 'This branch was probably created by bzr 0.15pre. ' |
1498 | - 'Create an empty file to silence this message.' |
1499 | - % (self.branch, )) |
1500 | - return {} |
1501 | - return self._deserialize_tag_dict(tag_content) |
1502 | - |
1503 | - def get_reverse_tag_dict(self): |
1504 | - """Returns a dict with revisions as keys |
1505 | - and a list of tags for that revision as value""" |
1506 | - d = self.get_tag_dict() |
1507 | - rev = defaultdict(set) |
1508 | - for key in d: |
1509 | - rev[d[key]].add(key) |
1510 | - return rev |
1511 | - |
1512 | - def delete_tag(self, tag_name): |
1513 | - """Delete a tag definition. |
1514 | - """ |
1515 | - with self.branch.lock_write(): |
1516 | - d = self.get_tag_dict() |
1517 | - try: |
1518 | - del d[tag_name] |
1519 | - except KeyError: |
1520 | - raise errors.NoSuchTag(tag_name) |
1521 | - master = self.branch.get_master_branch() |
1522 | - if master is not None: |
1523 | - try: |
1524 | - master.tags.delete_tag(tag_name) |
1525 | - except errors.NoSuchTag: |
1526 | - pass |
1527 | - self._set_tag_dict(d) |
1528 | - |
1529 | - def _set_tag_dict(self, new_dict): |
1530 | - """Replace all tag definitions |
1531 | - |
1532 | - WARNING: Calling this on an unlocked branch will lock it, and will |
1533 | - replace the tags without warning on conflicts. |
1534 | - |
1535 | - :param new_dict: Dictionary from tag name to target. |
1536 | - """ |
1537 | - return self.branch._set_tags_bytes(self._serialize_tag_dict(new_dict)) |
1538 | - |
1539 | - def _serialize_tag_dict(self, tag_dict): |
1540 | - td = dict((k.encode('utf-8'), v) |
1541 | - for k, v in tag_dict.items()) |
1542 | - return bencode.bencode(td) |
1543 | - |
1544 | - def _deserialize_tag_dict(self, tag_content): |
1545 | - """Convert the tag file into a dictionary of tags""" |
1546 | - # was a special case to make initialization easy, an empty definition |
1547 | - # is an empty dictionary |
1548 | - if tag_content == b'': |
1549 | - return {} |
1550 | - try: |
1551 | - r = {} |
1552 | - for k, v in bencode.bdecode(tag_content).items(): |
1553 | - r[k.decode('utf-8')] = v |
1554 | - return r |
1555 | - except ValueError as e: |
1556 | - raise ValueError("failed to deserialize tag dictionary %r: %s" |
1557 | - % (tag_content, e)) |
1558 | - |
1559 | - def merge_to(self, to_tags, overwrite=False, ignore_master=False): |
1560 | + _optimisers = [] |
1561 | + """The available optimised InterTags types.""" |
1562 | + |
1563 | + @classmethod |
1564 | + def is_compatible(klass, source, target): |
1565 | + # This is the default implementation |
1566 | + return True |
1567 | + |
1568 | + def merge(self, overwrite=False, ignore_master=False, selector=None): |
1569 | """Copy tags between repositories if necessary and possible. |
1570 | |
1571 | This method has common command-line behaviour about handling |
1572 | @@ -274,7 +207,9 @@ |
1573 | :param overwrite: Overwrite conflicting tags in the target branch |
1574 | :param ignore_master: Do not modify the tags in the target's master |
1575 | branch (if any). Default is false (so the master will be updated). |
1576 | - New in bzr 2.3. |
1577 | + :param selector: Callback that determines whether a tag should be |
1578 | + copied. It should take a tag name and as argument and return a |
1579 | + boolean. |
1580 | |
1581 | :returns: Tuple with tag_updates and tag_conflicts. |
1582 | tag_updates is a dictionary with new tags, None is used for |
1583 | @@ -284,12 +219,12 @@ |
1584 | done. |
1585 | """ |
1586 | with contextlib.ExitStack() as stack: |
1587 | - if self.branch == to_tags.branch: |
1588 | + if self.source.branch == self.target.branch: |
1589 | return {}, [] |
1590 | - if not self.branch.supports_tags(): |
1591 | + if not self.source.branch.supports_tags(): |
1592 | # obviously nothing to copy |
1593 | return {}, [] |
1594 | - source_dict = self.get_tag_dict() |
1595 | + source_dict = self.source.get_tag_dict() |
1596 | if not source_dict: |
1597 | # no tags in the source, and we don't want to clobber anything |
1598 | # that's in the destination |
1599 | @@ -306,44 +241,35 @@ |
1600 | # Ideally we'd improve this API to report the different conflicts |
1601 | # more clearly to the caller, but we don't want to break plugins |
1602 | # such as bzr-builddeb that use this API. |
1603 | - stack.enter_context(to_tags.branch.lock_write()) |
1604 | + stack.enter_context(self.target.branch.lock_write()) |
1605 | if ignore_master: |
1606 | master = None |
1607 | else: |
1608 | - master = to_tags.branch.get_master_branch() |
1609 | + master = self.target.branch.get_master_branch() |
1610 | if master is not None: |
1611 | stack.enter_context(master.lock_write()) |
1612 | - updates, conflicts = self._merge_to(to_tags, source_dict, overwrite) |
1613 | + updates, conflicts = self._merge_to( |
1614 | + self.target, source_dict, overwrite, selector=selector) |
1615 | if master is not None: |
1616 | - extra_updates, extra_conflicts = self._merge_to(master.tags, |
1617 | - source_dict, overwrite) |
1618 | + extra_updates, extra_conflicts = self._merge_to( |
1619 | + master.tags, source_dict, overwrite, selector=selector) |
1620 | updates.update(extra_updates) |
1621 | conflicts += extra_conflicts |
1622 | # We use set() to remove any duplicate conflicts from the master |
1623 | # branch. |
1624 | return updates, set(conflicts) |
1625 | |
1626 | - def _merge_to(self, to_tags, source_dict, overwrite): |
1627 | + @classmethod |
1628 | + def _merge_to(cls, to_tags, source_dict, overwrite, selector): |
1629 | dest_dict = to_tags.get_tag_dict() |
1630 | result, updates, conflicts = _reconcile_tags( |
1631 | - source_dict, dest_dict, overwrite) |
1632 | + source_dict, dest_dict, overwrite, selector) |
1633 | if result != dest_dict: |
1634 | to_tags._set_tag_dict(result) |
1635 | return updates, conflicts |
1636 | |
1637 | - def rename_revisions(self, rename_map): |
1638 | - """Rename revisions in this tags dictionary. |
1639 | - |
1640 | - :param rename_map: Dictionary mapping old revids to new revids |
1641 | - """ |
1642 | - reverse_tags = self.get_reverse_tag_dict() |
1643 | - for revid, names in reverse_tags.items(): |
1644 | - if revid in rename_map: |
1645 | - for name in names: |
1646 | - self.set_tag(name, rename_map[revid]) |
1647 | - |
1648 | - |
1649 | -class MemoryTags(_Tags): |
1650 | + |
1651 | +class MemoryTags(Tags): |
1652 | |
1653 | def __init__(self, tag_dict): |
1654 | self._tag_dict = tag_dict |
1655 | @@ -359,15 +285,6 @@ |
1656 | except KeyError: |
1657 | raise errors.NoSuchTag(tag_name) |
1658 | |
1659 | - def get_reverse_tag_dict(self): |
1660 | - """Returns a dict with revisions as keys |
1661 | - and a list of tags for that revision as value""" |
1662 | - d = self.get_tag_dict() |
1663 | - rev = defaultdict(set) |
1664 | - for key in d: |
1665 | - rev[d[key]].add(key) |
1666 | - return rev |
1667 | - |
1668 | def set_tag(self, name, revid): |
1669 | self._tag_dict[name] = revid |
1670 | |
1671 | @@ -385,11 +302,11 @@ |
1672 | def _set_tag_dict(self, result): |
1673 | self._tag_dict = dict(result.items()) |
1674 | |
1675 | - def merge_to(self, to_tags, overwrite=False, ignore_master=False): |
1676 | + def merge_to(self, to_tags, overwrite=False, ignore_master=False, selector=None): |
1677 | source_dict = self.get_tag_dict() |
1678 | dest_dict = to_tags.get_tag_dict() |
1679 | result, updates, conflicts = _reconcile_tags( |
1680 | - source_dict, dest_dict, overwrite) |
1681 | + source_dict, dest_dict, overwrite, selector) |
1682 | if result != dest_dict: |
1683 | to_tags._set_tag_dict(result) |
1684 | return updates, conflicts |
1685 | @@ -441,3 +358,6 @@ |
1686 | tag_sort_methods.register("alpha", sort_alpha, 'Sort tags lexicographically.') |
1687 | tag_sort_methods.register("time", sort_time, 'Sort tags chronologically.') |
1688 | tag_sort_methods.default_key = "natural" |
1689 | + |
1690 | + |
1691 | +InterTags.register_optimiser(InterTags) |
1692 | |
1693 | === modified file 'breezy/tests/per_branch/test_pull.py' |
1694 | --- breezy/tests/per_branch/test_pull.py 2018-11-17 20:50:40 +0000 |
1695 | +++ breezy/tests/per_branch/test_pull.py 2020-02-21 04:09:29 +0000 |
1696 | @@ -118,7 +118,7 @@ |
1697 | self.assertEqual(p1, result.old_revid) |
1698 | self.assertEqual(2, result.new_revno) |
1699 | self.assertEqual(m1, result.new_revid) |
1700 | - self.assertEqual([], result.tag_conflicts) |
1701 | + self.assertEqual([], list(result.tag_conflicts)) |
1702 | |
1703 | def test_pull_overwrite(self): |
1704 | tree_a = self.make_branch_and_tree('tree_a') |
1705 | |
1706 | === modified file 'breezy/tests/per_branch/test_tags.py' |
1707 | --- breezy/tests/per_branch/test_tags.py 2019-02-02 21:58:23 +0000 |
1708 | +++ breezy/tests/per_branch/test_tags.py 2020-02-21 04:09:29 +0000 |
1709 | @@ -142,6 +142,21 @@ |
1710 | self.assertEqual(updates, {}) |
1711 | self.assertEqual(b2.tags.lookup_tag('conflicts'), revid2) |
1712 | |
1713 | + def test_merge_tags_selector(self): |
1714 | + b1, [revid, revid1] = self.make_branch_with_revision_tuple('b1', 2) |
1715 | + w2 = b1.controldir.sprout('b2', revision_id=revid).open_workingtree() |
1716 | + revid2 = w2.commit('revision 2') |
1717 | + b2 = w2.branch |
1718 | + # if there are tags in the source and not the destination, then they |
1719 | + # just go across |
1720 | + b1.tags.set_tag('tag1', revid) |
1721 | + b1.tags.set_tag('tag2', revid2) |
1722 | + updates, conflicts = b1.tags.merge_to(b2.tags, selector=lambda x: x == 'tag1') |
1723 | + self.assertEqual({'tag1': revid}, updates) |
1724 | + self.assertEqual(set(), set(conflicts)) |
1725 | + self.assertEqual(b2.tags.lookup_tag('tag1'), revid) |
1726 | + self.assertRaises(errors.NoSuchTag, b2.tags.lookup_tag, 'tag2') |
1727 | + |
1728 | def test_unicode_tag(self): |
1729 | tag_name = u'\u3070' |
1730 | b1, [revid] = self.make_branch_with_revision_tuple('b', 1) |
1731 | |
1732 | === modified file 'breezy/tests/per_controldir/test_push.py' |
1733 | --- breezy/tests/per_controldir/test_push.py 2019-02-15 03:42:06 +0000 |
1734 | +++ breezy/tests/per_controldir/test_push.py 2020-02-21 04:09:29 +0000 |
1735 | @@ -99,3 +99,14 @@ |
1736 | self.assertEqual(2, result.branch_push_result.new_revno) |
1737 | self.assertEqual(tree.branch.base, result.source_branch.base) |
1738 | self.assertEqual(dir.open_branch().base, result.target_branch.base) |
1739 | + |
1740 | + def test_push_tag_selector(self): |
1741 | + tree, rev1 = self.create_simple_tree() |
1742 | + try: |
1743 | + tree.branch.tags.set_tag('tag1', rev1) |
1744 | + except TagsNotSupported: |
1745 | + raise TestNotApplicable('tags not supported') |
1746 | + tree.branch.tags.set_tag('tag2', rev1) |
1747 | + dir = self.make_repository('dir').controldir |
1748 | + dir.push_branch(tree.branch, tag_selector=lambda x: x == 'tag1') |
1749 | + self.assertEqual({'tag1': rev1}, dir.open_branch().tags.get_tag_dict()) |
1750 | |
1751 | === modified file 'breezy/tests/per_interbranch/test_pull.py' |
1752 | --- breezy/tests/per_interbranch/test_pull.py 2018-11-11 04:08:32 +0000 |
1753 | +++ breezy/tests/per_interbranch/test_pull.py 2020-02-21 04:09:29 +0000 |
1754 | @@ -144,7 +144,7 @@ |
1755 | self.assertEqual(p1, result.old_revid) |
1756 | self.assertEqual(2, result.new_revno) |
1757 | self.assertEqual(m1, result.new_revid) |
1758 | - self.assertEqual([], result.tag_conflicts) |
1759 | + self.assertEqual([], list(result.tag_conflicts)) |
1760 | |
1761 | def test_pull_overwrite(self): |
1762 | tree_a = self.make_from_branch_and_tree('tree_a') |
1763 | @@ -180,6 +180,26 @@ |
1764 | self.assertEqual(tree_b.branch.last_revision(), |
1765 | tree_a.branch.last_revision()) |
1766 | |
1767 | + def test_pull_tag_selector(self): |
1768 | + if not self.branch_format_from.supports_tags(): |
1769 | + raise TestNotApplicable('from format does not support tags') |
1770 | + if not self.branch_format_to.supports_tags(): |
1771 | + raise TestNotApplicable('to format does not support tags') |
1772 | + tree_a = self.make_from_branch_and_tree('tree_a') |
1773 | + revid1 = tree_a.commit('message 1') |
1774 | + try: |
1775 | + tree_b = self.sprout_to( |
1776 | + tree_a.controldir, 'tree_b').open_workingtree() |
1777 | + except errors.NoRoundtrippingSupport: |
1778 | + raise TestNotApplicable( |
1779 | + 'lossless push between %r and %r not supported' % |
1780 | + (self.branch_format_from, self.branch_format_to)) |
1781 | + tree_b.branch.tags.set_tag('tag1', revid1) |
1782 | + tree_b.branch.tags.set_tag('tag2', revid1) |
1783 | + tree_b.branch.get_config_stack().set('branch.fetch_tags', True) |
1784 | + tree_a.pull(tree_b.branch, tag_selector=lambda x: x == 'tag1') |
1785 | + self.assertEqual({'tag1': revid1}, tree_a.branch.tags.get_tag_dict()) |
1786 | + |
1787 | |
1788 | class TestPullHook(TestCaseWithInterBranch): |
1789 | |
1790 | |
1791 | === modified file 'breezy/tests/per_interbranch/test_push.py' |
1792 | --- breezy/tests/per_interbranch/test_push.py 2020-02-07 02:14:30 +0000 |
1793 | +++ breezy/tests/per_interbranch/test_push.py 2020-02-21 04:09:29 +0000 |
1794 | @@ -374,6 +374,26 @@ |
1795 | self.overrideAttr(SmartServerRepositoryGetParentMap, |
1796 | 'no_extra_results', True) |
1797 | |
1798 | + def test_push_tag_selector(self): |
1799 | + if not self.branch_format_from.supports_tags(): |
1800 | + raise tests.TestNotApplicable('from format does not support tags') |
1801 | + if not self.branch_format_to.supports_tags(): |
1802 | + raise tests.TestNotApplicable('to format does not support tags') |
1803 | + tree_a = self.make_from_branch_and_tree('tree_a') |
1804 | + revid1 = tree_a.commit('message 1') |
1805 | + try: |
1806 | + tree_b = self.sprout_to( |
1807 | + tree_a.controldir, 'tree_b').open_workingtree() |
1808 | + except errors.NoRoundtrippingSupport: |
1809 | + raise tests.TestNotApplicable( |
1810 | + 'lossless push between %r and %r not supported' % |
1811 | + (self.branch_format_from, self.branch_format_to)) |
1812 | + tree_b.branch.tags.set_tag('tag1', revid1) |
1813 | + tree_b.branch.tags.set_tag('tag2', revid1) |
1814 | + tree_b.branch.get_config_stack().set('branch.fetch_tags', True) |
1815 | + tree_b.branch.push(tree_a.branch, tag_selector=lambda x: x == 'tag1') |
1816 | + self.assertEqual({'tag1': revid1}, tree_a.branch.tags.get_tag_dict()) |
1817 | + |
1818 | |
1819 | class TestPushHook(TestCaseWithInterBranch): |
1820 | |
1821 | |
1822 | === modified file 'breezy/tests/test_foreign.py' |
1823 | --- breezy/tests/test_foreign.py 2018-11-25 20:44:56 +0000 |
1824 | +++ breezy/tests/test_foreign.py 2020-02-21 04:09:29 +0000 |
1825 | @@ -171,7 +171,7 @@ |
1826 | def is_compatible(source, target): |
1827 | return isinstance(target, DummyForeignVcsBranch) |
1828 | |
1829 | - def push(self, overwrite=False, stop_revision=None, lossy=False): |
1830 | + def push(self, overwrite=False, stop_revision=None, lossy=False, tag_selector=None): |
1831 | if not lossy: |
1832 | raise errors.NoRoundtrippingSupport(self.source, self.target) |
1833 | result = branch.BranchPushResult() |
1834 | |
1835 | === modified file 'breezy/tests/test_tag.py' |
1836 | --- breezy/tests/test_tag.py 2019-02-01 16:29:45 +0000 |
1837 | +++ breezy/tests/test_tag.py 2020-02-21 04:09:29 +0000 |
1838 | @@ -22,10 +22,12 @@ |
1839 | errors, |
1840 | ) |
1841 | from breezy.tag import ( |
1842 | - BasicTags, |
1843 | DisabledTags, |
1844 | MemoryTags, |
1845 | ) |
1846 | +from breezy.bzr.tag import ( |
1847 | + BasicTags, |
1848 | + ) |
1849 | from breezy.tests import ( |
1850 | TestCase, |
1851 | TestCaseWithTransport, |
1852 | @@ -120,6 +122,17 @@ |
1853 | self.assertEqual({u'tag-2': b'z'}, updates) |
1854 | self.assertEqual(b'z', b.tags.lookup_tag('tag-2')) |
1855 | |
1856 | + def test_merge_to_with_selector(self): |
1857 | + a = self.make_branch_supporting_tags('a') |
1858 | + b = self.make_branch_supporting_tags('b') |
1859 | + # simple merge |
1860 | + a.tags.set_tag('tag-1', b'x') |
1861 | + a.tags.set_tag('tag-2', b'y') |
1862 | + updates, conflicts = a.tags.merge_to(b.tags, selector=lambda x: x == 'tag-1') |
1863 | + self.assertEqual(list(conflicts), []) |
1864 | + self.assertEqual({u'tag-1': b'x'}, updates) |
1865 | + self.assertRaises(errors.NoSuchTag, b.tags.lookup_tag, 'tag-2') |
1866 | + |
1867 | |
1868 | class TestTagsInCheckouts(TestCaseWithTransport): |
1869 | """Tests for how tags are synchronised between the master and child branch |
1870 | |
1871 | === modified file 'breezy/workingtree.py' |
1872 | --- breezy/workingtree.py 2020-02-18 01:57:45 +0000 |
1873 | +++ breezy/workingtree.py 2020-02-21 04:09:29 +0000 |
1874 | @@ -819,13 +819,13 @@ |
1875 | |
1876 | def pull(self, source, overwrite=False, stop_revision=None, |
1877 | change_reporter=None, possible_transports=None, local=False, |
1878 | - show_base=False): |
1879 | + show_base=False, tag_selector=None): |
1880 | with self.lock_write(), source.lock_read(): |
1881 | old_revision_info = self.branch.last_revision_info() |
1882 | basis_tree = self.basis_tree() |
1883 | count = self.branch.pull(source, overwrite, stop_revision, |
1884 | possible_transports=possible_transports, |
1885 | - local=local) |
1886 | + local=local, tag_selector=tag_selector) |
1887 | new_revision_info = self.branch.last_revision_info() |
1888 | if new_revision_info != old_revision_info: |
1889 | repository = self.branch.repository |
Running landing tests failed /ci.breezy- vcs.org/ job/brz/ job/brz- land/722/
https:/