15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
from bzrlib.lazy_import import lazy_import
18
from .lazy_import import lazy_import
21
19
lazy_import(globals(), """
24
branch as _mod_branch,
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)
39
68
class RevisionInfo(object):
53
82
or treat the result as a tuple.
56
def __init__(self, branch, revno, rev_id=_marker):
85
def __init__(self, branch, revno=None, rev_id=None):
57
86
self.branch = branch
87
self._has_revno = (revno is not None)
90
if self.rev_id is None and self._revno is not None:
60
91
# 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...
92
self.rev_id = branch.get_rev_id(self._revno)
96
if not self._has_revno and self.rev_id is not None:
98
self._revno = self.branch.revision_id_to_revno(self.rev_id)
99
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
101
self._has_revno = True
70
105
if self.rev_id is None:
72
if self.revno is not None:
74
107
# TODO: otherwise, it should depend on how I was built -
75
108
# if it's in_history(branch), then check revision_history(),
76
109
# if it's in_store(branch), do the check below
77
110
return self.branch.repository.has_revision(self.rev_id)
112
__nonzero__ = __bool__
79
114
def __len__(self):
82
117
def __getitem__(self, index):
83
if index == 0: return self.revno
84
if index == 1: return self.rev_id
85
122
raise IndexError(index)
90
127
def __eq__(self, other):
91
128
if type(other) not in (tuple, list, type(self)):
93
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:
95
132
return tuple(self) == tuple(other)
97
134
def __repr__(self):
98
return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
135
return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
99
136
self.revno, self.rev_id, self.branch)
102
def from_revision_id(branch, revision_id, revs):
139
def from_revision_id(branch, revision_id):
103
140
"""Construct a RevisionInfo given just the id.
105
142
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)
144
return RevisionInfo(branch, revno=None, rev_id=revision_id)
119
147
class RevisionSpec(object):
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
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)
232
return self._match_on_and_check(branch, revs=None)
224
234
# FIXME: in_history is somewhat broken,
225
235
# it will return non-history revisions in many
312
324
"""Run the lookup and see what we can get."""
314
326
# 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:
327
if self._revno_regex.match(self.spec) is not None:
320
329
return self._try_spectype(RevisionSpec_revno, branch)
321
330
except RevisionSpec_revno.dwim_catchable_exceptions:
324
333
# Next see what has been registered
325
for rs_class in dwim_revspecs:
334
for objgetter in self._possible_revspecs:
335
rs_class = objgetter.get_obj()
327
337
return self._try_spectype(rs_class, branch)
328
338
except rs_class.dwim_catchable_exceptions:
331
341
# Well, I dunno what it is. Note that we don't try to keep track of the
332
342
# first of last exception raised during the DWIM tries as none seems
333
343
# really relevant.
334
raise errors.InvalidRevisionSpec(self.spec, branch)
344
raise InvalidRevisionSpec(self.spec, branch)
347
def append_possible_revspec(cls, revspec):
348
"""Append a possible DWIM revspec.
350
:param revspec: Revision spec to try.
352
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
355
def append_possible_lazy_revspec(cls, module_name, member_name):
356
"""Append a possible lazily loaded DWIM revspec.
358
:param module_name: Name of the module with the revspec
359
:param member_name: Name of the revspec within the module
361
cls._possible_revspecs.append(
362
registry._LazyObjectGetter(module_name, member_name))
337
365
class RevisionSpec_revno(RevisionSpec):
356
384
your history is very long.
358
386
prefix = 'revno:'
359
wants_revision_history = False
361
388
def _match_on(self, branch, revs):
362
389
"""Lookup a revision by revision number"""
363
branch, revno, revision_id = self._lookup(branch, revs)
390
branch, revno, revision_id = self._lookup(branch)
364
391
return RevisionInfo(branch, revno, revision_id)
366
def _lookup(self, branch, revs_or_none):
393
def _lookup(self, branch):
367
394
loc = self.spec.find(':')
369
396
revno_spec = self.spec
370
397
branch_spec = None
372
399
revno_spec = self.spec[:loc]
373
branch_spec = self.spec[loc+1:]
400
branch_spec = self.spec[loc + 1:]
375
402
if revno_spec == '':
376
403
if not branch_spec:
377
raise errors.InvalidRevisionSpec(self.user_spec,
378
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')
386
414
# but the from_string method is a little primitive
387
415
# right now - RBC 20060928
389
match_revno = tuple((int(number) for number in revno_spec.split('.')))
390
except ValueError, e:
391
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)
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)
425
# the user has overriden the branch to look in.
426
branch = _mod_branch.Branch.open(branch_spec)
405
430
revision_id = branch.dotted_revno_to_revision_id(match_revno,
407
except errors.NoSuchRevision:
408
raise errors.InvalidRevisionSpec(self.user_spec, branch)
432
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
433
raise InvalidRevisionSpec(self.user_spec, branch)
410
435
# there is no traditional 'revno' for dotted-decimal revnos.
411
# so for API compatability we return None.
436
# so for API compatibility we return None.
412
437
return branch, None, revision_id
414
439
last_revno, last_revision_id = branch.last_revision_info()
421
446
revno = last_revno + revno + 1
423
revision_id = branch.get_rev_id(revno, revs_or_none)
424
except errors.NoSuchRevision:
425
raise errors.InvalidRevisionSpec(self.user_spec, branch)
448
revision_id = branch.get_rev_id(revno)
449
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
450
raise InvalidRevisionSpec(self.user_spec, branch)
426
451
return branch, revno, revision_id
428
453
def _as_revision_id(self, context_branch):
429
454
# We would have the revno here, but we don't really care
430
branch, revno, revision_id = self._lookup(context_branch, None)
455
branch, revno, revision_id = self._lookup(context_branch)
431
456
return revision_id
433
458
def needs_branch(self):
460
492
prefix = 'revid:'
462
def _match_on(self, branch, revs):
494
def _as_revision_id(self, context_branch):
463
495
# self.spec comes straight from parsing the command line arguments,
464
496
# so we expect it to be a Unicode string. Switch it to the internal
465
497
# 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)
498
if isinstance(self.spec, str):
499
return cache_utf8.encode(self.spec)
474
503
class RevisionSpec_last(RevisionSpec):
502
531
offset = int(self.spec)
503
except ValueError, e:
504
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
532
except ValueError as e:
533
raise InvalidRevisionSpec(self.user_spec, context_branch, e)
507
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
508
'you must supply a positive value')
536
raise InvalidRevisionSpec(
537
self.user_spec, context_branch,
538
'you must supply a positive value')
510
540
revno = last_revno - offset + 1
512
revision_id = context_branch.get_rev_id(revno, revs_or_none)
513
except errors.NoSuchRevision:
514
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
542
revision_id = context_branch.get_rev_id(revno)
543
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
544
raise InvalidRevisionSpec(self.user_spec, context_branch)
515
545
return revno, revision_id
517
547
def _as_revision_id(self, context_branch):
518
548
# We compute the revno as part of the process, but we don't really care
520
revno, revision_id = self._revno_and_revision_id(context_branch, None)
550
revno, revision_id = self._revno_and_revision_id(context_branch)
521
551
return revision_id
525
554
class RevisionSpec_before(RevisionSpec):
526
555
"""Selects the parent of the revision specified."""
549
578
def _match_on(self, branch, revs):
550
579
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
552
raise errors.InvalidRevisionSpec(self.user_spec, branch,
553
'cannot go before the null: revision')
581
raise InvalidRevisionSpec(
582
self.user_spec, branch,
583
'cannot go before the null: revision')
554
584
if r.revno is None:
555
585
# We need to use the repository history here
556
586
rev = branch.repository.get_revision(r.rev_id)
557
587
if not rev.parent_ids:
559
588
revision_id = revision.NULL_REVISION
561
590
revision_id = rev.parent_ids[0]
563
revno = revs.index(revision_id) + 1
567
593
revno = r.revno - 1
569
595
revision_id = branch.get_rev_id(revno, revs)
570
except errors.NoSuchRevision:
571
raise errors.InvalidRevisionSpec(self.user_spec,
596
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
597
raise InvalidRevisionSpec(self.user_spec, branch)
573
598
return RevisionInfo(branch, revno, revision_id)
575
600
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)
601
base_revision_id = RevisionSpec.from_string(
602
self.spec)._as_revision_id(context_branch)
578
603
if base_revision_id == revision.NULL_REVISION:
579
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
580
'cannot go before the null: revision')
604
raise InvalidRevisionSpec(
605
self.user_spec, context_branch,
606
'cannot go before the null: revision')
581
607
context_repo = context_branch.repository
582
context_repo.lock_read()
608
with context_repo.lock_read():
584
609
parent_map = context_repo.get_parent_map([base_revision_id])
586
context_repo.unlock()
587
610
if base_revision_id not in parent_map:
588
611
# Ghost, or unknown revision id
589
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
590
'cannot find the matching revision')
612
raise InvalidRevisionSpec(
613
self.user_spec, context_branch, 'cannot find the matching revision')
591
614
parents = parent_map[base_revision_id]
592
615
if len(parents) < 1:
593
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
594
'No parents for revision.')
616
raise InvalidRevisionSpec(
617
self.user_spec, context_branch, 'No parents for revision.')
595
618
return parents[0]
599
621
class RevisionSpec_tag(RevisionSpec):
600
622
"""Select a revision identified by tag name"""
610
632
def _match_on(self, branch, revs):
611
633
# Can raise tags not supported, NoSuchTag, etc
612
634
return RevisionInfo.from_revision_id(branch,
613
branch.tags.lookup_tag(self.spec),
635
branch.tags.lookup_tag(self.spec))
616
637
def _as_revision_id(self, context_branch):
617
638
return context_branch.tags.lookup_tag(self.spec)
621
641
class _RevListToTimestamps(object):
622
642
"""This takes a list of revisions, and allows you to bisect by date"""
624
__slots__ = ['revs', 'branch']
644
__slots__ = ['branch']
626
def __init__(self, revs, branch):
646
def __init__(self, branch):
628
647
self.branch = branch
630
649
def __getitem__(self, index):
631
650
"""Get the date of the index'd item"""
632
r = self.branch.repository.get_revision(self.revs[index])
651
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
633
652
# TODO: Handle timezone.
634
653
return datetime.datetime.fromtimestamp(r.timestamp)
636
655
def __len__(self):
637
return len(self.revs)
656
return self.branch.revno()
640
659
class RevisionSpec_date(RevisionSpec):
708
hour, minute, second = 0,0,0
728
hour, minute, second = 0, 0, 0
709
729
except ValueError:
710
raise errors.InvalidRevisionSpec(self.user_spec,
711
branch, 'invalid date')
730
raise InvalidRevisionSpec(
731
self.user_spec, branch, 'invalid date')
713
733
dt = datetime.datetime(year=year, month=month, day=day,
714
hour=hour, minute=minute, second=second)
717
rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
721
raise errors.InvalidRevisionSpec(self.user_spec, branch)
723
return RevisionInfo(branch, rev + 1)
734
hour=hour, minute=minute, second=second)
735
with branch.lock_read():
736
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
737
if rev == branch.revno():
738
raise InvalidRevisionSpec(self.user_spec, branch)
739
return RevisionInfo(branch, rev)
727
742
class RevisionSpec_ancestor(RevisionSpec):
757
772
def _find_revision_info(branch, other_location):
758
773
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)
775
return RevisionInfo(branch, None, revision_id)
767
778
def _find_revision_id(branch, other_location):
768
from bzrlib.branch import Branch
779
from .branch import Branch
781
with branch.lock_read():
772
782
revision_a = revision.ensure_null(branch.last_revision())
773
783
if revision_a == revision.NULL_REVISION:
774
784
raise errors.NoCommits(branch)
775
785
if other_location == '':
776
786
other_location = branch.get_parent()
777
787
other_branch = Branch.open(other_location)
778
other_branch.lock_read()
788
with other_branch.lock_read():
780
789
revision_b = revision.ensure_null(other_branch.last_revision())
781
790
if revision_b == revision.NULL_REVISION:
782
791
raise errors.NoCommits(other_branch)
783
792
graph = branch.repository.get_graph(other_branch.repository)
784
793
rev_id = graph.find_unique_lca(revision_a, revision_b)
786
other_branch.unlock()
787
794
if rev_id == revision.NULL_REVISION:
788
795
raise errors.NoCommonAncestor(revision_a, revision_b)
796
799
class RevisionSpec_branch(RevisionSpec):
808
811
dwim_catchable_exceptions = (errors.NotBranchError,)
810
813
def _match_on(self, branch, revs):
811
from bzrlib.branch import Branch
814
from .branch import Branch
812
815
other_branch = Branch.open(self.spec)
813
816
revision_b = other_branch.last_revision()
814
817
if revision_b in (None, revision.NULL_REVISION):
815
818
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)
820
branch = other_branch
823
# pull in the remote revisions so we can diff
824
branch.fetch(other_branch, revision_b)
825
except errors.ReadOnlyError:
826
branch = other_branch
827
return RevisionInfo(branch, None, revision_b)
824
829
def _as_revision_id(self, context_branch):
825
from bzrlib.branch import Branch
830
from .branch import Branch
826
831
other_branch = Branch.open(self.spec)
827
832
last_revision = other_branch.last_revision()
828
833
last_revision = revision.ensure_null(last_revision)
871
881
location_type = 'parent branch'
872
882
if submit_location is None:
873
883
raise errors.NoSubmitBranch(branch)
874
trace.note('Using %s %s', location_type, submit_location)
884
trace.note(gettext('Using {0} {1}').format(location_type,
875
886
return submit_location
877
888
def _match_on(self, branch, revs):
878
889
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
879
890
return self._find_revision_info(branch,
880
self._get_submit_location(branch))
891
self._get_submit_location(branch))
882
893
def _as_revision_id(self, context_branch):
883
894
return self._find_revision_id(context_branch,
884
self._get_submit_location(context_branch))
895
self._get_submit_location(context_branch))
898
class RevisionSpec_annotate(RevisionIDSpec):
902
help_txt = """Select the revision that last modified the specified line.
904
Select the revision that last modified the specified line. Line is
905
specified as path:number. Path is a relative path to the file. Numbers
906
start at 1, and are relative to the current version, not the last-
907
committed version of the file.
910
def _raise_invalid(self, numstring, context_branch):
911
raise InvalidRevisionSpec(
912
self.user_spec, context_branch,
913
'No such line: %s' % numstring)
915
def _as_revision_id(self, context_branch):
916
path, numstring = self.spec.rsplit(':', 1)
918
index = int(numstring) - 1
920
self._raise_invalid(numstring, context_branch)
921
tree, file_path = workingtree.WorkingTree.open_containing(path)
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)]
929
revision_id = revision_ids[index]
931
self._raise_invalid(numstring, context_branch)
932
if revision_id == revision.CURRENT_REVISION:
933
raise InvalidRevisionSpec(
934
self.user_spec, context_branch,
935
'Line %s has not been committed.' % numstring)
939
class RevisionSpec_mainline(RevisionIDSpec):
941
help_txt = """Select mainline revision that merged the specified revision.
943
Select the revision that merged the specified revision into mainline.
948
def _as_revision_id(self, context_branch):
949
revspec = RevisionSpec.from_string(self.spec)
950
if revspec.get_branch() is None:
951
spec_branch = context_branch
953
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
954
revision_id = revspec.as_revision_id(spec_branch)
955
graph = context_branch.repository.get_graph()
956
result = graph.find_lefthand_merger(revision_id,
957
context_branch.last_revision())
959
raise InvalidRevisionSpec(self.user_spec, context_branch)
887
963
# The order in which we want to DWIM a revision spec without any prefix.
888
964
# revno is always tried first and isn't listed here, this is used by
889
965
# 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
966
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
967
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
968
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
969
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
898
971
revspec_registry = registry.Registry()
899
974
def _register_revspec(revspec):
900
975
revspec_registry.register(revspec.prefix, revspec)
902
978
_register_revspec(RevisionSpec_revno)
903
979
_register_revspec(RevisionSpec_revid)
904
980
_register_revspec(RevisionSpec_last)