~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Jelmer Vernooij
  • Date: 2011-12-16 16:40:10 UTC
  • mto: This revision was merged to the branch mainline in revision 6391.
  • Revision ID: jelmer@samba.org-20111216164010-z3hy00xrnclnkf7a
Update tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
 
18
 
import re
19
 
 
20
18
from bzrlib.lazy_import import lazy_import
21
19
lazy_import(globals(), """
22
20
import bisect
23
21
import datetime
24
 
""")
25
22
 
26
23
from bzrlib import (
27
24
    branch as _mod_branch,
28
 
    errors,
29
25
    osutils,
30
 
    registry,
31
26
    revision,
32
27
    symbol_versioning,
 
28
    workingtree,
 
29
    )
 
30
from bzrlib.i18n import gettext
 
31
""")
 
32
 
 
33
from bzrlib import (
 
34
    errors,
 
35
    lazy_regex,
 
36
    registry,
33
37
    trace,
34
 
    workingtree,
35
38
    )
36
39
 
37
40
 
38
 
_marker = []
39
 
 
40
 
 
41
41
class RevisionInfo(object):
42
42
    """The results of applying a revision specification to a branch."""
43
43
 
55
55
    or treat the result as a tuple.
56
56
    """
57
57
 
58
 
    def __init__(self, branch, revno, rev_id=_marker):
 
58
    def __init__(self, branch, revno=None, rev_id=None):
59
59
        self.branch = branch
60
 
        self.revno = revno
61
 
        if rev_id is _marker:
 
60
        self._has_revno = (revno is not None)
 
61
        self._revno = revno
 
62
        self.rev_id = rev_id
 
63
        if self.rev_id is None and self._revno is not None:
62
64
            # allow caller to be lazy
63
 
            if self.revno is None:
64
 
                self.rev_id = None
65
 
            else:
66
 
                self.rev_id = branch.get_rev_id(self.revno)
67
 
        else:
68
 
            self.rev_id = rev_id
 
65
            self.rev_id = branch.get_rev_id(self._revno)
 
66
 
 
67
    @property
 
68
    def revno(self):
 
69
        if not self._has_revno and self.rev_id is not None:
 
70
            try:
 
71
                self._revno = self.branch.revision_id_to_revno(self.rev_id)
 
72
            except errors.NoSuchRevision:
 
73
                self._revno = None
 
74
            self._has_revno = True
 
75
        return self._revno
69
76
 
70
77
    def __nonzero__(self):
71
78
        # first the easy ones...
101
108
            self.revno, self.rev_id, self.branch)
102
109
 
103
110
    @staticmethod
104
 
    def from_revision_id(branch, revision_id, revs):
 
111
    def from_revision_id(branch, revision_id, revs=symbol_versioning.DEPRECATED_PARAMETER):
105
112
        """Construct a RevisionInfo given just the id.
106
113
 
107
114
        Use this if you don't know or care what the revno is.
108
115
        """
109
 
        if revision_id == revision.NULL_REVISION:
110
 
            return RevisionInfo(branch, 0, revision_id)
111
 
        try:
112
 
            revno = revs.index(revision_id) + 1
113
 
        except ValueError:
114
 
            revno = None
115
 
        return RevisionInfo(branch, revno, revision_id)
116
 
 
117
 
 
118
 
_revno_regex = None
 
116
        if symbol_versioning.deprecated_passed(revs):
 
117
            symbol_versioning.warn(
 
118
                'RevisionInfo.from_revision_id(revs) was deprecated in 2.5.',
 
119
                DeprecationWarning,
 
120
                stacklevel=2)
 
121
        return RevisionInfo(branch, revno=None, rev_id=revision_id)
119
122
 
120
123
 
121
124
class RevisionSpec(object):
138
141
    """
139
142
 
140
143
    prefix = None
141
 
    wants_revision_history = True
 
144
    # wants_revision_history has been deprecated in 2.5.
 
145
    wants_revision_history = False
142
146
    dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
143
147
    """Exceptions that RevisionSpec_dwim._match_on will catch.
144
148
 
168
172
                         spectype.__name__, spec)
169
173
            return spectype(spec, _internal=True)
170
174
        else:
171
 
            for spectype in SPEC_TYPES:
172
 
                if spec.startswith(spectype.prefix):
173
 
                    trace.mutter('Returning RevisionSpec %s for %s',
174
 
                                 spectype.__name__, spec)
175
 
                    return spectype(spec, _internal=True)
176
175
            # Otherwise treat it as a DWIM, build the RevisionSpec object and
177
176
            # wait for _match_on to be called.
