~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: 2009-06-03 15:02:09 UTC
  • mfrom: (4398.2.1 export-test-fix)
  • Revision ID: pqm@pqm.ubuntu.com-20090603150209-szap3popp2j8fpl3
(John Szakmeister) Fix error formatting for tar related KnowFailure
        on Mac

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
24
24
""")
25
25
 
26
26
from bzrlib import (
27
 
    branch as _mod_branch,
28
27
    errors,
29
28
    osutils,
30
29
    registry,
31
30
    revision,
32
31
    symbol_versioning,
33
32
    trace,
34
 
    workingtree,
35
33
    )
36
34
 
37
35
 
115
113
        return RevisionInfo(branch, revno, revision_id)
116
114
 
117
115
 
 
116
# classes in this list should have a "prefix" attribute, against which
 
117
# string specs are matched
118
118
_revno_regex = None
119
119
 
120
120
 
123
123
 
124
124
    help_txt = """A parsed revision specification.
125
125
 
126
 
    A revision specification is a string, which may be unambiguous about
127
 
    what it represents by giving a prefix like 'date:' or 'revid:' etc,
128
 
    or it may have no prefix, in which case it's tried against several
129
 
    specifier types in sequence to determine what the user meant.
 
126
    A revision specification can be an integer, in which case it is
 
127
    assumed to be a revno (though this will translate negative values
 
128
    into positive ones); or it can be a string, in which case it is
 
129
    parsed for something like 'date:' or 'revid:' etc.
130
130
 
131
131
    Revision specs are an UI element, and they have been moved out
132
132
    of the branch class to leave "back-end" classes unaware of such
139
139
 
140
140
    prefix = None
141
141
    wants_revision_history = True
142
 
    dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
143
 
    """Exceptions that RevisionSpec_dwim._match_on will catch.
144
 
 
145
 
    If the revspec is part of ``dwim_revspecs``, it may be tried with an
146
 
    invalid revspec and raises some exception. The exceptions mentioned here
147
 
    will not be reported to the user but simply ignored without stopping the
148
 
    dwim processing.
149
 
    """
150
142
 
151
143
    @staticmethod
152
144
    def from_string(spec):
169
161
            return spectype(spec, _internal=True)
170
162
        else:
171
163
            for spectype in SPEC_TYPES:
 
164
                trace.mutter('Returning RevisionSpec %s for %s',
 
165
                             spectype.__name__, spec)
172
166
                if spec.startswith(spectype.prefix):
173
 
                    trace.mutter('Returning RevisionSpec %s for %s',
174
 
                                 spectype.__name__, spec)
175
167
                    return spectype(spec, _internal=True)
176
 
            # Otherwise treat it as a DWIM, build the RevisionSpec object and
177
 
            # wait for _match_on to be called.
178
 
            return RevisionSpec_dwim(spec, _internal=True)
 
168
            # RevisionSpec_revno is special cased, because it is the only
 
169
            # one that directly handles plain integers
 
170
            # TODO: This should not be special cased rather it should be
 
171
            # a method invocation on spectype.canparse()
 
172
            global _revno_regex
 
173
            if _revno_regex is None:
 
174
                _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
 
175
            if _revno_regex.match(spec) is not None:
 
176
                return RevisionSpec_revno(spec, _internal=True)
 
177
 
 
178
            raise errors.NoSuchRevisionSpec(spec)
179
179
 
180
180
    def __init__(self, spec, _internal=False):
181
181
        """Create a RevisionSpec referring to the Null revision.
185
185
            called directly. Only from RevisionSpec.from_string()
186
186
        """
187
187
        if not _internal:
 
188
            # XXX: Update this after 0.10 is released
