Merge lp:~jelmer/brz/memorytags 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/memorytags
Merge into: lp:brz
Diff against target: 282 lines (+151/-41)
3 files modified
breezy/tag.py (+85/-39)
breezy/tests/per_branch/test_tags.py (+2/-2)
breezy/tests/test_tag.py (+64/-0)
To merge this branch: bzr merge lp:~jelmer/brz/memorytags
Reviewer Review Type Date Requested Status
Martin Packman Approve
Review via email: mp+362620@code.launchpad.net

Commit message

Add a MemoryTags object that provides the Tags implementation but stores tags in a dict.

Description of the change

Add a MemoryTags object that provides the Tags implementation but stores tags in a dict.

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

No content in this branch.

Revision history for this message
Martin Packman (gz) wrote :

Thanks, couple of small comments inline.

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/tag.py'
--- breezy/tag.py 2018-11-12 01:41:38 +0000
+++ breezy/tag.py 2019-02-02 21:58:58 +0000
@@ -24,6 +24,8 @@
2424
25from __future__ import absolute_import25from __future__ import absolute_import
2626
27from collections import defaultdict
28
27# NOTE: I was going to call this tags.py, but vim seems to think all files29# NOTE: I was going to call this tags.py, but vim seems to think all files
28# called tags* are ctags files... mbp 20070220.30# called tags* are ctags files... mbp 20070220.
2931
@@ -44,6 +46,32 @@
44""")46""")
4547
4648
49def _reconcile_tags(source_dict, dest_dict, overwrite):
50 """Do a two-way merge of two tag dictionaries.
51
52 * only in source => source value
53 * only in destination => destination value
54 * same definitions => that
55 * different definitions => if overwrite is False, keep destination
56 value and add to conflict list, otherwise use the source value
57
58 :returns: (result_dict, updates,
59 [(conflicting_tag, source_target, dest_target)])
60 """
61 conflicts = []
62 updates = {}
63 result = dict(dest_dict) # copy
64 for name, target in source_dict.items():
65 if result.get(name) == target:
66 pass
67 elif name not in result or overwrite:
68 updates[name] = target
69 result[name] = target
70 else:
71 conflicts.append((name, target, result[name]))
72 return result, updates, conflicts
73
74
47class _Tags(object):75class _Tags(object):
4876
49 def __init__(self, branch):77 def __init__(self, branch):
@@ -148,16 +176,13 @@
148 Behaviour if the tag is already present is not defined (yet).176 Behaviour if the tag is already present is not defined (yet).
149 """177 """
150 # all done with a write lock held, so this looks atomic178 # all done with a write lock held, so this looks atomic
151 self.branch.lock_write()179 with self.branch.lock_write():
152 try:
153 master = self.branch.get_master_branch()180 master = self.branch.get_master_branch()
154 if master is not None:181 if master is not None:
155 master.tags.set_tag(tag_name, tag_target)182 master.tags.set_tag(tag_name, tag_target)
156 td = self.get_tag_dict()183 td = self.get_tag_dict()
157 td[tag_name] = tag_target184 td[tag_name] = tag_target
158 self._set_tag_dict(td)185 self._set_tag_dict(td)
159 finally:
160 self.branch.unlock()
161186
162 def lookup_tag(self, tag_name):187 def lookup_tag(self, tag_name):
163 """Return the referent string of a tag"""188 """Return the referent string of a tag"""
@@ -184,19 +209,15 @@
184 """Returns a dict with revisions as keys209 """Returns a dict with revisions as keys
185 and a list of tags for that revision as value"""210 and a list of tags for that revision as value"""
186 d = self.get_tag_dict()211 d = self.get_tag_dict()
187 rev = {}212 rev = defaultdict(set)
188 for key in d:213 for key in d:
189 try:214 rev[d[key]].add(key)
190 rev[d[key]].append(key)
191 except KeyError:
192 rev[d[key]] = [key]
193 return rev215 return rev
194216
195 def delete_tag(self, tag_name):217 def delete_tag(self, tag_name):
196 """Delete a tag definition.218 """Delete a tag definition.
197 """219 """
198 self.branch.lock_write()220 with self.branch.lock_write():
199 try:
200 d = self.get_tag_dict()221 d = self.get_tag_dict()
201 try:222 try:
202 del d[tag_name]223 del d[tag_name]
@@ -209,8 +230,6 @@
209 except errors.NoSuchTag:230 except errors.NoSuchTag:
210 pass231 pass
211 self._set_tag_dict(d)232 self._set_tag_dict(d)
212 finally:
213 self.branch.unlock()
214233
215 def _set_tag_dict(self, new_dict):234 def _set_tag_dict(self, new_dict):
216 """Replace all tag definitions235 """Replace all tag definitions
@@ -310,8 +329,8 @@
310329
311 def _merge_to(self, to_tags, source_dict, overwrite):330 def _merge_to(self, to_tags, source_dict, overwrite):
312 dest_dict = to_tags.get_tag_dict()331 dest_dict = to_tags.get_tag_dict()
313 result, updates, conflicts = self._reconcile_tags(source_dict,332 result, updates, conflicts = _reconcile_tags(
314 dest_dict, overwrite)333 source_dict, dest_dict, overwrite)
315 if result != dest_dict:334 if result != dest_dict:
316 to_tags._set_tag_dict(result)335 to_tags._set_tag_dict(result)
317 return updates, conflicts336 return updates, conflicts
@@ -327,30 +346,57 @@
327 for name in names:346 for name in names:
328 self.set_tag(name, rename_map[revid])347 self.set_tag(name, rename_map[revid])
329348
330 def _reconcile_tags(self, source_dict, dest_dict, overwrite):349
331 """Do a two-way merge of two tag dictionaries.350class MemoryTags(_Tags):
332351
333 * only in source => source value352 def __init__(self, tag_dict):
334 * only in destination => destination value353 self._tag_dict = tag_dict
335 * same definitions => that354
336 * different definitions => if overwrite is False, keep destination355 def get_tag_dict(self):
337 value and give a warning, otherwise use the source value356 return self._tag_dict
338357
339 :returns: (result_dict, updates,358 def lookup_tag(self, tag_name):
340 [(conflicting_tag, source_target, dest_target)])359 """Return the referent string of a tag"""
341 """360 td = self.get_tag_dict()
342 conflicts = []361 try:
343 updates = {}362 return td[tag_name]
344 result = dict(dest_dict) # copy363 except KeyError:
345 for name, target in source_dict.items():364 raise errors.NoSuchTag(tag_name)
346 if result.get(name) == target:365
347 pass366 def get_reverse_tag_dict(self):
348 elif name not in result or overwrite:367 """Returns a dict with revisions as keys
349 updates[name] = target368 and a list of tags for that revision as value"""
350 result[name] = target369 d = self.get_tag_dict()
351 else:370 rev = defaultdict(set)
352 conflicts.append((name, target, result[name]))371 for key in d:
353 return result, updates, conflicts372 rev[d[key]].add(key)
373 return rev
374
375 def set_tag(self, name, revid):
376 self._tag_dict[name] = revid
377
378 def delete_tag(self, name):
379 try:
380 del self._tag_dict[name]
381 except KeyError:
382 raise errors.NoSuchTag(name)
383
384 def rename_revisions(self, revid_map):
385 self._tag_dict = {
386 name: revid_map.get(revid, revid)
387 for name, revid in self._tag_dict.items()}
388
389 def _set_tag_dict(self, result):
390 self._tag_dict = dict(result.items())
391
392 def merge_to(self, to_tags, overwrite=False, ignore_master=False):
393 source_dict = self.get_tag_dict()
394 dest_dict = to_tags.get_tag_dict()
395 result, updates, conflicts = _reconcile_tags(
396 source_dict, dest_dict, overwrite)
397 if result != dest_dict:
398 to_tags._set_tag_dict(result)
399 return updates, conflicts
354400
355401
356def sort_natural(branch, tags):402def sort_natural(branch, tags):
357403
=== modified file 'breezy/tests/per_branch/test_tags.py'
--- breezy/tests/per_branch/test_tags.py 2018-11-11 04:08:32 +0000
+++ breezy/tests/per_branch/test_tags.py 2019-02-02 21:58:58 +0000
@@ -91,8 +91,8 @@
91 b = branch.Branch.open('b')91 b = branch.Branch.open('b')
92 self.assertEqual(92 self.assertEqual(
93 dict(b.tags.get_reverse_tag_dict()),93 dict(b.tags.get_reverse_tag_dict()),
94 {target_revid1: ['tag-name'],94 {target_revid1: set(['tag-name']),
95 target_revid2: ['other-name'],95 target_revid2: set(['other-name']),
96 })96 })
9797
98 def test_ghost_tag(self):98 def test_ghost_tag(self):
9999
=== modified file 'breezy/tests/test_tag.py'
--- breezy/tests/test_tag.py 2018-11-11 04:08:32 +0000
+++ breezy/tests/test_tag.py 2019-02-02 21:58:58 +0000
@@ -24,6 +24,7 @@
24from breezy.tag import (24from breezy.tag import (
25 BasicTags,25 BasicTags,
26 DisabledTags,26 DisabledTags,
27 MemoryTags,
27 )28 )
28from breezy.tests import (29from breezy.tests import (
29 TestCase,30 TestCase,
@@ -187,3 +188,66 @@
187188
188 def test_get_reverse_tag_dict(self):189 def test_get_reverse_tag_dict(self):
189 self.assertEqual(self.tags.get_reverse_tag_dict(), {})190 self.assertEqual(self.tags.get_reverse_tag_dict(), {})
191
192
193class MemoryTagsTests(TestCase):
194
195 def setUp(self):
196 super(MemoryTagsTests, self).setUp()
197 self.tags = MemoryTags({})
198
199 def test_set_tag(self):
200 self.tags.set_tag('foo', b'revid1')
201 self.assertEqual({'foo': b'revid1'}, self.tags.get_tag_dict())
202
203 def test_reverse_tag_dict(self):
204 self.tags.set_tag('foo', b'revid1')
205 self.tags.set_tag('bar', b'revid2')
206 self.tags.set_tag('blah', b'revid1')
207 self.assertEqual({
208 b'revid1': set(['foo', 'blah']),
209 b'revid2': set(['bar'])},
210 self.tags.get_reverse_tag_dict())
211
212 def test_lookup_tag(self):
213 self.tags.set_tag('foo', b'revid1')
214 self.assertEqual(b'revid1', self.tags.lookup_tag('foo'))
215 self.assertRaises(errors.NoSuchTag, self.tags.lookup_tag, 'bar')
216
217 def test_delete_tag(self):
218 self.tags.set_tag('foo', b'revid1')
219 self.assertEqual(b'revid1', self.tags.lookup_tag('foo'))
220 self.tags.delete_tag('foo')
221 self.assertRaises(errors.NoSuchTag, self.tags.lookup_tag, 'foo')
222 self.assertRaises(errors.NoSuchTag, self.tags.delete_tag, 'foo')
223
224 def test_has_tag(self):
225 self.tags.set_tag('foo', b'revid1')
226 self.assertTrue(self.tags.has_tag('foo'))
227 self.assertFalse(self.tags.has_tag('bar'))
228
229 def test_rename_revisions(self):
230 self.tags.set_tag('foo', b'revid1')
231 self.assertEqual({'foo': b'revid1'}, self.tags.get_tag_dict())
232 self.tags.rename_revisions({b'revid1': b'revid2'})
233 self.assertEqual({'foo': b'revid2'}, self.tags.get_tag_dict())
234
235 def test_merge_to(self):
236 other_tags = MemoryTags({})
237 other_tags.set_tag('tag-1', b'x')
238 self.tags.set_tag('tag-2', b'y')
239 other_tags.merge_to(self.tags)
240 self.assertEqual(b'x', self.tags.lookup_tag('tag-1'))
241 self.assertEqual(b'y', self.tags.lookup_tag('tag-2'))
242 self.assertRaises(errors.NoSuchTag, other_tags.lookup_tag, 'tag-2')
243 # conflicting merge
244 other_tags.set_tag('tag-2', b'z')
245 updates, conflicts = other_tags.merge_to(self.tags)
246 self.assertEqual({}, updates)
247 self.assertEqual(list(conflicts), [('tag-2', b'z', b'y')])
248 self.assertEqual(b'y', self.tags.lookup_tag('tag-2'))
249 # overwrite conflicts
250 updates, conflicts = other_tags.merge_to(self.tags, overwrite=True)
251 self.assertEqual(list(conflicts), [])
252 self.assertEqual({u'tag-2': b'z'}, updates)
253 self.assertEqual(b'z', self.tags.lookup_tag('tag-2'))

Subscribers

People subscribed via source and target branches