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
18
20
from .lazy_import import lazy_import
19
21
lazy_import(globals(), """
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)
68
46
class RevisionInfo(object):
167
dwim_catchable_exceptions = (InvalidRevisionSpec,)
143
dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
168
144
"""Exceptions that RevisionSpec_dwim._match_on will catch.
170
146
If the revspec is part of ``dwim_revspecs``, it may be tried with an
185
161
return RevisionSpec(None, _internal=True)
186
if not isinstance(spec, str):
162
if not isinstance(spec, (str, text_type)):
187
163
raise TypeError("revision spec needs to be text")
188
164
match = revspec_registry.get_prefix(spec)
189
165
if match is not None:
224
200
# special case - nothing supplied
226
202
elif self.prefix:
227
raise InvalidRevisionSpec(self.user_spec, branch)
203
raise errors.InvalidRevisionSpec(self.user_spec, branch)
229
raise InvalidRevisionSpec(self.spec, branch)
205
raise errors.InvalidRevisionSpec(self.spec, branch)
231
207
def in_history(self, branch):
232
208
return self._match_on_and_check(branch, revs=None)
341
317
# Well, I dunno what it is. Note that we don't try to keep track of the
342
318
# first of last exception raised during the DWIM tries as none seems
343
319
# really relevant.
344
raise InvalidRevisionSpec(self.spec, branch)
320
raise errors.InvalidRevisionSpec(self.spec, branch)
347
323
def append_possible_revspec(cls, revspec):
397
373
branch_spec = None
399
375
revno_spec = self.spec[:loc]
400
branch_spec = self.spec[loc + 1:]
376
branch_spec = self.spec[loc+1:]
402
378
if revno_spec == '':
403
379
if not branch_spec:
404
raise InvalidRevisionSpec(
405
self.user_spec, branch,
406
'cannot have an empty revno and no branch')
380
raise errors.InvalidRevisionSpec(self.user_spec,
381
branch, 'cannot have an empty revno and no branch')
414
389
# but the from_string method is a little primitive
415
390
# right now - RBC 20060928
417
match_revno = tuple((int(number)
418
for number in revno_spec.split('.')))
392
match_revno = tuple((int(number) for number in revno_spec.split('.')))
419
393
except ValueError as e:
420
raise InvalidRevisionSpec(self.user_spec, branch, e)
394
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
430
404
revision_id = branch.dotted_revno_to_revision_id(match_revno,
432
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
433
raise InvalidRevisionSpec(self.user_spec, branch)
406
except errors.NoSuchRevision:
407
raise errors.InvalidRevisionSpec(self.user_spec, branch)
435
409
# there is no traditional 'revno' for dotted-decimal revnos.
436
410
# so for API compatibility we return None.
446
420
revno = last_revno + revno + 1
448
422
revision_id = branch.get_rev_id(revno)
449
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
450
raise InvalidRevisionSpec(self.user_spec, branch)
423
except errors.NoSuchRevision:
424
raise errors.InvalidRevisionSpec(self.user_spec, branch)
451
425
return branch, revno, revision_id
453
427
def _as_revision_id(self, context_branch):
495
468
# self.spec comes straight from parsing the command line arguments,
496
469
# so we expect it to be a Unicode string. Switch it to the internal
497
470
# representation.
498
if isinstance(self.spec, str):
471
if isinstance(self.spec, unicode):
499
472
return cache_utf8.encode(self.spec)
503
477
class RevisionSpec_last(RevisionSpec):
504
478
"""Selects the nth revision from the end."""
531
505
offset = int(self.spec)
532
506
except ValueError as e:
533
raise InvalidRevisionSpec(self.user_spec, context_branch, e)
507
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
536
raise InvalidRevisionSpec(
537
self.user_spec, context_branch,
538
'you must supply a positive value')
510
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
511
'you must supply a positive value')
540
513
revno = last_revno - offset + 1
542
515
revision_id = context_branch.get_rev_id(revno)
543
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
544
raise InvalidRevisionSpec(self.user_spec, context_branch)
516
except errors.NoSuchRevision:
517
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
545
518
return revno, revision_id
547
520
def _as_revision_id(self, context_branch):
578
552
def _match_on(self, branch, revs):
579
553
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
581
raise InvalidRevisionSpec(
582
self.user_spec, branch,
583
'cannot go before the null: revision')
555
raise errors.InvalidRevisionSpec(self.user_spec, branch,
556
'cannot go before the null: revision')
584
557
if r.revno is None:
585
558
# We need to use the repository history here
586
559
rev = branch.repository.get_revision(r.rev_id)
593
566
revno = r.revno - 1
595
568
revision_id = branch.get_rev_id(revno, revs)
596
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
597
raise InvalidRevisionSpec(self.user_spec, branch)
569
except errors.NoSuchRevision:
570
raise errors.InvalidRevisionSpec(self.user_spec,
598
572
return RevisionInfo(branch, revno, revision_id)
600
574
def _as_revision_id(self, context_branch):
601
base_revision_id = RevisionSpec.from_string(
602
self.spec)._as_revision_id(context_branch)
575
base_revision_id = RevisionSpec.from_string(self.spec)._as_revision_id(context_branch)
603
576
if base_revision_id == revision.NULL_REVISION:
604
raise InvalidRevisionSpec(
605
self.user_spec, context_branch,
606
'cannot go before the null: revision')
577
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
578
'cannot go before the null: revision')
607
579
context_repo = context_branch.repository
608
with context_repo.lock_read():
580
context_repo.lock_read()
609
582
parent_map = context_repo.get_parent_map([base_revision_id])
584
context_repo.unlock()
610
585
if base_revision_id not in parent_map:
611
586
# Ghost, or unknown revision id
612
raise InvalidRevisionSpec(
613
self.user_spec, context_branch, 'cannot find the matching revision')
587
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
588
'cannot find the matching revision')
614
589
parents = parent_map[base_revision_id]
615
590
if len(parents) < 1:
616
raise InvalidRevisionSpec(
617
self.user_spec, context_branch, 'No parents for revision.')
591
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
592
'No parents for revision.')
618
593
return parents[0]
621
597
class RevisionSpec_tag(RevisionSpec):
622
598
"""Select a revision identified by tag name"""
632
608
def _match_on(self, branch, revs):
633
609
# Can raise tags not supported, NoSuchTag, etc
634
610
return RevisionInfo.from_revision_id(branch,
635
branch.tags.lookup_tag(self.spec))
611
branch.tags.lookup_tag(self.spec))
637
613
def _as_revision_id(self, context_branch):
638
614
return context_branch.tags.lookup_tag(self.spec)
641
618
class _RevListToTimestamps(object):
642
619
"""This takes a list of revisions, and allows you to bisect by date"""
680
657
_date_regex = lazy_regex.lazy_compile(
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))?)?'
658
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
660
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
686
663
def _match_on(self, branch, revs):
693
670
# XXX: This doesn't actually work
694
671
# So the proper way of saying 'give me all entries for today' is:
695
672
# -r date:yesterday..date:today
696
today = datetime.datetime.fromordinal(
697
datetime.date.today().toordinal())
673
today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
698
674
if self.spec.lower() == 'yesterday':
699
675
dt = today - datetime.timedelta(days=1)
700
676
elif self.spec.lower() == 'today':
705
681
m = self._date_regex.match(self.spec)
706
682
if not m or (not m.group('date') and not m.group('time')):
707
raise InvalidRevisionSpec(
708
self.user_spec, branch, 'invalid date')
683
raise errors.InvalidRevisionSpec(self.user_spec,
684
branch, 'invalid date')
711
687
if m.group('date'):
728
hour, minute, second = 0, 0, 0
704
hour, minute, second = 0,0,0
729
705
except ValueError:
730
raise InvalidRevisionSpec(
731
self.user_spec, branch, 'invalid date')
706
raise errors.InvalidRevisionSpec(self.user_spec,
707
branch, 'invalid date')
733
709
dt = datetime.datetime(year=year, month=month, day=day,
734
hour=hour, minute=minute, second=second)
735
with branch.lock_read():
710
hour=hour, minute=minute, second=second)
736
713
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
737
716
if rev == branch.revno():
738
raise InvalidRevisionSpec(self.user_spec, branch)
717
raise errors.InvalidRevisionSpec(self.user_spec, branch)
739
718
return RevisionInfo(branch, rev)
742
722
class RevisionSpec_ancestor(RevisionSpec):
743
723
"""Selects a common ancestor with a second branch."""
778
758
def _find_revision_id(branch, other_location):
779
759
from .branch import Branch
781
with branch.lock_read():
782
763
revision_a = revision.ensure_null(branch.last_revision())
783
764
if revision_a == revision.NULL_REVISION:
784
765
raise errors.NoCommits(branch)
785
766
if other_location == '':
786
767
other_location = branch.get_parent()
787
768
other_branch = Branch.open(other_location)
788
with other_branch.lock_read():
769
other_branch.lock_read()
789
771
revision_b = revision.ensure_null(other_branch.last_revision())
790
772
if revision_b == revision.NULL_REVISION:
791
773
raise errors.NoCommits(other_branch)
792
774
graph = branch.repository.get_graph(other_branch.repository)
793
775
rev_id = graph.find_unique_lca(revision_a, revision_b)
777
other_branch.unlock()
794
778
if rev_id == revision.NULL_REVISION:
795
779
raise errors.NoCommonAncestor(revision_a, revision_b)
799
787
class RevisionSpec_branch(RevisionSpec):
882
871
if submit_location is None:
883
872
raise errors.NoSubmitBranch(branch)
884
873
trace.note(gettext('Using {0} {1}').format(location_type,
886
875
return submit_location
888
877
def _match_on(self, branch, revs):
889
878
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
890
879
return self._find_revision_info(branch,
891
self._get_submit_location(branch))
880
self._get_submit_location(branch))
893
882
def _as_revision_id(self, context_branch):
894
883
return self._find_revision_id(context_branch,
895
self._get_submit_location(context_branch))
884
self._get_submit_location(context_branch))
898
887
class RevisionSpec_annotate(RevisionIDSpec):
910
899
def _raise_invalid(self, numstring, context_branch):
911
raise InvalidRevisionSpec(
912
self.user_spec, context_branch,
900
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
913
901
'No such line: %s' % numstring)
915
903
def _as_revision_id(self, context_branch):
919
907
except ValueError:
920
908
self._raise_invalid(numstring, context_branch)
921
909
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)]
912
file_id = tree.path2id(file_path)
914
raise errors.InvalidRevisionSpec(self.user_spec,
915
context_branch, "File '%s' is not versioned." %
917
revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
929
921
revision_id = revision_ids[index]
930
922
except IndexError:
931
923
self._raise_invalid(numstring, context_branch)
932
924
if revision_id == revision.CURRENT_REVISION:
933
raise InvalidRevisionSpec(
934
self.user_spec, context_branch,
925
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
935
926
'Line %s has not been committed.' % numstring)
936
927
return revision_id
956
947
result = graph.find_lefthand_merger(revision_id,
957
948
context_branch.last_revision())
958
949
if result is None:
959
raise InvalidRevisionSpec(self.user_spec, context_branch)
950
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
969
960
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
971
962
revspec_registry = registry.Registry()
974
963
def _register_revspec(revspec):
975
964
revspec_registry.register(revspec.prefix, revspec)
978
966
_register_revspec(RevisionSpec_revno)
979
967
_register_revspec(RevisionSpec_revid)
980
968
_register_revspec(RevisionSpec_last)