~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

merge trunk

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
 
22
 
 
23
from bzrlib import (
 
24
    branch as _mod_branch,
 
25
    osutils,
 
26
    revision,
 
27
    symbol_versioning,
 
28
    workingtree,
 
29
    )
24
30
""")
25
31
 
26
32
from bzrlib import (
27
33
    errors,
28
 
    osutils,
 
34
    lazy_regex,
29
35
    registry,
30
 
    revision,
31
 
    symbol_versioning,
32
36
    trace,
33
37
    )
34
38
 
113
117
        return RevisionInfo(branch, revno, revision_id)
114
118
 
115
119
 
116
 
_revno_regex = None
117
 
 
118
 
 
119
120
class RevisionSpec(object):
120
121
    """A parsed revision specification."""
121
122
 
166
167
                         spectype.__name__, spec)
167
168
            return spectype(spec, _internal=True)
168
169
        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
170
            # Otherwise treat it as a DWIM, build the RevisionSpec object and
175
171
            # wait for _match_on to be called.
176
172
            return RevisionSpec_dwim(spec, _internal=True)
302
298
    # each revspec we try.
303
299
    wants_revision_history = False
304
300
 
 
301
    _revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
 
302
 
 
303
    # The revspecs to try
 
304
    _possible_revspecs = []
 
305
 
305
306
    def _try_spectype(self, rstype, branch):
306
307
        rs = rstype(self.spec, _internal=True)
307
308
        # Hit in_history to find out if it exists, or we need to try the
312
313
        """Run the lookup and see what we can get."""
313
314
 
314
315
        # 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:
 
316
        if self._revno_regex.match(self.spec) is not None:
319
317
            try:
320
318
                return self._try_spectype(RevisionSpec_revno, branch)
321
319
            except RevisionSpec_revno.dwim_catchable_exceptions:
322
320
                pass
323
321
 
324
322
        # Next see what has been registered
 
323
        for objgetter in self._possible_revspecs:
 
324
            rs_class = objgetter.get_obj()
 
325
            try:
 
326
                return self._try_spectype(rs_class, branch)
 
327
            except rs_class.dwim_catchable_exceptions:
 
328
                pass
 
329
 
 
330
        # Try the old (deprecated) dwim list:
325
331
        for rs_class in dwim_revspecs:
326
332
            try:
327
333
                return self._try_spectype(rs_class, branch)
333
339
        # really relevant.
334
340
        raise errors.InvalidRevisionSpec(self.spec, branch)
335
341
 
 
342
    @classmethod
 
343
    def append_possible_revspec(cls, revspec):
 
344
        """Append a possible DWIM revspec.
 
345
 
 
346
        :param revspec: Revision spec to try.
 
347
        """
 
348
        cls._possible_revspecs.append(registry._ObjectGetter(revspec))
 
349
 
 
350
    @classmethod
 
351
    def append_possible_lazy_revspec(cls, module_name, member_name):
 
352
        """Append a possible lazily loaded DWIM revspec.
 
353
 
 
354
        :param module_name: Name of the module with the revspec
 
355
        :param member_name: Name of the revspec within the module
 
356
        """
 
357
        cls._possible_revspecs.append(
 
358
            registry._LazyObjectGetter(module_name, member_name))
 
359
 
336
360
 
337
361
class RevisionSpec_revno(RevisionSpec):
338
362
    """Selects a revision using a number."""
444
468
 
445
469
 
446
470
 
447
 
class RevisionSpec_revid(RevisionSpec):
 
471
class RevisionIDSpec(RevisionSpec):
 
472
 
 
473
    def _match_on(self, branch, revs):
 
474
        revision_id = self.as_revision_id(branch)
 
475
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
 
476
 
 
477
 
 
478
class RevisionSpec_revid(RevisionIDSpec):
448
479
    """Selects a revision using the revision id."""
449
480
 
450
481
    help_txt = """Selects a revision using the revision id.
459
490
 
460
491
    prefix = 'revid:'
461
492
 
462
 
    def _match_on(self, branch, revs):
 
493
    def _as_revision_id(self, context_branch):
463
494
        # self.spec comes straight from parsing the command line arguments,
464
495
        # so we expect it to be a Unicode string. Switch it to the internal
465
496
        # 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):
470
497
        return osutils.safe_revision_id(self.spec, warn=False)
471
498
 
472
499
 
658
685
                                   August 14th, 2006 at 5:10pm.
