55
53
or treat the result as a tuple.
58
def __init__(self, branch, revno=None, rev_id=None):
56
def __init__(self, branch, revno, rev_id=_marker):
59
57
self.branch = branch
60
self._has_revno = (revno is not None)
63
if self.rev_id is None and self._revno is not None:
64
60
# allow caller to be lazy
65
self.rev_id = branch.get_rev_id(self._revno)
69
if not self._has_revno and self.rev_id is not None:
71
self._revno = self.branch.revision_id_to_revno(self.rev_id)
72
except errors.NoSuchRevision:
74
self._has_revno = True
61
if self.revno is None:
64
self.rev_id = branch.get_rev_id(self.revno)
77
68
def __nonzero__(self):
78
69
# first the easy ones...
108
99
self.revno, self.rev_id, self.branch)
111
def from_revision_id(branch, revision_id, revs=symbol_versioning.DEPRECATED_PARAMETER):
102
def from_revision_id(branch, revision_id, revs):
112
103
"""Construct a RevisionInfo given just the id.
114
105
Use this if you don't know or care what the revno is.
116
if symbol_versioning.deprecated_passed(revs):
117
symbol_versioning.warn(
118
'RevisionInfo.from_revision_id(revs) was deprecated in 2.5.',
121
return RevisionInfo(branch, revno=None, rev_id=revision_id)
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)
124
119
class RevisionSpec(object):
172
166
spectype.__name__, spec)
173
167
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)
175
174
# Otherwise treat it as a DWIM, build the RevisionSpec object and
176
175
# wait for _match_on to be called.
177
176
return RevisionSpec_dwim(spec, _internal=True)
326
312
"""Run the lookup and see what we can get."""
328
314
# First, see if it's a revno
329
if self._revno_regex.match(self.spec) is not None:
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:
331
320
return self._try_spectype(RevisionSpec_revno, branch)
332
321
except RevisionSpec_revno.dwim_catchable_exceptions:
335
324
# Next see what has been registered
336
for objgetter in self._possible_revspecs:
337
rs_class = objgetter.get_obj()
339
return self._try_spectype(rs_class, branch)
340
except rs_class.dwim_catchable_exceptions:
343
# Try the old (deprecated) dwim list:
344
325
for rs_class in dwim_revspecs:
346
327
return self._try_spectype(rs_class, branch)
352
333
# really relevant.
353
334
raise errors.InvalidRevisionSpec(self.spec, branch)
356
def append_possible_revspec(cls, revspec):
357
"""Append a possible DWIM revspec.
359
:param revspec: Revision spec to try.
361
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
364
def append_possible_lazy_revspec(cls, module_name, member_name):
365
"""Append a possible lazily loaded DWIM revspec.
367
:param module_name: Name of the module with the revspec
368
:param member_name: Name of the revspec within the module
370
cls._possible_revspecs.append(
371
registry._LazyObjectGetter(module_name, member_name))
374
337
class RevisionSpec_revno(RevisionSpec):
375
338
"""Selects a revision using a number."""
393
356
your history is very long.
395
358
prefix = 'revno:'
359
wants_revision_history = False
397
361
def _match_on(self, branch, revs):
398
362
"""Lookup a revision by revision number"""
399
branch, revno, revision_id = self._lookup(branch)
363
branch, revno, revision_id = self._lookup(branch, revs)
400
364
return RevisionInfo(branch, revno, revision_id)
402
def _lookup(self, branch):
366
def _lookup(self, branch, revs_or_none):
403
367
loc = self.spec.find(':')
405
369
revno_spec = self.spec
453
421
revno = last_revno + revno + 1
455
revision_id = branch.get_rev_id(revno)
423
revision_id = branch.get_rev_id(revno, revs_or_none)
456
424
except errors.NoSuchRevision:
457
425
raise errors.InvalidRevisionSpec(self.user_spec, branch)
458
426
return branch, revno, revision_id
460
428
def _as_revision_id(self, context_branch):
461
429
# We would have the revno here, but we don't really care
462
branch, revno, revision_id = self._lookup(context_branch)
430
branch, revno, revision_id = self._lookup(context_branch, None)
463
431
return revision_id
465
433
def needs_branch(self):
475
443
RevisionSpec_int = RevisionSpec_revno
478
class RevisionIDSpec(RevisionSpec):
480
def _match_on(self, branch, revs):
481
revision_id = self.as_revision_id(branch)
482
return RevisionInfo.from_revision_id(branch, revision_id)
485
class RevisionSpec_revid(RevisionIDSpec):
447
class RevisionSpec_revid(RevisionSpec):
486
448
"""Selects a revision using the revision id."""
488
450
help_txt = """Selects a revision using the revision id.
498
460
prefix = 'revid:'
500
def _as_revision_id(self, context_branch):
462
def _match_on(self, branch, revs):
501
463
# self.spec comes straight from parsing the command line arguments,
502
464
# so we expect it to be a Unicode string. Switch it to the internal
503
465
# 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):
504
470
return osutils.safe_revision_id(self.spec, warn=False)
523
489
def _match_on(self, branch, revs):
524
revno, revision_id = self._revno_and_revision_id(branch)
490
revno, revision_id = self._revno_and_revision_id(branch, revs)
525
491
return RevisionInfo(branch, revno, revision_id)
527
def _revno_and_revision_id(self, context_branch):
493
def _revno_and_revision_id(self, context_branch, revs_or_none):
528
494
last_revno, last_revision_id = context_branch.last_revision_info()
530
496
if self.spec == '':
603
573
return RevisionInfo(branch, revno, revision_id)
605
575
def _as_revision_id(self, context_branch):
606
base_revision_id = RevisionSpec.from_string(self.spec)._as_revision_id(context_branch)
576
base_revspec = RevisionSpec.from_string(self.spec)
577
base_revision_id = base_revspec.as_revision_id(context_branch)
607
578
if base_revision_id == revision.NULL_REVISION:
608
579
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
609
580
'cannot go before the null: revision')
649
621
class _RevListToTimestamps(object):
650
622
"""This takes a list of revisions, and allows you to bisect by date"""
652
__slots__ = ['branch']
624
__slots__ = ['revs', 'branch']
654
def __init__(self, branch):
626
def __init__(self, revs, branch):
655
628
self.branch = branch
657
630
def __getitem__(self, index):
658
631
"""Get the date of the index'd item"""
659
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
632
r = self.branch.repository.get_revision(self.revs[index])
660
633
# TODO: Handle timezone.
661
634
return datetime.datetime.fromtimestamp(r.timestamp)
663
636
def __len__(self):
664
return self.branch.revno()
637
return len(self.revs)
667
640
class RevisionSpec_date(RevisionSpec):
741
714
hour=hour, minute=minute, second=second)
742
715
branch.lock_read()
744
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
717
rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
747
if rev == branch.revno():
748
721
raise errors.InvalidRevisionSpec(self.user_spec, branch)
749
return RevisionInfo(branch, rev)
723
return RevisionInfo(branch, rev + 1)
783
757
def _find_revision_info(branch, other_location):
784
758
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
786
return RevisionInfo(branch, None, revision_id)
761
revno = branch.revision_id_to_revno(revision_id)
762
except errors.NoSuchRevision:
764
return RevisionInfo(branch, revno, revision_id)
789
767
def _find_revision_id(branch, other_location):
835
813
revision_b = other_branch.last_revision()
836
814
if revision_b in (None, revision.NULL_REVISION):
837
815
raise errors.NoCommits(other_branch)
839
branch = other_branch
842
# pull in the remote revisions so we can diff
843
branch.fetch(other_branch, revision_b)
844
except errors.ReadOnlyError:
845
branch = other_branch
846
return RevisionInfo(branch, None, revision_b)
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)
848
824
def _as_revision_id(self, context_branch):
849
825
from bzrlib.branch import Branch
915
884
self._get_submit_location(context_branch))
918
class RevisionSpec_annotate(RevisionIDSpec):
922
help_txt = """Select the revision that last modified the specified line.
924
Select the revision that last modified the specified line. Line is
925
specified as path:number. Path is a relative path to the file. Numbers
926
start at 1, and are relative to the current version, not the last-
927
committed version of the file.
930
def _raise_invalid(self, numstring, context_branch):
931
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
932
'No such line: %s' % numstring)
934
def _as_revision_id(self, context_branch):
935
path, numstring = self.spec.rsplit(':', 1)
937
index = int(numstring) - 1
939
self._raise_invalid(numstring, context_branch)
940
tree, file_path = workingtree.WorkingTree.open_containing(path)
943
file_id = tree.path2id(file_path)
945
raise errors.InvalidRevisionSpec(self.user_spec,
946
context_branch, "File '%s' is not versioned." %
948
revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
952
revision_id = revision_ids[index]
954
self._raise_invalid(numstring, context_branch)
955
if revision_id == revision.CURRENT_REVISION:
956
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
957
'Line %s has not been committed.' % numstring)
961
class RevisionSpec_mainline(RevisionIDSpec):
963
help_txt = """Select mainline revision that merged the specified revision.
965
Select the revision that merged the specified revision into mainline.
970
def _as_revision_id(self, context_branch):
971
revspec = RevisionSpec.from_string(self.spec)
972
if revspec.get_branch() is None:
973
spec_branch = context_branch
975
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
976
revision_id = revspec.as_revision_id(spec_branch)
977
graph = context_branch.repository.get_graph()
978
result = graph.find_lefthand_merger(revision_id,
979
context_branch.last_revision())
981
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
985
887
# The order in which we want to DWIM a revision spec without any prefix.
986
888
# revno is always tried first and isn't listed here, this is used by
987
889
# RevisionSpec_dwim._match_on
988
dwim_revspecs = symbol_versioning.deprecated_list(
989
symbol_versioning.deprecated_in((2, 4, 0)), "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
991
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
992
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
993
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
994
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
996
898
revspec_registry = registry.Registry()
997
899
def _register_revspec(revspec):
1006
908
_register_revspec(RevisionSpec_ancestor)
1007
909
_register_revspec(RevisionSpec_branch)
1008
910
_register_revspec(RevisionSpec_submit)
1009
_register_revspec(RevisionSpec_annotate)
1010
_register_revspec(RevisionSpec_mainline)
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", [])