178
177
            return RevisionSpec_dwim(spec, _internal=True)
214
213
    def in_history(self, branch):
215
214
        if branch:
216
215
            if self.wants_revision_history:
217
 
                revs = branch.revision_history()
 
216
                symbol_versioning.warn(
 
217
                    "RevisionSpec.wants_revision_history was "
 
218
                    "deprecated in 2.5 (%s)." % self.__class__.__name__,
 
219
                    DeprecationWarning)
 
220
                branch.lock_read()
 
221
                try:
 
222
                    graph = branch.repository.get_graph()
 
223
                    revs = list(graph.iter_lefthand_ancestry(
 
224
                        branch.last_revision(), [revision.NULL_REVISION]))
 
225
                finally:
 
226
                    branch.unlock()
 
227
                revs.reverse()
218
228
            else:
219
229
                revs = None
220
230
        else:
300
310
    """
301
311
 
302
312
    help_txt = None
303
 
    # We don't need to build the revision history ourself, that's delegated to
304
 
    # each revspec we try.
305
 
    wants_revision_history = False
 
313
 
 
314
    _revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
 
315
 
 
316
    # The revspecs to try
 
317
    _possible_revspecs = []
306
318
 
307
319
    def _try_spectype(self, rstype, branch):
308
320
        rs = rstype(self.spec, _internal=True)
314
326
        """Run the lookup and see what we can get."""
315
327
 
316
328
        # First, see if it's a revno
317
 
        global _revno_regex
318
 
        if _revno_regex is None:
319
 
            _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
320
 
        if _revno_regex.match(self.spec) is not None:
 
329
        if self._revno_regex.match(self.spec) is not None:
321
330
            try:
322
331
                return self._try_spectype(RevisionSpec_revno, branch)
323
332
            except RevisionSpec_revno.dwim_catchable_exceptions:
324
333
                pass
325
334
 
326
335
        # Next see what has been registered
 
336
        for objgetter in self._possible_revspecs:
 
337
            rs_class = objgetter.get_obj()
 
338
            try:
 
339
                return self._try_spectype(rs_class, branch)
 
340
            except rs_class.dwim_catchable_exceptions:
 
341
                pass
 
342
 
 
343
        # Try the old (deprecated) dwim list:
327
344
        for rs_class in dwim_revspecs:
328
345
            try:
329
346
                return self._try_spectype(rs_class, branch)
335
352
        # really relevant.
336
353
        raise errors.InvalidRevisionSpec(self.spec, branch)
337
354
 
 
355
    @classmethod
 
356
    def append_possible_revspec(cls, revspec):
 
357
        """Append a possible DWIM revspec.
 
358
 
 
359
        :param revspec: Revision spec to try.
 
360
        """
 
361
        cls._possible_revspecs.append(registry._ObjectGetter(revspec))
 
362
 
 
363
    @classmethod
 
364
    def append_possible_lazy_revspec(cls, module_name, member_name):
 
365
        """Append a possible lazily loaded DWIM revspec.
 
366
 
 
367
        :param module_name: Name of the module with the revspec
 
368
        :param member_name: Name of the revspec within the module
 
369
        """
 
370
        cls._possible_revspecs.append(
 
371
            registry._LazyObjectGetter(module_name, member_name))
 
372
 
338
373
 
339
374
class RevisionSpec_revno(RevisionSpec):
340
375
    """Selects a revision using a number."""
358
393
                                   your history is very long.
359
394
    """
360
395
    prefix = 'revno:'
361
 
    wants_revision_history = False
362
396
 
363
397
    def _match_on(self, branch, revs):
364
398
        """Lookup a revision by revision number"""
365
 
        branch, revno, revision_id = self._lookup(branch, revs)
 
399
        branch, revno, revision_id = self._lookup(branch)
366
400
        return RevisionInfo(branch, revno, revision_id)
367
401
 
368
 
    def _lookup(self, branch, revs_or_none):
 
402
    def _lookup(self, branch):
369
403
        loc = self.spec.find(':')
370
404
        if loc == -1:
371
405
            revno_spec = self.spec
395
429
                dotted = True
396
430
 
397
431
        if branch_spec:
398
 
            # the user has override the branch to look in.
399
 
            # we need to refresh the revision_history map and
400
 
            # the branch object.
401
 
            from bzrlib.branch import Branch
402
 
            branch = Branch.open(branch_spec)
403
 
            revs_or_none = None
 
432
            # the user has overriden the branch to look in.
 
433
            branch = _mod_branch.Branch.open(branch_spec)
404
434
 
405
435
        if dotted:
406
436
            try:
410
440
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
411
441
            else:
