/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 bzrlib/revisionspec.py

  • Committer: Martin von Gagern
  • Date: 2010-04-20 08:47:38 UTC
  • mfrom: (5167 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5195.
  • Revision ID: martin.vgagern@gmx.net-20100420084738-ygymnqmdllzrhpfn
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
 
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
 
 
18
import re
 
19
 
 
20
from bzrlib.lazy_import import lazy_import
 
21
lazy_import(globals(), """
18
22
import bisect
19
23
import datetime
20
 
import re
 
24
""")
21
25
 
22
26
from bzrlib import (
23
27
    errors,
24
28
    osutils,
 
29
    registry,
25
30
    revision,
26
31
    symbol_versioning,
27
32
    trace,
28
 
    tsort,
29
33
    )
30
34
 
31
35
 
109
113
        return RevisionInfo(branch, revno, revision_id)
110
114
 
111
115
 
112
 
# classes in this list should have a "prefix" attribute, against which
113
 
# string specs are matched
114
 
SPEC_TYPES = []
115
116
_revno_regex = None
116
117
 
117
118
 
120
121
 
121
122
    help_txt = """A parsed revision specification.
122
123
 
123
 
    A revision specification can be an integer, in which case it is
124
 
    assumed to be a revno (though this will translate negative values
125
 
    into positive ones); or it can be a string, in which case it is
126
 
    parsed for something like 'date:' or 'revid:' etc.
 
124
    A revision specification is a string, which may be unambiguous about
 
125
    what it represents by giving a prefix like 'date:' or 'revid:' etc,
 
126
    or it may have no prefix, in which case it's tried against several
 
127
    specifier types in sequence to determine what the user meant.
127
128
 
128
129
    Revision specs are an UI element, and they have been moved out
129
130
    of the branch class to leave "back-end" classes unaware of such
136
137
 
137
138
    prefix = None
138
139
    wants_revision_history = True
 
140
    dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
 
141
    """Exceptions that RevisionSpec_dwim._match_on will catch.
 
142
 
 
143
    If the revspec is part of ``dwim_revspecs``, it may be tried with an
 
144
    invalid revspec and raises some exception. The exceptions mentioned here
 
145
    will not be reported to the user but simply ignored without stopping the
 
146
    dwim processing.
 
147
    """
139
148
 
140
149
    @staticmethod
141
150
    def from_string(spec):
150
159
 
151
160
        if spec is None:
152
161
            return RevisionSpec(None, _internal=True)
153
 
        for spectype in SPEC_TYPES:
154
 
            if spec.startswith(spectype.prefix):
155
 
                trace.mutter('Returning RevisionSpec %s for %s',
156
 
                             spectype.__name__, spec)
157
 
                return spectype(spec, _internal=True)
 
162
        match = revspec_registry.get_prefix(spec)
 
163
        if match is not None:
 
164
            spectype, specsuffix = match
 
165
            trace.mutter('Returning RevisionSpec %s for %s',
 
166
                         spectype.__name__, spec)
 
167
            return spectype(spec, _internal=True)
158
168
        else:
159
 
            # RevisionSpec_revno is special cased, because it is the only
160
 
            # one that directly handles plain integers
161
 
            # TODO: This should not be special cased rather it should be
162
 
            # a method invocation on spectype.canparse()
163
 
            global _revno_regex
164
 
            if _revno_regex is None:
165
 
                _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
166
 
            if _revno_regex.match(spec) is not None:
167
 
                return RevisionSpec_revno(spec, _internal=True)
168
 
 
169
 
            raise errors.NoSuchRevisionSpec(spec)
 
169
            for spectype in SPEC_TYPES:
 
170
                if spec.startswith(spectype.prefix):
 
171
                    trace.mutter('Returning RevisionSpec %s for %s',
 
172
                                 spectype.__name__, spec)
 
173
                    return spectype(spec, _internal=True)
 
174
            # Otherwise treat it as a DWIM, build the RevisionSpec object and
 
175
            # wait for _match_on to be called.
 
176
            return RevisionSpec_dwim(spec, _internal=True)
170
177
 
171
178
    def __init__(self, spec, _internal=False):
172
179
        """Create a RevisionSpec referring to the Null revision.
176
183
            called directly. Only from RevisionSpec.from_string()
177
184
        """
178
185
        if not _internal:
179
 
            # XXX: Update this after 0.10 is released
180
186
            symbol_versioning.warn('Creating a RevisionSpec directly has'
181
187
                                   ' been deprecated in version 0.11. Use'
182
188
                                   ' RevisionSpec.from_string()'
264
270
        # this is mostly for helping with testing
265
271
        return '<%s %s>' % (self.__class__.__name__,
266
272
                              self.user_spec)
267
 
    
 
273
 
268
274
    def needs_branch(self):
269
275
        """Whether this revision spec needs a branch.
270
276
 
274
280
 
275
281
    def get_branch(self):
276
282
        """When the revision specifier contains a branch location, return it.
277
 
        
 
283
 
278
284
        Otherwise, return None.
279
285
        """
280
286
        return None
282
288
 
283
289
# private API
284
290
 
 
291
class RevisionSpec_dwim(RevisionSpec):
 
292
    """Provides a DWIMish revision specifier lookup.
 
293
 
 
294
    Note that this does not go in the revspec_registry because by definition
 
295
    there is no prefix to identify it.  It's solely called from
 
296
    RevisionSpec.from_string() because the DWIMification happen when _match_on
 
297
    is called so the string describing the revision is kept here until needed.
 
298
    """
 
299
 
 
300
    help_txt = None
 
301
    # We don't need to build the revision history ourself, that's delegated to
 
302
    # each revspec we try.
 
303
    wants_revision_history = False
 
304
 
 
305
    def _try_spectype(self, rstype, branch):
 
306
        rs = rstype(self.spec, _internal=True)
 
307
        # Hit in_history to find out if it exists, or we need to try the
 
308
        # next type.
 
309
        return rs.in_history(branch)
 
310
 
 
311
    def _match_on(self, branch, revs):
 
312
        """Run the lookup and see what we can get."""
 
313
 
 
314
        # First, see if it's a revno
 
315
        global _revno_regex
 
316
        if _revno_regex is None:
 
317
            _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
 
318
        if _revno_regex.match(self.spec) is not None:
 
319
            try:
 
320
                return self._try_spectype(RevisionSpec_revno, branch)
 
321
            except RevisionSpec_revno.dwim_catchable_exceptions:
 
322
                pass
 
323
 
 
324
        # Next see what has been registered
 
325
        for rs_class in dwim_revspecs:
 
326
            try:
 
327
                return self._try_spectype(rs_class, branch)
 
328
            except rs_class.dwim_catchable_exceptions:
 
329
                pass
 
330
 
 
331
        # Well, I dunno what it is. Note that we don't try to keep track of the
 
332
        # first of last exception raised during the DWIM tries as none seems
 
333
        # really relevant.
 
334
        raise errors.InvalidRevisionSpec(self.spec, branch)
 
335
 
 
336
 
285
337
class RevisionSpec_revno(RevisionSpec):
286
338
    """Selects a revision using a number."""
287
339
 
288
340
    help_txt = """Selects a revision using a number.
289
341
 
290
342
    Use an integer to specify a revision in the history of the branch.
291
 
    Optionally a branch can be specified. The 'revno:' prefix is optional.
292
 
    A negative number will count from the end of the branch (-1 is the
293
 
    last revision, -2 the previous one). If the negative number is larger
294
 
    than the branch's history, the first revision is returned.
 
343
    Optionally a branch can be specified.  A negative number will count
 
344
    from the end of the branch (-1 is the last revision, -2 the previous
 
345
    one). If the negative number is larger than the branch's history, the
 
346
    first revision is returned.
295
347
    Examples::
296
348
 
297
349
      revno:1                   -> return the first revision of this branch
331
383
                dotted = False
332
384
            except ValueError:
333
385
                # dotted decimal. This arguably should not be here
334
 
                # but the from_string method is a little primitive 
 
386
                # but the from_string method is a little primitive
335
387
                # right now - RBC 20060928
336
388
                try:
337
389
                    match_revno = tuple((int(number) for number in revno_spec.split('.')))
349
401
            revs_or_none = None
350
402
 
351
403
        if dotted:
352
 
            branch.lock_read()
353
404
            try:
354
 
                revision_id_to_revno = branch.get_revision_id_to_revno_map()
355
 
                revisions = [revision_id for revision_id, revno
356
 
                             in revision_id_to_revno.iteritems()
357
 
                             if revno == match_revno]
358
 
            finally:
359
 
                branch.unlock()
360
 
            if len(revisions) != 1:
361
 
                return branch, None, None
 
405
                revision_id = branch.dotted_revno_to_revision_id(match_revno,
 
406
                    _cache_reverse=True)
 
407
            except errors.NoSuchRevision:
 
408
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
362
409
            else:
363
410
                # there is no traditional 'revno' for dotted-decimal revnos.
364
411
                # so for  API compatability we return None.
365
 
                return branch, None, revisions[0]
 
412
                return branch, None, revision_id
366
413
        else:
367
414
            last_revno, last_revision_id = branch.last_revision_info()
368
415
            if revno < 0:
392
439
        else:
393
440
            return self.spec[self.spec.find(':')+1:]
394
441
 
395
 
# Old compatibility 
 
442
# Old compatibility
396
443
RevisionSpec_int = RevisionSpec_revno
397
444
 
398
 
SPEC_TYPES.append(RevisionSpec_revno)
399
445
 
400
446
 
401
447
class RevisionSpec_revid(RevisionSpec):
404
450
    help_txt = """Selects a revision using the revision id.
405
451
 
406
452
    Supply a specific revision id, that can be used to specify any
407
 
    revision id in the ancestry of the branch. 
 
453
    revision id in the ancestry of the branch.
408
454
    Including merges, and pending merges.
409
455
    Examples::
410
456
 
423
469
    def _as_revision_id(self, context_branch):
424
470
        return osutils.safe_revision_id(self.spec, warn=False)
425
471
 
426
 
SPEC_TYPES.append(RevisionSpec_revid)
427
472
 
428
473
 
429
474
class RevisionSpec_last(RevisionSpec):
475
520
        revno, revision_id = self._revno_and_revision_id(context_branch, None)
476
521
        return revision_id
477
522
 
478
 
SPEC_TYPES.append(RevisionSpec_last)
479
523
 
480
524
 
481
525
class RevisionSpec_before(RevisionSpec):
501
545
    """
502
546
 
503
547
    prefix = 'before:'
504
 
    
 
548
 
505
549
    def _match_on(self, branch, revs):
506
550
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
507
551
        if r.revno == 0:
550
594
                'No parents for revision.')
551
595
        return parents[0]
552
596
 
553
 
SPEC_TYPES.append(RevisionSpec_before)
554
597
 
555
598
 
556
599
class RevisionSpec_tag(RevisionSpec):
562
605
    """
563
606
 
564
607
    prefix = 'tag:'
 
608
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
565
609
 
566
610
    def _match_on(self, branch, revs):
567
611
        # Can raise tags not supported, NoSuchTag, etc
572
616
    def _as_revision_id(self, context_branch):
573
617
        return context_branch.tags.lookup_tag(self.spec)
574
618
 
575
 
SPEC_TYPES.append(RevisionSpec_tag)
576
619
 
577
620
 
578
621
class _RevListToTimestamps(object):
613
656
      date:yesterday            -> select the first revision since yesterday
614
657
      date:2006-08-14,17:10:14  -> select the first revision after
615
658
                                   August 14th, 2006 at 5:10pm.
616
 
    """    
 
659
    """
617
660
    prefix = 'date:'
618
661
    _date_re = re.compile(
619
662
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
679
722
        else:
680
723
            return RevisionInfo(branch, rev + 1)
681
724
 
682
 
SPEC_TYPES.append(RevisionSpec_date)
683
725
 
684
726
 
685
727
class RevisionSpec_ancestor(RevisionSpec):
730
772
            revision_a = revision.ensure_null(branch.last_revision())
731
773
            if revision_a == revision.NULL_REVISION:
732
774
                raise errors.NoCommits(branch)
 
775
            if other_location == '':
 
776
                other_location = branch.get_parent()
733
777
            other_branch = Branch.open(other_location)
734
778
            other_branch.lock_read()
735
779
            try:
747
791
            branch.unlock()
748
792
 
749
793
 
750
 
SPEC_TYPES.append(RevisionSpec_ancestor)
751
794
 
752
795
 
753
796
class RevisionSpec_branch(RevisionSpec):
762
805
      branch:/path/to/branch
763
806
    """
764
807
    prefix = 'branch:'
 
808
    dwim_catchable_exceptions = (errors.NotBranchError,)
765
809
 
766
810
    def _match_on(self, branch, revs):
767
811
        from bzrlib.branch import Branch
796
840
            raise errors.NoCommits(other_branch)
797
841
        return other_branch.repository.revision_tree(last_revision)
798
842
 
799
 
SPEC_TYPES.append(RevisionSpec_branch)
800
843
 
801
844
 
802
845
class RevisionSpec_submit(RevisionSpec_ancestor):
841
884
            self._get_submit_location(context_branch))
842
885
 
843
886
 
844
 
SPEC_TYPES.append(RevisionSpec_submit)
 
887
# The order in which we want to DWIM a revision spec without any prefix.
 
888
# revno is always tried first and isn't listed here, this is used by
 
889
# RevisionSpec_dwim._match_on
 
890
dwim_revspecs = [
 
891
    RevisionSpec_tag, # Let's try for a tag
 
892
    RevisionSpec_revid, # Maybe it's a revid?
 
893
    RevisionSpec_date, # Perhaps a date?
 
894
    RevisionSpec_branch, # OK, last try, maybe it's a branch
 
895
    ]
 
896
 
 
897
 
 
898
revspec_registry = registry.Registry()
 
899
def _register_revspec(revspec):
 
900
    revspec_registry.register(revspec.prefix, revspec)
 
901
 
 
902
_register_revspec(RevisionSpec_revno)
 
903
_register_revspec(RevisionSpec_revid)
 
904
_register_revspec(RevisionSpec_last)
 
905
_register_revspec(RevisionSpec_before)
 
906
_register_revspec(RevisionSpec_tag)
 
907
_register_revspec(RevisionSpec_date)
 
908
_register_revspec(RevisionSpec_ancestor)
 
909
_register_revspec(RevisionSpec_branch)
 
910
_register_revspec(RevisionSpec_submit)
 
911
 
 
912
# classes in this list should have a "prefix" attribute, against which
 
913
# string specs are matched
 
914
SPEC_TYPES = symbol_versioning.deprecated_list(
 
915
    symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])