64
72
self.assertFalse(b.tags.has_tag('imaginary'))
66
74
def test_reverse_tag_dict(self):
67
b = self.make_branch('b')
75
b = self.make_branch_with_revisions('b',
76
['target-revid-1', 'target-revid-2'])
68
77
b.tags.set_tag('tag-name', 'target-revid-1')
69
78
b.tags.set_tag('other-name', 'target-revid-2')
70
79
# then reopen the branch and check reverse map id->tags list
71
80
b = branch.Branch.open('b')
72
self.assertEqual(b.tags.get_reverse_tag_dict(),
81
self.assertEqual(dict(b.tags.get_reverse_tag_dict()),
73
82
{'target-revid-1': ['tag-name'],
74
83
'target-revid-2': ['other-name'],
86
def test_ghost_tag(self):
87
b = self.make_branch('b')
88
if not b._format.supports_tags_referencing_ghosts():
89
self.assertRaises(errors.GhostTagsNotSupported,
90
b.tags.set_tag, "ghost", "idontexist")
92
b.tags.set_tag("ghost", "idontexist")
93
self.assertEqual("idontexist", b.tags.lookup_tag("ghost"))
77
95
def test_no_such_tag(self):
78
96
b = self.make_branch('b')
80
98
b.tags.lookup_tag('bosko')
81
99
except errors.NoSuchTag, e:
82
self.assertEquals(e.tag_name, 'bosko')
83
self.assertEquals(str(e), 'No such tag: bosko')
100
self.assertEqual(e.tag_name, 'bosko')
101
self.assertEqual(str(e), 'No such tag: bosko')
85
103
self.fail("didn't get expected exception")
87
105
def test_merge_tags(self):
88
b1 = self.make_branch('b1')
89
b2 = self.make_branch('b2')
106
b1 = self.make_branch_with_revisions('b1', ['revid', 'revid-1'])
107
b2 = self.make_branch_with_revisions('b2', ['revid', 'revid-2'])
90
108
# if there are tags in the source and not the destination, then they
92
110
b1.tags.set_tag('tagname', 'revid')
93
111
b1.tags.merge_to(b2.tags)
94
self.assertEquals(b2.tags.lookup_tag('tagname'), 'revid')
112
self.assertEqual(b2.tags.lookup_tag('tagname'), 'revid')
95
113
# if a tag is in the destination and not in the source, it is not
96
114
# removed when we merge them
97
115
b2.tags.set_tag('in-destination', 'revid')
98
result = b1.tags.merge_to(b2.tags)
99
self.assertEquals(result, [])
100
self.assertEquals(b2.tags.lookup_tag('in-destination'), 'revid')
116
updates, conflicts = b1.tags.merge_to(b2.tags)
117
self.assertEqual(list(conflicts), [])
118
self.assertEqual(updates, {})
119
self.assertEqual(b2.tags.lookup_tag('in-destination'), 'revid')
101
120
# if there's a conflicting tag, it's reported -- the command line
102
121
# interface will say "these tags couldn't be copied"
103
122
b1.tags.set_tag('conflicts', 'revid-1')
104
123
b2.tags.set_tag('conflicts', 'revid-2')
105
result = b1.tags.merge_to(b2.tags)
106
self.assertEquals(result,
124
updates, conflicts = b1.tags.merge_to(b2.tags)
125
self.assertEqual(list(conflicts),
107
126
[('conflicts', 'revid-1', 'revid-2')])
108
127
# and it keeps the same value
109
self.assertEquals(b2.tags.lookup_tag('conflicts'), 'revid-2')
128
self.assertEqual(updates, {})
129
self.assertEqual(b2.tags.lookup_tag('conflicts'), 'revid-2')
112
131
def test_unicode_tag(self):
113
b1 = self.make_branch('b')
114
132
tag_name = u'\u3070'
115
133
# in anticipation of the planned change to treating revision ids as
116
134
# just 8bit strings
117
135
revid = ('revid' + tag_name).encode('utf-8')
136
b1 = self.make_branch_with_revisions('b', [revid])
118
137
b1.tags.set_tag(tag_name, revid)
119
self.assertEquals(b1.tags.lookup_tag(tag_name), revid)
138
self.assertEqual(b1.tags.lookup_tag(tag_name), revid)
121
140
def test_delete_tag(self):
122
b = self.make_branch('b')
123
141
tag_name = u'\N{GREEK SMALL LETTER ALPHA}'
124
142
revid = ('revid' + tag_name).encode('utf-8')
143
b = self.make_branch_with_revisions('b', [revid])
125
144
b.tags.set_tag(tag_name, revid)
126
145
# now try to delete it
127
146
b.tags.delete_tag(tag_name)
143
162
b2 = self.make_branch('b2')
144
163
b1.tags.merge_to(b2.tags)
165
def test_read_lock_caches_tags(self):
166
"""Tags are read from a branch only once during a read-lock."""
167
# Open the same branch twice. Read-lock one, and then mutate the tags
168
# in the second. The read-locked branch never re-reads the tags, so it
169
# never observes the changed/new tags.
170
b1 = self.make_branch_with_revisions('b',
171
['rev-1', 'rev-1-changed', 'rev-2'])
172
b1.tags.set_tag('one', 'rev-1')
173
b2 = controldir.ControlDir.open('b').open_branch()
175
self.assertEqual({'one': 'rev-1'}, b1.tags.get_tag_dict())
176
# Add a tag and modify a tag in b2. b1 is read-locked and has already
177
# read the tags, so it is unaffected.
178
b2.tags.set_tag('one', 'rev-1-changed')
179
b2.tags.set_tag('two', 'rev-2')
180
self.assertEqual({'one': 'rev-1'}, b1.tags.get_tag_dict())
182
# Once unlocked the cached value is forgotten, so now the latest tags
185
{'one': 'rev-1-changed', 'two': 'rev-2'}, b1.tags.get_tag_dict())
187
def test_unlocked_does_not_cache_tags(self):
188
"""Unlocked branches do not cache tags."""
189
# Open the same branch twice.
190
b1 = self.make_branch_with_revisions('b',
191
['rev-1', 'rev-1-changed', 'rev-2'])
192
b1.tags.set_tag('one', 'rev-1')
193
b2 = b1.bzrdir.open_branch()
194
self.assertEqual({'one': 'rev-1'}, b1.tags.get_tag_dict())
195
# Add a tag and modify a tag in b2. b1 isn't locked, so it will
196
# immediately return the new tags too.
197
b2.tags.set_tag('one', 'rev-1-changed')
198
b2.tags.set_tag('two', 'rev-2')
200
{'one': 'rev-1-changed', 'two': 'rev-2'}, b1.tags.get_tag_dict())
202
def test_cached_tag_dict_not_accidentally_mutable(self):
203
"""When there's a cached version of the tags, b.tags.get_tag_dict
204
returns a copy of the cached data so that callers cannot accidentally
207
b = self.make_branch_with_revisions('b',
208
['rev-1', 'rev-2', 'rev-3'])
209
b.tags.set_tag('one', 'rev-1')
210
self.addCleanup(b.lock_read().unlock)
211
# The first time the data returned will not be in the cache
212
tags_dict = b.tags.get_tag_dict()
213
tags_dict['two'] = 'rev-2'
214
# The second time the data comes from the cache
215
tags_dict = b.tags.get_tag_dict()
216
tags_dict['three'] = 'rev-3'
217
# The get_tag_dict() result should still be unchanged, even though we
218
# mutated its earlier return values.
219
self.assertEqual({'one': 'rev-1'}, b.tags.get_tag_dict())
221
def make_write_locked_branch_with_one_tag(self):
222
b = self.make_branch_with_revisions('b',
223
['rev-1', 'rev-1-changed', 'rev-2'])
224
b.tags.set_tag('one', 'rev-1')
225
self.addCleanup(b.lock_write().unlock)
227
b.tags.get_tag_dict()
230
def test_set_tag_invalides_cache(self):
231
b = self.make_write_locked_branch_with_one_tag()
232
b.tags.set_tag('one', 'rev-1-changed')
233
self.assertEqual({'one': 'rev-1-changed'}, b.tags.get_tag_dict())
235
def test_delete_tag_invalides_cache(self):
236
b = self.make_write_locked_branch_with_one_tag()
237
b.tags.delete_tag('one')
238
self.assertEqual({}, b.tags.get_tag_dict())
240
def test_merge_to_invalides_cache(self):
241
b1 = self.make_write_locked_branch_with_one_tag()
242
b2 = self.make_branch_with_revisions('b2', ['rev-2', 'rev-1'])
243
b2.tags.set_tag('two', 'rev-2')
244
b2.tags.merge_to(b1.tags)
246
{'one': 'rev-1', 'two': 'rev-2'}, b1.tags.get_tag_dict())
248
def test_rename_revisions_invalides_cache(self):
249
b = self.make_write_locked_branch_with_one_tag()
250
b.tags.rename_revisions({'rev-1': 'rev-1-changed'})
251
self.assertEqual({'one': 'rev-1-changed'}, b.tags.get_tag_dict())
254
class TestTagsMergeToInCheckouts(per_branch.TestCaseWithBranch):
255
"""Tests for checkout.branch.tags.merge_to.
257
In particular this exercises variations in tag conflicts in the master
258
branch and/or the checkout (child). It may seem strange to have different
259
tags in the child and master, but 'bzr merge' intentionally updates the
260
child and not the master (instead the next 'bzr commit', if the user
261
decides to commit, will update the master). Also, merge_to in bzr < 2.3
262
didn't propagate changes to the master, and current bzr versions may find
263
themselves operating on checkouts touched by older bzrs
265
So we need to make sure bzr copes gracefully with differing tags in the
266
master versus the child.
268
See also <https://bugs.launchpad.net/bzr/+bug/603395>.
272
super(TestTagsMergeToInCheckouts, self).setUp()
273
branch1 = self.make_branch('tags-probe')
274
if not branch1._format.supports_tags():
275
raise tests.TestSkipped(
276
"format %s doesn't support tags" % branch1._format)
277
branch2 = self.make_branch('bind-probe')
279
branch2.bind(branch1)
280
except errors.UpgradeRequired:
281
raise tests.TestNotApplicable(
282
"format %s doesn't support bound branches" % branch2._format)
284
def test_merge_to_propagates_tags(self):
285
"""merge_to(child) also merges tags to the master."""
286
master = self.make_branch('master')
287
other = self.make_branch('other')
288
other.tags.set_tag('foo', 'rev-1')
289
child = self.make_branch('child')
292
other.tags.merge_to(child.tags)
293
self.assertEqual('rev-1', child.tags.lookup_tag('foo'))
294
self.assertEqual('rev-1', master.tags.lookup_tag('foo'))
296
def test_ignore_master_disables_tag_propagation(self):
297
"""merge_to(child, ignore_master=True) does not merge tags to the
300
master = self.make_branch('master')
301
other = self.make_branch('other')
302
other.tags.set_tag('foo', 'rev-1')
303
child = self.make_branch('child')
306
other.tags.merge_to(child.tags, ignore_master=True)
307
self.assertEqual('rev-1', child.tags.lookup_tag('foo'))
308
self.assertRaises(errors.NoSuchTag, master.tags.lookup_tag, 'foo')
310
def test_merge_to_overwrite_conflict_in_master(self):
311
"""merge_to(child, overwrite=True) overwrites any conflicting tags in
314
master = self.make_branch('master')
315
other = self.make_branch('other')
316
other.tags.set_tag('foo', 'rev-1')
317
child = self.make_branch('child')
320
master.tags.set_tag('foo', 'rev-2')
321
tag_updates, tag_conflicts = other.tags.merge_to(child.tags, overwrite=True)
322
self.assertEqual('rev-1', child.tags.lookup_tag('foo'))
323
self.assertEqual('rev-1', master.tags.lookup_tag('foo'))
324
self.assertEqual({"foo": "rev-1"}, tag_updates)
325
self.assertLength(0, tag_conflicts)
327
def test_merge_to_overwrite_conflict_in_child_and_master(self):
328
"""merge_to(child, overwrite=True) overwrites any conflicting tags in
329
both the child and the master.
331
master = self.make_branch('master')
332
master.tags.set_tag('foo', 'rev-2')
333
other = self.make_branch('other')
334
other.tags.set_tag('foo', 'rev-1')
335
child = self.make_branch('child')
338
tag_updates, tag_conflicts = other.tags.merge_to(
339
child.tags, overwrite=True)
340
self.assertEqual('rev-1', child.tags.lookup_tag('foo'))
341
self.assertEqual('rev-1', master.tags.lookup_tag('foo'))
342
self.assertEqual({u'foo': 'rev-1'}, tag_updates)
343
self.assertLength(0, tag_conflicts)
345
def test_merge_to_conflict_in_child_only(self):
346
"""When new_tags.merge_to(child.tags) conflicts with the child but not
347
the master, a conflict is reported and the child receives the new tag.
349
master = self.make_branch('master')
350
master.tags.set_tag('foo', 'rev-2')
351
other = self.make_branch('other')
352
other.tags.set_tag('foo', 'rev-1')
353
child = self.make_branch('child')
356
master.tags.delete_tag('foo')
357
tag_updates, tag_conflicts = other.tags.merge_to(child.tags)
358
# Conflict in child, so it is unchanged.
359
self.assertEqual('rev-2', child.tags.lookup_tag('foo'))
360
# No conflict in the master, so the 'foo' tag equals other's value here.
361
self.assertEqual('rev-1', master.tags.lookup_tag('foo'))
362
# The conflict is reported.
363
self.assertEqual([(u'foo', 'rev-1', 'rev-2')], list(tag_conflicts))
364
self.assertEqual({u'foo': 'rev-1'}, tag_updates)
366
def test_merge_to_conflict_in_master_only(self):
367
"""When new_tags.merge_to(child.tags) conflicts with the master but not
368
the child, a conflict is reported and the child receives the new tag.
370
master = self.make_branch('master')
371
other = self.make_branch('other')
372
other.tags.set_tag('foo', 'rev-1')
373
child = self.make_branch('child')
376
master.tags.set_tag('foo', 'rev-2')
377
tag_updates, tag_conflicts = other.tags.merge_to(child.tags)
378
# No conflict in the child, so the 'foo' tag equals other's value here.
379
self.assertEqual('rev-1', child.tags.lookup_tag('foo'))
380
# Conflict in master, so it is unchanged.
381
self.assertEqual('rev-2', master.tags.lookup_tag('foo'))
382
# The conflict is reported.
383
self.assertEqual({u'foo': 'rev-1'}, tag_updates)
384
self.assertEqual([(u'foo', 'rev-1', 'rev-2')], list(tag_conflicts))
386
def test_merge_to_same_conflict_in_master_and_child(self):
387
"""When new_tags.merge_to(child.tags) conflicts the same way with the
388
master and the child a single conflict is reported.
390
master = self.make_branch('master')
391
master.tags.set_tag('foo', 'rev-2')
392
other = self.make_branch('other')
393
other.tags.set_tag('foo', 'rev-1')
394
child = self.make_branch('child')
397
tag_updates, tag_conflicts = other.tags.merge_to(child.tags)
398
# Both master and child conflict, so both stay as rev-2
399
self.assertEqual('rev-2', child.tags.lookup_tag('foo'))
400
self.assertEqual('rev-2', master.tags.lookup_tag('foo'))
401
# The conflict is reported exactly once, even though it occurs in both
403
self.assertEqual({}, tag_updates)
404
self.assertEqual([(u'foo', 'rev-1', 'rev-2')], list(tag_conflicts))
406
def test_merge_to_different_conflict_in_master_and_child(self):
407
"""When new_tags.merge_to(child.tags) conflicts differently in the
408
master and the child both conflicts are reported.
410
master = self.make_branch('master')
411
master.tags.set_tag('foo', 'rev-2')
412
other = self.make_branch('other')
413
other.tags.set_tag('foo', 'rev-1')
414
child = self.make_branch('child')
417
# We use the private method _set_tag_dict because normally bzr tries to
418
# avoid this scenario.
419
child.tags._set_tag_dict({'foo': 'rev-3'})
420
tag_updates, tag_conflicts = other.tags.merge_to(child.tags)
421
# Both master and child conflict, so both stay as they were.
422
self.assertEqual('rev-3', child.tags.lookup_tag('foo'))
423
self.assertEqual('rev-2', master.tags.lookup_tag('foo'))
424
# Both conflicts are reported.
425
self.assertEqual({}, tag_updates)
427
[(u'foo', 'rev-1', 'rev-2'), (u'foo', 'rev-1', 'rev-3')],
428
sorted(tag_conflicts))
147
431
class TestUnsupportedTags(per_branch.TestCaseWithBranch):
148
432
"""Formats that don't support tags should give reasonable errors."""