412
442
                # there is no traditional 'revno' for dotted-decimal revnos.
413
 
                # so for  API compatability we return None.
 
443
                # so for API compatibility we return None.
414
444
                return branch, None, revision_id
415
445
        else:
416
446
            last_revno, last_revision_id = branch.last_revision_info()
422
452
                else:
423
453
                    revno = last_revno + revno + 1
424
454
            try:
425
 
                revision_id = branch.get_rev_id(revno, revs_or_none)
 
455
                revision_id = branch.get_rev_id(revno)
426
456
            except errors.NoSuchRevision:
427
457
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
428
458
        return branch, revno, revision_id
429
459
 
430
460
    def _as_revision_id(self, context_branch):
431
461
        # We would have the revno here, but we don't really care
432
 
        branch, revno, revision_id = self._lookup(context_branch, None)
 
462
        branch, revno, revision_id = self._lookup(context_branch)
433
463
        return revision_id
434
464
 
435
465
    def needs_branch(self):
445
475
RevisionSpec_int = RevisionSpec_revno
446
476
 
447
477
 
448
 
 
449
478
class RevisionIDSpec(RevisionSpec):
450
479
 
451
480
    def _match_on(self, branch, revs):
452
481
        revision_id = self.as_revision_id(branch)
453
 
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
 
482
        return RevisionInfo.from_revision_id(branch, revision_id)
454
483
 
455
484
 
456
485
class RevisionSpec_revid(RevisionIDSpec):
492
521
    prefix = 'last:'
493
522
 
494
523
    def _match_on(self, branch, revs):
495
 
        revno, revision_id = self._revno_and_revision_id(branch, revs)
 
524
        revno, revision_id = self._revno_and_revision_id(branch)
496
525
        return RevisionInfo(branch, revno, revision_id)
497
526
 
498
 
    def _revno_and_revision_id(self, context_branch, revs_or_none):
 
527
    def _revno_and_revision_id(self, context_branch):
499
528
        last_revno, last_revision_id = context_branch.last_revision_info()
500
529
 
501
530
        if self.spec == '':
514
543
 
515
544
        revno = last_revno - offset + 1
516
545
        try:
517
 
            revision_id = context_branch.get_rev_id(revno, revs_or_none)
 
546
            revision_id = context_branch.get_rev_id(revno)
518
547
        except errors.NoSuchRevision:
519
548
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
520
549
        return revno, revision_id
522
551
    def _as_revision_id(self, context_branch):
523
552
        # We compute the revno as part of the process, but we don't really care
524
553
        # about it.
525
 
        revno, revision_id = self._revno_and_revision_id(context_branch, None)
 
554
        revno, revision_id = self._revno_and_revision_id(context_branch)
526
555
        return revision_id
527
556
 
528
557
 
560
589
            # We need to use the repository history here
561
590
            rev = branch.repository.get_revision(r.rev_id)
562
591
            if not rev.parent_ids:
563
 
                revno = 0
564
592
                revision_id = revision.NULL_REVISION
565
593
            else:
566
594
                revision_id = rev.parent_ids[0]
567
 
                try:
568
 
                    revno = revs.index(revision_id) + 1
569
 
                except ValueError:
570
 
                    revno = None
 
595
            revno = None
571
596
        else:
572
597
            revno = r.revno - 1
573
598
            try:
578
603
        return RevisionInfo(branch, revno, revision_id)
579
604
 
580
605
    def _as_revision_id(self, context_branch):
581
 
        base_revspec = RevisionSpec.from_string(self.spec)
582
 
        base_revision_id = base_revspec.as_revision_id(context_branch)
 
606
        base_revision_id = RevisionSpec.from_string(self.spec)._as_revision_id(context_branch)
583
607
        if base_revision_id == revision.NULL_REVISION:
584
608
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
585
609
                                         'cannot go before the null: revision')
615
639
    def _match_on(self, branch, revs):
616
640
        # Can raise tags not supported, NoSuchTag, etc
617
641
        return RevisionInfo.from_revision_id(branch,
618
 
            branch.tags.lookup_tag(self.spec),
619
 
            revs)
 
642
            branch.tags.lookup_tag(self.spec))
620
643
 
621
644
    def _as_revision_id(self, context_branch):
622
645
        return context_branch.tags.lookup_tag(self.spec)
626
649
class _RevListToTimestamps(object):
627
650
    """This takes a list of revisions, and allows you to bisect by date"""
628
651
 
629
 
    __slots__ = ['revs', 'branch']
 
652
    __slots__ = ['branch']
630
653
 
631
 
    def __init__(self, revs, branch):
