/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:
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
 
 
18
import re
 
19
 
18
20
from bzrlib.lazy_import import lazy_import
19
21
lazy_import(globals(), """
20
22
import bisect
21
23
import datetime
 
24
""")
22
25
 
23
26
from bzrlib import (
24
 
    branch as _mod_branch,
 
27
    errors,
25
28
    osutils,
 
29
    registry,
26
30
    revision,
27
31
    symbol_versioning,
28
 
    workingtree,
29
 
    )
30
 
from bzrlib.i18n import gettext
31
 
""")
32
 
 
33
 
from bzrlib import (
34
 
    errors,
35
 
    lazy_regex,
36
 
    registry,
37
32
    trace,
38
33
    )
39
34
 
40
35
 
 
36
_marker = []
 
37
 
 
38
 
41
39
class RevisionInfo(object):
42
40
    """The results of applying a revision specification to a branch."""
43
41
 
55
53
    or treat the result as a tuple.
56
54
    """
57
55
 
58
 
    def __init__(self, branch, revno=None, rev_id=None):
 
56
    def __init__(self, branch, revno, rev_id=_marker):
59
57
        self.branch = branch
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:
 
58
        self.revno = revno
 
59
        if rev_id is _marker:
64
60
            # allow caller to be lazy
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
 
61
            if self.revno is None:
 
62
                self.rev_id = None
 
63
            else:
 
64
                self.rev_id = branch.get_rev_id(self.revno)
 
65
        else:
 
66
            self.rev_id = rev_id
76
67
 
77
68
    def __nonzero__(self):
78
69
        # first the easy ones...
108
99
            self.revno, self.rev_id, self.branch)
109
100
 
110
101
    @staticmethod
111
 
    def from_revision_id(branch, revision_id, revs=symbol_versioning.DEPRECATED_PARAMETER):
 
102
    def from_revision_id(branch, revision_id, revs):
112
103
        """Construct a RevisionInfo given just the id.
113
104
 
114
105
        Use this if you don't know or care what the revno is.
115
106
        """
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)
 
107
        if revision_id == revision.NULL_REVISION:
 
108
            return RevisionInfo(branch, 0, revision_id)
 
109
        try:
 
110
            revno = revs.index(revision_id) + 1
 
111
        except ValueError:
 
112
            revno = None
 
113
        return RevisionInfo(branch, revno, revision_id)
 
114
 
 
115
 
 
116
_revno_regex = None
122
117
 
123
118
 
124
119
class RevisionSpec(object):
141
136
    """
142
137
 
143
138
    prefix = None
144
 
    # wants_revision_history has been deprecated in 2.5.
145
 
    wants_revision_history = False
 
139
    wants_revision_history = True
146
140
    dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
147
141
    """Exceptions that RevisionSpec_dwim._match_on will catch.
148
142
 
172
166
                         spectype.__name__, spec)
173
167
            return spectype(spec, _internal=True)
174
168
        else:
 
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)
175
174
            # Otherwise treat it as a DWIM, build the RevisionSpec object and
176
175
            # wait for _match_on to be called.
177
176
            return RevisionSpec_dwim(spec, _internal=True)
213
212
    def in_history(self, branch):
214
213
        if branch:
215
214
            if self.wants_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()
 
215
                revs = branch.revision_history()
228
216
            else:
229
217
                revs = None
230
218
        else:
310
298
    """
311
299
 
312
300
    help_txt = None
313
 
 
314
 
    _revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
315
 
 
316
 
    # The revspecs to try
317
 
    _possible_revspecs = []
 
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
318
304
 
319
305
    def _try_spectype(self, rstype, branch):
320
306
        rs = rstype(self.spec, _internal=True)
326
312
        """Run the lookup and see what we can get."""
327
313
 
328
314
        # First, see if it's a revno
329
 
        if self._revno_regex.match(self.spec) is not None:
 
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:
330
319
            try:
331
320
                return self._try_spectype(RevisionSpec_revno, branch)
332
321
            except RevisionSpec_revno.dwim_catchable_exceptions:
333
322
                pass
334
323
 
335
324
        # 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:
344
325
        for rs_class in dwim_revspecs:
345
326
            try:
346
327
                return self._try_spectype(rs_class, branch)
352
333
        # really relevant.
353
334
        raise errors.InvalidRevisionSpec(self.spec, branch)
354
335
 
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
 
 
373
336
 
374
337
class RevisionSpec_revno(RevisionSpec):
375
338
    """Selects a revision using a number."""
393
356
                                   your history is very long.
394
357
    """
395
358
    prefix = 'revno:'
 
359
    wants_revision_history = False
396
360
 
397
361
    def _match_on(self, branch, revs):
