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
18
from .lazy_import import lazy_import
21
19
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)
46
54
class RevisionInfo(object):
143
dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
153
dwim_catchable_exceptions = (InvalidRevisionSpec,)
144
154
"""Exceptions that RevisionSpec_dwim._match_on will catch.
146
156
If the revspec is part of ``dwim_revspecs``, it may be tried with an
161
171
return RevisionSpec(None, _internal=True)
162
if not isinstance(spec, (str, text_type)):
172
if not isinstance(spec, str):
163
173
raise TypeError("revision spec needs to be text")
164
174
match = revspec_registry.get_prefix(spec)
165
175
if match is not None:
200
210
# special case - nothing supplied
202
212
elif self.prefix:
203
raise errors.InvalidRevisionSpec(self.user_spec, branch)
213
raise InvalidRevisionSpec(self.user_spec, branch)
205
raise errors.InvalidRevisionSpec(self.spec, branch)
215
raise InvalidRevisionSpec(self.spec, branch)
207
217
def in_history(self, branch):
208
218
return self._match_on_and_check(branch, revs=None)
317
327
# Well, I dunno what it is. Note that we don't try to keep track of the
318
328
# first of last exception raised during the DWIM tries as none seems
319
329
# really relevant.
320
raise errors.InvalidRevisionSpec(self.spec, branch)
330
raise InvalidRevisionSpec(self.spec, branch)
323
333
def append_possible_revspec(cls, revspec):
373
383
branch_spec = None
375
385
revno_spec = self.spec[:loc]
376
branch_spec = self.spec[loc+1:]
386
branch_spec = self.spec[loc + 1:]
378
388
if revno_spec == '':
379
389
if not branch_spec:
380
raise errors.InvalidRevisionSpec(self.user_spec,
381
branch, 'cannot have an empty revno and no branch')
390
raise InvalidRevisionSpec(
391
self.user_spec, branch,
392
'cannot have an empty revno and no branch')
389
400
# but the from_string method is a little primitive
390
401
# right now - RBC 20060928
392
match_revno = tuple((int(number) for number in revno_spec.split('.')))
403
match_revno = tuple((int(number)
404
for number in revno_spec.split('.')))
393
405
except ValueError as e:
394
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
406
raise InvalidRevisionSpec(self.user_spec, branch, e)
404
416
revision_id = branch.dotted_revno_to_revision_id(match_revno,
406
except errors.NoSuchRevision:
407
raise errors.InvalidRevisionSpec(self.user_spec, branch)
418
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
419
raise InvalidRevisionSpec(self.user_spec, branch)
409
421
# there is no traditional 'revno' for dotted-decimal revnos.
410
422
# so for API compatibility we return None.
420
432
revno = last_revno + revno + 1
422
434
revision_id = branch.get_rev_id(revno)
423
except errors.NoSuchRevision:
424
raise errors.InvalidRevisionSpec(self.user_spec, branch)
435
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
436
raise InvalidRevisionSpec(self.user_spec, branch)
425
437
return branch, revno, revision_id
427
439
def _as_revision_id(self, context_branch):
468
481
# self.spec comes straight from parsing the command line arguments,
469
482
# so we expect it to be a Unicode string. Switch it to the internal
470
483
# representation.
471
if isinstance(self.spec, unicode):
484
if isinstance(self.spec, str):
472
485
return cache_utf8.encode(self.spec)
477
489
class RevisionSpec_last(RevisionSpec):
478
490
"""Selects the nth revision from the end."""
505
517
offset = int(self.spec)
506
518
except ValueError as e:
507
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
519
raise InvalidRevisionSpec(self.user_spec, context_branch, e)
510
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
511
'you must supply a positive value')
522
raise InvalidRevisionSpec(
523
self.user_spec, context_branch,
524
'you must supply a positive value')
513
526
revno = last_revno - offset + 1
515
528
revision_id = context_branch.get_rev_id(revno)
516
except errors.NoSuchRevision:
517
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
529
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
530
raise InvalidRevisionSpec(self.user_spec, context_branch)
518
531
return revno, revision_id
520
533
def _as_revision_id(self, context_branch):
552
564
def _match_on(self, branch, revs):
553
565
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
555
raise errors.InvalidRevisionSpec(self.user_spec, branch,
556
'cannot go before the null: revision')
567
raise InvalidRevisionSpec(
568
self.user_spec, branch,
569
'cannot go before the null: revision')
557
570
if r.revno is None:
558
571
# We need to use the repository history here
559
572
rev = branch.repository.get_revision(r.rev_id)
566
579
revno = r.revno - 1
568
581
revision_id = branch.get_rev_id(revno, revs)
569
except errors.NoSuchRevision:
570
raise errors.InvalidRevisionSpec(self.user_spec,
582
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
583
raise InvalidRevisionSpec(self.user_spec, branch)
572
584
return RevisionInfo(branch, revno, revision_id)
574
586
def _as_revision_id(self, context_branch):
575
base_revision_id = RevisionSpec.from_string(self.spec)._as_revision_id(context_branch)
587
base_revision_id = RevisionSpec.from_string(
588
self.spec)._as_revision_id(context_branch)
576
589
if base_revision_id == revision.NULL_REVISION:
577
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
578
'cannot go before the null: revision')
590
raise InvalidRevisionSpec(
591
self.user_spec, context_branch,
592
'cannot go before the null: revision')
579
593
context_repo = context_branch.repository
580
context_repo.lock_read()
594
with context_repo.lock_read():
582
595
parent_map = context_repo.get_parent_map([base_revision_id])
584
context_repo.unlock()
585
596
if base_revision_id not in parent_map:
586
597
# Ghost, or unknown revision id
587
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
588
'cannot find the matching revision')
598
raise InvalidRevisionSpec(
599
self.user_spec, context_branch, 'cannot find the matching revision')
589
600
parents = parent_map[base_revision_id]
590
601
if len(parents) < 1:
591
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
592
'No parents for revision.')
602
raise errors.InvalidRevisionSpec(
603
self.user_spec, context_branch, 'No parents for revision.')
593
604
return parents[0]
597
607
class RevisionSpec_tag(RevisionSpec):
598
608
"""Select a revision identified by tag name"""
608
618
def _match_on(self, branch, revs):
609
619
# Can raise tags not supported, NoSuchTag, etc
610
620
return RevisionInfo.from_revision_id(branch,
611
branch.tags.lookup_tag(self.spec))
621
branch.tags.lookup_tag(self.spec))
613
623
def _as_revision_id(self, context_branch):
614
624
return context_branch.tags.lookup_tag(self.spec)
618
627
class _RevListToTimestamps(object):
619
628
"""This takes a list of revisions, and allows you to bisect by date"""
657
666
_date_regex = lazy_regex.lazy_compile(
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))?)?'
667
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
669
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
663
672
def _match_on(self, branch, revs):
670
679
# XXX: This doesn't actually work
671
680
# So the proper way of saying 'give me all entries for today' is:
672
681
# -r date:yesterday..date:today
673
today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
682
today = datetime.datetime.fromordinal(
683
datetime.date.today().toordinal())
674
684
if self.spec.lower() == 'yesterday':
675
685
dt = today - datetime.timedelta(days=1)
676
686
elif self.spec.lower() == 'today':
681
691
m = self._date_regex.match(self.spec)
682
692
if not m or (not m.group('date') and not m.group('time')):
683
raise errors.InvalidRevisionSpec(self.user_spec,
684
branch, 'invalid date')
693
raise InvalidRevisionSpec(
694
self.user_spec, branch, 'invalid date')
687
697
if m.group('date'):
704
714
hour, minute, second = 0, 0, 0
705
715
except ValueError:
706
raise errors.InvalidRevisionSpec(self.user_spec,
707
branch, 'invalid date')
716
raise InvalidRevisionSpec(
717
self.user_spec, branch, 'invalid date')
709
719
dt = datetime.datetime(year=year, month=month, day=day,
710
hour=hour, minute=minute, second=second)
720
hour=hour, minute=minute, second=second)
711
721
with branch.lock_read():
712
722
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
713
723
if rev == branch.revno():
714
raise errors.InvalidRevisionSpec(self.user_spec, branch)
724
raise InvalidRevisionSpec(self.user_spec, branch)
715
725
return RevisionInfo(branch, rev)
719
728
class RevisionSpec_ancestor(RevisionSpec):
720
729
"""Selects a common ancestor with a second branch."""
860
868
if submit_location is None:
861
869
raise errors.NoSubmitBranch(branch)
862
870
trace.note(gettext('Using {0} {1}').format(location_type,
864
872
return submit_location
866
874
def _match_on(self, branch, revs):
867
875
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
868
876
return self._find_revision_info(branch,
869
self._get_submit_location(branch))
877
self._get_submit_location(branch))
871
879
def _as_revision_id(self, context_branch):
872
880
return self._find_revision_id(context_branch,
873
self._get_submit_location(context_branch))
881
self._get_submit_location(context_branch))
876
884
class RevisionSpec_annotate(RevisionIDSpec):
888
896
def _raise_invalid(self, numstring, context_branch):
889
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
897
raise InvalidRevisionSpec(
898
self.user_spec, context_branch,
890
899
'No such line: %s' % numstring)
892
901
def _as_revision_id(self, context_branch):
898
907
tree, file_path = workingtree.WorkingTree.open_containing(path)
899
908
with tree.lock_read():
900
909
if not tree.has_filename(file_path):
901
raise errors.InvalidRevisionSpec(self.user_spec,
902
context_branch, "File '%s' is not versioned." %
910
raise InvalidRevisionSpec(
911
self.user_spec, context_branch,
912
"File '%s' is not versioned." % file_path)
904
913
revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
906
915
revision_id = revision_ids[index]
907
916
except IndexError:
908
917
self._raise_invalid(numstring, context_branch)
909
918
if revision_id == revision.CURRENT_REVISION:
910
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
919
raise InvalidRevisionSpec(
920
self.user_spec, context_branch,
911
921
'Line %s has not been committed.' % numstring)
912
922
return revision_id
932
942
result = graph.find_lefthand_merger(revision_id,
933
943
context_branch.last_revision())
934
944
if result is None:
935
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
945
raise InvalidRevisionSpec(self.user_spec, context_branch)
945
955
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
947
957
revspec_registry = registry.Registry()
948
960
def _register_revspec(revspec):
949
961
revspec_registry.register(revspec.prefix, revspec)
951
964
_register_revspec(RevisionSpec_revno)
952
965
_register_revspec(RevisionSpec_revid)
953
966
_register_revspec(RevisionSpec_last)