/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tag.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2020-02-21 04:40:09 UTC
  • mfrom: (7494.1.1 trunk-3.1)
  • Revision ID: breezy.the.bot@gmail.com-20200221044009-cijpllwtz5ql9tar
Merge lp:brz/3.1.

Merged from https://code.launchpad.net/~jelmer/brz/trunk-3.1/+merge/379600

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
 
25
25
from collections import defaultdict
26
26
import contextlib
 
27
import itertools
 
28
import re
 
29
import sys
27
30
 
28
31
# NOTE: I was going to call this tags.py, but vim seems to think all files
29
32
# called tags* are ctags files... mbp 20070220.
30
33
 
 
34
from .inter import InterObject
31
35
from .registry import Registry
32
 
from .lazy_import import lazy_import
33
 
lazy_import(globals(), """
34
 
import itertools
35
 
import re
36
 
import sys
37
 
 
38
 
from breezy import (
39
 
    bencode,
40
 
    trace,
41
 
    )
42
 
""")
43
36
 
44
37
from . import (
45
38
    errors,
46
39
    )
47
40
 
48
41
 
49
 
def _reconcile_tags(source_dict, dest_dict, overwrite):
 
42
def _reconcile_tags(source_dict, dest_dict, overwrite, selector):
50
43
    """Do a two-way merge of two tag dictionaries.
51
44
 
52
45
    * only in source => source value
62
55
    updates = {}
63
56
    result = dict(dest_dict)  # copy
64
57
    for name, target in source_dict.items():
 
58
        if selector and not selector(name):
 
59
            continue
65
60
        if result.get(name) == target:
66
61
            pass
67
62
        elif name not in result or overwrite:
72
67
    return result, updates, conflicts
73
68
 
74
69
 
75
 
class _Tags(object):
 
70
class Tags(object):
76
71
 
77
72
    def __init__(self, branch):
78
73
        self.branch = branch
83
78
        raise NotImplementedError(self.get_tag_dict)
84
79
 
85
80
    def get_reverse_tag_dict(self):
86
 
        """Return a dictionary mapping revision ids to list of tags.
87
 
        """
88
 
        raise NotImplementedError(self.get_reverse_tag_dict)
89
 
 
90
 
    def merge_to(self, to_tags, overwrite=False, ignore_master=False):
91
 
        """Merge new tags from this tags container into another.
92
 
 
93
 
        :param to_tags: Tags container to merge into
94
 
        :param overwrite: Whether to overwrite existing, divergent, tags.
 
81
        """Returns a dict with revisions as keys
 
82
           and a list of tags for that revision as value"""
 
83
        d = self.get_tag_dict()
 
84
        rev = defaultdict(set)
 
85
        for key in d:
 
86
            rev[d[key]].add(key)
 
87
        return rev
 
88
 
 
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
100
        :param ignore_master: Do not modify the tags in the target's master
96
101
            branch (if any).  Default is false (so the master will be updated).
97
 
            New in bzr 2.3.
98
 
        :return: Tuple with tag updates as dictionary and tag conflicts
 
102
 
 
103
        :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)
101
114
 
102
115
    def set_tag(self, tag_name, revision):
103
116
        """Set a tag.
127
140
        raise NotImplementedError(self.delete_tag)
128
141
 
129
142
    def rename_revisions(self, rename_map):
130
 
        """Replace revision ids according to a rename map.
 
143
        """Rename revisions in this tags dictionary.
131
144
 
132
 
        :param rename_map: Dictionary mapping old revision ids to
133
 
            new revision ids.
 
145
        :param rename_map: Dictionary mapping old revids to new revids
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])
136
152
 
137
153
    def has_tag(self, tag_name):
138
154
        return tag_name in self.get_tag_dict()
139
155
 
140
156
 
141
 
class DisabledTags(_Tags):
 
157
class DisabledTags(Tags):
142
158
    """Tag storage that refuses to store anything.
143
159
 
144
160
    This is used by older formats that can't store tags.
153
169
    lookup_tag = _not_supported
154
170
    delete_tag = _not_supported
155
171
 
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
173
        # we never have anything to copy
158
174
        return {}, []
159
175
 
166
182
        return {}
167
183
 
168
184
 
169
 
class BasicTags(_Tags):
170
 
    """Tag storage in an unversioned branch control file.
 
185
class InterTags(InterObject):
 
186
    """Operations between sets of tags.
171
187
    """
172
188
 
173
 
    def set_tag(self, tag_name, tag_target):
174
 
        """Add a tag definition to the branch.