398
362
        """Lookup a revision by revision number"""
399
 
        branch, revno, revision_id = self._lookup(branch)
 
363
        branch, revno, revision_id = self._lookup(branch, revs)
400
364
        return RevisionInfo(branch, revno, revision_id)
401
365
 
402
 
    def _lookup(self, branch):
 
366
    def _lookup(self, branch, revs_or_none):
403
367
        loc = self.spec.find(':')
404
368
        if loc == -1:
405
369
            revno_spec = self.spec
429
393
                dotted = True
430
394
 
431
395
        if branch_spec:
432
 
            # the user has overriden the branch to look in.
433
 
            branch = _mod_branch.Branch.open(branch_spec)
 
396
            # the user has override the branch to look in.
 
397
            # we need to refresh the revision_history map and
 
398
            # the branch object.
 
399
            from bzrlib.branch import Branch
 
400
            branch = Branch.open(branch_spec)
 
401
            revs_or_none = None
434
402
 
435
403
        if dotted:
436
404
            try:
440
408
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
441
409
            else:
442
410
                # there is no traditional 'revno' for dotted-decimal revnos.
443
 
                # so for API compatibility we return None.
 
411
                # so for  API compatability we return None.
444
412
                return branch, None, revision_id
445
413
        else:
446
414
            last_revno, last_revision_id = branch.last_revision_info()
452
420
                else:
453
421
                    revno = last_revno + revno + 1
454
422
            try:
455
 
                revision_id = branch.get_rev_id(revno)
 
423
                revision_id = branch.get_rev_id(revno, revs_or_none)
456
424
            except errors.NoSuchRevision:
457
425
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
458
426
        return branch, revno, revision_id
459
427
 
460
428
    def _as_revision_id(self, context_branch):
461
429
        # We would have the revno here, but we don't really care
462
 
        branch, revno, revision_id = self._lookup(context_branch)
 
430
        branch, revno, revision_id = self._lookup(context_branch, None)
463
431
        return revision_id
464
432
 
465
433
    def needs_branch(self):
475
443
RevisionSpec_int = RevisionSpec_revno
476
444
 
477
445
 
478
 
class RevisionIDSpec(RevisionSpec):
479
 
 
480
 
    def _match_on(self, branch, revs):
481
 
        revision_id = self.as_revision_id(branch)
482
 
        return RevisionInfo.from_revision_id(branch, revision_id)
483
 
 
484
 
 
485
 
class RevisionSpec_revid(RevisionIDSpec):
 
446
 
 
447
class RevisionSpec_revid(RevisionSpec):
486
448
    """Selects a revision using the revision id."""
487
449
 
488
450
    help_txt = """Selects a revision using the revision id.
497
459
 
498
460
    prefix = 'revid:'
499
461
 
500
 
    def _as_revision_id(self, context_branch):
 
462
    def _match_on(self, branch, revs):
501
463
        # self.spec comes straight from parsing the command line arguments,
502
464
        # so we expect it to be a Unicode string. Switch it to the internal
503
465
        # representation.
 
466
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
 
467
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
 
468
 
 
469
    def _as_revision_id(self, context_branch):
504
470
        return osutils.safe_revision_id(self.spec, warn=False)
505
471
 
506
472
 
521
487
    prefix = 'last:'
522
488
 
523
489
    def _match_on(self, branch, revs):
524
 
        revno, revision_id = self._revno_and_revision_id(branch)
 
490
        revno, revision_id = self._revno_and_revision_id(branch, revs)
525
491
        return RevisionInfo(branch, revno, revision_id)
526
492
 
527
 
    def _revno_and_revision_id(self, context_branch):
 
493
    def _revno_and_revision_id(self, context_branch, revs_or_none):
528
494
        last_revno, last_revision_id = context_branch.last_revision_info()
529
495
 
530
496
        if self.spec == '':
543
509
 
544
510
        revno = last_revno - offset + 1
545
511
        try:
546
 
            revision_id = context_branch.get_rev_id(revno)
 
512
            revision_id = context_branch.get_rev_id(revno, revs_or_none)
547
513
        except errors.NoSuchRevision:
548
514
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
549
515
        return revno, revision_id
551
517
    def _as_revision_id(self, context_branch):
552
518
        # We compute the revno as part of the process, but we don't really care
553
519
        # about it.
554
 
        revno, revision_id = self._revno_and_revision_id(context_branch)
 
520
        revno, revision_id = self._revno_and_revision_id(context_branch, None)
555
521
        return revision_id
556
522
 
557
523
 
589
555
            # We need to use the repository history here
590
556
            rev = branch.repository.get_revision(r.rev_id)
591
557
            if not rev.parent_ids:
 
558
                revno = 0
592
559
                revision_id = revision.NULL_REVISION
593
560
            else:
594
561
                revision_id = rev.parent_ids[0]
595
 
            revno = None
 
562
                try:
 
563
                    revno = revs.index(revision_id) + 1
 
564
                except ValueError:
 
565
                    revno = None
596
566
        else:
597
567
            revno = r.revno - 1
598
568
            try:
603
573
        return RevisionInfo(branch, revno, revision_id)
604
574
 
605
575
    def _as_revision_id(self, context_branch):
606
 
        base_revision_id = RevisionSpec.from_string(self.spec)._as_revision_id(context_branch)
 
576
        base_revspec = RevisionSpec.from_string(self.spec)
 
577
        base_revision_id = base_revspec.as_revision_id(context_branch)
607
578
        if base_revision_id == revision.NULL_REVISION:
608
579
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
609
580
                                         'cannot go before the null: revision')
639
610
    def _match_on(self, branch, revs):
640
611
        # Can raise tags not supported, NoSuchTag, etc
641
612
        return RevisionInfo.from_revision_id(branch,
642
 
            branch.tags.lookup_tag(self.spec))
 
613
            branch.tags.lookup_tag(self.spec),
 
614
            revs)
643
615
 
644
616
    def _as_revision_id(self, context_branch):
645
617
        return context_branch.tags.lookup_tag(self.spec)
649
621
class _RevListToTimestamps(object):
650
622
    """This takes a list of revisions, and allows you to bisect by date"""
651
623
 
652
 
    __slots__ = ['branch']
 
624
    __slots__ = ['revs', 'branch']
653
625
 
654
 
    def __init__(self, branch):
 
626
    def __init__(self, revs, branch):
 
627
        self.revs = revs
655
628
        self.branch = branch
656
629
 
657
630
    def __getitem__(self, index):
658
631
        """Get the date of the index'd item"""
