Merge lp:~jelmer/brz/memorytags into lp:brz

Proposed by Jelmer Vernooij on 2019-02-01
Status: Merged
Approved by: Jelmer Vernooij on 2019-02-02
Approved revision: 7265
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 2019-02-01 Approve on 2019-02-02
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.
Martin Packman (gz) wrote :

No content in this branch.

Martin Packman (gz) wrote :

Thanks, couple of small comments inline.

review: Approve
lp:~jelmer/brz/memorytags updated on 2019-02-02
7265. By Jelmer Vernooij on 2019-02-02

Fix test.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/tag.py'
2--- breezy/tag.py 2018-11-12 01:41:38 +0000
3+++ breezy/tag.py 2019-02-02 21:58:58 +0000
4@@ -24,6 +24,8 @@
5
6 from __future__ import absolute_import
7
8+from collections import defaultdict
9+
10 # NOTE: I was going to call this tags.py, but vim seems to think all files
11 # called tags* are ctags files... mbp 20070220.
12
13@@ -44,6 +46,32 @@
14 """)
15
16
17+def _reconcile_tags(source_dict, dest_dict, overwrite):
18+ """Do a two-way merge of two tag dictionaries.
19+
20+ * only in source => source value
21+ * only in destination => destination value
22+ * same definitions => that
23+ * different definitions => if overwrite is False, keep destination
24+ value and add to conflict list, otherwise use the source value
25+
26+ :returns: (result_dict, updates,
27+ [(conflicting_tag, source_target, dest_target)])
28+ """
29+ conflicts = []
30+ updates = {}
31+ result = dict(dest_dict) # copy
32+ for name, target in source_dict.items():
33+ if result.get(name) == target:
34+ pass
35+ elif name not in result or overwrite:
36+ updates[name] = target
37+ result[name] = target
38+ else:
39+ conflicts.append((name, target, result[name]))
40+ return result, updates, conflicts
41+
42+
43 class _Tags(object):
44
45 def __init__(self, branch):
46@@ -148,16 +176,13 @@
47 Behaviour if the tag is already present is not defined (yet).
48 """
49 # all done with a write lock held, so this looks atomic
50- self.branch.lock_write()
51- try:
52+ with self.branch.lock_write():
53 master = self.branch.get_master_branch()
54 if master is not None:
55 master.tags.set_tag(tag_name, tag_target)
56 td = self.get_tag_dict()
57 td[tag_name] = tag_target
58 self._set_tag_dict(td)
59- finally:
60- self.branch.unlock()
61
62 def lookup_tag(self, tag_name):
63 """Return the referent string of a tag"""
64@@ -184,19 +209,15 @@
65 """Returns a dict with revisions as keys
66 and a list of tags for that revision as value"""
67 d = self.get_tag_dict()
68- rev = {}
69+ rev = defaultdict(set)
70 for key in d:
71- try:
72- rev[d[key]].append(key)
73- except KeyError:
74- rev[d[key]] = [key]
75+ rev[d[key]].add(key)
76 return rev
77
78 def delete_tag(self, tag_name):
79 """Delete a tag definition.
80 """
81- self.branch.lock_write()
82- try:
83+ with self.branch.lock_write():
84 d = self.get_tag_dict()
85 try:
86 del d[tag_name]
87@@ -209,8 +230,6 @@
88 except errors.NoSuchTag:
89 pass
90 self._set_tag_dict(d)
91- finally:
92- self.branch.unlock()
93
94 def _set_tag_dict(self, new_dict):
95 """Replace all tag definitions
96@@ -310,8 +329,8 @@
97
98 def _merge_to(self, to_tags, source_dict, overwrite):
99 dest_dict = to_tags.get_tag_dict()
100- result, updates, conflicts = self._reconcile_tags(source_dict,
101- dest_dict, overwrite)
102+ result, updates, conflicts = _reconcile_tags(
103+ source_dict, dest_dict, overwrite)
104 if result != dest_dict:
105 to_tags._set_tag_dict(result)
106 return updates, conflicts
107@@ -327,30 +346,57 @@
108 for name in names:
109 self.set_tag(name, rename_map[revid])
110
111- def _reconcile_tags(self, source_dict, dest_dict, overwrite):
112- """Do a two-way merge of two tag dictionaries.
113-
114- * only in source => source value
115- * only in destination => destination value
116- * same definitions => that
117- * different definitions => if overwrite is False, keep destination
118- value and give a warning, otherwise use the source value
119-
120- :returns: (result_dict, updates,
121- [(conflicting_tag, source_target, dest_target)])
122- """
123- conflicts = []
124- updates = {}
125- result = dict(dest_dict) # copy
126- for name, target in source_dict.items():
127- if result.get(name) == target:
128- pass
129- elif name not in result or overwrite:
130- updates[name] = target
131- result[name] = target
132- else:
133- conflicts.append((name, target, result[name]))
134- return result, updates, conflicts
135+
136+class MemoryTags(_Tags):
137+
138+ def __init__(self, tag_dict):
139+ self._tag_dict = tag_dict
140+
141+ def get_tag_dict(self):
142+ return self._tag_dict
143+
144+ def lookup_tag(self, tag_name):
145+ """Return the referent string of a tag"""
146+ td = self.get_tag_dict()
147+ try:
148+ return td[tag_name]
149+ except KeyError:
150+ raise errors.NoSuchTag(tag_name)
151+
152+ def get_reverse_tag_dict(self):
153+ """Returns a dict with revisions as keys
154+ and a list of tags for that revision as value"""
155+ d = self.get_tag_dict()
156+ rev = defaultdict(set)
157+ for key in d:
158+ rev[d[key]].add(key)
159+ return rev
160+
161+ def set_tag(self, name, revid):
162+ self._tag_dict[name] = revid
163+
164+ def delete_tag(self, name):
165+ try:
166+ del self._tag_dict[name]
167+ except KeyError:
168+ raise errors.NoSuchTag(name)
169+
170+ def rename_revisions(self, revid_map):
171+ self._tag_dict = {
172+ name: revid_map.get(revid, revid)
173+ for name, revid in self._tag_dict.items()}
174+
175+ def _set_tag_dict(self, result):
176+ self._tag_dict = dict(result.items())
177+
178+ def merge_to(self, to_tags, overwrite=False, ignore_master=False):
179+ source_dict = self.get_tag_dict()
180+ dest_dict = to_tags.get_tag_dict()
181+ result, updates, conflicts = _reconcile_tags(
182+ source_dict, dest_dict, overwrite)
183+ if result != dest_dict:
184+ to_tags._set_tag_dict(result)
185+ return updates, conflicts
186
187
188 def sort_natural(branch, tags):
189
190=== modified file 'breezy/tests/per_branch/test_tags.py'
191--- breezy/tests/per_branch/test_tags.py 2018-11-11 04:08:32 +0000
192+++ breezy/tests/per_branch/test_tags.py 2019-02-02 21:58:58 +0000
193@@ -91,8 +91,8 @@
194 b = branch.Branch.open('b')
195 self.assertEqual(
196 dict(b.tags.get_reverse_tag_dict()),
197- {target_revid1: ['tag-name'],
198- target_revid2: ['other-name'],
199+ {target_revid1: set(['tag-name']),
200+ target_revid2: set(['other-name']),
201 })
202
203 def test_ghost_tag(self):
204
205=== modified file 'breezy/tests/test_tag.py'
206--- breezy/tests/test_tag.py 2018-11-11 04:08:32 +0000
207+++ breezy/tests/test_tag.py 2019-02-02 21:58:58 +0000
208@@ -24,6 +24,7 @@
209 from breezy.tag import (
210 BasicTags,
211 DisabledTags,
212+ MemoryTags,
213 )
214 from breezy.tests import (
215 TestCase,
216@@ -187,3 +188,66 @@
217
218 def test_get_reverse_tag_dict(self):
219 self.assertEqual(self.tags.get_reverse_tag_dict(), {})
220+
221+
222+class MemoryTagsTests(TestCase):
223+
224+ def setUp(self):
225+ super(MemoryTagsTests, self).setUp()
226+ self.tags = MemoryTags({})
227+
228+ def test_set_tag(self):
229+ self.tags.set_tag('foo', b'revid1')
230+ self.assertEqual({'foo': b'revid1'}, self.tags.get_tag_dict())
231+
232+ def test_reverse_tag_dict(self):
233+ self.tags.set_tag('foo', b'revid1')
234+ self.tags.set_tag('bar', b'revid2')
235+ self.tags.set_tag('blah', b'revid1')
236+ self.assertEqual({
237+ b'revid1': set(['foo', 'blah']),
238+ b'revid2': set(['bar'])},
239+ self.tags.get_reverse_tag_dict())
240+
241+ def test_lookup_tag(self):
242+ self.tags.set_tag('foo', b'revid1')
243+ self.assertEqual(b'revid1', self.tags.lookup_tag('foo'))
244+ self.assertRaises(errors.NoSuchTag, self.tags.lookup_tag, 'bar')
245+
246+ def test_delete_tag(self):
247+ self.tags.set_tag('foo', b'revid1')
248+ self.assertEqual(b'revid1', self.tags.lookup_tag('foo'))
249+ self.tags.delete_tag('foo')
250+ self.assertRaises(errors.NoSuchTag, self.tags.lookup_tag, 'foo')
251+ self.assertRaises(errors.NoSuchTag, self.tags.delete_tag, 'foo')
252+
253+ def test_has_tag(self):
254+ self.tags.set_tag('foo', b'revid1')
255+ self.assertTrue(self.tags.has_tag('foo'))
256+ self.assertFalse(self.tags.has_tag('bar'))
257+
258+ def test_rename_revisions(self):
259+ self.tags.set_tag('foo', b'revid1')
260+ self.assertEqual({'foo': b'revid1'}, self.tags.get_tag_dict())
261+ self.tags.rename_revisions({b'revid1': b'revid2'})
262+ self.assertEqual({'foo': b'revid2'}, self.tags.get_tag_dict())
263+
264+ def test_merge_to(self):
265+ other_tags = MemoryTags({})
266+ other_tags.set_tag('tag-1', b'x')
267+ self.tags.set_tag('tag-2', b'y')
268+ other_tags.merge_to(self.tags)
269+ self.assertEqual(b'x', self.tags.lookup_tag('tag-1'))
270+ self.assertEqual(b'y', self.tags.lookup_tag('tag-2'))
271+ self.assertRaises(errors.NoSuchTag, other_tags.lookup_tag, 'tag-2')
272+ # conflicting merge
273+ other_tags.set_tag('tag-2', b'z')
274+ updates, conflicts = other_tags.merge_to(self.tags)
275+ self.assertEqual({}, updates)
276+ self.assertEqual(list(conflicts), [('tag-2', b'z', b'y')])
277+ self.assertEqual(b'y', self.tags.lookup_tag('tag-2'))
278+ # overwrite conflicts
279+ updates, conflicts = other_tags.merge_to(self.tags, overwrite=True)
280+ self.assertEqual(list(conflicts), [])
281+ self.assertEqual({u'tag-2': b'z'}, updates)
282+ self.assertEqual(b'z', self.tags.lookup_tag('tag-2'))

Subscribers

People subscribed via source and target branches