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
20
from bzrlib.lazy_import import lazy_import
17
from __future__ import absolute_import
20
from .lazy_import import lazy_import
21
21
lazy_import(globals(), """
26
branch as _mod_branch,
32
from breezy.i18n import gettext
39
46
class RevisionInfo(object):
53
60
or treat the result as a tuple.
56
def __init__(self, branch, revno, rev_id=_marker):
63
def __init__(self, branch, revno=None, rev_id=None):
57
64
self.branch = branch
65
self._has_revno = (revno is not None)
68
if self.rev_id is None and self._revno is not None:
60
69
# allow caller to be lazy
61
if self.revno is None:
64
self.rev_id = branch.get_rev_id(self.revno)
68
def __nonzero__(self):
69
# first the easy ones...
70
self.rev_id = branch.get_rev_id(self._revno)
74
if not self._has_revno and self.rev_id is not None:
76
self._revno = self.branch.revision_id_to_revno(self.rev_id)
77
except errors.NoSuchRevision:
79
self._has_revno = True
70
83
if self.rev_id is None:
72
if self.revno is not None:
74
85
# TODO: otherwise, it should depend on how I was built -
75
86
# if it's in_history(branch), then check revision_history(),
76
87
# if it's in_store(branch), do the check below
77
88
return self.branch.repository.has_revision(self.rev_id)
90
__nonzero__ = __bool__
90
103
def __eq__(self, other):
91
104
if type(other) not in (tuple, list, type(self)):
93
if type(other) is type(self) and self.branch is not other.branch:
106
if isinstance(other, type(self)) and self.branch is not other.branch:
95
108
return tuple(self) == tuple(other)
97
110
def __repr__(self):
98
return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
111
return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
99
112
self.revno, self.rev_id, self.branch)
102
def from_revision_id(branch, revision_id, revs):
115
def from_revision_id(branch, revision_id):
103
116
"""Construct a RevisionInfo given just the id.
105
118
Use this if you don't know or care what the revno is.
107
if revision_id == revision.NULL_REVISION:
108
return RevisionInfo(branch, 0, revision_id)
110
revno = revs.index(revision_id) + 1
113
return RevisionInfo(branch, revno, revision_id)
120
return RevisionInfo(branch, revno=None, rev_id=revision_id)
119
123
class RevisionSpec(object):
154
157
:return: A RevisionSpec object that understands how to parse the
155
158
supplied notation.
157
if not isinstance(spec, (type(None), basestring)):
158
raise TypeError('error')
161
161
return RevisionSpec(None, _internal=True)
162
if not isinstance(spec, (str, text_type)):
163
raise TypeError("revision spec needs to be text")
162
164
match = revspec_registry.get_prefix(spec)
163
165
if match is not None:
164
166
spectype, specsuffix = match
166
168
spectype.__name__, spec)
167
169
return spectype(spec, _internal=True)
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)
183
180
called directly. Only from RevisionSpec.from_string()
185
182
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)
183
raise AssertionError(
184
'Creating a RevisionSpec directly is not supported. '
185
'Use RevisionSpec.from_string() instead.')
191
186
self.user_spec = spec
192
187
if self.prefix and spec.startswith(self.prefix):
193
188
spec = spec[len(self.prefix):]
210
205
raise errors.InvalidRevisionSpec(self.spec, branch)
212
207
def in_history(self, branch):
214
if self.wants_revision_history:
215
revs = branch.revision_history()
219
# this should never trigger.
220
# TODO: make it a deprecated code path. RBC 20060928
222
return self._match_on_and_check(branch, revs)
208
return self._match_on_and_check(branch, revs=None)
224
210
# FIXME: in_history is somewhat broken,
225
211
# it will return non-history revisions in many
312
300
"""Run the lookup and see what we can get."""
314
302
# First, see if it's a revno
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:
303
if self._revno_regex.match(self.spec) is not None:
320
305
return self._try_spectype(RevisionSpec_revno, branch)
321
306
except RevisionSpec_revno.dwim_catchable_exceptions:
324
309
# Next see what has been registered
325
for rs_class in dwim_revspecs:
310
for objgetter in self._possible_revspecs:
311
rs_class = objgetter.get_obj()
327
313
return self._try_spectype(rs_class, branch)
328
314
except rs_class.dwim_catchable_exceptions:
333
319
# really relevant.
334
320
raise errors.InvalidRevisionSpec(self.spec, branch)
323
def append_possible_revspec(cls, revspec):
324
"""Append a possible DWIM revspec.
326
:param revspec: Revision spec to try.
328
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
331
def append_possible_lazy_revspec(cls, module_name, member_name):
332
"""Append a possible lazily loaded DWIM revspec.
334
:param module_name: Name of the module with the revspec
335
:param member_name: Name of the revspec within the module
337
cls._possible_revspecs.append(
338
registry._LazyObjectGetter(module_name, member_name))
337
341
class RevisionSpec_revno(RevisionSpec):
338
342
"""Selects a revision using a number."""
356
360
your history is very long.
358
362
prefix = 'revno:'
359
wants_revision_history = False
361
364
def _match_on(self, branch, revs):
362
365
"""Lookup a revision by revision number"""
363
branch, revno, revision_id = self._lookup(branch, revs)
366
branch, revno, revision_id = self._lookup(branch)
364
367
return RevisionInfo(branch, revno, revision_id)
366
def _lookup(self, branch, revs_or_none):
369
def _lookup(self, branch):
367
370
loc = self.spec.find(':')
369
372
revno_spec = self.spec
387
390
# right now - RBC 20060928
389
392
match_revno = tuple((int(number) for number in revno_spec.split('.')))
390
except ValueError, e:
393
except ValueError as e:
391
394
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
396
# the user has override the branch to look in.
397
# we need to refresh the revision_history map and
399
from bzrlib.branch import Branch
400
branch = Branch.open(branch_spec)
399
# the user has overriden the branch to look in.
400
branch = _mod_branch.Branch.open(branch_spec)
421
420
revno = last_revno + revno + 1
423
revision_id = branch.get_rev_id(revno, revs_or_none)
422
revision_id = branch.get_rev_id(revno)
424
423
except errors.NoSuchRevision:
425
424
raise errors.InvalidRevisionSpec(self.user_spec, branch)
426
425
return branch, revno, revision_id
428
427
def _as_revision_id(self, context_branch):
429
428
# We would have the revno here, but we don't really care
430
branch, revno, revision_id = self._lookup(context_branch, None)
429
branch, revno, revision_id = self._lookup(context_branch)
431
430
return revision_id
433
432
def needs_branch(self):
443
442
RevisionSpec_int = RevisionSpec_revno
447
class RevisionSpec_revid(RevisionSpec):
445
class RevisionIDSpec(RevisionSpec):
447
def _match_on(self, branch, revs):
448
revision_id = self.as_revision_id(branch)
449
return RevisionInfo.from_revision_id(branch, revision_id)
452
class RevisionSpec_revid(RevisionIDSpec):
448
453
"""Selects a revision using the revision id."""
450
455
help_txt = """Selects a revision using the revision id.
460
465
prefix = 'revid:'
462
def _match_on(self, branch, revs):
467
def _as_revision_id(self, context_branch):
463
468
# self.spec comes straight from parsing the command line arguments,
464
469
# so we expect it to be a Unicode string. Switch it to the internal
465
470
# representation.
466
revision_id = osutils.safe_revision_id(self.spec, warn=False)
467
return RevisionInfo.from_revision_id(branch, revision_id, revs)
469
def _as_revision_id(self, context_branch):
470
return osutils.safe_revision_id(self.spec, warn=False)
471
if isinstance(self.spec, text_type):
472
return cache_utf8.encode(self.spec)
489
492
def _match_on(self, branch, revs):
490
revno, revision_id = self._revno_and_revision_id(branch, revs)
493
revno, revision_id = self._revno_and_revision_id(branch)
491
494
return RevisionInfo(branch, revno, revision_id)
493
def _revno_and_revision_id(self, context_branch, revs_or_none):
496
def _revno_and_revision_id(self, context_branch):
494
497
last_revno, last_revision_id = context_branch.last_revision_info()
496
499
if self.spec == '':
573
572
return RevisionInfo(branch, revno, revision_id)
575
574
def _as_revision_id(self, context_branch):
576
base_revspec = RevisionSpec.from_string(self.spec)
577
base_revision_id = base_revspec.as_revision_id(context_branch)
575
base_revision_id = RevisionSpec.from_string(self.spec)._as_revision_id(context_branch)
578
576
if base_revision_id == revision.NULL_REVISION:
579
577
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
580
578
'cannot go before the null: revision')
610
608
def _match_on(self, branch, revs):
611
609
# Can raise tags not supported, NoSuchTag, etc
612
610
return RevisionInfo.from_revision_id(branch,
613
branch.tags.lookup_tag(self.spec),
611
branch.tags.lookup_tag(self.spec))
616
613
def _as_revision_id(self, context_branch):
617
614
return context_branch.tags.lookup_tag(self.spec)
621
618
class _RevListToTimestamps(object):
622
619
"""This takes a list of revisions, and allows you to bisect by date"""
624
__slots__ = ['revs', 'branch']
621
__slots__ = ['branch']
626
def __init__(self, revs, branch):
623
def __init__(self, branch):
628
624
self.branch = branch
630
626
def __getitem__(self, index):
631
627
"""Get the date of the index'd item"""
632
r = self.branch.repository.get_revision(self.revs[index])
628
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
633
629
# TODO: Handle timezone.
634
630
return datetime.datetime.fromtimestamp(r.timestamp)
636
632
def __len__(self):
637
return len(self.revs)
633
return self.branch.revno()
640
636
class RevisionSpec_date(RevisionSpec):
708
hour, minute, second = 0,0,0
704
hour, minute, second = 0, 0, 0
709
705
except ValueError:
710
706
raise errors.InvalidRevisionSpec(self.user_spec,
711
707
branch, 'invalid date')
713
709
dt = datetime.datetime(year=year, month=month, day=day,
714
710
hour=hour, minute=minute, second=second)
717
rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
711
with branch.lock_read():
712
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
713
if rev == branch.revno():
721
714
raise errors.InvalidRevisionSpec(self.user_spec, branch)
723
return RevisionInfo(branch, rev + 1)
715
return RevisionInfo(branch, rev)
757
749
def _find_revision_info(branch, other_location):
758
750
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
761
revno = branch.revision_id_to_revno(revision_id)
762
except errors.NoSuchRevision:
764
return RevisionInfo(branch, revno, revision_id)
752
return RevisionInfo(branch, None, revision_id)
767
755
def _find_revision_id(branch, other_location):
768
from bzrlib.branch import Branch
756
from .branch import Branch
758
with branch.lock_read():
772
759
revision_a = revision.ensure_null(branch.last_revision())
773
760
if revision_a == revision.NULL_REVISION:
774
761
raise errors.NoCommits(branch)
775
762
if other_location == '':
776
763
other_location = branch.get_parent()
777
764
other_branch = Branch.open(other_location)
778
other_branch.lock_read()
765
with other_branch.lock_read():
780
766
revision_b = revision.ensure_null(other_branch.last_revision())
781
767
if revision_b == revision.NULL_REVISION:
782
768
raise errors.NoCommits(other_branch)
783
769
graph = branch.repository.get_graph(other_branch.repository)
784
770
rev_id = graph.find_unique_lca(revision_a, revision_b)
786
other_branch.unlock()
787
771
if rev_id == revision.NULL_REVISION:
788
772
raise errors.NoCommonAncestor(revision_a, revision_b)
796
776
class RevisionSpec_branch(RevisionSpec):
808
788
dwim_catchable_exceptions = (errors.NotBranchError,)
810
790
def _match_on(self, branch, revs):
811
from bzrlib.branch import Branch
791
from .branch import Branch
812
792
other_branch = Branch.open(self.spec)
813
793
revision_b = other_branch.last_revision()
814
794
if revision_b in (None, revision.NULL_REVISION):
815
795
raise errors.NoCommits(other_branch)
816
# pull in the remote revisions so we can diff
817
branch.fetch(other_branch, revision_b)
819
revno = branch.revision_id_to_revno(revision_b)
820
except errors.NoSuchRevision:
822
return RevisionInfo(branch, revno, revision_b)
797
branch = other_branch
800
# pull in the remote revisions so we can diff
801
branch.fetch(other_branch, revision_b)
802
except errors.ReadOnlyError:
803
branch = other_branch
804
return RevisionInfo(branch, None, revision_b)
824
806
def _as_revision_id(self, context_branch):
825
from bzrlib.branch import Branch
807
from .branch import Branch
826
808
other_branch = Branch.open(self.spec)
827
809
last_revision = other_branch.last_revision()
828
810
last_revision = revision.ensure_null(last_revision)
884
873
self._get_submit_location(context_branch))
876
class RevisionSpec_annotate(RevisionIDSpec):
880
help_txt = """Select the revision that last modified the specified line.
882
Select the revision that last modified the specified line. Line is
883
specified as path:number. Path is a relative path to the file. Numbers
884
start at 1, and are relative to the current version, not the last-
885
committed version of the file.
888
def _raise_invalid(self, numstring, context_branch):
889
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
890
'No such line: %s' % numstring)
892
def _as_revision_id(self, context_branch):
893
path, numstring = self.spec.rsplit(':', 1)
895
index = int(numstring) - 1
897
self._raise_invalid(numstring, context_branch)
898
tree, file_path = workingtree.WorkingTree.open_containing(path)
899
with tree.lock_read():
900
if not tree.has_filename(file_path):
901
raise errors.InvalidRevisionSpec(self.user_spec,
902
context_branch, "File '%s' is not versioned." %
904
revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
906
revision_id = revision_ids[index]
908
self._raise_invalid(numstring, context_branch)
909
if revision_id == revision.CURRENT_REVISION:
910
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
911
'Line %s has not been committed.' % numstring)
915
class RevisionSpec_mainline(RevisionIDSpec):
917
help_txt = """Select mainline revision that merged the specified revision.
919
Select the revision that merged the specified revision into mainline.
924
def _as_revision_id(self, context_branch):
925
revspec = RevisionSpec.from_string(self.spec)
926
if revspec.get_branch() is None:
927
spec_branch = context_branch
929
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
930
revision_id = revspec.as_revision_id(spec_branch)
931
graph = context_branch.repository.get_graph()
932
result = graph.find_lefthand_merger(revision_id,
933
context_branch.last_revision())
935
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
887
939
# The order in which we want to DWIM a revision spec without any prefix.
888
940
# revno is always tried first and isn't listed here, this is used by
889
941
# RevisionSpec_dwim._match_on
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
942
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
943
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
944
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
945
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
898
947
revspec_registry = registry.Registry()
899
948
def _register_revspec(revspec):
908
957
_register_revspec(RevisionSpec_ancestor)
909
958
_register_revspec(RevisionSpec_branch)
910
959
_register_revspec(RevisionSpec_submit)
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", [])
960
_register_revspec(RevisionSpec_annotate)
961
_register_revspec(RevisionSpec_mainline)