Merge lp:~jelmer/brz/trunk-3.1 into lp:brz

Proposed by Jelmer Vernooij
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
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
=== modified file 'breezy/branch.py'
--- breezy/branch.py 2020-02-18 01:57:45 +0000
+++ breezy/branch.py 2020-02-21 04:09:29 +0000
@@ -1188,7 +1188,8 @@
1188 if revno < 1 or revno > self.revno():1188 if revno < 1 or revno > self.revno():
1189 raise errors.InvalidRevisionNumber(revno)1189 raise errors.InvalidRevisionNumber(revno)
11901190
1191 def clone(self, to_controldir, revision_id=None, repository_policy=None):1191 def clone(self, to_controldir, revision_id=None, repository_policy=None,
1192 tag_selector=None):
1192 """Clone this branch into to_controldir preserving all semantic values.1193 """Clone this branch into to_controldir preserving all semantic values.
11931194
1194 Most API users will want 'create_clone_on_transport', which creates a1195 Most API users will want 'create_clone_on_transport', which creates a
@@ -1201,11 +1202,12 @@
1201 with self.lock_read(), result.lock_write():1202 with self.lock_read(), result.lock_write():
1202 if repository_policy is not None:1203 if repository_policy is not None:
1203 repository_policy.configure_branch(result)1204 repository_policy.configure_branch(result)
1204 self.copy_content_into(result, revision_id=revision_id)1205 self.copy_content_into(
1206 result, revision_id=revision_id, tag_selector=tag_selector)
1205 return result1207 return result
12061208
1207 def sprout(self, to_controldir, revision_id=None, repository_policy=None,1209 def sprout(self, to_controldir, revision_id=None, repository_policy=None,
1208 repository=None, lossy=False):1210 repository=None, lossy=False, tag_selector=None):
1209 """Create a new line of development from the branch, into to_controldir.1211 """Create a new line of development from the branch, into to_controldir.
12101212
1211 to_controldir controls the branch format.1213 to_controldir controls the branch format.
@@ -1222,7 +1224,8 @@
1222 with self.lock_read(), result.lock_write():1224 with self.lock_read(), result.lock_write():
1223 if repository_policy is not None:1225 if repository_policy is not None:
1224 repository_policy.configure_branch(result)1226 repository_policy.configure_branch(result)
1225 self.copy_content_into(result, revision_id=revision_id)1227 self.copy_content_into(
1228 result, revision_id=revision_id, tag_selector=tag_selector)
1226 master_url = self.get_bound_location()1229 master_url = self.get_bound_location()
1227 if master_url is None:1230 if master_url is None:
1228 result.set_parent(self.user_url)1231 result.set_parent(self.user_url)
@@ -1255,14 +1258,16 @@
1255 revno = 11258 revno = 1
1256 destination.set_last_revision_info(revno, revision_id)1259 destination.set_last_revision_info(revno, revision_id)
12571260
1258 def copy_content_into(self, destination, revision_id=None):1261 def copy_content_into(self, destination, revision_id=None, tag_selector=None):
1259 """Copy the content of self into destination.1262 """Copy the content of self into destination.
12601263
1261 revision_id: if not None, the revision history in the new branch will1264 revision_id: if not None, the revision history in the new branch will
1262 be truncated to end with revision_id.1265 be truncated to end with revision_id.
1266 tag_selector: Optional callback that receives a tag name
1267 and should return a boolean to indicate whether a tag should be copied
1263 """1268 """
1264 return InterBranch.get(self, destination).copy_content_into(1269 return InterBranch.get(self, destination).copy_content_into(
1265 revision_id=revision_id)1270 revision_id=revision_id, tag_selector=tag_selector)
12661271
1267 def update_references(self, target):1272 def update_references(self, target):
1268 if not self._format.supports_reference_locations:1273 if not self._format.supports_reference_locations:
@@ -1307,7 +1312,8 @@
13071312
1308 def create_clone_on_transport(self, to_transport, revision_id=None,1313 def create_clone_on_transport(self, to_transport, revision_id=None,
1309 stacked_on=None, create_prefix=False,1314 stacked_on=None, create_prefix=False,
1310 use_existing_dir=False, no_tree=None):1315 use_existing_dir=False, no_tree=None,
1316 tag_selector=None):
1311 """Create a clone of this branch and its bzrdir.1317 """Create a clone of this branch and its bzrdir.
13121318
1313 :param to_transport: The transport to clone onto.1319 :param to_transport: The transport to clone onto.
@@ -1327,7 +1333,7 @@
1327 dir_to = self.controldir.clone_on_transport(1333 dir_to = self.controldir.clone_on_transport(
1328 to_transport, revision_id=revision_id, stacked_on=stacked_on,1334 to_transport, revision_id=revision_id, stacked_on=stacked_on,
1329 create_prefix=create_prefix, use_existing_dir=use_existing_dir,1335 create_prefix=create_prefix, use_existing_dir=use_existing_dir,
1330 no_tree=no_tree)1336 no_tree=no_tree, tag_selector=tag_selector)
1331 return dir_to.open_branch()1337 return dir_to.open_branch()
13321338
1333 def create_checkout(self, to_location, revision_id=None,1339 def create_checkout(self, to_location, revision_id=None,
@@ -2054,7 +2060,7 @@
2054 raise NotImplementedError(klass._get_branch_formats_to_test)2060 raise NotImplementedError(klass._get_branch_formats_to_test)
20552061
2056 def pull(self, overwrite=False, stop_revision=None,2062 def pull(self, overwrite=False, stop_revision=None,
2057 possible_transports=None, local=False):2063 possible_transports=None, local=False, tag_selector=None):
2058 """Mirror source into target branch.2064 """Mirror source into target branch.
20592065
2060 The target branch is considered to be 'local', having low latency.2066 The target branch is considered to be 'local', having low latency.
@@ -2064,18 +2070,21 @@
2064 raise NotImplementedError(self.pull)2070 raise NotImplementedError(self.pull)
20652071
2066 def push(self, overwrite=False, stop_revision=None, lossy=False,2072 def push(self, overwrite=False, stop_revision=None, lossy=False,
2067 _override_hook_source_branch=None):2073 _override_hook_source_branch=None, tag_selector=None):
2068 """Mirror the source branch into the target branch.2074 """Mirror the source branch into the target branch.
20692075
2070 The source branch is considered to be 'local', having low latency.2076 The source branch is considered to be 'local', having low latency.
2071 """2077 """
2072 raise NotImplementedError(self.push)2078 raise NotImplementedError(self.push)
20732079
2074 def copy_content_into(self, revision_id=None):2080 def copy_content_into(self, revision_id=None, tag_selector=None):
2075 """Copy the content of source into target2081 """Copy the content of source into target
20762082
2077 revision_id: if not None, the revision history in the new branch will2083 :param revision_id:
2078 be truncated to end with revision_id.2084 if not None, the revision history in the new branch will
2085 be truncated to end with revision_id.
2086 :param tag_selector: Optional callback that can decide
2087 to copy or not copy tags.
2079 """2088 """
2080 raise NotImplementedError(self.copy_content_into)2089 raise NotImplementedError(self.copy_content_into)
20812090
@@ -2122,7 +2131,7 @@
2122 return format._custom_format2131 return format._custom_format
2123 return format2132 return format
21242133
2125 def copy_content_into(self, revision_id=None):2134 def copy_content_into(self, revision_id=None, tag_selector=None):
2126 """Copy the content of source into target2135 """Copy the content of source into target
21272136
2128 revision_id: if not None, the revision history in the new branch will2137 revision_id: if not None, the revision history in the new branch will
@@ -2139,7 +2148,7 @@
2139 if parent:2148 if parent:
2140 self.target.set_parent(parent)2149 self.target.set_parent(parent)
2141 if self.source._push_should_merge_tags():2150 if self.source._push_should_merge_tags():
2142 self.source.tags.merge_to(self.target.tags)2151 self.source.tags.merge_to(self.target.tags, selector=tag_selector)
21432152
2144 def fetch(self, stop_revision=None, limit=None, lossy=False):2153 def fetch(self, stop_revision=None, limit=None, lossy=False):
2145 if self.target.base == self.source.base:2154 if self.target.base == self.source.base:
@@ -2200,7 +2209,8 @@
22002209
2201 def pull(self, overwrite=False, stop_revision=None,2210 def pull(self, overwrite=False, stop_revision=None,
2202 possible_transports=None, run_hooks=True,2211 possible_transports=None, run_hooks=True,
2203 _override_hook_target=None, local=False):2212 _override_hook_target=None, local=False,
2213 tag_selector=None):
2204 """Pull from source into self, updating my master if any.2214 """Pull from source into self, updating my master if any.
22052215
2206 :param run_hooks: Private parameter - if false, this branch2216 :param run_hooks: Private parameter - if false, this branch
@@ -2231,15 +2241,17 @@
2231 if master_branch:2241 if master_branch:
2232 # pull from source into master.2242 # pull from source into master.
2233 master_branch.pull(2243 master_branch.pull(
2234 self.source, overwrite, stop_revision, run_hooks=False)2244 self.source, overwrite, stop_revision, run_hooks=False,
2245 tag_selector=tag_selector)
2235 return self._pull(2246 return self._pull(
2236 overwrite, stop_revision, _hook_master=master_branch,2247 overwrite, stop_revision, _hook_master=master_branch,
2237 run_hooks=run_hooks,2248 run_hooks=run_hooks,
2238 _override_hook_target=_override_hook_target,2249 _override_hook_target=_override_hook_target,
2239 merge_tags_to_master=not source_is_master)2250 merge_tags_to_master=not source_is_master,
2251 tag_selector=tag_selector)
22402252
2241 def push(self, overwrite=False, stop_revision=None, lossy=False,2253 def push(self, overwrite=False, stop_revision=None, lossy=False,
2242 _override_hook_source_branch=None):2254 _override_hook_source_branch=None, tag_selector=None):
2243 """See InterBranch.push.2255 """See InterBranch.push.
22442256
2245 This is the basic concrete implementation of push()2257 This is the basic concrete implementation of push()
@@ -2271,18 +2283,21 @@
2271 with master_branch.lock_write():2283 with master_branch.lock_write():
2272 # push into the master from the source branch.2284 # push into the master from the source branch.
2273 master_inter = InterBranch.get(self.source, master_branch)2285 master_inter = InterBranch.get(self.source, master_branch)
2274 master_inter._basic_push(overwrite, stop_revision)2286 master_inter._basic_push(
2287 overwrite, stop_revision, tag_selector=tag_selector)
2275 # and push into the target branch from the source. Note2288 # and push into the target branch from the source. Note
2276 # that we push from the source branch again, because it's2289 # that we push from the source branch again, because it's
2277 # considered the highest bandwidth repository.2290 # considered the highest bandwidth repository.
2278 result = self._basic_push(overwrite, stop_revision)2291 result = self._basic_push(
2292 overwrite, stop_revision, tag_selector=tag_selector)
2279 result.master_branch = master_branch2293 result.master_branch = master_branch
2280 result.local_branch = self.target2294 result.local_branch = self.target
2281 _run_hooks()2295 _run_hooks()
2282 else:2296 else:
2283 master_branch = None2297 master_branch = None
2284 # no master branch2298 # no master branch
2285 result = self._basic_push(overwrite, stop_revision)2299 result = self._basic_push(
2300 overwrite, stop_revision, tag_selector=tag_selector)
2286 # TODO: Why set master_branch and local_branch if there's no2301 # TODO: Why set master_branch and local_branch if there's no
2287 # binding? Maybe cleaner to just leave them unset? -- mbp2302 # binding? Maybe cleaner to just leave them unset? -- mbp
2288 # 200705042303 # 20070504
@@ -2291,7 +2306,7 @@
2291 _run_hooks()2306 _run_hooks()
2292 return result2307 return result
22932308
2294 def _basic_push(self, overwrite, stop_revision):2309 def _basic_push(self, overwrite, stop_revision, tag_selector=None):
2295 """Basic implementation of push without bound branches or hooks.2310 """Basic implementation of push without bound branches or hooks.
22962311
2297 Must be called with source read locked and target write locked.2312 Must be called with source read locked and target write locked.
@@ -2310,7 +2325,7 @@
2310 if self.source._push_should_merge_tags():2325 if self.source._push_should_merge_tags():
2311 result.tag_updates, result.tag_conflicts = (2326 result.tag_updates, result.tag_conflicts = (
2312 self.source.tags.merge_to(2327 self.source.tags.merge_to(
2313 self.target.tags, "tags" in overwrite))2328 self.target.tags, "tags" in overwrite, selector=tag_selector))
2314 self.update_references()2329 self.update_references()
2315 result.new_revno, result.new_revid = self.target.last_revision_info()2330 result.new_revno, result.new_revid = self.target.last_revision_info()
2316 return result2331 return result
@@ -2318,7 +2333,7 @@
2318 def _pull(self, overwrite=False, stop_revision=None,2333 def _pull(self, overwrite=False, stop_revision=None,
2319 possible_transports=None, _hook_master=None, run_hooks=True,2334 possible_transports=None, _hook_master=None, run_hooks=True,
2320 _override_hook_target=None, local=False,2335 _override_hook_target=None, local=False,
2321 merge_tags_to_master=True):2336 merge_tags_to_master=True, tag_selector=None):
2322 """See Branch.pull.2337 """See Branch.pull.
23232338
2324 This function is the core worker, used by GenericInterBranch.pull to2339 This function is the core worker, used by GenericInterBranch.pull to
@@ -2362,7 +2377,8 @@
2362 result.tag_updates, result.tag_conflicts = (2377 result.tag_updates, result.tag_conflicts = (
2363 self.source.tags.merge_to(2378 self.source.tags.merge_to(
2364 self.target.tags, "tags" in overwrite,2379 self.target.tags, "tags" in overwrite,
2365 ignore_master=not merge_tags_to_master))2380 ignore_master=not merge_tags_to_master,
2381 selector=tag_selector))
2366 self.update_references()2382 self.update_references()
2367 result.new_revno, result.new_revid = (2383 result.new_revno, result.new_revid = (
2368 self.target.last_revision_info())2384 self.target.last_revision_info())
23692385
=== modified file 'breezy/bzr/branch.py'
--- breezy/bzr/branch.py 2020-02-18 01:57:45 +0000
+++ breezy/bzr/branch.py 2020-02-21 04:09:29 +0000
@@ -27,6 +27,8 @@
27 lockdir,27 lockdir,
28 rio,28 rio,
29 shelf,29 shelf,
30 )
31from breezy.bzr import (
30 tag as _mod_tag,32 tag as _mod_tag,
31 )33 )
32""")34""")
@@ -1022,7 +1024,7 @@
1022 def _make_reference_clone_function(format, a_branch):1024 def _make_reference_clone_function(format, a_branch):
1023 """Create a clone() routine for a branch dynamically."""1025 """Create a clone() routine for a branch dynamically."""
1024 def clone(to_bzrdir, revision_id=None,1026 def clone(to_bzrdir, revision_id=None,
1025 repository_policy=None):1027 repository_policy=None, tag_selector=None):
1026 """See Branch.clone()."""1028 """See Branch.clone()."""
1027 return format.initialize(to_bzrdir, target_branch=a_branch)1029 return format.initialize(to_bzrdir, target_branch=a_branch)
1028 # cannot obey revision_id limits when cloning a reference ...1030 # cannot obey revision_id limits when cloning a reference ...
10291031
=== modified file 'breezy/bzr/bzrdir.py'
--- breezy/bzr/bzrdir.py 2020-02-18 01:57:45 +0000
+++ breezy/bzr/bzrdir.py 2020-02-21 04:09:29 +0000
@@ -141,7 +141,8 @@
141141
142 def clone_on_transport(self, transport, revision_id=None,142 def clone_on_transport(self, transport, revision_id=None,
143 force_new_repo=False, preserve_stacking=False, stacked_on=None,143 force_new_repo=False, preserve_stacking=False, stacked_on=None,
144 create_prefix=False, use_existing_dir=True, no_tree=False):144 create_prefix=False, use_existing_dir=True, no_tree=False,
145 tag_selector=None):
145 """Clone this bzrdir and its contents to transport verbatim.146 """Clone this bzrdir and its contents to transport verbatim.
146147
147 :param transport: The transport for the location to produce the clone148 :param transport: The transport for the location to produce the clone
@@ -233,7 +234,8 @@
233 if local_branch is not None:234 if local_branch is not None:
234 local_branch.clone(235 local_branch.clone(
235 result, revision_id=revision_id,236 result, revision_id=revision_id,
236 repository_policy=repository_policy)237 repository_policy=repository_policy,
238 tag_selector=tag_selector)
237 try:239 try:
238 # Cheaper to check if the target is not local, than to try making240 # Cheaper to check if the target is not local, than to try making
239 # the tree and fail.241 # the tree and fail.
240242
=== modified file 'breezy/bzr/knitpack_repo.py'
--- breezy/bzr/knitpack_repo.py 2020-02-18 01:57:45 +0000
+++ breezy/bzr/knitpack_repo.py 2020-02-21 04:09:29 +0000
@@ -264,8 +264,6 @@
264class RepositoryFormatKnitPack5(RepositoryFormatPack):264class RepositoryFormatKnitPack5(RepositoryFormatPack):
265 """Repository that supports external references to allow stacking.265 """Repository that supports external references to allow stacking.
266266
267 New in release 1.6.
268
269 Supports external lookups, which results in non-truncated ghosts after267 Supports external lookups, which results in non-truncated ghosts after
270 reconcile compared to pack-0.92 formats.268 reconcile compared to pack-0.92 formats.
271 """269 """
@@ -303,8 +301,6 @@
303class RepositoryFormatKnitPack5RichRoot(RepositoryFormatPack):301class RepositoryFormatKnitPack5RichRoot(RepositoryFormatPack):
304 """A repository with rich roots and stacking.302 """A repository with rich roots and stacking.
305303
306 New in release 1.6.1.
307
308 Supports stacking on other repositories, allowing data to be accessed304 Supports stacking on other repositories, allowing data to be accessed
309 without being stored locally.305 without being stored locally.
310 """306 """
@@ -344,8 +340,6 @@
344class RepositoryFormatKnitPack5RichRootBroken(RepositoryFormatPack):340class RepositoryFormatKnitPack5RichRootBroken(RepositoryFormatPack):
345 """A repository with rich roots and external references.341 """A repository with rich roots and external references.
346342
347 New in release 1.6.
348
349 Supports external lookups, which results in non-truncated ghosts after343 Supports external lookups, which results in non-truncated ghosts after
350 reconcile compared to pack-0.92 formats.344 reconcile compared to pack-0.92 formats.
351345
352346
=== modified file 'breezy/bzr/remote.py'
--- breezy/bzr/remote.py 2020-02-18 01:57:45 +0000
+++ breezy/bzr/remote.py 2020-02-21 04:09:29 +0000
@@ -4019,12 +4019,12 @@
4019 source, overwrite=overwrite, stop_revision=stop_revision,4019 source, overwrite=overwrite, stop_revision=stop_revision,
4020 _override_hook_target=self, **kwargs)4020 _override_hook_target=self, **kwargs)
40214021
4022 def push(self, target, overwrite=False, stop_revision=None, lossy=False):4022 def push(self, target, overwrite=False, stop_revision=None, lossy=False, tag_selector=None):
4023 with self.lock_read():4023 with self.lock_read():
4024 self._ensure_real()4024 self._ensure_real()
4025 return self._real_branch.push(4025 return self._real_branch.push(
4026 target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,4026 target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
4027 _override_hook_source_branch=self)4027 _override_hook_source_branch=self, tag_selector=tag_selector)
40284028
4029 def peek_lock_mode(self):4029 def peek_lock_mode(self):
4030 return self._lock_mode4030 return self._lock_mode
40314031
=== added file 'breezy/bzr/tag.py'
--- breezy/bzr/tag.py 1970-01-01 00:00:00 +0000
+++ breezy/bzr/tag.py 2020-02-21 04:09:29 +0000
@@ -0,0 +1,113 @@
1# Copyright (C) 2005-2012 Canonical Ltd
2# Copyright (C) 2020 Breezy Developers
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
18from __future__ import absolute_import
19
20from ..tag import Tags
21
22from .. import (
23 bencode,
24 errors,
25 trace,
26 )
27
28
29class BasicTags(Tags):
30 """Tag storage in an unversioned branch control file.
31 """
32
33 def set_tag(self, tag_name, tag_target):
34 """Add a tag definition to the branch.
35
36 Behaviour if the tag is already present is not defined (yet).
37 """
38 # all done with a write lock held, so this looks atomic
39 with self.branch.lock_write():
40 master = self.branch.get_master_branch()
41 if master is not None:
42 master.tags.set_tag(tag_name, tag_target)
43 td = self.get_tag_dict()
44 td[tag_name] = tag_target
45 self._set_tag_dict(td)
46
47 def lookup_tag(self, tag_name):
48 """Return the referent string of a tag"""
49 td = self.get_tag_dict()
50 try:
51 return td[tag_name]
52 except KeyError:
53 raise errors.NoSuchTag(tag_name)
54
55 def get_tag_dict(self):
56 with self.branch.lock_read():
57 try:
58 tag_content = self.branch._get_tags_bytes()
59 except errors.NoSuchFile:
60 # ugly, but only abentley should see this :)
61 trace.warning('No branch/tags file in %s. '
62 'This branch was probably created by bzr 0.15pre. '
63 'Create an empty file to silence this message.'
64 % (self.branch, ))
65 return {}
66 return self._deserialize_tag_dict(tag_content)
67
68 def delete_tag(self, tag_name):
69 """Delete a tag definition.
70 """
71 with self.branch.lock_write():
72 d = self.get_tag_dict()
73 try:
74 del d[tag_name]
75 except KeyError:
76 raise errors.NoSuchTag(tag_name)
77 master = self.branch.get_master_branch()
78 if master is not None:
79 try:
80 master.tags.delete_tag(tag_name)
81 except errors.NoSuchTag:
82 pass
83 self._set_tag_dict(d)
84
85 def _set_tag_dict(self, new_dict):
86 """Replace all tag definitions
87
88 WARNING: Calling this on an unlocked branch will lock it, and will
89 replace the tags without warning on conflicts.
90
91 :param new_dict: Dictionary from tag name to target.
92 """
93 return self.branch._set_tags_bytes(self._serialize_tag_dict(new_dict))
94
95 def _serialize_tag_dict(self, tag_dict):
96 td = dict((k.encode('utf-8'), v)
97 for k, v in tag_dict.items())
98 return bencode.bencode(td)
99
100 def _deserialize_tag_dict(self, tag_content):
101 """Convert the tag file into a dictionary of tags"""
102 # was a special case to make initialization easy, an empty definition
103 # is an empty dictionary
104 if tag_content == b'':
105 return {}
106 try:
107 r = {}
108 for k, v in bencode.bdecode(tag_content).items():
109 r[k.decode('utf-8')] = v
110 return r
111 except ValueError as e:
112 raise ValueError("failed to deserialize tag dictionary %r: %s"
113 % (tag_content, e))
0114
=== modified file 'breezy/controldir.py'
--- breezy/controldir.py 2020-02-18 01:57:45 +0000
+++ breezy/controldir.py 2020-02-21 04:09:29 +0000
@@ -398,7 +398,8 @@
398 raise NotImplementedError(self.sprout)398 raise NotImplementedError(self.sprout)
399399
400 def push_branch(self, source, revision_id=None, overwrite=False,400 def push_branch(self, source, revision_id=None, overwrite=False,
401 remember=False, create_prefix=False, lossy=False):401 remember=False, create_prefix=False, lossy=False,
402 tag_selector=None):
402 """Push the source branch into this ControlDir."""403 """Push the source branch into this ControlDir."""
403 br_to = None404 br_to = None
404 # If we can open a branch, use its direct repository, otherwise see405 # If we can open a branch, use its direct repository, otherwise see
@@ -422,7 +423,9 @@
422 # revision423 # revision
423 revision_id = source.last_revision()424 revision_id = source.last_revision()
424 repository_to.fetch(source.repository, revision_id=revision_id)425 repository_to.fetch(source.repository, revision_id=revision_id)
425 br_to = source.sprout(self, revision_id=revision_id, lossy=lossy)426 br_to = source.sprout(
427 self, revision_id=revision_id, lossy=lossy,
428 tag_selector=tag_selector)
426 if source.get_push_location() is None or remember:429 if source.get_push_location() is None or remember:
427 # FIXME: Should be done only if we succeed ? -- vila 2012-01-18430 # FIXME: Should be done only if we succeed ? -- vila 2012-01-18
428 source.set_push_location(br_to.base)431 source.set_push_location(br_to.base)
@@ -442,17 +445,19 @@
442 tree_to = self.open_workingtree()445 tree_to = self.open_workingtree()
443 except errors.NotLocalUrl:446 except errors.NotLocalUrl:
444 push_result.branch_push_result = source.push(447 push_result.branch_push_result = source.push(
445 br_to, overwrite, stop_revision=revision_id, lossy=lossy)448 br_to, overwrite, stop_revision=revision_id, lossy=lossy,
449 tag_selector=tag_selector)
446 push_result.workingtree_updated = False450 push_result.workingtree_updated = False
447 except errors.NoWorkingTree:451 except errors.NoWorkingTree:
448 push_result.branch_push_result = source.push(452 push_result.branch_push_result = source.push(
449 br_to, overwrite, stop_revision=revision_id, lossy=lossy)453 br_to, overwrite, stop_revision=revision_id, lossy=lossy,
454 tag_selector=tag_selector)
450 push_result.workingtree_updated = None # Not applicable455 push_result.workingtree_updated = None # Not applicable
451 else:456 else:
452 with tree_to.lock_write():457 with tree_to.lock_write():
453 push_result.branch_push_result = source.push(458 push_result.branch_push_result = source.push(
454 tree_to.branch, overwrite, stop_revision=revision_id,459 tree_to.branch, overwrite, stop_revision=revision_id,
455 lossy=lossy)460 lossy=lossy, tag_selector=tag_selector)
456 tree_to.update()461 tree_to.update()
457 push_result.workingtree_updated = True462 push_result.workingtree_updated = True
458 push_result.old_revno = push_result.branch_push_result.old_revno463 push_result.old_revno = push_result.branch_push_result.old_revno
@@ -491,7 +496,7 @@
491 raise NotImplementedError(self.check_conversion_target)496 raise NotImplementedError(self.check_conversion_target)
492497
493 def clone(self, url, revision_id=None, force_new_repo=False,498 def clone(self, url, revision_id=None, force_new_repo=False,
494 preserve_stacking=False):499 preserve_stacking=False, tag_selector=None):
495 """Clone this controldir and its contents to url verbatim.500 """Clone this controldir and its contents to url verbatim.
496501
497 :param url: The url create the clone at. If url's last component does502 :param url: The url create the clone at. If url's last component does
@@ -507,11 +512,13 @@
507 return self.clone_on_transport(_mod_transport.get_transport(url),512 return self.clone_on_transport(_mod_transport.get_transport(url),
508 revision_id=revision_id,513 revision_id=revision_id,
509 force_new_repo=force_new_repo,514 force_new_repo=force_new_repo,
510 preserve_stacking=preserve_stacking)515 preserve_stacking=preserve_stacking,
516 tag_selector=tag_selector)
511517
512 def clone_on_transport(self, transport, revision_id=None,518 def clone_on_transport(self, transport, revision_id=None,
513 force_new_repo=False, preserve_stacking=False, stacked_on=None,519 force_new_repo=False, preserve_stacking=False, stacked_on=None,
514 create_prefix=False, use_existing_dir=True, no_tree=False):520 create_prefix=False, use_existing_dir=True, no_tree=False,
521 tag_selector=None):
515 """Clone this controldir and its contents to transport verbatim.522 """Clone this controldir and its contents to transport verbatim.
516523
517 :param transport: The transport for the location to produce the clone524 :param transport: The transport for the location to produce the clone
518525
=== modified file 'breezy/git/branch.py'
--- breezy/git/branch.py 2020-02-18 01:57:45 +0000
+++ breezy/git/branch.py 2020-02-21 04:09:29 +0000
@@ -41,7 +41,6 @@
41 lock,41 lock,
42 repository as _mod_repository,42 repository as _mod_repository,
43 revision,43 revision,
44 tag,
45 trace,44 trace,
46 transport,45 transport,
47 urlutils,46 urlutils,
@@ -50,6 +49,10 @@
50from ..revision import (49from ..revision import (
51 NULL_REVISION,50 NULL_REVISION,
52 )51 )
52from ..tag import (
53 Tags,
54 InterTags,
55 )
53from ..trace import (56from ..trace import (
54 is_quiet,57 is_quiet,
55 mutter,58 mutter,
@@ -111,44 +114,75 @@
111 return self._lookup_revno(self.new_revid)114 return self._lookup_revno(self.new_revid)
112115
113116
114class GitTags(tag.BasicTags):117class InterTagsFromGitToRemoteGit(InterTags):
115 """Ref-based tag dictionary."""118
116119 @classmethod
117 def __init__(self, branch):120 def is_compatible(klass, source, target):
118 self.branch = branch121 if not isinstance(source, GitTags):
119 self.repository = branch.repository122 return False
120123 if not isinstance(target, GitTags):
121 def _merge_to_remote_git(self, target_repo, source_tag_refs,124 return False
122 overwrite=False):125 if getattr(target.branch.repository, "_git", None) is not None:
126 return False
127 return True
128
129 def merge(self, overwrite=False, ignore_master=False, selector=None):
130 if self.source.branch.repository.has_same_location(self.target.branch.repository):
131 return {}, []
123 updates = {}132 updates = {}
124 conflicts = []133 conflicts = []
134 source_tag_refs = self.source.branch.get_tag_refs()
125135
126 def get_changed_refs(old_refs):136 def get_changed_refs(old_refs):
127 ret = dict(old_refs)137 ret = dict(old_refs)
128 for ref_name, tag_name, peeled, unpeeled in (138 for ref_name, tag_name, peeled, unpeeled in (
129 source_tag_refs.iteritems()):139 source_tag_refs.iteritems()):
140 if selector and not selector(tag_name):
141 continue
130 if old_refs.get(ref_name) == unpeeled:142 if old_refs.get(ref_name) == unpeeled:
131 pass143 pass
132 elif overwrite or ref_name not in old_refs:144 elif overwrite or ref_name not in old_refs:
133 ret[ref_name] = unpeeled145 ret[ref_name] = unpeeled
134 updates[tag_name] = target_repo.lookup_foreign_revision_id(146 updates[tag_name] = self.target.branch.repository.lookup_foreign_revision_id(
135 peeled)147 peeled)
148 self.target.branch._tag_refs = None
136 else:149 else:
137 conflicts.append(150 conflicts.append(
138 (tag_name,151 (tag_name,
139 self.repository.lookup_foreign_revision_id(peeled),152 self.repository.lookup_foreign_revision_id(peeled),
140 target_repo.lookup_foreign_revision_id(153 self.target.branch.repository.lookup_foreign_revision_id(
141 old_refs[ref_name])))154 old_refs[ref_name])))
142 return ret155 return ret
143 target_repo.controldir.send_pack(156 self.target.branch.repository.controldir.send_pack(
144 get_changed_refs, lambda have, want: [])157 get_changed_refs, lambda have, want: [])
145 return updates, conflicts158 return updates, set(conflicts)
146159
147 def _merge_to_local_git(self, target_repo, source_tag_refs,160
148 overwrite=False):161class InterTagsFromGitToLocalGit(InterTags):
162
163 @classmethod
164 def is_compatible(klass, source, target):
165 if not isinstance(source, GitTags):
166 return False
167 if not isinstance(target, GitTags):
168 return False
169 if getattr(target.branch.repository, "_git", None) is None:
170 return False
171 return True
172
173 def merge(self, overwrite=False, ignore_master=False, selector=None):
174 if self.source.branch.repository.has_same_location(self.target.branch.repository):
175 return {}, []
176
149 conflicts = []177 conflicts = []
150 updates = {}178 updates = {}
179 source_tag_refs = self.source.branch.get_tag_refs()
180
181 target_repo = self.target.branch.repository
182
151 for ref_name, tag_name, peeled, unpeeled in source_tag_refs:183 for ref_name, tag_name, peeled, unpeeled in source_tag_refs:
184 if selector and not selector(tag_name):
185 continue
152 if target_repo._git.refs.get(ref_name) == unpeeled:186 if target_repo._git.refs.get(ref_name) == unpeeled:
153 pass187 pass
154 elif overwrite or ref_name not in target_repo._git.refs:188 elif overwrite or ref_name not in target_repo._git.refs:
@@ -164,9 +198,10 @@
164 tag_name)198 tag_name)
165 continue199 continue
166 target_repo._git.refs[ref_name] = unpeeled or peeled200 target_repo._git.refs[ref_name] = unpeeled or peeled
201 self.target.branch._tag_refs = None
167 else:202 else:
168 try:203 try:
169 source_revid = self.repository.lookup_foreign_revision_id(204 source_revid = self.source.branch.repository.lookup_foreign_revision_id(
170 peeled)205 peeled)
171 target_revid = target_repo.lookup_foreign_revision_id(206 target_revid = target_repo.lookup_foreign_revision_id(
172 target_repo._git.refs[ref_name])207 target_repo._git.refs[ref_name])
@@ -179,32 +214,54 @@
179 tag_name)214 tag_name)
180 continue215 continue
181 conflicts.append((tag_name, source_revid, target_revid))216 conflicts.append((tag_name, source_revid, target_revid))
182 return updates, conflicts217 return updates, set(conflicts)
183218
184 def _merge_to_git(self, to_tags, source_tag_refs, overwrite=False):219
185 target_repo = to_tags.repository220class InterTagsFromGitToNonGit(InterTags):
186 if self.repository.has_same_location(target_repo):221
187 return {}, []222 @classmethod
188 try:223 def is_compatible(klass, source, target):
189 if getattr(target_repo, "_git", None):224 if not isinstance(source, GitTags):
190 return self._merge_to_local_git(225 return False
191 target_repo, source_tag_refs, overwrite)226 if isinstance(target, GitTags):
192 else:227 return False
193 return self._merge_to_remote_git(228 return True
194 target_repo, source_tag_refs, overwrite)229
195 finally:230 def merge(self, overwrite=False, ignore_master=False, selector=None):
196 to_tags.branch._tag_refs = None231 """See Tags.merge_to."""
197232 source_tag_refs = self.source.branch.get_tag_refs()
198 def _merge_to_non_git(self, to_tags, source_tag_refs, overwrite=False):233 if ignore_master:
234 master = None
235 else:
236 master = self.target.branch.get_master_branch()
237 with contextlib.ExitStack() as es:
238 if master is not None:
239 es.enter_context(master.lock_write())
240 updates, conflicts = self._merge_to(
241 self.target, source_tag_refs, overwrite=overwrite,
242 selector=selector)
243 if master is not None:
244 extra_updates, extra_conflicts = self._merge_to(
245 master.tags, overwrite=overwrite,
246 source_tag_refs=source_tag_refs,
247 ignore_master=ignore_master, selector=selector)
248 updates.update(extra_updates)
249 conflicts.update(extra_conflicts)
250 return updates, conflicts
251
252 def _merge_to(self, to_tags, source_tag_refs, overwrite=False,
253 selector=None):
199 unpeeled_map = defaultdict(set)254 unpeeled_map = defaultdict(set)
200 conflicts = []255 conflicts = []
201 updates = {}256 updates = {}
202 result = dict(to_tags.get_tag_dict())257 result = dict(to_tags.get_tag_dict())
203 for ref_name, tag_name, peeled, unpeeled in source_tag_refs:258 for ref_name, tag_name, peeled, unpeeled in source_tag_refs:
259 if selector and not selector(tag_name):
260 continue
204 if unpeeled is not None:261 if unpeeled is not None:
205 unpeeled_map[peeled].add(unpeeled)262 unpeeled_map[peeled].add(unpeeled)
206 try:263 try:
207 bzr_revid = self.branch.lookup_foreign_revision_id(peeled)264 bzr_revid = self.source.branch.lookup_foreign_revision_id(peeled)
208 except NotCommitError:265 except NotCommitError:
209 continue266 continue
210 if result.get(tag_name) == bzr_revid:267 if result.get(tag_name) == bzr_revid:
@@ -219,36 +276,20 @@
219 map_file = UnpeelMap.from_repository(to_tags.branch.repository)276 map_file = UnpeelMap.from_repository(to_tags.branch.repository)
220 map_file.update(unpeeled_map)277 map_file.update(unpeeled_map)
221 map_file.save_in_repository(to_tags.branch.repository)278 map_file.save_in_repository(to_tags.branch.repository)
222 return updates, conflicts279 return updates, set(conflicts)
223280
224 def merge_to(self, to_tags, overwrite=False, ignore_master=False,281
225 source_tag_refs=None):282InterTags.register_optimiser(InterTagsFromGitToRemoteGit)
226 """See Tags.merge_to."""283InterTags.register_optimiser(InterTagsFromGitToLocalGit)
227 if source_tag_refs is None:284InterTags.register_optimiser(InterTagsFromGitToNonGit)
228 source_tag_refs = self.branch.get_tag_refs()285
229 if self == to_tags:286
230 return {}, []287class GitTags(Tags):
231 if isinstance(to_tags, GitTags):288 """Ref-based tag dictionary."""
232 return self._merge_to_git(to_tags, source_tag_refs,289
233 overwrite=overwrite)290 def __init__(self, branch):
234 else:291 self.branch = branch
235 if ignore_master:292 self.repository = branch.repository
236 master = None
237 else:
238 master = to_tags.branch.get_master_branch()
239 with contextlib.ExitStack() as es:
240 if master is not None:
241 es.enter_context(master.lock_write())
242 updates, conflicts = self._merge_to_non_git(
243 to_tags, source_tag_refs, overwrite=overwrite)
244 if master is not None:
245 extra_updates, extra_conflicts = self.merge_to(
246 master.tags, overwrite=overwrite,
247 source_tag_refs=source_tag_refs,
248 ignore_master=ignore_master)
249 updates.update(extra_updates)
250 conflicts += extra_conflicts
251 return updates, conflicts
252293
253 def get_tag_dict(self):294 def get_tag_dict(self):
254 ret = {}295 ret = {}
@@ -262,6 +303,15 @@
262 ret[tag_name] = bzr_revid303 ret[tag_name] = bzr_revid
263 return ret304 return ret
264305
306 def lookup_tag(self, tag_name):
307 """Return the referent string of a tag"""
308 # TODO(jelmer): Replace with something more efficient for local tags.
309 td = self.get_tag_dict()
310 try:
311 return td[tag_name]
312 except KeyError:
313 raise errors.NoSuchTag(tag_name)
314
265315
266class LocalGitTagDict(GitTags):316class LocalGitTagDict(GitTags):
267 """Dictionary with tags in a local repository."""317 """Dictionary with tags in a local repository."""
@@ -612,9 +662,10 @@
612 return revision.NULL_REVISION662 return revision.NULL_REVISION
613 return self.lookup_foreign_revision_id(self.head)663 return self.lookup_foreign_revision_id(self.head)
614664
615 def _basic_push(self, target, overwrite=False, stop_revision=None):665 def _basic_push(self, target, overwrite=False, stop_revision=None,
666 tag_selector=None):
616 return branch.InterBranch.get(self, target)._basic_push(667 return branch.InterBranch.get(self, target)._basic_push(
617 overwrite, stop_revision)668 overwrite, stop_revision, tag_selector=tag_selector)
618669
619 def lookup_foreign_revision_id(self, foreign_revid):670 def lookup_foreign_revision_id(self, foreign_revid):
620 try:671 try:
@@ -934,7 +985,7 @@
934 stop_revision, fetch_tags=fetch_tags, limit=limit, lossy=lossy)985 stop_revision, fetch_tags=fetch_tags, limit=limit, lossy=lossy)
935 return _mod_repository.FetchResult()986 return _mod_repository.FetchResult()
936987
937 def fetch_objects(self, stop_revision, fetch_tags, limit=None, lossy=False):988 def fetch_objects(self, stop_revision, fetch_tags, limit=None, lossy=False, tag_selector=None):
938 interrepo = self._get_interrepo(self.source, self.target)989 interrepo = self._get_interrepo(self.source, self.target)
939 if fetch_tags is None:990 if fetch_tags is None:
940 c = self.source.get_config_stack()991 c = self.source.get_config_stack()
@@ -952,7 +1003,7 @@
952 else:1003 else:
953 self._last_revid = stop_revision1004 self._last_revid = stop_revision
954 real = interrepo.get_determine_wants_revids(1005 real = interrepo.get_determine_wants_revids(
955 [self._last_revid], include_tags=fetch_tags)1006 [self._last_revid], include_tags=fetch_tags, tag_selector=tag_selector)
956 return real(heads)1007 return real(heads)
957 pack_hint, head, refs = interrepo.fetch_objects(1008 pack_hint, head, refs = interrepo.fetch_objects(
958 determine_wants, self.source.mapping, limit=limit,1009 determine_wants, self.source.mapping, limit=limit,
@@ -962,8 +1013,8 @@
962 self.target.repository.pack(hint=pack_hint)1013 self.target.repository.pack(hint=pack_hint)
963 return head, refs1014 return head, refs
9641015
965 def _update_revisions(self, stop_revision=None, overwrite=False):1016 def _update_revisions(self, stop_revision=None, overwrite=False, tag_selector=None):
966 head, refs = self.fetch_objects(stop_revision, fetch_tags=None)1017 head, refs = self.fetch_objects(stop_revision, fetch_tags=None, tag_selector=tag_selector)
967 if overwrite:1018 if overwrite:
968 prev_last_revid = None1019 prev_last_revid = None
969 else:1020 else:
@@ -988,7 +1039,7 @@
988 pass1039 pass
9891040
990 def _basic_pull(self, stop_revision, overwrite, run_hooks,1041 def _basic_pull(self, stop_revision, overwrite, run_hooks,
991 _override_hook_target, _hook_master):1042 _override_hook_target, _hook_master, tag_selector=None):
992 if overwrite is True:1043 if overwrite is True:
993 overwrite = set(["history", "tags"])1044 overwrite = set(["history", "tags"])
994 elif not overwrite:1045 elif not overwrite:
@@ -1005,7 +1056,8 @@
1005 (result.old_revno, result.old_revid) = \1056 (result.old_revno, result.old_revid) = \
1006 self.target.last_revision_info()1057 self.target.last_revision_info()
1007 result.new_git_head, remote_refs = self._update_revisions(1058 result.new_git_head, remote_refs = self._update_revisions(
1008 stop_revision, overwrite=("history" in overwrite))1059 stop_revision, overwrite=("history" in overwrite),
1060 tag_selector=tag_selector)
1009 tags_ret = self.source.tags.merge_to(1061 tags_ret = self.source.tags.merge_to(
1010 self.target.tags, ("tags" in overwrite), ignore_master=True)1062 self.target.tags, ("tags" in overwrite), ignore_master=True)
1011 if isinstance(tags_ret, tuple):1063 if isinstance(tags_ret, tuple):
@@ -1028,7 +1080,7 @@
10281080
1029 def pull(self, overwrite=False, stop_revision=None,1081 def pull(self, overwrite=False, stop_revision=None,
1030 possible_transports=None, _hook_master=None, run_hooks=True,1082 possible_transports=None, _hook_master=None, run_hooks=True,
1031 _override_hook_target=None, local=False):1083 _override_hook_target=None, local=False, tag_selector=None):
1032 """See Branch.pull.1084 """See Branch.pull.
10331085
1034 :param _hook_master: Private parameter - set the branch to1086 :param _hook_master: Private parameter - set the branch to
@@ -1066,9 +1118,10 @@
1066 master_branch = None1118 master_branch = None
1067 return self._basic_pull(stop_revision, overwrite, run_hooks,1119 return self._basic_pull(stop_revision, overwrite, run_hooks,
1068 _override_hook_target,1120 _override_hook_target,
1069 _hook_master=master_branch)1121 _hook_master=master_branch,
1122 tag_selector=tag_selector)
10701123
1071 def _basic_push(self, overwrite, stop_revision):1124 def _basic_push(self, overwrite, stop_revision, tag_selector=None):
1072 if overwrite is True:1125 if overwrite is True:
1073 overwrite = set(["history", "tags"])1126 overwrite = set(["history", "tags"])
1074 elif not overwrite:1127 elif not overwrite:
@@ -1078,9 +1131,11 @@
1078 result.target_branch = self.target1131 result.target_branch = self.target
1079 result.old_revno, result.old_revid = self.target.last_revision_info()1132 result.old_revno, result.old_revid = self.target.last_revision_info()
1080 result.new_git_head, remote_refs = self._update_revisions(1133 result.new_git_head, remote_refs = self._update_revisions(
1081 stop_revision, overwrite=("history" in overwrite))1134 stop_revision, overwrite=("history" in overwrite),
1135 tag_selector=tag_selector)
1082 tags_ret = self.source.tags.merge_to(1136 tags_ret = self.source.tags.merge_to(
1083 self.target.tags, "tags" in overwrite, ignore_master=True)1137 self.target.tags, "tags" in overwrite, ignore_master=True,
1138 selector=tag_selector)
1084 (result.tag_updates, result.tag_conflicts) = tags_ret1139 (result.tag_updates, result.tag_conflicts) = tags_ret
1085 result.new_revno, result.new_revid = self.target.last_revision_info()1140 result.new_revno, result.new_revid = self.target.last_revision_info()
1086 self.update_references(revid=result.new_revid)1141 self.update_references(revid=result.new_revid)
@@ -1109,7 +1164,7 @@
1109 return (isinstance(source, LocalGitBranch) and1164 return (isinstance(source, LocalGitBranch) and
1110 isinstance(target, RemoteGitBranch))1165 isinstance(target, RemoteGitBranch))
11111166
1112 def _basic_push(self, overwrite, stop_revision):1167 def _basic_push(self, overwrite, stop_revision, tag_selector=None):
1113 result = GitBranchPushResult()1168 result = GitBranchPushResult()
1114 result.source_branch = self.source1169 result.source_branch = self.source
1115 result.target_branch = self.target1170 result.target_branch = self.target
@@ -1134,6 +1189,8 @@
1134 result.new_revid = stop_revision1189 result.new_revid = stop_revision
1135 for name, sha in (1190 for name, sha in (
1136 self.source.repository._git.refs.as_dict(b"refs/tags").items()):1191 self.source.repository._git.refs.as_dict(b"refs/tags").items()):
1192 if tag_selector and not tag_selector(name):
1193 continue
1137 if sha not in self.source.repository._git:1194 if sha not in self.source.repository._git:
1138 trace.mutter('Ignoring missing SHA: %s', sha)1195 trace.mutter('Ignoring missing SHA: %s', sha)
1139 continue1196 continue
@@ -1173,7 +1230,7 @@
1173 interrepo.fetch_objects(determine_wants, limit=limit, lossy=lossy)1230 interrepo.fetch_objects(determine_wants, limit=limit, lossy=lossy)
1174 return _mod_repository.FetchResult()1231 return _mod_repository.FetchResult()
11751232
1176 def _basic_push(self, overwrite=False, stop_revision=None):1233 def _basic_push(self, overwrite=False, stop_revision=None, tag_selector=None):
1177 if overwrite is True:1234 if overwrite is True:
1178 overwrite = set(["history", "tags"])1235 overwrite = set(["history", "tags"])
1179 elif not overwrite:1236 elif not overwrite:
@@ -1189,8 +1246,8 @@
1189 other_branch=self.source)1246 other_branch=self.source)
1190 tags_ret = self.source.tags.merge_to(1247 tags_ret = self.source.tags.merge_to(
1191 self.target.tags,1248 self.target.tags,
1192 source_tag_refs=remote_refs_dict_to_tag_refs(refs),1249 overwrite=("tags" in overwrite),
1193 overwrite=("tags" in overwrite))1250 selector=tag_selector)
1194 if isinstance(tags_ret, tuple):1251 if isinstance(tags_ret, tuple):
1195 (result.tag_updates, result.tag_conflicts) = tags_ret1252 (result.tag_updates, result.tag_conflicts) = tags_ret
1196 else:1253 else:
@@ -1218,7 +1275,8 @@
1218 return result.refs, stop_revision1275 return result.refs, stop_revision
12191276
1220 def pull(self, stop_revision=None, overwrite=False,1277 def pull(self, stop_revision=None, overwrite=False,
1221 possible_transports=None, run_hooks=True, local=False):1278 possible_transports=None, run_hooks=True, local=False,
1279 tag_selector=None):
1222 # This type of branch can't be bound.1280 # This type of branch can't be bound.
1223 if local:1281 if local:
1224 raise errors.LocalRequiresBoundBranch()1282 raise errors.LocalRequiresBoundBranch()
@@ -1239,7 +1297,7 @@
1239 other_branch=self.source)1297 other_branch=self.source)
1240 tags_ret = self.source.tags.merge_to(1298 tags_ret = self.source.tags.merge_to(
1241 self.target.tags, overwrite=("tags" in overwrite),1299 self.target.tags, overwrite=("tags" in overwrite),
1242 source_tag_refs=remote_refs_dict_to_tag_refs(refs))1300 selector=tag_selector)
1243 if isinstance(tags_ret, tuple):1301 if isinstance(tags_ret, tuple):
1244 (result.tag_updates, result.tag_conflicts) = tags_ret1302 (result.tag_updates, result.tag_conflicts) = tags_ret
1245 else:1303 else:
@@ -1307,7 +1365,7 @@
1307 refs[ref] = (None, revid)1365 refs[ref] = (None, revid)
1308 return refs, main_ref, (stop_revno, stop_revision)1366 return refs, main_ref, (stop_revno, stop_revision)
13091367
1310 def _update_refs(self, result, old_refs, new_refs, overwrite):1368 def _update_refs(self, result, old_refs, new_refs, overwrite, tag_selector):
1311 mutter("updating refs. old refs: %r, new refs: %r",1369 mutter("updating refs. old refs: %r, new refs: %r",
1312 old_refs, new_refs)1370 old_refs, new_refs)
1313 result.tag_updates = {}1371 result.tag_updates = {}
@@ -1346,6 +1404,8 @@
1346 except ValueError:1404 except ValueError:
1347 pass1405 pass
1348 else:1406 else:
1407 if tag_selector and not tag_selector(tag_name):
1408 continue
1349 result.tag_updates[tag_name] = revid1409 result.tag_updates[tag_name] = revid
1350 ret[ref] = (git_sha, revid)1410 ret[ref] = (git_sha, revid)
1351 else:1411 else:
@@ -1381,7 +1441,8 @@
1381 for (old_revid, (new_sha, new_revid)) in revidmap.items()})1441 for (old_revid, (new_sha, new_revid)) in revidmap.items()})
13821442
1383 def pull(self, overwrite=False, stop_revision=None, local=False,1443 def pull(self, overwrite=False, stop_revision=None, local=False,
1384 possible_transports=None, run_hooks=True, _stop_revno=None):1444 possible_transports=None, run_hooks=True, _stop_revno=None,
1445 tag_selector=None):
1385 result = GitBranchPullResult()1446 result = GitBranchPullResult()
1386 result.source_branch = self.source1447 result.source_branch = self.source
1387 result.target_branch = self.target1448 result.target_branch = self.target
@@ -1390,7 +1451,7 @@
1390 stop_revision, stop_revno=_stop_revno)1451 stop_revision, stop_revno=_stop_revno)
13911452
1392 def update_refs(old_refs):1453 def update_refs(old_refs):
1393 return self._update_refs(result, old_refs, new_refs, overwrite)1454 return self._update_refs(result, old_refs, new_refs, overwrite, tag_selector)
1394 try:1455 try:
1395 result.revidmap, old_refs, new_refs = (1456 result.revidmap, old_refs, new_refs = (
1396 self.interrepo.fetch_refs(update_refs, lossy=False))1457 self.interrepo.fetch_refs(update_refs, lossy=False))
@@ -1410,7 +1471,8 @@
1410 return result1471 return result
14111472
1412 def push(self, overwrite=False, stop_revision=None, lossy=False,1473 def push(self, overwrite=False, stop_revision=None, lossy=False,
1413 _override_hook_source_branch=None, _stop_revno=None):1474 _override_hook_source_branch=None, _stop_revno=None,
1475 tag_selector=None):
1414 result = GitBranchPushResult()1476 result = GitBranchPushResult()
1415 result.source_branch = self.source1477 result.source_branch = self.source
1416 result.target_branch = self.target1478 result.target_branch = self.target
@@ -1421,7 +1483,7 @@
1421 stop_revision, stop_revno=_stop_revno)1483 stop_revision, stop_revno=_stop_revno)
14221484
1423 def update_refs(old_refs):1485 def update_refs(old_refs):
1424 return self._update_refs(result, old_refs, new_refs, overwrite)1486 return self._update_refs(result, old_refs, new_refs, overwrite, tag_selector)
1425 try:1487 try:
1426 result.revidmap, old_refs, new_refs = (1488 result.revidmap, old_refs, new_refs = (
1427 self.interrepo.fetch_refs(1489 self.interrepo.fetch_refs(
14281490
=== modified file 'breezy/git/dir.py'
--- breezy/git/dir.py 2020-02-18 01:57:45 +0000
+++ breezy/git/dir.py 2020-02-21 04:09:29 +0000
@@ -218,7 +218,8 @@
218 def clone_on_transport(self, transport, revision_id=None,218 def clone_on_transport(self, transport, revision_id=None,
219 force_new_repo=False, preserve_stacking=False,219 force_new_repo=False, preserve_stacking=False,
220 stacked_on=None, create_prefix=False,220 stacked_on=None, create_prefix=False,
221 use_existing_dir=True, no_tree=False):221 use_existing_dir=True, no_tree=False,
222 tag_selector=None):
222 """See ControlDir.clone_on_transport."""223 """See ControlDir.clone_on_transport."""
223 from ..repository import InterRepository224 from ..repository import InterRepository
224 from .mapping import default_mapping225 from .mapping import default_mapping
@@ -240,7 +241,7 @@
240 interrepo = InterRepository.get(source_repo, target_repo)241 interrepo = InterRepository.get(source_repo, target_repo)
241 if revision_id is not None:242 if revision_id is not None:
242 determine_wants = interrepo.get_determine_wants_revids(243 determine_wants = interrepo.get_determine_wants_revids(
243 [revision_id], include_tags=True)244 [revision_id], include_tags=True, tag_selector=tag_selector)
244 else:245 else:
245 determine_wants = interrepo.determine_wants_all246 determine_wants = interrepo.determine_wants_all
246 (pack_hint, _, refs) = interrepo.fetch_objects(determine_wants,247 (pack_hint, _, refs) = interrepo.fetch_objects(determine_wants,
@@ -312,7 +313,7 @@
312313
313 def push_branch(self, source, revision_id=None, overwrite=False,314 def push_branch(self, source, revision_id=None, overwrite=False,
314 remember=False, create_prefix=False, lossy=False,315 remember=False, create_prefix=False, lossy=False,
315 name=None):316 name=None, tag_selector=None):
316 """Push the source branch into this ControlDir."""317 """Push the source branch into this ControlDir."""
317 push_result = GitPushResult()318 push_result = GitPushResult()
318 push_result.workingtree_updated = None319 push_result.workingtree_updated = None
@@ -325,7 +326,7 @@
325 target = self.open_branch(name, nascent_ok=True)326 target = self.open_branch(name, nascent_ok=True)
326 push_result.branch_push_result = source.push(327 push_result.branch_push_result = source.push(
327 target, overwrite=overwrite, stop_revision=revision_id,328 target, overwrite=overwrite, stop_revision=revision_id,
328 lossy=lossy)329 lossy=lossy, tag_selector=tag_selector)
329 push_result.new_revid = push_result.branch_push_result.new_revid330 push_result.new_revid = push_result.branch_push_result.new_revid
330 push_result.old_revid = push_result.branch_push_result.old_revid331 push_result.old_revid = push_result.branch_push_result.old_revid
331 try:332 try:
332333
=== modified file 'breezy/git/interrepo.py'
--- breezy/git/interrepo.py 2020-02-18 01:57:45 +0000
+++ breezy/git/interrepo.py 2020-02-21 04:09:29 +0000
@@ -75,6 +75,7 @@
75 )75 )
76from .refs import (76from .refs import (
77 is_tag,77 is_tag,
78 ref_to_tag_name,
78 )79 )
79from .repository import (80from .repository import (
80 GitRepository,81 GitRepository,
@@ -403,7 +404,7 @@
403 def _target_has_shas(self, shas):404 def _target_has_shas(self, shas):
404 raise NotImplementedError(self._target_has_shas)405 raise NotImplementedError(self._target_has_shas)
405406
406 def get_determine_wants_heads(self, wants, include_tags=False):407 def get_determine_wants_heads(self, wants, include_tags=False, tag_selector=None):
407 wants = set(wants)408 wants = set(wants)
408409
409 def determine_wants(refs):410 def determine_wants(refs):
@@ -416,7 +417,11 @@
416 for k, sha in refs.items():417 for k, sha in refs.items():
417 if k.endswith(ANNOTATED_TAG_SUFFIX):418 if k.endswith(ANNOTATED_TAG_SUFFIX):
418 continue419 continue
419 if not is_tag(k):420 try:
421 tag_name = ref_to_tag_name(k)
422 except ValueError:
423 continue
424 if tag_selector and not tag_selector(tag_name):
420 continue425 continue
421 if sha == ZERO_SHA:426 if sha == ZERO_SHA:
422 continue427 continue
@@ -502,14 +507,15 @@
502 """507 """
503 raise NotImplementedError(self.fetch_objects)508 raise NotImplementedError(self.fetch_objects)
504509
505 def get_determine_wants_revids(self, revids, include_tags=False):510 def get_determine_wants_revids(self, revids, include_tags=False, tag_selector=None):
506 wants = set()511 wants = set()
507 for revid in set(revids):512 for revid in set(revids):
508 if self.target.has_revision(revid):513 if self.target.has_revision(revid):
509 continue514 continue
510 git_sha, mapping = self.source.lookup_bzr_revision_id(revid)515 git_sha, mapping = self.source.lookup_bzr_revision_id(revid)
511 wants.add(git_sha)516 wants.add(git_sha)
512 return self.get_determine_wants_heads(wants, include_tags=include_tags)517 return self.get_determine_wants_heads(
518 wants, include_tags=include_tags, tag_selector=tag_selector)
513519
514 def fetch(self, revision_id=None, find_ghosts=False,520 def fetch(self, revision_id=None, find_ghosts=False,
515 mapping=None, fetch_spec=None, include_tags=False, lossy=False):521 mapping=None, fetch_spec=None, include_tags=False, lossy=False):
@@ -682,14 +688,14 @@
682 result.refs = wants_recorder.remote_refs688 result.refs = wants_recorder.remote_refs
683 return result689 return result
684690
685 def get_determine_wants_revids(self, revids, include_tags=False):691 def get_determine_wants_revids(self, revids, include_tags=False, tag_selector=None):
686 wants = set()692 wants = set()
687 for revid in set(revids):693 for revid in set(revids):
688 if revid == NULL_REVISION:694 if revid == NULL_REVISION:
689 continue695 continue
690 git_sha, mapping = self.source.lookup_bzr_revision_id(revid)696 git_sha, mapping = self.source.lookup_bzr_revision_id(revid)
691 wants.add(git_sha)697 wants.add(git_sha)
692 return self.get_determine_wants_heads(wants, include_tags=include_tags)698 return self.get_determine_wants_heads(wants, include_tags=include_tags, tag_selector=tag_selector)
693699
694 def get_determine_wants_branches(self, branches, include_tags=False):700 def get_determine_wants_branches(self, branches, include_tags=False):
695 def determine_wants(refs):701 def determine_wants(refs):
696702
=== modified file 'breezy/git/workingtree.py'
--- breezy/git/workingtree.py 2020-02-18 01:57:45 +0000
+++ breezy/git/workingtree.py 2020-02-21 04:09:29 +0000
@@ -1210,12 +1210,12 @@
12101210
1211 def pull(self, source, overwrite=False, stop_revision=None,1211 def pull(self, source, overwrite=False, stop_revision=None,
1212 change_reporter=None, possible_transports=None, local=False,1212 change_reporter=None, possible_transports=None, local=False,
1213 show_base=False):1213 show_base=False, tag_selector=None):
1214 with self.lock_write(), source.lock_read():1214 with self.lock_write(), source.lock_read():
1215 old_revision = self.branch.last_revision()1215 old_revision = self.branch.last_revision()
1216 count = self.branch.pull(source, overwrite, stop_revision,1216 count = self.branch.pull(source, overwrite, stop_revision,
1217 possible_transports=possible_transports,1217 possible_transports=possible_transports,
1218 local=local)1218 local=local, tag_selector=tag_selector)
1219 self._update_git_tree(1219 self._update_git_tree(
1220 old_revision=old_revision,1220 old_revision=old_revision,
1221 new_revision=self.branch.last_revision(),1221 new_revision=self.branch.last_revision(),
12221222
=== modified file 'breezy/plugins/propose/github.py'
--- breezy/plugins/propose/github.py 2020-02-18 01:57:45 +0000
+++ breezy/plugins/propose/github.py 2020-02-21 04:09:29 +0000
@@ -275,11 +275,12 @@
275 return json.loads(response.text)275 return json.loads(response.text)
276 raise InvalidHttpResponse(path, response.text)276 raise InvalidHttpResponse(path, response.text)
277277
278 def _create_pull(self, path, title, head, base, body=None, labels=None, assignee=None):278 def _create_pull(self, path, title, head, base, body=None, labels=None, assignee=None, draft=False):
279 data = {279 data = {
280 'title': title,280 'title': title,
281 'head': head,281 'head': head,
282 'base': base,282 'base': base,
283 'draft': draft,
283 }284 }
284 if labels is not None:285 if labels is not None:
285 data['labels'] = labels286 data['labels'] = labels
@@ -373,7 +374,7 @@
373374
374 def publish_derived(self, local_branch, base_branch, name, project=None,375 def publish_derived(self, local_branch, base_branch, name, project=None,
375 owner=None, revision_id=None, overwrite=False,376 owner=None, revision_id=None, overwrite=False,
376 allow_lossy=True):377 allow_lossy=True, tag_selector=None):
377 base_owner, base_project, base_branch_name = parse_github_branch_url(base_branch)378 base_owner, base_project, base_branch_name = parse_github_branch_url(base_branch)
378 base_repo = self._get_repo(base_owner, base_project)379 base_repo = self._get_repo(base_owner, base_project)
379 if owner is None:380 if owner is None:
@@ -393,13 +394,14 @@
393 try:394 try:
394 push_result = remote_dir.push_branch(395 push_result = remote_dir.push_branch(
395 local_branch, revision_id=revision_id, overwrite=overwrite,396 local_branch, revision_id=revision_id, overwrite=overwrite,
396 name=name)397 name=name, tag_selector=tag_selector)
397 except errors.NoRoundtrippingSupport:398 except errors.NoRoundtrippingSupport:
398 if not allow_lossy:399 if not allow_lossy:
399 raise400 raise
400 push_result = remote_dir.push_branch(401 push_result = remote_dir.push_branch(
401 local_branch, revision_id=revision_id,402 local_branch, revision_id=revision_id,
402 overwrite=overwrite, name=name, lossy=True)403 overwrite=overwrite, name=name, lossy=True,
404 tag_selector=tag_selector)
403 return push_result.target_branch, github_url_to_bzr_url(405 return push_result.target_branch, github_url_to_bzr_url(
404 remote_repo['html_url'], name)406 remote_repo['html_url'], name)
405407
406408
=== modified file 'breezy/plugins/propose/gitlabs.py'
--- breezy/plugins/propose/gitlabs.py 2020-02-18 01:57:45 +0000
+++ breezy/plugins/propose/gitlabs.py 2020-02-21 04:09:29 +0000
@@ -413,7 +413,7 @@
413413
414 def publish_derived(self, local_branch, base_branch, name, project=None,414 def publish_derived(self, local_branch, base_branch, name, project=None,
415 owner=None, revision_id=None, overwrite=False,415 owner=None, revision_id=None, overwrite=False,
416 allow_lossy=True):416 allow_lossy=True, tag_selector=None):
417 (host, base_project, base_branch_name) = parse_gitlab_branch_url(base_branch)417 (host, base_project, base_branch_name) = parse_gitlab_branch_url(base_branch)
418 if owner is None:418 if owner is None:
419 owner = self._get_logged_in_username()419 owner = self._get_logged_in_username()
@@ -428,13 +428,13 @@
428 try:428 try:
429 push_result = remote_dir.push_branch(429 push_result = remote_dir.push_branch(
430 local_branch, revision_id=revision_id, overwrite=overwrite,430 local_branch, revision_id=revision_id, overwrite=overwrite,
431 name=name)431 name=name, tag_selector=tag_selector)
432 except errors.NoRoundtrippingSupport:432 except errors.NoRoundtrippingSupport:
433 if not allow_lossy:433 if not allow_lossy:
434 raise434 raise
435 push_result = remote_dir.push_branch(435 push_result = remote_dir.push_branch(
436 local_branch, revision_id=revision_id, overwrite=overwrite,436 local_branch, revision_id=revision_id, overwrite=overwrite,
437 name=name, lossy=True)437 name=name, lossy=True, tag_selector=tag_selector)
438 public_url = gitlab_url_to_bzr_url(438 public_url = gitlab_url_to_bzr_url(
439 target_project['http_url_to_repo'], name)439 target_project['http_url_to_repo'], name)
440 return push_result.target_branch, public_url440 return push_result.target_branch, public_url
441441
=== modified file 'breezy/plugins/propose/launchpad.py'
--- breezy/plugins/propose/launchpad.py 2020-02-18 01:57:45 +0000
+++ breezy/plugins/propose/launchpad.py 2020-02-21 04:09:29 +0000
@@ -258,7 +258,8 @@
258 return "~%s/%s" % (owner, project)258 return "~%s/%s" % (owner, project)
259259
260 def _publish_git(self, local_branch, base_path, name, owner, project=None,260 def _publish_git(self, local_branch, base_path, name, owner, project=None,
261 revision_id=None, overwrite=False, allow_lossy=True):261 revision_id=None, overwrite=False, allow_lossy=True,
262 tag_selector=None):
262 to_path = self._get_derived_git_path(base_path, owner, project)263 to_path = self._get_derived_git_path(base_path, owner, project)
263 to_transport = get_transport("git+ssh://git.launchpad.net/" + to_path)264 to_transport = get_transport("git+ssh://git.launchpad.net/" + to_path)
264 try:265 try:
@@ -270,21 +271,23 @@
270 if dir_to is None:271 if dir_to is None:
271 try:272 try:
272 br_to = local_branch.create_clone_on_transport(273 br_to = local_branch.create_clone_on_transport(
273 to_transport, revision_id=revision_id, name=name)274 to_transport, revision_id=revision_id, name=name,
275 tag_selector=tag_selector)
274 except errors.NoRoundtrippingSupport:276 except errors.NoRoundtrippingSupport:
275 br_to = local_branch.create_clone_on_transport(277 br_to = local_branch.create_clone_on_transport(
276 to_transport, revision_id=revision_id, name=name,278 to_transport, revision_id=revision_id, name=name,
277 lossy=True)279 lossy=True, tag_selector=tag_selector)
278 else:280 else:
279 try:281 try:
280 dir_to = dir_to.push_branch(282 dir_to = dir_to.push_branch(
281 local_branch, revision_id, overwrite=overwrite, name=name)283 local_branch, revision_id, overwrite=overwrite, name=name,
284 tag_selector=tag_selector)
282 except errors.NoRoundtrippingSupport:285 except errors.NoRoundtrippingSupport:
283 if not allow_lossy:286 if not allow_lossy:
284 raise287 raise
285 dir_to = dir_to.push_branch(288 dir_to = dir_to.push_branch(
286 local_branch, revision_id, overwrite=overwrite, name=name,289 local_branch, revision_id, overwrite=overwrite, name=name,
287 lossy=True)290 lossy=True, tag_selector=tag_selector)
288 br_to = dir_to.target_branch291 br_to = dir_to.target_branch
289 return br_to, (292 return br_to, (
290 "https://git.launchpad.net/%s/+ref/%s" % (to_path, name))293 "https://git.launchpad.net/%s/+ref/%s" % (to_path, name))
@@ -310,7 +313,7 @@
310313
311 def _publish_bzr(self, local_branch, base_branch, name, owner,314 def _publish_bzr(self, local_branch, base_branch, name, owner,
312 project=None, revision_id=None, overwrite=False,315 project=None, revision_id=None, overwrite=False,
313 allow_lossy=True):316 allow_lossy=True, tag_selector=None):
314 to_path = self._get_derived_bzr_path(base_branch, name, owner, project)317 to_path = self._get_derived_bzr_path(base_branch, name, owner, project)
315 to_transport = get_transport("lp:" + to_path)318 to_transport = get_transport("lp:" + to_path)
316 try:319 try:
@@ -321,10 +324,11 @@
321324
322 if dir_to is None:325 if dir_to is None:
323 br_to = local_branch.create_clone_on_transport(326 br_to = local_branch.create_clone_on_transport(
324 to_transport, revision_id=revision_id)327 to_transport, revision_id=revision_id, tag_selector=tag_selector)
325 else:328 else:
326 br_to = dir_to.push_branch(329 br_to = dir_to.push_branch(
327 local_branch, revision_id, overwrite=overwrite).target_branch330 local_branch, revision_id, overwrite=overwrite,
331 tag_selector=tag_selector).target_branch
328 return br_to, ("https://code.launchpad.net/" + to_path)332 return br_to, ("https://code.launchpad.net/" + to_path)
329333
330 def _split_url(self, url):334 def _split_url(self, url):
@@ -341,7 +345,7 @@
341345
342 def publish_derived(self, local_branch, base_branch, name, project=None,346 def publish_derived(self, local_branch, base_branch, name, project=None,
343 owner=None, revision_id=None, overwrite=False,347 owner=None, revision_id=None, overwrite=False,
344 allow_lossy=True):348 allow_lossy=True, tag_selector=None):
345 """Publish a branch to the site, derived from base_branch.349 """Publish a branch to the site, derived from base_branch.
346350
347 :param base_branch: branch to derive the new branch from351 :param base_branch: branch to derive the new branch from
@@ -360,12 +364,12 @@
360 return self._publish_bzr(364 return self._publish_bzr(
361 local_branch, base_branch, name, project=project, owner=owner,365 local_branch, base_branch, name, project=project, owner=owner,
362 revision_id=revision_id, overwrite=overwrite,366 revision_id=revision_id, overwrite=overwrite,
363 allow_lossy=allow_lossy)367 allow_lossy=allow_lossy, tag_selector=tag_selector)
364 elif base_vcs == 'git':368 elif base_vcs == 'git':
365 return self._publish_git(369 return self._publish_git(
366 local_branch, base_path, name, project=project, owner=owner,370 local_branch, base_path, name, project=project, owner=owner,
367 revision_id=revision_id, overwrite=overwrite,371 revision_id=revision_id, overwrite=overwrite,
368 allow_lossy=allow_lossy)372 allow_lossy=allow_lossy, tag_selector=tag_selector)
369 else:373 else:
370 raise AssertionError('not a valid Launchpad URL')374 raise AssertionError('not a valid Launchpad URL')
371375
372376
=== modified file 'breezy/plugins/weave_fmt/bzrdir.py'
--- breezy/plugins/weave_fmt/bzrdir.py 2020-02-18 01:57:45 +0000
+++ breezy/plugins/weave_fmt/bzrdir.py 2020-02-21 04:09:29 +0000
@@ -745,7 +745,7 @@
745 return self._format.__class__()745 return self._format.__class__()
746746
747 def clone(self, url, revision_id=None, force_new_repo=False,747 def clone(self, url, revision_id=None, force_new_repo=False,
748 preserve_stacking=False):748 preserve_stacking=False, tag_selector=None):
749 """See ControlDir.clone().749 """See ControlDir.clone().
750750
751 force_new_repo has no effect, since this family of formats always751 force_new_repo has no effect, since this family of formats always
@@ -757,7 +757,7 @@
757 result = self._format._initialize_for_clone(url)757 result = self._format._initialize_for_clone(url)
758 self.open_repository().clone(result, revision_id=revision_id)758 self.open_repository().clone(result, revision_id=revision_id)
759 from_branch = self.open_branch()759 from_branch = self.open_branch()
760 from_branch.clone(result, revision_id=revision_id)760 from_branch.clone(result, revision_id=revision_id, tag_selector=tag_selector)
761 try:761 try:
762 tree = self.open_workingtree()762 tree = self.open_workingtree()
763 except errors.NotLocalUrl:763 except errors.NotLocalUrl:
764764
=== modified file 'breezy/propose.py'
--- breezy/propose.py 2020-02-18 01:57:45 +0000
+++ breezy/propose.py 2020-02-21 04:09:29 +0000
@@ -237,7 +237,7 @@
237237
238 def publish_derived(self, new_branch, base_branch, name, project=None,238 def publish_derived(self, new_branch, base_branch, name, project=None,
239 owner=None, revision_id=None, overwrite=False,239 owner=None, revision_id=None, overwrite=False,
240 allow_lossy=True):240 allow_lossy=True, tag_selector=None):
241 """Publish a branch to the site, derived from base_branch.241 """Publish a branch to the site, derived from base_branch.
242242
243 :param base_branch: branch to derive the new branch from243 :param base_branch: branch to derive the new branch from
244244
=== modified file 'breezy/tag.py'
--- breezy/tag.py 2020-02-18 01:57:45 +0000
+++ breezy/tag.py 2020-02-21 04:09:29 +0000
@@ -24,29 +24,22 @@
2424
25from collections import defaultdict25from collections import defaultdict
26import contextlib26import contextlib
27import itertools
28import re
29import sys
2730
28# NOTE: I was going to call this tags.py, but vim seems to think all files31# NOTE: I was going to call this tags.py, but vim seems to think all files
29# called tags* are ctags files... mbp 20070220.32# called tags* are ctags files... mbp 20070220.
3033
34from .inter import InterObject
31from .registry import Registry35from .registry import Registry
32from .lazy_import import lazy_import
33lazy_import(globals(), """
34import itertools
35import re
36import sys
37
38from breezy import (
39 bencode,
40 trace,
41 )
42""")
4336
44from . import (37from . import (
45 errors,38 errors,
46 )39 )
4740
4841
49def _reconcile_tags(source_dict, dest_dict, overwrite):42def _reconcile_tags(source_dict, dest_dict, overwrite, selector):
50 """Do a two-way merge of two tag dictionaries.43 """Do a two-way merge of two tag dictionaries.
5144
52 * only in source => source value45 * only in source => source value
@@ -62,6 +55,8 @@
62 updates = {}55 updates = {}
63 result = dict(dest_dict) # copy56 result = dict(dest_dict) # copy
64 for name, target in source_dict.items():57 for name, target in source_dict.items():
58 if selector and not selector(name):
59 continue
65 if result.get(name) == target:60 if result.get(name) == target:
66 pass61 pass
67 elif name not in result or overwrite:62 elif name not in result or overwrite:
@@ -72,7 +67,7 @@
72 return result, updates, conflicts67 return result, updates, conflicts
7368
7469
75class _Tags(object):70class Tags(object):
7671
77 def __init__(self, branch):72 def __init__(self, branch):
78 self.branch = branch73 self.branch = branch
@@ -83,21 +78,39 @@
83 raise NotImplementedError(self.get_tag_dict)78 raise NotImplementedError(self.get_tag_dict)
8479
85 def get_reverse_tag_dict(self):80 def get_reverse_tag_dict(self):
86 """Return a dictionary mapping revision ids to list of tags.81 """Returns a dict with revisions as keys
87 """82 and a list of tags for that revision as value"""
88 raise NotImplementedError(self.get_reverse_tag_dict)83 d = self.get_tag_dict()
8984 rev = defaultdict(set)
90 def merge_to(self, to_tags, overwrite=False, ignore_master=False):85 for key in d:
91 """Merge new tags from this tags container into another.86 rev[d[key]].add(key)
9287 return rev
93 :param to_tags: Tags container to merge into88
94 :param overwrite: Whether to overwrite existing, divergent, tags.89 def merge_to(self, to_tags, overwrite=False, ignore_master=False, selector=None):
90 """Copy tags between repositories if necessary and possible.
91
92 This method has common command-line behaviour about handling
93 error cases.
94
95 All new definitions are copied across, except that tags that already
96 exist keep their existing definitions.
97
98 :param to_tags: Branch to receive these tags
99 :param overwrite: Overwrite conflicting tags in the target branch
95 :param ignore_master: Do not modify the tags in the target's master100 :param ignore_master: Do not modify the tags in the target's master
96 branch (if any). Default is false (so the master will be updated).101 branch (if any). Default is false (so the master will be updated).
97 New in bzr 2.3.102
98 :return: Tuple with tag updates as dictionary and tag conflicts103 :returns: Tuple with tag_updates and tag_conflicts.
104 tag_updates is a dictionary with new tags, None is used for
105 removed tags
106 tag_conflicts is a set of tags that conflicted, each of which is
107 (tagname, source_target, dest_target), or None if no copying was
108 done.
99 """109 """
100 raise NotImplementedError(self.merge_to)110 intertags = InterTags.get(self, to_tags)
111 return intertags.merge(
112 overwrite=overwrite, ignore_master=ignore_master,
113 selector=selector)
101114
102 def set_tag(self, tag_name, revision):115 def set_tag(self, tag_name, revision):
103 """Set a tag.116 """Set a tag.
@@ -127,18 +140,21 @@
127 raise NotImplementedError(self.delete_tag)140 raise NotImplementedError(self.delete_tag)
128141
129 def rename_revisions(self, rename_map):142 def rename_revisions(self, rename_map):
130 """Replace revision ids according to a rename map.143 """Rename revisions in this tags dictionary.
131144
132 :param rename_map: Dictionary mapping old revision ids to145 :param rename_map: Dictionary mapping old revids to new revids
133 new revision ids.
134 """146 """
135 raise NotImplementedError(self.rename_revisions)147 reverse_tags = self.get_reverse_tag_dict()
148 for revid, names in reverse_tags.items():
149 if revid in rename_map:
150 for name in names:
151 self.set_tag(name, rename_map[revid])
136152
137 def has_tag(self, tag_name):153 def has_tag(self, tag_name):
138 return tag_name in self.get_tag_dict()154 return tag_name in self.get_tag_dict()
139155
140156
141class DisabledTags(_Tags):157class DisabledTags(Tags):
142 """Tag storage that refuses to store anything.158 """Tag storage that refuses to store anything.
143159
144 This is used by older formats that can't store tags.160 This is used by older formats that can't store tags.
@@ -153,7 +169,7 @@
153 lookup_tag = _not_supported169 lookup_tag = _not_supported
154 delete_tag = _not_supported170 delete_tag = _not_supported
155171
156 def merge_to(self, to_tags, overwrite=False, ignore_master=False):172 def merge_to(self, to_tags, overwrite=False, ignore_master=False, selector=None):
157 # we never have anything to copy173 # we never have anything to copy
158 return {}, []174 return {}, []
159175
@@ -166,102 +182,19 @@
166 return {}182 return {}
167183
168184
169class BasicTags(_Tags):185class InterTags(InterObject):
170 """Tag storage in an unversioned branch control file.186 """Operations between sets of tags.
171 """187 """
172188
173 def set_tag(self, tag_name, tag_target):189 _optimisers = []
174 """Add a tag definition to the branch.190 """The available optimised InterTags types."""
175191
176 Behaviour if the tag is already present is not defined (yet).192 @classmethod
177 """193 def is_compatible(klass, source, target):
178 # all done with a write lock held, so this looks atomic194 # This is the default implementation
179 with self.branch.lock_write():195 return True
180 master = self.branch.get_master_branch()196
181 if master is not None:197 def merge(self, overwrite=False, ignore_master=False, selector=None):
182 master.tags.set_tag(tag_name, tag_target)
183 td = self.get_tag_dict()
184 td[tag_name] = tag_target
185 self._set_tag_dict(td)
186
187 def lookup_tag(self, tag_name):
188 """Return the referent string of a tag"""
189 td = self.get_tag_dict()
190 try:
191 return td[tag_name]
192 except KeyError:
193 raise errors.NoSuchTag(tag_name)
194
195 def get_tag_dict(self):
196 with self.branch.lock_read():
197 try:
198 tag_content = self.branch._get_tags_bytes()
199 except errors.NoSuchFile:
200 # ugly, but only abentley should see this :)
201 trace.warning('No branch/tags file in %s. '
202 'This branch was probably created by bzr 0.15pre. '
203 'Create an empty file to silence this message.'
204 % (self.branch, ))
205 return {}
206 return self._deserialize_tag_dict(tag_content)
207
208 def get_reverse_tag_dict(self):
209 """Returns a dict with revisions as keys
210 and a list of tags for that revision as value"""
211 d = self.get_tag_dict()
212 rev = defaultdict(set)
213 for key in d:
214 rev[d[key]].add(key)
215 return rev
216
217 def delete_tag(self, tag_name):
218 """Delete a tag definition.
219 """
220 with self.branch.lock_write():
221 d = self.get_tag_dict()
222 try:
223 del d[tag_name]
224 except KeyError:
225 raise errors.NoSuchTag(tag_name)
226 master = self.branch.get_master_branch()
227 if master is not None:
228 try:
229 master.tags.delete_tag(tag_name)
230 except errors.NoSuchTag:
231 pass
232 self._set_tag_dict(d)
233
234 def _set_tag_dict(self, new_dict):
235 """Replace all tag definitions
236
237 WARNING: Calling this on an unlocked branch will lock it, and will
238 replace the tags without warning on conflicts.
239
240 :param new_dict: Dictionary from tag name to target.
241 """
242 return self.branch._set_tags_bytes(self._serialize_tag_dict(new_dict))
243
244 def _serialize_tag_dict(self, tag_dict):
245 td = dict((k.encode('utf-8'), v)
246 for k, v in tag_dict.items())
247 return bencode.bencode(td)
248
249 def _deserialize_tag_dict(self, tag_content):
250 """Convert the tag file into a dictionary of tags"""
251 # was a special case to make initialization easy, an empty definition
252 # is an empty dictionary
253 if tag_content == b'':
254 return {}
255 try:
256 r = {}
257 for k, v in bencode.bdecode(tag_content).items():
258 r[k.decode('utf-8')] = v
259 return r
260 except ValueError as e:
261 raise ValueError("failed to deserialize tag dictionary %r: %s"
262 % (tag_content, e))
263
264 def merge_to(self, to_tags, overwrite=False, ignore_master=False):
265 """Copy tags between repositories if necessary and possible.198 """Copy tags between repositories if necessary and possible.
266199
267 This method has common command-line behaviour about handling200 This method has common command-line behaviour about handling
@@ -274,7 +207,9 @@
274 :param overwrite: Overwrite conflicting tags in the target branch207 :param overwrite: Overwrite conflicting tags in the target branch
275 :param ignore_master: Do not modify the tags in the target's master208 :param ignore_master: Do not modify the tags in the target's master
276 branch (if any). Default is false (so the master will be updated).209 branch (if any). Default is false (so the master will be updated).
277 New in bzr 2.3.210 :param selector: Callback that determines whether a tag should be
211 copied. It should take a tag name and as argument and return a
212 boolean.
278213
279 :returns: Tuple with tag_updates and tag_conflicts.214 :returns: Tuple with tag_updates and tag_conflicts.
280 tag_updates is a dictionary with new tags, None is used for215 tag_updates is a dictionary with new tags, None is used for
@@ -284,12 +219,12 @@
284 done.219 done.
285 """220 """
286 with contextlib.ExitStack() as stack:221 with contextlib.ExitStack() as stack:
287 if self.branch == to_tags.branch:222 if self.source.branch == self.target.branch:
288 return {}, []223 return {}, []
289 if not self.branch.supports_tags():224 if not self.source.branch.supports_tags():
290 # obviously nothing to copy225 # obviously nothing to copy
291 return {}, []226 return {}, []
292 source_dict = self.get_tag_dict()227 source_dict = self.source.get_tag_dict()
293 if not source_dict:228 if not source_dict:
294 # no tags in the source, and we don't want to clobber anything229 # no tags in the source, and we don't want to clobber anything
295 # that's in the destination230 # that's in the destination
@@ -306,44 +241,35 @@
306 # Ideally we'd improve this API to report the different conflicts241 # Ideally we'd improve this API to report the different conflicts
307 # more clearly to the caller, but we don't want to break plugins242 # more clearly to the caller, but we don't want to break plugins
308 # such as bzr-builddeb that use this API.243 # such as bzr-builddeb that use this API.
309 stack.enter_context(to_tags.branch.lock_write())244 stack.enter_context(self.target.branch.lock_write())
310 if ignore_master:245 if ignore_master:
311 master = None246 master = None
312 else:247 else:
313 master = to_tags.branch.get_master_branch()248 master = self.target.branch.get_master_branch()
314 if master is not None:249 if master is not None:
315 stack.enter_context(master.lock_write())250 stack.enter_context(master.lock_write())
316 updates, conflicts = self._merge_to(to_tags, source_dict, overwrite)251 updates, conflicts = self._merge_to(
252 self.target, source_dict, overwrite, selector=selector)
317 if master is not None:253 if master is not None:
318 extra_updates, extra_conflicts = self._merge_to(master.tags,254 extra_updates, extra_conflicts = self._merge_to(
319 source_dict, overwrite)255 master.tags, source_dict, overwrite, selector=selector)
320 updates.update(extra_updates)256 updates.update(extra_updates)
321 conflicts += extra_conflicts257 conflicts += extra_conflicts
322 # We use set() to remove any duplicate conflicts from the master258 # We use set() to remove any duplicate conflicts from the master
323 # branch.259 # branch.
324 return updates, set(conflicts)260 return updates, set(conflicts)
325261
326 def _merge_to(self, to_tags, source_dict, overwrite):262 @classmethod
263 def _merge_to(cls, to_tags, source_dict, overwrite, selector):
327 dest_dict = to_tags.get_tag_dict()264 dest_dict = to_tags.get_tag_dict()
328 result, updates, conflicts = _reconcile_tags(265 result, updates, conflicts = _reconcile_tags(
329 source_dict, dest_dict, overwrite)266 source_dict, dest_dict, overwrite, selector)
330 if result != dest_dict:267 if result != dest_dict:
331 to_tags._set_tag_dict(result)268 to_tags._set_tag_dict(result)
332 return updates, conflicts269 return updates, conflicts
333270
334 def rename_revisions(self, rename_map):271
335 """Rename revisions in this tags dictionary.272class MemoryTags(Tags):
336
337 :param rename_map: Dictionary mapping old revids to new revids
338 """
339 reverse_tags = self.get_reverse_tag_dict()
340 for revid, names in reverse_tags.items():
341 if revid in rename_map:
342 for name in names:
343 self.set_tag(name, rename_map[revid])
344
345
346class MemoryTags(_Tags):
347273
348 def __init__(self, tag_dict):274 def __init__(self, tag_dict):
349 self._tag_dict = tag_dict275 self._tag_dict = tag_dict
@@ -359,15 +285,6 @@
359 except KeyError:285 except KeyError:
360 raise errors.NoSuchTag(tag_name)286 raise errors.NoSuchTag(tag_name)
361287
362 def get_reverse_tag_dict(self):
363 """Returns a dict with revisions as keys
364 and a list of tags for that revision as value"""
365 d = self.get_tag_dict()
366 rev = defaultdict(set)
367 for key in d:
368 rev[d[key]].add(key)
369 return rev
370
371 def set_tag(self, name, revid):288 def set_tag(self, name, revid):
372 self._tag_dict[name] = revid289 self._tag_dict[name] = revid
373290
@@ -385,11 +302,11 @@
385 def _set_tag_dict(self, result):302 def _set_tag_dict(self, result):
386 self._tag_dict = dict(result.items())303 self._tag_dict = dict(result.items())
387304
388 def merge_to(self, to_tags, overwrite=False, ignore_master=False):305 def merge_to(self, to_tags, overwrite=False, ignore_master=False, selector=None):
389 source_dict = self.get_tag_dict()306 source_dict = self.get_tag_dict()
390 dest_dict = to_tags.get_tag_dict()307 dest_dict = to_tags.get_tag_dict()
391 result, updates, conflicts = _reconcile_tags(308 result, updates, conflicts = _reconcile_tags(
392 source_dict, dest_dict, overwrite)309 source_dict, dest_dict, overwrite, selector)
393 if result != dest_dict:310 if result != dest_dict:
394 to_tags._set_tag_dict(result)311 to_tags._set_tag_dict(result)
395 return updates, conflicts312 return updates, conflicts
@@ -441,3 +358,6 @@
441tag_sort_methods.register("alpha", sort_alpha, 'Sort tags lexicographically.')358tag_sort_methods.register("alpha", sort_alpha, 'Sort tags lexicographically.')
442tag_sort_methods.register("time", sort_time, 'Sort tags chronologically.')359tag_sort_methods.register("time", sort_time, 'Sort tags chronologically.')
443tag_sort_methods.default_key = "natural"360tag_sort_methods.default_key = "natural"
361
362
363InterTags.register_optimiser(InterTags)
444364
=== modified file 'breezy/tests/per_branch/test_pull.py'
--- breezy/tests/per_branch/test_pull.py 2018-11-17 20:50:40 +0000
+++ breezy/tests/per_branch/test_pull.py 2020-02-21 04:09:29 +0000
@@ -118,7 +118,7 @@
118 self.assertEqual(p1, result.old_revid)118 self.assertEqual(p1, result.old_revid)
119 self.assertEqual(2, result.new_revno)119 self.assertEqual(2, result.new_revno)
120 self.assertEqual(m1, result.new_revid)120 self.assertEqual(m1, result.new_revid)
121 self.assertEqual([], result.tag_conflicts)121 self.assertEqual([], list(result.tag_conflicts))
122122
123 def test_pull_overwrite(self):123 def test_pull_overwrite(self):
124 tree_a = self.make_branch_and_tree('tree_a')124 tree_a = self.make_branch_and_tree('tree_a')
125125
=== modified file 'breezy/tests/per_branch/test_tags.py'
--- breezy/tests/per_branch/test_tags.py 2019-02-02 21:58:23 +0000
+++ breezy/tests/per_branch/test_tags.py 2020-02-21 04:09:29 +0000
@@ -142,6 +142,21 @@
142 self.assertEqual(updates, {})142 self.assertEqual(updates, {})
143 self.assertEqual(b2.tags.lookup_tag('conflicts'), revid2)143 self.assertEqual(b2.tags.lookup_tag('conflicts'), revid2)
144144
145 def test_merge_tags_selector(self):
146 b1, [revid, revid1] = self.make_branch_with_revision_tuple('b1', 2)
147 w2 = b1.controldir.sprout('b2', revision_id=revid).open_workingtree()
148 revid2 = w2.commit('revision 2')
149 b2 = w2.branch
150 # if there are tags in the source and not the destination, then they
151 # just go across
152 b1.tags.set_tag('tag1', revid)
153 b1.tags.set_tag('tag2', revid2)
154 updates, conflicts = b1.tags.merge_to(b2.tags, selector=lambda x: x == 'tag1')
155 self.assertEqual({'tag1': revid}, updates)
156 self.assertEqual(set(), set(conflicts))
157 self.assertEqual(b2.tags.lookup_tag('tag1'), revid)
158 self.assertRaises(errors.NoSuchTag, b2.tags.lookup_tag, 'tag2')
159
145 def test_unicode_tag(self):160 def test_unicode_tag(self):
146 tag_name = u'\u3070'161 tag_name = u'\u3070'
147 b1, [revid] = self.make_branch_with_revision_tuple('b', 1)162 b1, [revid] = self.make_branch_with_revision_tuple('b', 1)
148163
=== modified file 'breezy/tests/per_controldir/test_push.py'
--- breezy/tests/per_controldir/test_push.py 2019-02-15 03:42:06 +0000
+++ breezy/tests/per_controldir/test_push.py 2020-02-21 04:09:29 +0000
@@ -99,3 +99,14 @@
99 self.assertEqual(2, result.branch_push_result.new_revno)99 self.assertEqual(2, result.branch_push_result.new_revno)
100 self.assertEqual(tree.branch.base, result.source_branch.base)100 self.assertEqual(tree.branch.base, result.source_branch.base)
101 self.assertEqual(dir.open_branch().base, result.target_branch.base)101 self.assertEqual(dir.open_branch().base, result.target_branch.base)
102
103 def test_push_tag_selector(self):
104 tree, rev1 = self.create_simple_tree()
105 try:
106 tree.branch.tags.set_tag('tag1', rev1)
107 except TagsNotSupported:
108 raise TestNotApplicable('tags not supported')
109 tree.branch.tags.set_tag('tag2', rev1)
110 dir = self.make_repository('dir').controldir
111 dir.push_branch(tree.branch, tag_selector=lambda x: x == 'tag1')
112 self.assertEqual({'tag1': rev1}, dir.open_branch().tags.get_tag_dict())
102113
=== modified file 'breezy/tests/per_interbranch/test_pull.py'
--- breezy/tests/per_interbranch/test_pull.py 2018-11-11 04:08:32 +0000
+++ breezy/tests/per_interbranch/test_pull.py 2020-02-21 04:09:29 +0000
@@ -144,7 +144,7 @@
144 self.assertEqual(p1, result.old_revid)144 self.assertEqual(p1, result.old_revid)
145 self.assertEqual(2, result.new_revno)145 self.assertEqual(2, result.new_revno)
146 self.assertEqual(m1, result.new_revid)146 self.assertEqual(m1, result.new_revid)
147 self.assertEqual([], result.tag_conflicts)147 self.assertEqual([], list(result.tag_conflicts))
148148
149 def test_pull_overwrite(self):149 def test_pull_overwrite(self):
150 tree_a = self.make_from_branch_and_tree('tree_a')150 tree_a = self.make_from_branch_and_tree('tree_a')
@@ -180,6 +180,26 @@
180 self.assertEqual(tree_b.branch.last_revision(),180 self.assertEqual(tree_b.branch.last_revision(),
181 tree_a.branch.last_revision())181 tree_a.branch.last_revision())
182182
183 def test_pull_tag_selector(self):
184 if not self.branch_format_from.supports_tags():
185 raise TestNotApplicable('from format does not support tags')
186 if not self.branch_format_to.supports_tags():
187 raise TestNotApplicable('to format does not support tags')
188 tree_a = self.make_from_branch_and_tree('tree_a')
189 revid1 = tree_a.commit('message 1')
190 try:
191 tree_b = self.sprout_to(
192 tree_a.controldir, 'tree_b').open_workingtree()
193 except errors.NoRoundtrippingSupport:
194 raise TestNotApplicable(
195 'lossless push between %r and %r not supported' %
196 (self.branch_format_from, self.branch_format_to))
197 tree_b.branch.tags.set_tag('tag1', revid1)
198 tree_b.branch.tags.set_tag('tag2', revid1)
199 tree_b.branch.get_config_stack().set('branch.fetch_tags', True)
200 tree_a.pull(tree_b.branch, tag_selector=lambda x: x == 'tag1')
201 self.assertEqual({'tag1': revid1}, tree_a.branch.tags.get_tag_dict())
202
183203
184class TestPullHook(TestCaseWithInterBranch):204class TestPullHook(TestCaseWithInterBranch):
185205
186206
=== modified file 'breezy/tests/per_interbranch/test_push.py'
--- breezy/tests/per_interbranch/test_push.py 2020-02-07 02:14:30 +0000
+++ breezy/tests/per_interbranch/test_push.py 2020-02-21 04:09:29 +0000
@@ -374,6 +374,26 @@
374 self.overrideAttr(SmartServerRepositoryGetParentMap,374 self.overrideAttr(SmartServerRepositoryGetParentMap,
375 'no_extra_results', True)375 'no_extra_results', True)
376376
377 def test_push_tag_selector(self):
378 if not self.branch_format_from.supports_tags():
379 raise tests.TestNotApplicable('from format does not support tags')
380 if not self.branch_format_to.supports_tags():
381 raise tests.TestNotApplicable('to format does not support tags')
382 tree_a = self.make_from_branch_and_tree('tree_a')
383 revid1 = tree_a.commit('message 1')
384 try:
385 tree_b = self.sprout_to(
386 tree_a.controldir, 'tree_b').open_workingtree()
387 except errors.NoRoundtrippingSupport:
388 raise tests.TestNotApplicable(
389 'lossless push between %r and %r not supported' %
390 (self.branch_format_from, self.branch_format_to))
391 tree_b.branch.tags.set_tag('tag1', revid1)
392 tree_b.branch.tags.set_tag('tag2', revid1)
393 tree_b.branch.get_config_stack().set('branch.fetch_tags', True)
394 tree_b.branch.push(tree_a.branch, tag_selector=lambda x: x == 'tag1')
395 self.assertEqual({'tag1': revid1}, tree_a.branch.tags.get_tag_dict())
396
377397
378class TestPushHook(TestCaseWithInterBranch):398class TestPushHook(TestCaseWithInterBranch):
379399
380400
=== modified file 'breezy/tests/test_foreign.py'
--- breezy/tests/test_foreign.py 2018-11-25 20:44:56 +0000
+++ breezy/tests/test_foreign.py 2020-02-21 04:09:29 +0000
@@ -171,7 +171,7 @@
171 def is_compatible(source, target):171 def is_compatible(source, target):
172 return isinstance(target, DummyForeignVcsBranch)172 return isinstance(target, DummyForeignVcsBranch)
173173
174 def push(self, overwrite=False, stop_revision=None, lossy=False):174 def push(self, overwrite=False, stop_revision=None, lossy=False, tag_selector=None):
175 if not lossy:175 if not lossy:
176 raise errors.NoRoundtrippingSupport(self.source, self.target)176 raise errors.NoRoundtrippingSupport(self.source, self.target)
177 result = branch.BranchPushResult()177 result = branch.BranchPushResult()
178178
=== modified file 'breezy/tests/test_tag.py'
--- breezy/tests/test_tag.py 2019-02-01 16:29:45 +0000
+++ breezy/tests/test_tag.py 2020-02-21 04:09:29 +0000
@@ -22,10 +22,12 @@
22 errors,22 errors,
23 )23 )
24from breezy.tag import (24from breezy.tag import (
25 BasicTags,
26 DisabledTags,25 DisabledTags,
27 MemoryTags,26 MemoryTags,
28 )27 )
28from breezy.bzr.tag import (
29 BasicTags,
30 )
29from breezy.tests import (31from breezy.tests import (
30 TestCase,32 TestCase,
31 TestCaseWithTransport,33 TestCaseWithTransport,
@@ -120,6 +122,17 @@
120 self.assertEqual({u'tag-2': b'z'}, updates)122 self.assertEqual({u'tag-2': b'z'}, updates)
121 self.assertEqual(b'z', b.tags.lookup_tag('tag-2'))123 self.assertEqual(b'z', b.tags.lookup_tag('tag-2'))
122124
125 def test_merge_to_with_selector(self):
126 a = self.make_branch_supporting_tags('a')
127 b = self.make_branch_supporting_tags('b')
128 # simple merge
129 a.tags.set_tag('tag-1', b'x')
130 a.tags.set_tag('tag-2', b'y')
131 updates, conflicts = a.tags.merge_to(b.tags, selector=lambda x: x == 'tag-1')
132 self.assertEqual(list(conflicts), [])
133 self.assertEqual({u'tag-1': b'x'}, updates)
134 self.assertRaises(errors.NoSuchTag, b.tags.lookup_tag, 'tag-2')
135
123136
124class TestTagsInCheckouts(TestCaseWithTransport):137class TestTagsInCheckouts(TestCaseWithTransport):
125 """Tests for how tags are synchronised between the master and child branch138 """Tests for how tags are synchronised between the master and child branch
126139
=== modified file 'breezy/workingtree.py'
--- breezy/workingtree.py 2020-02-18 01:57:45 +0000
+++ breezy/workingtree.py 2020-02-21 04:09:29 +0000
@@ -819,13 +819,13 @@
819819
820 def pull(self, source, overwrite=False, stop_revision=None,820 def pull(self, source, overwrite=False, stop_revision=None,
821 change_reporter=None, possible_transports=None, local=False,821 change_reporter=None, possible_transports=None, local=False,
822 show_base=False):822 show_base=False, tag_selector=None):
823 with self.lock_write(), source.lock_read():823 with self.lock_write(), source.lock_read():
824 old_revision_info = self.branch.last_revision_info()824 old_revision_info = self.branch.last_revision_info()
825 basis_tree = self.basis_tree()825 basis_tree = self.basis_tree()
826 count = self.branch.pull(source, overwrite, stop_revision,826 count = self.branch.pull(source, overwrite, stop_revision,
827 possible_transports=possible_transports,827 possible_transports=possible_transports,
828 local=local)828 local=local, tag_selector=tag_selector)
829 new_revision_info = self.branch.last_revision_info()829 new_revision_info = self.branch.last_revision_info()
830 if new_revision_info != old_revision_info:830 if new_revision_info != old_revision_info:
831 repository = self.branch.repository831 repository = self.branch.repository

Subscribers

People subscribed via source and target branches