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
20
from bzrlib.lazy_import import lazy_import
21
21
lazy_import(globals(), """
26
branch as _mod_branch,
32
from bzrlib.i18n import gettext
26
35
from bzrlib import (
39
43
class RevisionInfo(object):
40
44
"""The results of applying a revision specification to a branch."""
53
57
or treat the result as a tuple.
56
def __init__(self, branch, revno, rev_id=_marker):
60
def __init__(self, branch, revno=None, rev_id=None):
57
61
self.branch = branch
62
self._has_revno = (revno is not None)
65
if self.rev_id is None and self._revno is not None:
60
66
# allow caller to be lazy
61
if self.revno is None:
64
self.rev_id = branch.get_rev_id(self.revno)
67
self.rev_id = branch.get_rev_id(self._revno)
71
if not self._has_revno and self.rev_id is not None:
73
self._revno = self.branch.revision_id_to_revno(self.rev_id)
74
except errors.NoSuchRevision:
76
self._has_revno = True
68
79
def __nonzero__(self):
69
# first the easy ones...
70
80
if self.rev_id is None:
72
if self.revno is not None:
74
82
# TODO: otherwise, it should depend on how I was built -
75
83
# if it's in_history(branch), then check revision_history(),
76
84
# if it's in_store(branch), do the check below
99
107
self.revno, self.rev_id, self.branch)
102
def from_revision_id(branch, revision_id, revs):
110
def from_revision_id(branch, revision_id, revs=symbol_versioning.DEPRECATED_PARAMETER):
103
111
"""Construct a RevisionInfo given just the id.
105
113
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)
115
if symbol_versioning.deprecated_passed(revs):
116
symbol_versioning.warn(
117
'RevisionInfo.from_revision_id(revs) was deprecated in 2.5.',
120
return RevisionInfo(branch, revno=None, rev_id=revision_id)
119
123
class RevisionSpec(object):
166
171
spectype.__name__, spec)
167
172
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
174
# Otherwise treat it as a DWIM, build the RevisionSpec object and
175
175
# wait for _match_on to be called.
176
176
return RevisionSpec_dwim(spec, _internal=True)
312
325
"""Run the lookup and see what we can get."""
314
327
# 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:
328
if self._revno_regex.match(self.spec) is not None:
320
330
return self._try_spectype(RevisionSpec_revno, branch)
321
331
except RevisionSpec_revno.dwim_catchable_exceptions:
324
334
# Next see what has been registered
335
for objgetter in self._possible_revspecs:
336
rs_class = objgetter.get_obj()
338
return self._try_spectype(rs_class, branch)
339
except rs_class.dwim_catchable_exceptions:
342
# Try the old (deprecated) dwim list:
325
343
for rs_class in dwim_revspecs:
327
345
return self._try_spectype(rs_class, branch)
333
351
# really relevant.
334
352
raise errors.InvalidRevisionSpec(self.spec, branch)
355
def append_possible_revspec(cls, revspec):
356
"""Append a possible DWIM revspec.
358
:param revspec: Revision spec to try.
360
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
363
def append_possible_lazy_revspec(cls, module_name, member_name):
364
"""Append a possible lazily loaded DWIM revspec.
366
:param module_name: Name of the module with the revspec
367
:param member_name: Name of the revspec within the module
369
cls._possible_revspecs.append(
370
registry._LazyObjectGetter(module_name, member_name))
337
373
class RevisionSpec_revno(RevisionSpec):
338
374
"""Selects a revision using a number."""
356
392
your history is very long.
358
394
prefix = 'revno:'
359
wants_revision_history = False
361
396
def _match_on(self, branch, revs):
362
397
"""Lookup a revision by revision number"""
363
branch, revno, revision_id = self._lookup(branch, revs)
398
branch, revno, revision_id = self._lookup(branch)
364
399
return RevisionInfo(branch, revno, revision_id)
366
def _lookup(self, branch, revs_or_none):
401
def _lookup(self, branch):
367
402
loc = self.spec.find(':')
369
404
revno_spec = self.spec
421
452
revno = last_revno + revno + 1
423
revision_id = branch.get_rev_id(revno, revs_or_none)
454
revision_id = branch.get_rev_id(revno)
424
455
except errors.NoSuchRevision:
425
456
raise errors.InvalidRevisionSpec(self.user_spec, branch)
426
457
return branch, revno, revision_id
428
459
def _as_revision_id(self, context_branch):
429
460
# We would have the revno here, but we don't really care
430
branch, revno, revision_id = self._lookup(context_branch, None)
461
branch, revno, revision_id = self._lookup(context_branch)
431
462
return revision_id
433
464
def needs_branch(self):
443
474
RevisionSpec_int = RevisionSpec_revno
447
class RevisionSpec_revid(RevisionSpec):
477
class RevisionIDSpec(RevisionSpec):
479
def _match_on(self, branch, revs):
480
revision_id = self.as_revision_id(branch)
481
return RevisionInfo.from_revision_id(branch, revision_id)
484
class RevisionSpec_revid(RevisionIDSpec):
448
485
"""Selects a revision using the revision id."""
450
487
help_txt = """Selects a revision using the revision id.
460
497
prefix = 'revid:'
462
def _match_on(self, branch, revs):
499
def _as_revision_id(self, context_branch):
463
500
# self.spec comes straight from parsing the command line arguments,
464
501
# so we expect it to be a Unicode string. Switch it to the internal
465
502
# 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
503
return osutils.safe_revision_id(self.spec, warn=False)
489
522
def _match_on(self, branch, revs):
490
revno, revision_id = self._revno_and_revision_id(branch, revs)
523
revno, revision_id = self._revno_and_revision_id(branch)
491
524
return RevisionInfo(branch, revno, revision_id)
493
def _revno_and_revision_id(self, context_branch, revs_or_none):
526
def _revno_and_revision_id(self, context_branch):
494
527
last_revno, last_revision_id = context_branch.last_revision_info()
496
529
if self.spec == '':
573
602
return RevisionInfo(branch, revno, revision_id)
575
604
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)
605
base_revision_id = RevisionSpec.from_string(self.spec)._as_revision_id(context_branch)
578
606
if base_revision_id == revision.NULL_REVISION:
579
607
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
580
608
'cannot go before the null: revision')
621
648
class _RevListToTimestamps(object):
622
649
"""This takes a list of revisions, and allows you to bisect by date"""
624
__slots__ = ['revs', 'branch']
651
__slots__ = ['branch']
626
def __init__(self, revs, branch):
653
def __init__(self, branch):
628
654
self.branch = branch
630
656
def __getitem__(self, index):
631
657
"""Get the date of the index'd item"""
632
r = self.branch.repository.get_revision(self.revs[index])
658
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
633
659
# TODO: Handle timezone.
634
660
return datetime.datetime.fromtimestamp(r.timestamp)
636
662
def __len__(self):
637
return len(self.revs)
663
return self.branch.revno()
640
666
class RevisionSpec_date(RevisionSpec):
714
740
hour=hour, minute=minute, second=second)
715
741
branch.lock_read()
717
rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
743
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
746
if rev == branch.revno():
721
747
raise errors.InvalidRevisionSpec(self.user_spec, branch)
723
return RevisionInfo(branch, rev + 1)
748
return RevisionInfo(branch, rev)
757
782
def _find_revision_info(branch, other_location):
758
783
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)
785
return RevisionInfo(branch, None, revision_id)
767
788
def _find_revision_id(branch, other_location):
821
842
branch.fetch(other_branch, revision_b)
822
843
except errors.ReadOnlyError:
823
844
branch = other_branch
825
revno = branch.revision_id_to_revno(revision_b)
826
except errors.NoSuchRevision:
828
return RevisionInfo(branch, revno, revision_b)
845
return RevisionInfo(branch, None, revision_b)
830
847
def _as_revision_id(self, context_branch):
831
848
from bzrlib.branch import Branch
896
914
self._get_submit_location(context_branch))
917
class RevisionSpec_annotate(RevisionIDSpec):
921
help_txt = """Select the revision that last modified the specified line.
923
Select the revision that last modified the specified line. Line is
924
specified as path:number. Path is a relative path to the file. Numbers
925
start at 1, and are relative to the current version, not the last-
926
committed version of the file.
929
def _raise_invalid(self, numstring, context_branch):
930
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
931
'No such line: %s' % numstring)
933
def _as_revision_id(self, context_branch):
934
path, numstring = self.spec.rsplit(':', 1)
936
index = int(numstring) - 1
938
self._raise_invalid(numstring, context_branch)
939
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)]
951
revision_id = revision_ids[index]
953
self._raise_invalid(numstring, context_branch)
954
if revision_id == revision.CURRENT_REVISION:
955
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
956
'Line %s has not been committed.' % numstring)
960
class RevisionSpec_mainline(RevisionIDSpec):
962
help_txt = """Select mainline revision that merged the specified revision.
964
Select the revision that merged the specified revision into mainline.
969
def _as_revision_id(self, context_branch):
970
revspec = RevisionSpec.from_string(self.spec)
971
if revspec.get_branch() is None:
972
spec_branch = context_branch
974
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
975
revision_id = revspec.as_revision_id(spec_branch)
976
graph = context_branch.repository.get_graph()
977
result = graph.find_lefthand_merger(revision_id,
978
context_branch.last_revision())
980
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
899
984
# The order in which we want to DWIM a revision spec without any prefix.
900
985
# revno is always tried first and isn't listed here, this is used by
901
986
# RevisionSpec_dwim._match_on
903
RevisionSpec_tag, # Let's try for a tag
904
RevisionSpec_revid, # Maybe it's a revid?
905
RevisionSpec_date, # Perhaps a date?
906
RevisionSpec_branch, # OK, last try, maybe it's a branch
987
dwim_revspecs = symbol_versioning.deprecated_list(
988
symbol_versioning.deprecated_in((2, 4, 0)), "dwim_revspecs", [])
990
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
991
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
992
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
993
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
910
995
revspec_registry = registry.Registry()
911
996
def _register_revspec(revspec):
920
1005
_register_revspec(RevisionSpec_ancestor)
921
1006
_register_revspec(RevisionSpec_branch)
922
1007
_register_revspec(RevisionSpec_submit)
924
# classes in this list should have a "prefix" attribute, against which
925
# string specs are matched
926
SPEC_TYPES = symbol_versioning.deprecated_list(
927
symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])
1008
_register_revspec(RevisionSpec_annotate)
1009
_register_revspec(RevisionSpec_mainline)