/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: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
 
18
18
import re
26
26
from bzrlib import (
27
27
    errors,
28
28
    osutils,
 
29
    registry,
29
30
    revision,
30
31
    symbol_versioning,
31
32
    trace,
112
113
        return RevisionInfo(branch, revno, revision_id)
113
114
 
114
115
 
115
 
# classes in this list should have a "prefix" attribute, against which
116
 
# string specs are matched
117
 
SPEC_TYPES = []
118
116
_revno_regex = None
119
117
 
120
118
 
123
121
 
124
122
    help_txt = """A parsed revision specification.
125
123
 
126
 
    A revision specification can be an integer, in which case it is
127
 
    assumed to be a revno (though this will translate negative values
128
 
    into positive ones); or it can be a string, in which case it is
129
 
    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.
130
128
 
131
129
    Revision specs are an UI element, and they have been moved out
132
130
    of the branch class to leave "back-end" classes unaware of such
139
137
 
140
138
    prefix = None
141
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
    """
142
148
 
143
149
    @staticmethod
144
150
    def from_string(spec):
153
159
 
154
160
        if spec is None:
155
161
            return RevisionSpec(None, _internal=True)
156
 
        for spectype in SPEC_TYPES:
157
 
            if spec.startswith(spectype.prefix):
158
 
                trace.mutter('Returning RevisionSpec %s for %s',
159
 
                             spectype.__name__, spec)
160
 
                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)
161
168
        else:
162
 
            # RevisionSpec_revno is special cased, because it is the only
163
 
            # one that directly handles plain integers
164
 
            # TODO: This should not be special cased rather it should be
165
 
            # a method invocation on spectype.canparse()
166
 
            global _revno_regex
167
 
            if _revno_regex is None:
168
 
                _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
169
 
            if _revno_regex.match(spec) is not None:
170
 
                return RevisionSpec_revno(spec, _internal=True)
171
 
 
172
 
            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)
173
177
 
174
178
    def __init__(self, spec, _internal=False):
175
179
        """Create a RevisionSpec referring to the Null revision.
179
183
            called directly. Only from RevisionSpec.from_string()
180
184
        """
181
185
        if not _internal:
182
 
            # XXX: Update this after 0.10 is released
183
186
            symbol_versioning.warn('Creating a RevisionSpec directly has'
184
187
                                   ' been deprecated in version 0.11. Use'
185
188
                                   ' RevisionSpec.from_string()'
267
270
        # this is mostly for helping with testing
268
271
        return '<%s %s>' % (self.__class__.__name__,
269
272
                              self.user_spec)
270
 
    
 
273
 
271
274
    def needs_branch(self):
272
275
        """Whether this revision spec needs a branch.
273
276
 
277
280
 
278
281
    def get_branch(self):
279
282
        """When the revision specifier contains a branch location, return it.
280
 
        
 
283
 
281
284
        Otherwise, return None.
282
285
        """
283
286
        return None
285
288
 
286
289
# private API
287
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
 
288
337
class RevisionSpec_revno(RevisionSpec):
289
338
    """Selects a revision using a number."""
290
339
 
291
340
    help_txt = """Selects a revision using a number.
292
341
 
293
342
    Use an integer to specify a revision in the history of the branch.
294
 
    Optionally a branch can be specified. The 'revno:' prefix is optional.
295
 
    A negative number will count from the end of the branch (-1 is the
296
 
    last revision, -2 the previous one). If the negative number is larger
297
 
    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.
298
347
    Examples::
299
348
 
300
349
      revno:1                   -> return the first revision of this branch
334
383
                dotted = False
335
384
            except ValueError:
336
385
                # dotted decimal. This arguably should not be here
337
 
                # but the from_string method is a little primitive 
 
386
                # but the from_string method is a little primitive
338
387
                # right now - RBC 20060928
339
388
                try:
340
389
                    match_revno = tuple((int(number) for number in revno_spec.split('.')))
352
401
            revs_or_none = None
353
402
 
354
403
        if dotted:
355
 
            branch.lock_read()
356
404
            try:
357
 
                revision_id_to_revno = branch.get_revision_id_to_revno_map()
358
 
                revisions = [revision_id for revision_id, revno
359
 
                             in revision_id_to_revno.iteritems()
360
 
                             if revno == match_revno]
361
 
            finally:
362
 
                branch.unlock()
363
 
            if len(revisions) != 1:
 
405
                revision_id = branch.dotted_revno_to_revision_id(match_revno,
 
406
                    _cache_reverse=True)
 
407
            except errors.NoSuchRevision:
364
408
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
365
409
            else:
366
410
                # there is no traditional 'revno' for dotted-decimal revnos.
367
411
                # so for  API compatability we return None.
368
 
                return branch, None, revisions[0]
 
412
                return branch, None, revision_id
369
413
        else:
370
414
            last_revno, last_revision_id = branch.last_revision_info()
371
415
            if revno < 0:
395
439
        else:
396
440
            return self.spec[self.spec.find(':')+1:]
397
441
 
398
 
# Old compatibility 
 
442
# Old compatibility
399
443
RevisionSpec_int = RevisionSpec_revno
400
444
 
401
 
SPEC_TYPES.append(RevisionSpec_revno)
402
445
 
403
446
 
404
447
class RevisionSpec_revid(RevisionSpec):
407
450
    help_txt = """Selects a revision using the revision id.
408
451
 
409
452
    Supply a specific revision id, that can be used to specify any
410
 
    revision id in the ancestry of the branch. 
 
453
    revision id in the ancestry of the branch.
411
454
    Including merges, and pending merges.
412
455
    Examples::
413
456
 
426
469
    def _as_revision_id(self, context_branch):
427
470
        return osutils.safe_revision_id(self.spec, warn=False)
428
471
 
429
 
SPEC_TYPES.append(RevisionSpec_revid)
430
472
 
431
473
 
432
474
class RevisionSpec_last(RevisionSpec):
478
520
        revno, revision_id = self._revno_and_revision_id(context_branch, None)
479
521
        return revision_id
480
522
 
481
 
SPEC_TYPES.append(RevisionSpec_last)
482
523
 
483
524
 
484
525
class RevisionSpec_before(RevisionSpec):
504
545
    """
505
546
 
506
547
    prefix = 'before:'
507
 
    
 
548
 
508
549
    def _match_on(self, branch, revs):
509
550
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
510
551
        if r.revno == 0:
553
594
                'No parents for revision.')