188
189
            symbol_versioning.warn('Creating a RevisionSpec directly has'
189
190
                                   ' been deprecated in version 0.11. Use'
190
191
                                   ' RevisionSpec.from_string()'
290
291
 
291
292
# private API
292
293
 
293
 
class RevisionSpec_dwim(RevisionSpec):
294
 
    """Provides a DWIMish revision specifier lookup.
295
 
 
296
 
    Note that this does not go in the revspec_registry because by definition
297
 
    there is no prefix to identify it.  It's solely called from
298
 
    RevisionSpec.from_string() because the DWIMification happen when _match_on
299
 
    is called so the string describing the revision is kept here until needed.
300
 
    """
301
 
 
302
 
    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
306
 
 
307
 
    def _try_spectype(self, rstype, branch):
308
 
        rs = rstype(self.spec, _internal=True)
309
 
        # Hit in_history to find out if it exists, or we need to try the
310
 
        # next type.
311
 
        return rs.in_history(branch)
312
 
 
313
 
    def _match_on(self, branch, revs):
314
 
        """Run the lookup and see what we can get."""
315
 
 
316
 
        # 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:
321
 
            try:
322
 
                return self._try_spectype(RevisionSpec_revno, branch)
323
 
            except RevisionSpec_revno.dwim_catchable_exceptions:
324
 
                pass
325
 
 
326
 
        # Next see what has been registered
327
 
        for rs_class in dwim_revspecs:
328
 
            try:
329
 
                return self._try_spectype(rs_class, branch)
330
 
            except rs_class.dwim_catchable_exceptions:
331
 
                pass
332
 
 
333
 
        # Well, I dunno what it is. Note that we don't try to keep track of the
334
 
        # first of last exception raised during the DWIM tries as none seems
335
 
        # really relevant.
336
 
        raise errors.InvalidRevisionSpec(self.spec, branch)
337
 
 
338
 
 
339
294
class RevisionSpec_revno(RevisionSpec):
340
295
    """Selects a revision using a number."""
341
296
 
342
297
    help_txt = """Selects a revision using a number.
343
298
 
344
299
    Use an integer to specify a revision in the history of the branch.
345
 
    Optionally a branch can be specified.  A negative number will count
346
 
    from the end of the branch (-1 is the last revision, -2 the previous
347
 
    one). If the negative number is larger than the branch's history, the
348
 
    first revision is returned.
 
300
    Optionally a branch can be specified. The 'revno:' prefix is optional.
 
301
    A negative number will count from the end of the branch (-1 is the
 
302
    last revision, -2 the previous one). If the negative number is larger
 
303
    than the branch's history, the first revision is returned.
349
304
    Examples::
350
305
 
351
306
      revno:1                   -> return the first revision of this branch
446
401
 
447
402
 
448
403
 
449
 
class RevisionIDSpec(RevisionSpec):
450
 
 
451
 
    def _match_on(self, branch, revs):
452
 
        revision_id = self.as_revision_id(branch)
453
 
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
454
 
 
455
 
 
456
 
class RevisionSpec_revid(RevisionIDSpec):
 
404
class RevisionSpec_revid(RevisionSpec):
457
405
    """Selects a revision using the revision id."""
458
406
 
459
407
    help_txt = """Selects a revision using the revision id.
468
416
 
469
417
    prefix = 'revid:'
470
418
 
471
 
    def _as_revision_id(self, context_branch):
 
419
    def _match_on(self, branch, revs):
472
420
        # self.spec comes straight from parsing the command line arguments,
473
421
        # so we expect it to be a Unicode string. Switch it to the internal
474
422
        # representation.
 
423
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
 
424
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
 
425
 
 
426
    def _as_revision_id(self, context_branch):
475
427
        return osutils.safe_revision_id(self.spec, warn=False)
476
428
 
477
429
 
610
562
    """
611
563
 
612
564
    prefix = 'tag:'
613
 
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
614
565
 
615
566
    def _match_on(self, branch, revs):
616
567
        # Can raise tags not supported, NoSuchTag, etc
810
761
      branch:/path/to/branch
811
762
    """
812
763
    prefix = 'branch:'
813
 
    dwim_catchable_exceptions = (errors.NotBranchError,)
814
764
 
815
765
    def _match_on(self, branch, revs):
816
766
        from bzrlib.branch import Branch
818
768
        revision_b = other_branch.last_revision()
819
769
        if revision_b in (None, revision.NULL_REVISION):
820
770
            raise errors.NoCommits(other_branch)
821
 
        if branch is None:
822
 
            branch = other_branch
823
 
        else:
824
 
            try:
825
 
                # pull in the remote revisions so we can diff
826
 
                branch.fetch(other_branch, revision_b)
827
 
            except errors.ReadOnlyError:
828
 
                branch = other_branch
 
771
        # pull in the remote revisions so we can diff
 
772
        branch.fetch(other_branch, revision_b)
829
773
        try:
830
774
            revno = branch.revision_id_to_revno(revision_b)
831
775
        except errors.NoSuchRevision:
851
795
            raise errors.NoCommits(other_branch)
852
796
        return other_branch.repository.revision_tree(last_revision)
853
797
 
854
 
    def needs_branch(self):
855
 
        return False
856
 
 
857
 
    def get_branch(self):
858
 
        return self.spec
859
 
 
860
798
 
861
799
 
862
800
class RevisionSpec_submit(RevisionSpec_ancestor):
901
839
            self._get_submit_location(context_branch))
