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
17
from __future__ import absolute_import
20
from bzrlib.lazy_import import lazy_import
18
from .lazy_import import lazy_import
21
19
lazy_import(globals(), """
26
24
branch as _mod_branch,
32
from bzrlib.i18n import gettext
29
from breezy.i18n import gettext
40
class InvalidRevisionSpec(errors.BzrError):
42
_fmt = ("Requested revision: '%(spec)s' does not exist in branch:"
43
" %(branch_url)s%(extra)s")
45
def __init__(self, spec, branch, extra=None):
46
errors.BzrError.__init__(self, branch=branch, spec=spec)
47
self.branch_url = getattr(branch, 'user_url', str(branch))
49
self.extra = '\n' + str(extra)
54
class InvalidRevisionSpec(errors.BzrError):
56
_fmt = ("Requested revision: '%(spec)s' does not exist in branch:"
57
" %(branch_url)s%(extra)s")
59
def __init__(self, spec, branch, extra=None):
60
errors.BzrError.__init__(self, branch=branch, spec=spec)
61
self.branch_url = getattr(branch, 'user_url', str(branch))
63
self.extra = '\n' + str(extra)
43
68
class RevisionInfo(object):
44
69
"""The results of applying a revision specification to a branch."""
71
96
if not self._has_revno and self.rev_id is not None:
73
98
self._revno = self.branch.revision_id_to_revno(self.rev_id)
74
except errors.NoSuchRevision:
99
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
75
100
self._revno = None
76
101
self._has_revno = True
77
102
return self._revno
79
def __nonzero__(self):
80
105
if self.rev_id is None:
82
107
# TODO: otherwise, it should depend on how I was built -
84
109
# if it's in_store(branch), do the check below
85
110
return self.branch.repository.has_revision(self.rev_id)
112
__nonzero__ = __bool__
87
114
def __len__(self):
90
117
def __getitem__(self, index):
91
if index == 0: return self.revno
92
if index == 1: return self.rev_id
93
122
raise IndexError(index)
98
127
def __eq__(self, other):
99
128
if type(other) not in (tuple, list, type(self)):
101
if type(other) is type(self) and self.branch is not other.branch:
130
if isinstance(other, type(self)) and self.branch is not other.branch:
103
132
return tuple(self) == tuple(other)
105
134
def __repr__(self):
106
return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
135
return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
107
136
self.revno, self.rev_id, self.branch)
110
def from_revision_id(branch, revision_id, revs=symbol_versioning.DEPRECATED_PARAMETER):
139
def from_revision_id(branch, revision_id):
111
140
"""Construct a RevisionInfo given just the id.
113
142
Use this if you don't know or care what the revno is.
115
if symbol_versioning.deprecated_passed(revs):
116
symbol_versioning.warn(
117
'RevisionInfo.from_revision_id(revs) was deprecated in 2.5.',
120
144
return RevisionInfo(branch, revno=None, rev_id=revision_id)
143
# wants_revision_history has been deprecated in 2.5.
144
wants_revision_history = False
145
dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
167
dwim_catchable_exceptions = (InvalidRevisionSpec,)
146
168
"""Exceptions that RevisionSpec_dwim._match_on will catch.
148
170
If the revspec is part of ``dwim_revspecs``, it may be tried with an
159
181
:return: A RevisionSpec object that understands how to parse the
160
182
supplied notation.
162
if not isinstance(spec, (type(None), basestring)):
163
raise TypeError('error')
166
185
return RevisionSpec(None, _internal=True)
186
if not isinstance(spec, str):
187
raise TypeError("revision spec needs to be text")
167
188
match = revspec_registry.get_prefix(spec)
168
189
if match is not None:
169
190
spectype, specsuffix = match
183
204
called directly. Only from RevisionSpec.from_string()
185
206
if not _internal:
186
symbol_versioning.warn('Creating a RevisionSpec directly has'
187
' been deprecated in version 0.11. Use'
188
' RevisionSpec.from_string()'
190
DeprecationWarning, stacklevel=2)
207
raise AssertionError(
208
'Creating a RevisionSpec directly is not supported. '
209
'Use RevisionSpec.from_string() instead.')
191
210
self.user_spec = spec
192
211
if self.prefix and spec.startswith(self.prefix):
193
212
spec = spec[len(self.prefix):]
205
224
# special case - nothing supplied
207
226
elif self.prefix:
208
raise errors.InvalidRevisionSpec(self.user_spec, branch)
227
raise InvalidRevisionSpec(self.user_spec, branch)
210
raise errors.InvalidRevisionSpec(self.spec, branch)
229
raise InvalidRevisionSpec(self.spec, branch)
212
231
def in_history(self, branch):
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__,
221
graph = branch.repository.get_graph()
222
revs = list(graph.iter_lefthand_ancestry(
223
branch.last_revision(), [revision.NULL_REVISION]))
230
# this should never trigger.
231
# TODO: make it a deprecated code path. RBC 20060928
233
return self._match_on_and_check(branch, revs)
232
return self._match_on_and_check(branch, revs=None)
235
234
# FIXME: in_history is somewhat broken,
236
235
# it will return non-history revisions in many
339
338
except rs_class.dwim_catchable_exceptions:
342
# Try the old (deprecated) dwim list:
343
for rs_class in dwim_revspecs:
345
return self._try_spectype(rs_class, branch)
346
except rs_class.dwim_catchable_exceptions:
349
341
# Well, I dunno what it is. Note that we don't try to keep track of the
350
342
# first of last exception raised during the DWIM tries as none seems
351
343
# really relevant.
352
raise errors.InvalidRevisionSpec(self.spec, branch)
344
raise InvalidRevisionSpec(self.spec, branch)
355
347
def append_possible_revspec(cls, revspec):
405
397
branch_spec = None
407
399
revno_spec = self.spec[:loc]
408
branch_spec = self.spec[loc+1:]
400
branch_spec = self.spec[loc + 1:]
410
402
if revno_spec == '':
411
403
if not branch_spec:
412
raise errors.InvalidRevisionSpec(self.user_spec,
413
branch, 'cannot have an empty revno and no branch')
404
raise InvalidRevisionSpec(
405
self.user_spec, branch,
406
'cannot have an empty revno and no branch')
421
414
# but the from_string method is a little primitive
422
415
# right now - RBC 20060928
424
match_revno = tuple((int(number) for number in revno_spec.split('.')))
425
except ValueError, e:
426
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
417
match_revno = tuple((int(number)
418
for number in revno_spec.split('.')))
419
except ValueError as e:
420
raise InvalidRevisionSpec(self.user_spec, branch, e)
436
430
revision_id = branch.dotted_revno_to_revision_id(match_revno,
438
except errors.NoSuchRevision:
439
raise errors.InvalidRevisionSpec(self.user_spec, branch)
432
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
433
raise InvalidRevisionSpec(self.user_spec, branch)
441
435
# there is no traditional 'revno' for dotted-decimal revnos.
442
436
# so for API compatibility we return None.
452
446
revno = last_revno + revno + 1
454
448
revision_id = branch.get_rev_id(revno)
455
except errors.NoSuchRevision:
456
raise errors.InvalidRevisionSpec(self.user_spec, branch)
449
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
450
raise InvalidRevisionSpec(self.user_spec, branch)
457
451
return branch, revno, revision_id
459
453
def _as_revision_id(self, context_branch):
500
495
# self.spec comes straight from parsing the command line arguments,
501
496
# so we expect it to be a Unicode string. Switch it to the internal
502
497
# representation.
503
return osutils.safe_revision_id(self.spec, warn=False)
498
if isinstance(self.spec, str):
499
return cache_utf8.encode(self.spec)
507
503
class RevisionSpec_last(RevisionSpec):
535
531
offset = int(self.spec)
536
except ValueError, e:
537
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
532
except ValueError as e:
533
raise InvalidRevisionSpec(self.user_spec, context_branch, e)
540
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
541
'you must supply a positive value')
536
raise InvalidRevisionSpec(
537
self.user_spec, context_branch,
538
'you must supply a positive value')
543
540
revno = last_revno - offset + 1
545
542
revision_id = context_branch.get_rev_id(revno)
546
except errors.NoSuchRevision:
547
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
543
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
544
raise InvalidRevisionSpec(self.user_spec, context_branch)
548
545
return revno, revision_id
550
547
def _as_revision_id(self, context_branch):
582
578
def _match_on(self, branch, revs):
583
579
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
585
raise errors.InvalidRevisionSpec(self.user_spec, branch,
586
'cannot go before the null: revision')
581
raise InvalidRevisionSpec(
582
self.user_spec, branch,
583
'cannot go before the null: revision')
587
584
if r.revno is None:
588
585
# We need to use the repository history here
589
586
rev = branch.repository.get_revision(r.rev_id)
596
593
revno = r.revno - 1
598
595
revision_id = branch.get_rev_id(revno, revs)
599
except errors.NoSuchRevision:
600
raise errors.InvalidRevisionSpec(self.user_spec,
596
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
597
raise InvalidRevisionSpec(self.user_spec, branch)
602
598
return RevisionInfo(branch, revno, revision_id)
604
600
def _as_revision_id(self, context_branch):
605
base_revision_id = RevisionSpec.from_string(self.spec)._as_revision_id(context_branch)
601
base_revision_id = RevisionSpec.from_string(
602
self.spec)._as_revision_id(context_branch)
606
603
if base_revision_id == revision.NULL_REVISION:
607
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
608
'cannot go before the null: revision')
604
raise InvalidRevisionSpec(
605
self.user_spec, context_branch,
606
'cannot go before the null: revision')
609
607
context_repo = context_branch.repository
610
context_repo.lock_read()
608
with context_repo.lock_read():
612
609
parent_map = context_repo.get_parent_map([base_revision_id])
614
context_repo.unlock()
615
610
if base_revision_id not in parent_map:
616
611
# Ghost, or unknown revision id
617
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
618
'cannot find the matching revision')
612
raise InvalidRevisionSpec(
613
self.user_spec, context_branch, 'cannot find the matching revision')
619
614
parents = parent_map[base_revision_id]
620
615
if len(parents) < 1:
621
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
622
'No parents for revision.')
616
raise InvalidRevisionSpec(
617
self.user_spec, context_branch, 'No parents for revision.')
623
618
return parents[0]
627
621
class RevisionSpec_tag(RevisionSpec):
628
622
"""Select a revision identified by tag name"""
638
632
def _match_on(self, branch, revs):
639
633
# Can raise tags not supported, NoSuchTag, etc
640
634
return RevisionInfo.from_revision_id(branch,
641
branch.tags.lookup_tag(self.spec))
635
branch.tags.lookup_tag(self.spec))
643
637
def _as_revision_id(self, context_branch):
644
638
return context_branch.tags.lookup_tag(self.spec)
648
641
class _RevListToTimestamps(object):
649
642
"""This takes a list of revisions, and allows you to bisect by date"""
687
680
_date_regex = lazy_regex.lazy_compile(
688
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
690
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
681
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
683
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
693
686
def _match_on(self, branch, revs):
700
693
# XXX: This doesn't actually work
701
694
# So the proper way of saying 'give me all entries for today' is:
702
695
# -r date:yesterday..date:today
703
today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
696
today = datetime.datetime.fromordinal(
697
datetime.date.today().toordinal())
704
698
if self.spec.lower() == 'yesterday':
705
699
dt = today - datetime.timedelta(days=1)
706
700
elif self.spec.lower() == 'today':
711
705
m = self._date_regex.match(self.spec)
712
706
if not m or (not m.group('date') and not m.group('time')):
713
raise errors.InvalidRevisionSpec(self.user_spec,
714
branch, 'invalid date')
707
raise InvalidRevisionSpec(
708
self.user_spec, branch, 'invalid date')
717
711
if m.group('date'):
734
hour, minute, second = 0,0,0
728
hour, minute, second = 0, 0, 0
735
729
except ValueError:
736
raise errors.InvalidRevisionSpec(self.user_spec,
737
branch, 'invalid date')
730
raise InvalidRevisionSpec(
731
self.user_spec, branch, 'invalid date')
739
733
dt = datetime.datetime(year=year, month=month, day=day,
740
hour=hour, minute=minute, second=second)
734
hour=hour, minute=minute, second=second)
735
with branch.lock_read():
743
736
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
746
737
if rev == branch.revno():
747
raise errors.InvalidRevisionSpec(self.user_spec, branch)
738
raise InvalidRevisionSpec(self.user_spec, branch)
748
739
return RevisionInfo(branch, rev)
752
742
class RevisionSpec_ancestor(RevisionSpec):
753
743
"""Selects a common ancestor with a second branch."""
788
778
def _find_revision_id(branch, other_location):
789
from bzrlib.branch import Branch
779
from .branch import Branch
781
with branch.lock_read():
793
782
revision_a = revision.ensure_null(branch.last_revision())
794
783
if revision_a == revision.NULL_REVISION:
795
784
raise errors.NoCommits(branch)
796
785
if other_location == '':
797
786
other_location = branch.get_parent()
798
787
other_branch = Branch.open(other_location)
799
other_branch.lock_read()
788
with other_branch.lock_read():
801
789
revision_b = revision.ensure_null(other_branch.last_revision())
802
790
if revision_b == revision.NULL_REVISION:
803
791
raise errors.NoCommits(other_branch)
804
792
graph = branch.repository.get_graph(other_branch.repository)
805
793
rev_id = graph.find_unique_lca(revision_a, revision_b)
807
other_branch.unlock()
808
794
if rev_id == revision.NULL_REVISION:
809
795
raise errors.NoCommonAncestor(revision_a, revision_b)
817
799
class RevisionSpec_branch(RevisionSpec):
829
811
dwim_catchable_exceptions = (errors.NotBranchError,)
831
813
def _match_on(self, branch, revs):
832
from bzrlib.branch import Branch
814
from .branch import Branch
833
815
other_branch = Branch.open(self.spec)
834
816
revision_b = other_branch.last_revision()
835
817
if revision_b in (None, revision.NULL_REVISION):
845
827
return RevisionInfo(branch, None, revision_b)
847
829
def _as_revision_id(self, context_branch):
848
from bzrlib.branch import Branch
830
from .branch import Branch
849
831
other_branch = Branch.open(self.spec)
850
832
last_revision = other_branch.last_revision()
851
833
last_revision = revision.ensure_null(last_revision)
855
837
return last_revision
857
839
def _as_tree(self, context_branch):
858
from bzrlib.branch import Branch
840
from .branch import Branch
859
841
other_branch = Branch.open(self.spec)
860
842
last_revision = other_branch.last_revision()
861
843
last_revision = revision.ensure_null(last_revision)
901
882
if submit_location is None:
902
883
raise errors.NoSubmitBranch(branch)
903
884
trace.note(gettext('Using {0} {1}').format(location_type,
905
886
return submit_location
907
888
def _match_on(self, branch, revs):
908
889
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
909
890
return self._find_revision_info(branch,
910
self._get_submit_location(branch))
891
self._get_submit_location(branch))
912
893
def _as_revision_id(self, context_branch):
913
894
return self._find_revision_id(context_branch,
914
self._get_submit_location(context_branch))
895
self._get_submit_location(context_branch))
917
898
class RevisionSpec_annotate(RevisionIDSpec):
929
910
def _raise_invalid(self, numstring, context_branch):
930
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
911
raise InvalidRevisionSpec(
912
self.user_spec, context_branch,
931
913
'No such line: %s' % numstring)
933
915
def _as_revision_id(self, context_branch):
937
919
except ValueError:
938
920
self._raise_invalid(numstring, context_branch)
939
921
tree, file_path = workingtree.WorkingTree.open_containing(path)
942
file_id = tree.path2id(file_path)
944
raise errors.InvalidRevisionSpec(self.user_spec,
945
context_branch, "File '%s' is not versioned." %
947
revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
922
with tree.lock_read():
923
if not tree.has_filename(file_path):
924
raise InvalidRevisionSpec(
925
self.user_spec, context_branch,
926
"File '%s' is not versioned." % file_path)
927
revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
951
929
revision_id = revision_ids[index]
952
930
except IndexError:
953
931
self._raise_invalid(numstring, context_branch)
954
932
if revision_id == revision.CURRENT_REVISION:
955
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
933
raise InvalidRevisionSpec(
934
self.user_spec, context_branch,
956
935
'Line %s has not been committed.' % numstring)
957
936
return revision_id
977
956
result = graph.find_lefthand_merger(revision_id,
978
957
context_branch.last_revision())
979
958
if result is None:
980
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
959
raise InvalidRevisionSpec(self.user_spec, context_branch)
984
963
# The order in which we want to DWIM a revision spec without any prefix.
985
964
# revno is always tried first and isn't listed here, this is used by
986
965
# RevisionSpec_dwim._match_on
987
dwim_revspecs = symbol_versioning.deprecated_list(
988
symbol_versioning.deprecated_in((2, 4, 0)), "dwim_revspecs", [])
990
966
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
991
967
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
992
968
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
993
969
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
995
971
revspec_registry = registry.Registry()
996
974
def _register_revspec(revspec):
997
975
revspec_registry.register(revspec.prefix, revspec)
999
978
_register_revspec(RevisionSpec_revno)
1000
979
_register_revspec(RevisionSpec_revid)
1001
980
_register_revspec(RevisionSpec_last)