632
 
        self.revs = revs
 
654
    def __init__(self, branch):
633
655
        self.branch = branch
634
656
 
635
657
    def __getitem__(self, index):
636
658
        """Get the date of the index'd item"""
637
 
        r = self.branch.repository.get_revision(self.revs[index])
 
659
        r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
638
660
        # TODO: Handle timezone.
639
661
        return datetime.datetime.fromtimestamp(r.timestamp)
640
662
 
641
663
    def __len__(self):
642
 
        return len(self.revs)
 
664
        return self.branch.revno()
643
665
 
644
666
 
645
667
class RevisionSpec_date(RevisionSpec):
663
685
                                   August 14th, 2006 at 5:10pm.
664
686
    """
665
687
    prefix = 'date:'
666
 
    _date_re = re.compile(
 
688
    _date_regex = lazy_regex.lazy_compile(
667
689
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
668
690
            r'(,|T)?\s*'
669
691
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
687
709
        elif self.spec.lower() == 'tomorrow':
688
710
            dt = today + datetime.timedelta(days=1)
689
711
        else:
690
 
            m = self._date_re.match(self.spec)
 
712
            m = self._date_regex.match(self.spec)
691
713
            if not m or (not m.group('date') and not m.group('time')):
692
714
                raise errors.InvalidRevisionSpec(self.user_spec,
693
715
                                                 branch, 'invalid date')
719
741
                    hour=hour, minute=minute, second=second)
720
742
        branch.lock_read()
721
743
        try:
722
 
            rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
 
744
            rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
723
745
        finally:
724
746
            branch.unlock()
725
 
        if rev == len(revs):
 
747
        if rev == branch.revno():
726
748
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
727
 
        else:
728
 
            return RevisionInfo(branch, rev + 1)
 
749
        return RevisionInfo(branch, rev)
729
750
 
730
751
 
731
752
 
762
783
    def _find_revision_info(branch, other_location):
763
784
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
764
785
                                                              other_location)
765
 
        try:
766
 
            revno = branch.revision_id_to_revno(revision_id)
767
 
        except errors.NoSuchRevision:
768
 
            revno = None
769
 
        return RevisionInfo(branch, revno, revision_id)
 
786
        return RevisionInfo(branch, None, revision_id)
770
787
 
771
788
    @staticmethod
772
789
    def _find_revision_id(branch, other_location):
826
843
                branch.fetch(other_branch, revision_b)
827
844
            except errors.ReadOnlyError:
828
845
                branch = other_branch
829
 
        try:
830
 
            revno = branch.revision_id_to_revno(revision_b)
831
 
        except errors.NoSuchRevision:
832
 
            revno = None
833
 
        return RevisionInfo(branch, revno, revision_b)
 
846
        return RevisionInfo(branch, None, revision_b)
834
847
 
835
848
    def _as_revision_id(self, context_branch):
836
849
        from bzrlib.branch import Branch
888
901
            location_type = 'parent branch'
889
902
        if submit_location is None:
890
903
            raise errors.NoSubmitBranch(branch)
891
 
        trace.note('Using %s %s', location_type, submit_location)
 
904
        trace.note(gettext('Using {0} {1}').format(location_type,
 
905
                                                        submit_location))
892
906
        return submit_location
893
907
 
894
908
    def _match_on(self, branch, revs):
971
985
# The order in which we want to DWIM a revision spec without any prefix.
972
986
# revno is always tried first and isn't listed here, this is used by
973
987
# RevisionSpec_dwim._match_on
974
 
dwim_revspecs = [
975
 
    RevisionSpec_tag, # Let's try for a tag
976
 
    RevisionSpec_revid, # Maybe it's a revid?
977
 
    RevisionSpec_date, # Perhaps a date?
978
 
    RevisionSpec_branch, # OK, last try, maybe it's a branch
979
 
    ]
 
988
dwim_revspecs = symbol_versioning.deprecated_list(
 
989
    symbol_versioning.deprecated_in((2, 4, 0)), "dwim_revspecs", [])
980
990
 
 
991
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
 
992
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
 
993
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
 
994
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
981
995
 
982
996
revspec_registry = registry.Registry()
983
997
def _register_revspec(revspec):
994
1008
_register_revspec(RevisionSpec_submit)
995
1009
_register_revspec(RevisionSpec_annotate)
996
1010
_register_revspec(RevisionSpec_mainline)
997
 
 
998
 
# classes in this list should have a "prefix" attribute, against which
999
 
# string specs are matched
1000
 
SPEC_TYPES = symbol_versioning.deprecated_list(
1001
 
    symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])