~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-03-25 00:02:51 UTC
  • mfrom: (5106.1.1 version-bump)
  • Revision ID: pqm@pqm.ubuntu.com-20100325000251-bwsv5c5d3l9x3lnn
(Jelmer) Bump API version for 2.2.0.

Show diffs side-by-side

added added

removed removed

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