~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

(jameinel) Allow 'bzr serve' to interpret SIGHUP as a graceful shutdown.
 (bug #795025) (John A Meinel)

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