554
595
        return parents[0]
555
596
 
556
 
SPEC_TYPES.append(RevisionSpec_before)
557
597
 
558
598
 
559
599
class RevisionSpec_tag(RevisionSpec):
565
605
    """
566
606
 
567
607
    prefix = 'tag:'
 
608
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
568
609
 
569
610
    def _match_on(self, branch, revs):
570
611
        # Can raise tags not supported, NoSuchTag, etc
575
616
    def _as_revision_id(self, context_branch):
576
617
        return context_branch.tags.lookup_tag(self.spec)
577
618
 
578
 
SPEC_TYPES.append(RevisionSpec_tag)
579
619
 
580
620
 
581
621
class _RevListToTimestamps(object):
616
656
      date:yesterday            -> select the first revision since yesterday
617
657
      date:2006-08-14,17:10:14  -> select the first revision after
618
658
                                   August 14th, 2006 at 5:10pm.
619
 
    """    
 
659
    """
620
660
    prefix = 'date:'
621
661
    _date_re = re.compile(
622
662
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
682
722
        else:
683
723
            return RevisionInfo(branch, rev + 1)
684
724
 
685
 
SPEC_TYPES.append(RevisionSpec_date)
686
725
 
687
726
 
688
727
class RevisionSpec_ancestor(RevisionSpec):
733
772
            revision_a = revision.ensure_null(branch.last_revision())
734
773
            if revision_a == revision.NULL_REVISION:
735
774
                raise errors.NoCommits(branch)
 
775
            if other_location == '':
 
776
                other_location = branch.get_parent()
736
777
            other_branch = Branch.open(other_location)
737
778
            other_branch.lock_read()
738
779
            try:
750
791
            branch.unlock()
751
792
 
752
793
 
753
 
SPEC_TYPES.append(RevisionSpec_ancestor)
754
794
 
755
795
 
756
796
class RevisionSpec_branch(RevisionSpec):
765
805
      branch:/path/to/branch
766
806
    """
767
807
    prefix = 'branch:'
 
808
    dwim_catchable_exceptions = (errors.NotBranchError,)
768
809
 
769
810
    def _match_on(self, branch, revs):
770
811
        from bzrlib.branch import Branch
799
840
            raise errors.NoCommits(other_branch)
800
841
        return other_branch.repository.revision_tree(last_revision)
801
842
 
802
 
SPEC_TYPES.append(RevisionSpec_branch)
803
843
 
804
844
 
805
845
class RevisionSpec_submit(RevisionSpec_ancestor):
844
884
            self._get_submit_location(context_branch))
845
885
 
846
886
 
847
 
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", [])