~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tag.py

merge trunk

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
25
25
# NOTE: I was going to call this tags.py, but vim seems to think all files
26
26
# called tags* are ctags files... mbp 20070220.
27
27
 
 
28
from bzrlib.registry import Registry
 
29
from bzrlib.lazy_import import lazy_import
 
30
lazy_import(globals(), """
 
31
import itertools
 
32
import re
 
33
import sys
28
34
 
29
35
from bzrlib import (
30
36
    bencode,
 
37
    cleanup,
31
38
    errors,
 
39
    symbol_versioning,
32
40
    trace,
33
41
    )
 
42
""")
34
43
 
35
44
 
36
45
class _Tags(object):
57
66
    lookup_tag = _not_supported
58
67
    delete_tag = _not_supported
59
68
 
60
 
    def merge_to(self, to_tags, overwrite=False):
 
69
    def merge_to(self, to_tags, overwrite=False, ignore_master=False):
61
70
        # we never have anything to copy
62
71
        pass
63
72
 
177
186
            raise ValueError("failed to deserialize tag dictionary %r: %s"
178
187
                % (tag_content, e))
179
188
 
180
 
    def merge_to(self, to_tags, overwrite=False):
 
189
    def merge_to(self, to_tags, overwrite=False, ignore_master=False):
181
190
        """Copy tags between repositories if necessary and possible.
182
191
 
183
192
        This method has common command-line behaviour about handling
188
197
 
189
198
        :param to_tags: Branch to receive these tags
190
199
        :param overwrite: Overwrite conflicting tags in the target branch
 
200
        :param ignore_master: Do not modify the tags in the target's master
 
201
            branch (if any).  Default is false (so the master will be updated).
 
202
            New in bzr 2.3.
191
203
 
192
 
        :returns: A list of tags that conflicted, each of which is
 
204
        :returns: A set of tags that conflicted, each of which is
193
205
            (tagname, source_target, dest_target), or None if no copying was
194
206
            done.
195
207
        """
 
208
        operation = cleanup.OperationWithCleanups(self._merge_to_operation)
 
209
        return operation.run(to_tags, overwrite, ignore_master)
 
210
 
 
211
    def _merge_to_operation(self, operation, to_tags, overwrite, ignore_master):
 
212
        add_cleanup = operation.add_cleanup
196
213
        if self.branch == to_tags.branch:
197
214
            return
198
215
        if not self.branch.supports_tags():
203
220
            # no tags in the source, and we don't want to clobber anything
204
221
            # that's in the destination
205
222
            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()
 
223
        # We merge_to both master and child individually.
 
224
        #
 
225
        # It's possible for master and child to have differing sets of
 
226
        # tags, in which case it's possible to have different sets of
 
227
        # conflicts.  We report the union of both conflict sets.  In
 
228
        # that case it's likely the child and master have accepted
 
229
        # different tags from the source, which may be a surprising result, but
 
230
        # the best we can do in the circumstances.
 
231
        #
 
232
        # Ideally we'd improve this API to report the different conflicts
 
233
        # more clearly to the caller, but we don't want to break plugins
 
234
        # such as bzr-builddeb that use this API.
 
235
        add_cleanup(to_tags.branch.lock_write().unlock)
 
236
        if ignore_master:
 
237
            master = None
 
238
        else:
 
239
            master = to_tags.branch.get_master_branch()
 
240
        if master is not None:
 
241
            add_cleanup(master.lock_write().unlock)
 
242
        conflicts = self._merge_to(to_tags, source_dict, overwrite)
 
243
        if master is not None:
 
244
            conflicts += self._merge_to(master.tags, source_dict,
 
245
                overwrite)
 
246
        # We use set() to remove any duplicate conflicts from the master
 
247
        # branch.
 
248
        return set(conflicts)
 
249
 
 
250
    def _merge_to(self, to_tags, source_dict, overwrite):
 
251
        dest_dict = to_tags.get_tag_dict()
 