175
 
 
176
 
        Behaviour if the tag is already present is not defined (yet).
177
 
        """
178
 
        # all done with a write lock held, so this looks atomic
179
 
        with self.branch.lock_write():
180
 
            master = self.branch.get_master_branch()
181
 
            if master is not 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):
 
189
    _optimisers = []
 
190
    """The available optimised InterTags types."""
 
191
 
 
192
    @classmethod
 
193
    def is_compatible(klass, source, target):
 
194
        # This is the default implementation
 
195
        return True
 
196
 
 
197
    def merge(self, overwrite=False, ignore_master=False, selector=None):
265
198
        """Copy tags between repositories if necessary and possible.
266
199
 
267
200
        This method has common command-line behaviour about handling
274
207
        :param overwrite: Overwrite conflicting tags in the target branch
275
208
        :param ignore_master: Do not modify the tags in the target's master
276
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.
278
213
 
279
214
        :returns: Tuple with tag_updates and tag_conflicts.
280
215
            tag_updates is a dictionary with new tags, None is used for
284
219
            done.
285
220
        """
286
221
        with contextlib.ExitStack() as stack:
287
 
            if self.branch == to_tags.branch:
 
222
            if self.source.branch == self.target.branch:
288
223
                return {}, []
289
 
            if not self.branch.supports_tags():
 
224
            if not self.source.branch.supports_tags():
290
225
                # obviously nothing to copy
291
226
                return {}, []
292
 
            source_dict = self.get_tag_dict()
 
227
            source_dict = self.source.get_tag_dict()
293
228
            if not source_dict:
294
229
                # no tags in the source, and we don't want to clobber anything
295
230
                # that's in the destination
306
241
            # Ideally we'd improve this API to report the different conflicts
307
242
            # more clearly to the caller, but we don't want to break plugins
308
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
245
            if ignore_master:
311
246
                master = None
312
247
            else:
313
 
                master = to_tags.branch.get_master_branch()
 
248
                master = self.target.branch.get_master_branch()
314
249
            if master is not None:
315
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
253
            if master is not None:
318
 
                extra_updates, extra_conflicts = self._merge_to(master.tags,
319
 
                                                                source_dict, overwrite)
 
254
                extra_updates, extra_conflicts = self._merge_to(
 
255
                    master.tags, source_dict, overwrite, selector=selector)
320
256
                updates.update(extra_updates)
321
257
                conflicts += extra_conflicts
322
258
            # We use set() to remove any duplicate conflicts from the master
323
259
            # branch.
324
260
            return updates, set(conflicts)
325
261
 
326
 
    def _merge_to(self, to_tags, source_dict, overwrite):
 
262
    @classmethod
 
263
    def _merge_to(cls, to_tags, source_dict, overwrite, selector):
327
264
        dest_dict = to_tags.get_tag_dict()
328
265
        result, updates, conflicts = _reconcile_tags(
329
 
            source_dict, dest_dict, overwrite)
 
266
            source_dict, dest_dict, overwrite, selector)
330
267
        if result != dest_dict:
331
268
            to_tags._set_tag_dict(result)
332
269
        return updates, conflicts
333
270
 
334
 
    def rename_revisions(self, rename_map):
335
 
        """Rename revisions in this tags dictionary.
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
 
 
346
 
class MemoryTags(_Tags):
 
271
 
 
272
class MemoryTags(Tags):
347
273
 
348
274
    def __init__(self, tag_dict):
349
275
        self._tag_dict = tag_dict
359
285
        except KeyError:
360
286
            raise errors.NoSuchTag(tag_name)
361
287
 
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
288
    def set_tag(self, name, revid):
372
289
        self._tag_dict[name] = revid
373
290
 
385
302
    def _set_tag_dict(self, result):
386
303
        self._tag_dict = dict(result.items())
387
304
 
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
306
        source_dict = self.get_tag_dict()
390
307
        dest_dict = to_tags.get_tag_dict()
391
308
        result, updates, conflicts = _reconcile_tags(
392
 
            source_dict, dest_dict, overwrite)
 
309
            source_dict, dest_dict, overwrite, selector)
393
310
        if result != dest_dict:
394
311
            to_tags._set_tag_dict(result)
395
312
        return updates, conflicts
441
358
tag_sort_methods.register("alpha", sort_alpha, 'Sort tags lexicographically.')
442
359
tag_sort_methods.register("time", sort_time, 'Sort tags chronologically.')
443
360
tag_sort_methods.default_key = "natural"
 
361
 
 
362
 
 
363
InterTags.register_optimiser(InterTags)