~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tag.py

  • Committer: Richard Wilbur
  • Date: 2016-02-04 19:07:28 UTC
  • mto: This revision was merged to the branch mainline in revision 6618.
  • Revision ID: richard.wilbur@gmail.com-20160204190728-p0zvfii6zase0fw7
Update COPYING.txt from the original http://www.gnu.org/licenses/gpl-2.0.txt  (Only differences were in whitespace.)  Thanks to Petr Stodulka for pointing out the discrepancy.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
 
1
# Copyright (C) 2007-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
22
22
  Branch.tags.add('name', 'value')
23
23
"""
24
24
 
 
25
from __future__ import absolute_import
 
26
 
25
27
# NOTE: I was going to call this tags.py, but vim seems to think all files
26
28
# called tags* are ctags files... mbp 20070220.
27
29
 
 
30
from bzrlib.registry import Registry
 
31
from bzrlib.lazy_import import lazy_import
 
32
lazy_import(globals(), """
 
33
import itertools
 
34
import re
 
35
import sys
28
36
 
29
37
from bzrlib import (
30
38
    bencode,
 
39
    cleanup,
31
40
    errors,
 
41
    symbol_versioning,
32
42
    trace,
33
43
    )
 
44
""")
34
45
 
35
46
 
36
47
class _Tags(object):
38
49
    def __init__(self, branch):
39
50
        self.branch = branch
40
51
 
 
52
    def get_tag_dict(self):
 
53
        """Return a dictionary mapping tags to revision ids.
 
54
        """
 
55
        raise NotImplementedError(self.get_tag_dict)
 
56
 
 
57
    def get_reverse_tag_dict(self):
 
58
        """Return a dictionary mapping revision ids to list of tags.
 
59
        """
 
60
        raise NotImplementedError(self.get_reverse_tag_dict)
 
61
 
 
62
    def merge_to(self, to_tags, overwrite=False, ignore_master=False):
 
63
        """Merge new tags from this tags container into another.
 
64
 
 
65
        :param to_tags: Tags container to merge into
 
66
        :param overwrite: Whether to overwrite existing, divergent, tags.
 
67
        :param ignore_master: Do not modify the tags in the target's master
 
68
            branch (if any).  Default is false (so the master will be updated).
 
69
            New in bzr 2.3.
 
70
        :return: Tuple with tag updates as dictionary and tag conflicts
 
71
        """
 
72
        raise NotImplementedError(self.merge_to)
 
73
 
 
74
    def set_tag(self, tag_name, revision):
 
75
        """Set a tag.
 
76
 
 
77
        :param tag_name: Tag name
 
78
        :param revision: Revision id
 
79
        :raise GhostTagsNotSupported: if revision is not present in
 
80
            the branch repository
 
81
        """
 
82
        raise NotImplementedError(self.set_tag)
 
83
 
 
84
    def lookup_tag(self, tag_name):
 
85
        """Look up a tag.
 
86
 
 
87
        :param tag_name: Tag to look up
 
88
        :raise NoSuchTag: Raised when tag does not exist
 
89
        :return: Matching revision id
 
90
        """
 
91
        raise NotImplementedError(self.lookup_tag)
 
92
 
 
93
    def delete_tag(self, tag_name):
 
94
        """Delete a tag.
 
95
 
 
96
        :param tag_name: Tag to delete
 
97
        :raise NoSuchTag: Raised when tag does not exist
 
98
        """
 
99
        raise NotImplementedError(self.delete_tag)
 
100
 
 
101
    def rename_revisions(self, rename_map):
 
102
        """Replace revision ids according to a rename map.
 
103
 
 
104
        :param rename_map: Dictionary mapping old revision ids to
 
105
            new revision ids.
 
106
        """
 
107
        raise NotImplementedError(self.rename_revisions)
 
108
 
41
109
    def has_tag(self, tag_name):
42
110
        return self.get_tag_dict().has_key(tag_name)
43
111
 
57
125
    lookup_tag = _not_supported
58
126
    delete_tag = _not_supported
59
127
 
60
 
    def merge_to(self, to_tags, overwrite=False):
 
128
    def merge_to(self, to_tags, overwrite=False, ignore_master=False):
61
129
        # we never have anything to copy
62
 
        pass
 
130
        return {}, []
63
131
 
64
132
    def rename_revisions(self, rename_map):
65
133
        # No tags, so nothing to rename
177
245
            raise ValueError("failed to deserialize tag dictionary %r: %s"
178
246
                % (tag_content, e))
179
247
 
180
 
    def merge_to(self, to_tags, overwrite=False):
 
248
    def merge_to(self, to_tags, overwrite=False, ignore_master=False):
181
249
        """Copy tags between repositories if necessary and possible.