659
 
        r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
 
632
        r = self.branch.repository.get_revision(self.revs[index])
660
633
        # TODO: Handle timezone.
661
634
        return datetime.datetime.fromtimestamp(r.timestamp)
662
635
 
663
636
    def __len__(self):
664
 
        return self.branch.revno()
 
637
        return len(self.revs)
665
638
 
666
639
 
667
640
class RevisionSpec_date(RevisionSpec):
685
658
                                   August 14th, 2006 at 5:10pm.
686
659
    """
687
660
    prefix = 'date:'
688
 
    _date_regex = lazy_regex.lazy_compile(
 
661
    _date_re = re.compile(
689
662
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
690
663
            r'(,|T)?\s*'
691
664
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
709
682
        elif self.spec.lower() == 'tomorrow':
710
683
            dt = today + datetime.timedelta(days=1)
711
684
        else:
712
 
            m = self._date_regex.match(self.spec)
 
685
            m = self._date_re.match(self.spec)
713
686
            if not m or (not m.group('date') and not m.group('time')):
714
687
                raise errors.InvalidRevisionSpec(self.user_spec,
715
688
                                                 branch, 'invalid date')
741
714
                    hour=hour, minute=minute, second=second)
742
715
        branch.lock_read()
743
716
        try:
744
 
            rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
 
717
            rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
745
718
        finally:
746
719
            branch.unlock()
747
 
        if rev == branch.revno():
 
720
        if rev == len(revs):
748
721
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
749
 
        return RevisionInfo(branch, rev)
 
722
        else:
 
723
            return RevisionInfo(branch, rev + 1)
750
724
 
751
725
 
752
726
 
783
757
    def _find_revision_info(branch, other_location):
784
758
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
785
759
                                                              other_location)
786
 
        return RevisionInfo(branch, None, revision_id)
 
760
        try:
 
761
            revno = branch.revision_id_to_revno(revision_id)
 
762
        except errors.NoSuchRevision:
 
763
            revno = None
 
764
        return RevisionInfo(branch, revno, revision_id)
787
765
 
788
766
    @staticmethod
789
767
    def _find_revision_id(branch, other_location):
835
813
        revision_b = other_branch.last_revision()
836
814
        if revision_b in (None, revision.NULL_REVISION):
837
815
            raise errors.NoCommits(other_branch)
838
 
        if branch is None:
839
 
            branch = other_branch
840
 
        else:
841
 
            try:
842
 
                # pull in the remote revisions so we can diff
843
 
                branch.fetch(other_branch, revision_b)
844
 
            except errors.ReadOnlyError:
845
 
                branch = other_branch
846
 
        return RevisionInfo(branch, None, revision_b)
 
816
        # pull in the remote revisions so we can diff
 
817
        branch.fetch(other_branch, revision_b)
 
818
        try:
 
819
            revno = branch.revision_id_to_revno(revision_b)
 
820
        except errors.NoSuchRevision:
 
821
            revno = None
 
822
        return RevisionInfo(branch, revno, revision_b)
847
823
 
848
824
    def _as_revision_id(self, context_branch):
849
825
        from bzrlib.branch import Branch
864
840
            raise errors.NoCommits(other_branch)
865
841
        return other_branch.repository.revision_tree(last_revision)
866
842
 
867
 
    def needs_branch(self):
868
 
        return False
869
 
 
870
 
    def get_branch(self):
871
 
        return self.spec
872
 
 
873
843
 
874
844
 
875
845
class RevisionSpec_submit(RevisionSpec_ancestor):
901
871
            location_type = 'parent branch'
902
872
        if submit_location is None:
903
873
            raise errors.NoSubmitBranch(branch)
904
 
        trace.note(gettext('Using {0} {1}').format(location_type,
905
 
                                                        submit_location))
 
874
        trace.note('Using %s %s', location_type, submit_location)
906
875
        return submit_location
907
876
 
908
877
    def _match_on(self, branch, revs):
915
884
            self._get_submit_location(context_branch))
916
885
 
917
886
 
918
 
class RevisionSpec_annotate(RevisionIDSpec):
919
 
 
920
 
    prefix = 'annotate:'
921
 
 
922
 
    help_txt = """Select the revision that last modified the specified line.