659
686
    """
660
687
    prefix = 'date:'
661
 
    _date_re = re.compile(
 
688
    _date_regex = lazy_regex.lazy_compile(
662
689
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
663
690
            r'(,|T)?\s*'
664
691
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
682
709
        elif self.spec.lower() == 'tomorrow':
683
710
            dt = today + datetime.timedelta(days=1)
684
711
        else:
685
 
            m = self._date_re.match(self.spec)
 
712
            m = self._date_regex.match(self.spec)
686
713
            if not m or (not m.group('date') and not m.group('time')):
687
714
                raise errors.InvalidRevisionSpec(self.user_spec,
688
715
                                                 branch, 'invalid date')
896
923
            self._get_submit_location(context_branch))
897
924
 
898
925
 
 
926
class RevisionSpec_annotate(RevisionIDSpec):
 
927
 
 
928
    prefix = 'annotate:'
 
929
 
 
930
    help_txt = """Select the revision that last modified the specified line.
 
931
 
 
932
    Select the revision that last modified the specified line.  Line is
 
933
    specified as path:number.  Path is a relative path to the file.  Numbers
 
934
    start at 1, and are relative to the current version, not the last-
 
935
    committed version of the file.
 
936
    """
 
937
 
 
938
    def _raise_invalid(self, numstring, context_branch):
 
939
        raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
940
            'No such line: %s' % numstring)
 
941
 
 
942
    def _as_revision_id(self, context_branch):
 
943
        path, numstring = self.spec.rsplit(':', 1)
 
944
        try:
 
945
            index = int(numstring) - 1
 
946
        except ValueError:
 
947
            self._raise_invalid(numstring, context_branch)
 
948
        tree, file_path = workingtree.WorkingTree.open_containing(path)
 
949
        tree.lock_read()
 
950
        try:
 
951
            file_id = tree.path2id(file_path)
 
952
            if file_id is None:
 
953
                raise errors.InvalidRevisionSpec(self.user_spec,
 
954
                    context_branch, "File '%s' is not versioned." %
 
955
                    file_path)
 
956
            revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
 
957
        finally:
 
958
            tree.unlock()
 
959
        try:
 
960
            revision_id = revision_ids[index]
 
961
        except IndexError:
 
962
            self._raise_invalid(numstring, context_branch)
 
963
        if revision_id == revision.CURRENT_REVISION:
 
964
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
965
                'Line %s has not been committed.' % numstring)
 
966
        return revision_id
 
967
 
 
968
 
 
969
class RevisionSpec_mainline(RevisionIDSpec):
 
970
 
 
971
    help_txt = """Select mainline revision that merged the specified revision.
 
972
 
 
973
    Select the revision that merged the specified revision into mainline.
 
974
    """
 
975
 
 
976
    prefix = 'mainline:'
 
977
 
 
978
    def _as_revision_id(self, context_branch):
 
979
        revspec = RevisionSpec.from_string(self.spec)
 
980
        if revspec.get_branch() is None:
 
981
            spec_branch = context_branch
 
982
        else:
 
983
            spec_branch = _mod_branch.Branch.open(revspec.get_branch())
 
984
        revision_id = revspec.as_revision_id(spec_branch)
 
985
        graph = context_branch.repository.get_graph()
 
986
        result = graph.find_lefthand_merger(revision_id,
 
987
                                            context_branch.last_revision())
 
988
        if result is None:
 
989
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
 
990
        return result
 
991
 
 
992
 
899
993
# The order in which we want to DWIM a revision spec without any prefix.
900
994
# revno is always tried first and isn't listed here, this is used by
901
995
# RevisionSpec_dwim._match_on
902
 
dwim_revspecs = [
903
 
    RevisionSpec_tag, # Let's try for a tag
904
 
    RevisionSpec_revid, # Maybe it's a revid?
905
 
    RevisionSpec_date, # Perhaps a date?
906
 
    RevisionSpec_branch, # OK, last try, maybe it's a branch
907
 
    ]
 
996
dwim_revspecs = symbol_versioning.deprecated_list(
 
997
    symbol_versioning.deprecated_in((2, 4, 0)), "dwim_revspecs", [])
908
998
 
 
999
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
 
1000
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
 
1001
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
 
1002
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
909
1003
 
910
1004
revspec_registry = registry.Registry()
911
1005
def _register_revspec(revspec):
920
1014
_register_revspec(RevisionSpec_ancestor)
921
1015
_register_revspec(RevisionSpec_branch)
922
1016
_register_revspec(RevisionSpec_submit)
923
 
 
924
 
# classes in this list should have a "prefix" attribute, against which
925
 
# string specs are matched
926
 
SPEC_TYPES = symbol_versioning.deprecated_list(
927
 
    symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])
 
1017
_register_revspec(RevisionSpec_annotate)
 
1018
_register_revspec(RevisionSpec_mainline)