182
250
 
183
251
        This method has common command-line behaviour about handling
188
256
 
189
257
        :param to_tags: Branch to receive these tags
190
258
        :param overwrite: Overwrite conflicting tags in the target branch
 
259
        :param ignore_master: Do not modify the tags in the target's master
 
260
            branch (if any).  Default is false (so the master will be updated).
 
261
            New in bzr 2.3.
191
262
 
192
 
        :returns: A list of tags that conflicted, each of which is
 
263
        :returns: Tuple with tag_updates and tag_conflicts.
 
264
            tag_updates is a dictionary with new tags, None is used for
 
265
            removed tags
 
266
            tag_conflicts is a set of tags that conflicted, each of which is
193
267
            (tagname, source_target, dest_target), or None if no copying was
194
268
            done.
195
269
        """
 
270
        operation = cleanup.OperationWithCleanups(self._merge_to_operation)
 
271
        return operation.run(to_tags, overwrite, ignore_master)
 
272
 
 
273
    def _merge_to_operation(self, operation, to_tags, overwrite, ignore_master):
 
274
        add_cleanup = operation.add_cleanup
196
275
        if self.branch == to_tags.branch:
197
 
            return
 
276
            return {}, []
198
277
        if not self.branch.supports_tags():
199
278
            # obviously nothing to copy
200
 
            return
 
279
            return {}, []
201
280
        source_dict = self.get_tag_dict()
202
281
        if not source_dict:
203
282
            # no tags in the source, and we don't want to clobber anything
204
283
            # that's in the destination
205
 
            return
206
 
        to_tags.branch.lock_write()
207
 
        try:
208
 
            dest_dict = to_tags.get_tag_dict()
209
 
            result, conflicts = self._reconcile_tags(source_dict, dest_dict,
210
 
                                                     overwrite)
211
 
            if result != dest_dict:
212
 
                to_tags._set_tag_dict(result)
213
 
        finally:
214
 
            to_tags.branch.unlock()
215
 
        return conflicts
 
284
            return {}, []
 
285
        # We merge_to both master and child individually.
 
286
        #
 
287
        # It's possible for master and child to have differing sets of
 
288
        # tags, in which case it's possible to have different sets of
 
289
        # conflicts.  We report the union of both conflict sets.  In
 
290
        # that case it's likely the child and master have accepted
 
291
        # different tags from the source, which may be a surprising result, but
 
292
        # the best we can do in the circumstances.
 
293
        #
 
294
        # Ideally we'd improve this API to report the different conflicts
 
295
        # more clearly to the caller, but we don't want to break plugins
 
296
        # such as bzr-builddeb that use this API.
 
297
        add_cleanup(to_tags.branch.lock_write().unlock)
 
298
        if ignore_master:
 
299
            master = None
 
300
        else:
 
301
            master = to_tags.branch.get_master_branch()
 
302
        if master is not None:
 
303
            add_cleanup(master.lock_write().unlock)
 
304
        updates, conflicts = self._merge_to(to_tags, source_dict, overwrite)
 
305
        if master is not None:
 
306
            extra_updates, extra_conflicts = self._merge_to(master.tags,
 
307
                source_dict, overwrite)
 
308
            updates.update(extra_updates)
 
309
            conflicts += extra_conflicts
 
310
        # We use set() to remove any duplicate conflicts from the master
 
311
        # branch.
 
312
        return updates, set(conflicts)
 
313
 
 
314
    def _merge_to(self, to_tags, source_dict, overwrite):
 
315
        dest_dict = to_tags.get_tag_dict()
 
316
        result, updates, conflicts = self._reconcile_tags(source_dict,
 
317
            dest_dict, overwrite)
 
318
        if result != dest_dict:
 
319
            to_tags._set_tag_dict(result)
 
320
        return updates, conflicts
216
321
 
217
322
    def rename_revisions(self, rename_map):
218
323
        """Rename revisions in this tags dictionary.
219
 
        
 
324
 
220
325
        :param rename_map: Dictionary mapping old revids to new revids
221
326
        """
222
327
        reverse_tags = self.get_reverse_tag_dict()
228
333
    def _reconcile_tags(self, source_dict, dest_dict, overwrite):
229
334
        """Do a two-way merge of two tag dictionaries.
230
335
 
231
 
        only in source => source value
232
 
        only in destination => destination value
233
 
        same definitions => that
234
 
        different definitions => if overwrite is False, keep destination
235
 
            value and give a warning, otherwise use the source value
 