902
840
 
903
841
 
904
 
class RevisionSpec_annotate(RevisionIDSpec):
905
 
 
906
 
    prefix = 'annotate:'
907
 
 
908
 
    help_txt = """Select the revision that last modified the specified line.
909
 
 
910
 
    Select the revision that last modified the specified line.  Line is
911
 
    specified as path:number.  Path is a relative path to the file.  Numbers
912
 
    start at 1, and are relative to the current version, not the last-
913
 
    committed version of the file.
914
 
    """
915
 
 
916
 
    def _raise_invalid(self, numstring, context_branch):
917
 
        raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
918
 
            'No such line: %s' % numstring)
919
 
 
920
 
    def _as_revision_id(self, context_branch):
921
 
        path, numstring = self.spec.rsplit(':', 1)
922
 
        try:
923
 
            index = int(numstring) - 1
924
 
        except ValueError:
925
 
            self._raise_invalid(numstring, context_branch)
926
 
        tree, file_path = workingtree.WorkingTree.open_containing(path)
927
 
        tree.lock_read()
928
 
        try:
929
 
            file_id = tree.path2id(file_path)
930
 
            if file_id is None:
931
 
                raise errors.InvalidRevisionSpec(self.user_spec,
932
 
                    context_branch, "File '%s' is not versioned." %
933
 
                    file_path)
934
 
            revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
935
 
        finally:
936
 
            tree.unlock()
937
 
        try:
938
 
            revision_id = revision_ids[index]
939
 
        except IndexError:
940
 
            self._raise_invalid(numstring, context_branch)
941
 
        if revision_id == revision.CURRENT_REVISION:
942
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
943
 
                'Line %s has not been committed.' % numstring)
944
 
        return revision_id
945
 
 
946
 
 
947
 
class RevisionSpec_mainline(RevisionIDSpec):
948
 
 
949
 
    help_txt = """Select mainline revision that merged the specified revision.
950
 
 
951
 
    Select the revision that merged the specified revision into mainline.
952
 
    """
953
 
 
954
 
    prefix = 'mainline:'
955
 
 
956
 
    def _as_revision_id(self, context_branch):
957
 
        revspec = RevisionSpec.from_string(self.spec)
958
 
        if revspec.get_branch() is None:
959
 
            spec_branch = context_branch
960
 
        else:
961
 
            spec_branch = _mod_branch.Branch.open(revspec.get_branch())
962
 
        revision_id = revspec.as_revision_id(spec_branch)
963
 
        graph = context_branch.repository.get_graph()
964
 
        result = graph.find_lefthand_merger(revision_id,
965
 
                                            context_branch.last_revision())
966
 
        if result is None:
967
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
968
 
        return result
969
 
 
970
 
 
971
 
# The order in which we want to DWIM a revision spec without any prefix.
972
 
# revno is always tried first and isn't listed here, this is used by
973
 
# 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
 
    ]
980
 
 
981
 
 
982
842
revspec_registry = registry.Registry()
983
843
def _register_revspec(revspec):
984
844
    revspec_registry.register(revspec.prefix, revspec)
992
852
_register_revspec(RevisionSpec_ancestor)
993
853
_register_revspec(RevisionSpec_branch)
994
854
_register_revspec(RevisionSpec_submit)
995
 
_register_revspec(RevisionSpec_annotate)
996
 
_register_revspec(RevisionSpec_mainline)
997
855
 
998
 
# classes in this list should have a "prefix" attribute, against which
999
 
# string specs are matched
1000
856
SPEC_TYPES = symbol_versioning.deprecated_list(
1001
857
    symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])