252
        result, conflicts = self._reconcile_tags(source_dict, dest_dict,
 
253
                                                 overwrite)
 
254
        if result != dest_dict:
 
255
            to_tags._set_tag_dict(result)
215
256
        return conflicts
216
257
 
217
258
    def rename_revisions(self, rename_map):
249
290
        return result, conflicts
250
291
 
251
292
 
252
 
def _merge_tags_if_possible(from_branch, to_branch):
253
 
    from_branch.tags.merge_to(to_branch.tags)
254
 
 
 
293
def _merge_tags_if_possible(from_branch, to_branch, ignore_master=False):
 
294
    # Try hard to support merge_to implementations that don't expect
 
295
    # 'ignore_master' (new in bzr 2.3).  First, if the flag isn't set then we
 
296
    # can safely avoid passing ignore_master at all.
 
297
    if not ignore_master:
 
298
        from_branch.tags.merge_to(to_branch.tags)
 
299
        return
 
300
    # If the flag is set, try to pass it, but be ready to catch TypeError.
 
301
    try:
 
302
        from_branch.tags.merge_to(to_branch.tags, ignore_master=ignore_master)
 
303
    except TypeError:
 
304
        # Probably this implementation of 'merge_to' is from a plugin that
 
305
        # doesn't expect the 'ignore_master' keyword argument (e.g. bzr-svn
 
306
        # 1.0.4).  There's a small risk that the TypeError is actually caused
 
307
        # by a completely different problem (which is why we don't catch it for
 
308
        # the ignore_master=False case), but even then there's probably no harm
 
309
        # in calling a second time.
 
310
        symbol_versioning.warn(
 
311
            symbol_versioning.deprecated_in((2,3)) % (
 
312
                "Tags.merge_to (of %r) that doesn't accept ignore_master kwarg"
 
313
                % (from_branch.tags,),),
 
314
            DeprecationWarning)
 
315
        from_branch.tags.merge_to(to_branch.tags)
 
316
 
 
317
 
 
318
def sort_natural(branch, tags):
 
319
    """Sort tags, with numeric substrings as numbers.
 
320
 
 
321
    :param branch: Branch
 
322
    :param tags: List of tuples with tag name and revision id.
 
323
    """
 
324
    def natural_sort_key(tag):
 
325
        return [f(s) for f,s in
 
326
                zip(itertools.cycle((unicode.lower,int)),
 
327
                                    re.split('([0-9]+)', tag[0]))]
 
328
    tags.sort(key=natural_sort_key)
 
329
 
 
330
 
 
331
def sort_alpha(branch, tags):
 
332
    """Sort tags lexicographically, in place.
 
333
 
 
334
    :param branch: Branch
 
335
    :param tags: List of tuples with tag name and revision id.
 
336
    """
 
337
    tags.sort()
 
338
 
 
339
 
 
340
def sort_time(branch, tags):
 
341
    """Sort tags by time inline.
 
342
 
 
343
    :param branch: Branch
 
344
    :param tags: List of tuples with tag name and revision id.
 
345
    """
 
346
    timestamps = {}
 
347
    for tag, revid in tags:
 
348
        try:
 
349
            revobj = branch.repository.get_revision(revid)
 
350
        except errors.NoSuchRevision:
 
351
            timestamp = sys.maxint # place them at the end
 
352
        else:
 
353
            timestamp = revobj.timestamp
 
354
        timestamps[revid] = timestamp
 
355
    tags.sort(key=lambda x: timestamps[x[1]])
 
356
 
 
357
 
 
358
tag_sort_methods = Registry()
 
359
tag_sort_methods.register("natural", sort_natural,
 
360
    'Sort numeric substrings as numbers. (default)')
 
361
tag_sort_methods.register("alpha", sort_alpha, 'Sort tags lexicographically.')
 
362
tag_sort_methods.register("time", sort_time, 'Sort tags chronologically.')
 
363
tag_sort_methods.default_key = "natural"