336
        * only in source => source value
 
337
        * only in destination => destination value
 
338
        * same definitions => that
 
339
        * different definitions => if overwrite is False, keep destination
 
340
          value and give a warning, otherwise use the source value
236
341
 
237
 
        :returns: (result_dict,
 
342
        :returns: (result_dict, updates,
238
343
            [(conflicting_tag, source_target, dest_target)])
239
344
        """
240
345
        conflicts = []
 
346
        updates = {}
241
347
        result = dict(dest_dict) # copy
242
348
        for name, target in source_dict.items():
243
 
            if name not in result or overwrite:
 
349
            if result.get(name) == target:
 
350
                pass
 
351
            elif name not in result or overwrite:
 
352
                updates[name] = target
244
353
                result[name] = target
245
 
            elif result[name] == target:
246
 
                pass
247
354
            else:
248
355
                conflicts.append((name, target, result[name]))
249
 
        return result, conflicts
250
 
 
251
 
 
252
 
def _merge_tags_if_possible(from_branch, to_branch):
253
 
    from_branch.tags.merge_to(to_branch.tags)
254
 
 
 
356
        return result, updates, conflicts
 
357
 
 
358
 
 
359
def _merge_tags_if_possible(from_branch, to_branch, ignore_master=False):
 
360
    # Try hard to support merge_to implementations that don't expect
 
361
    # 'ignore_master' (new in bzr 2.3).  First, if the flag isn't set then we
 
362
    # can safely avoid passing ignore_master at all.
 
363
    if not ignore_master:
 
364
        from_branch.tags.merge_to(to_branch.tags)
 
365
        return
 
366
    # If the flag is set, try to pass it, but be ready to catch TypeError.
 
367
    try:
 
368
        from_branch.tags.merge_to(to_branch.tags, ignore_master=ignore_master)
 
369
    except TypeError:
 
370
        # Probably this implementation of 'merge_to' is from a plugin that
 
371
        # doesn't expect the 'ignore_master' keyword argument (e.g. bzr-svn
 
372
        # 1.0.4).  There's a small risk that the TypeError is actually caused
 
373
        # by a completely different problem (which is why we don't catch it for
 
374
        # the ignore_master=False case), but even then there's probably no harm
 
375
        # in calling a second time.
 
376
        symbol_versioning.warn(
 
377
            symbol_versioning.deprecated_in((2,3)) % (
 
378
                "Tags.merge_to (of %r) that doesn't accept ignore_master kwarg"
 
379
                % (from_branch.tags,),),
 
380
            DeprecationWarning)
 
381
        from_branch.tags.merge_to(to_branch.tags)
 
382
 
 
383
 
 
384
def sort_natural(branch, tags):
 
385
    """Sort tags, with numeric substrings as numbers.
 
386
 
 
387
    :param branch: Branch
 
388
    :param tags: List of tuples with tag name and revision id.
 
389
    """
 
390
    def natural_sort_key(tag):
 
391
        return [f(s) for f,s in
 
392
                zip(itertools.cycle((unicode.lower,int)),
 
393
                                    re.split('([0-9]+)', tag[0]))]
 
394
    tags.sort(key=natural_sort_key)
 
395
 
 
396
 
 
397
def sort_alpha(branch, tags):
 
398
    """Sort tags lexicographically, in place.
 
399
 
 
400
    :param branch: Branch
 
401
    :param tags: List of tuples with tag name and revision id.
 
402
    """
 
403
    tags.sort()
 
404
 
 
405
 
 
406
def sort_time(branch, tags):
 
407
    """Sort tags by time inline.
 
408
 
 
409
    :param branch: Branch
 
410
    :param tags: List of tuples with tag name and revision id.
 
411
    """
 
412
    timestamps = {}
 
413
    for tag, revid in tags:
 
414
        try:
 
415
            revobj = branch.repository.get_revision(revid)
 
416
        except errors.NoSuchRevision:
 
417
            timestamp = sys.maxint # place them at the end
 
418
        else:
 
419
            timestamp = revobj.timestamp
 
420
        timestamps[revid] = timestamp
 
421
    tags.sort(key=lambda x: timestamps[x[1]])
 
422
 
 
423
 
 
424
tag_sort_methods = Registry()
 
425
tag_sort_methods.register("natural", sort_natural,
 
426
    'Sort numeric substrings as numbers. (default)')
 
427
tag_sort_methods.register("alpha", sort_alpha, 'Sort tags lexicographically.')
 
428
tag_sort_methods.register("time", sort_time, 'Sort tags chronologically.')
 
429
tag_sort_methods.default_key = "natural"