923
 
 
924
 
    Select the revision that last modified the specified line.  Line is
925
 
    specified as path:number.  Path is a relative path to the file.  Numbers
926
 
    start at 1, and are relative to the current version, not the last-
927
 
    committed version of the file.
928
 
    """
929
 
 
930
 
    def _raise_invalid(self, numstring, context_branch):
931
 
        raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
932
 
            'No such line: %s' % numstring)
933
 
 
934
 
    def _as_revision_id(self, context_branch):
935
 
        path, numstring = self.spec.rsplit(':', 1)
936
 
        try:
937
 
            index = int(numstring) - 1
938
 
        except ValueError:
939
 
            self._raise_invalid(numstring, context_branch)
940
 
        tree, file_path = workingtree.WorkingTree.open_containing(path)
941
 
        tree.lock_read()
942
 
        try:
943
 
            file_id = tree.path2id(file_path)
944
 
            if file_id is None:
945
 
                raise errors.InvalidRevisionSpec(self.user_spec,
946
 
                    context_branch, "File '%s' is not versioned." %
947
 
                    file_path)
948
 
            revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
949
 
        finally:
950
 
            tree.unlock()
951
 
        try:
952
 
            revision_id = revision_ids[index]
953
 
        except IndexError:
954
 
            self._raise_invalid(numstring, context_branch)
955
 
        if revision_id == revision.CURRENT_REVISION:
956
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
957
 
                'Line %s has not been committed.' % numstring)
958
 
        return revision_id
959
 
 
960
 
 
961
 
class RevisionSpec_mainline(RevisionIDSpec):
962
 
 
963
 
    help_txt = """Select mainline revision that merged the specified revision.
964
 
 
965
 
    Select the revision that merged the specified revision into mainline.
966
 
    """
967
 
 
968
 
    prefix = 'mainline:'
969
 
 
970
 
    def _as_revision_id(self, context_branch):
971
 
        revspec = RevisionSpec.from_string(self.spec)
972
 
        if revspec.get_branch() is None:
973
 
            spec_branch = context_branch
974
 
        else:
975
 
            spec_branch = _mod_branch.Branch.open(revspec.get_branch())
976
 
        revision_id = revspec.as_revision_id(spec_branch)
977
 
        graph = context_branch.repository.get_graph()
978
 
        result = graph.find_lefthand_merger(revision_id,
979
 
                                            context_branch.last_revision())
980
 
        if result is None:
981
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
982
 
        return result
983
 
 
984
 
 
985
887
# The order in which we want to DWIM a revision spec without any prefix.
986
888
# revno is always tried first and isn't listed here, this is used by
987
889
# RevisionSpec_dwim._match_on
988
 
dwim_revspecs = symbol_versioning.deprecated_list(
989
 
    symbol_versioning.deprecated_in((2, 4, 0)), "dwim_revspecs", [])
 
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
    ]
990
896
 
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)
995
897
 
996
898
revspec_registry = registry.Registry()
997
899
def _register_revspec(revspec):
1006
908
_register_revspec(RevisionSpec_ancestor)
1007
909
_register_revspec(RevisionSpec_branch)
1008
910
_register_revspec(RevisionSpec_submit)
1009
 
_register_revspec(RevisionSpec_annotate)
1010
 
_register_revspec(RevisionSpec_mainline)
 
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", [])