59
53
or treat the result as a tuple.
62
def __init__(self, branch, revno=None, rev_id=None):
56
def __init__(self, branch, revno, rev_id=_marker):
63
57
self.branch = branch
64
self._has_revno = (revno is not None)
67
if self.rev_id is None and self._revno is not None:
68
60
# allow caller to be lazy
69
self.rev_id = branch.get_rev_id(self._revno)
73
if not self._has_revno and self.rev_id is not None:
75
self._revno = self.branch.revision_id_to_revno(self.rev_id)
76
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
78
self._has_revno = True
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...
82
70
if self.rev_id is None:
72
if self.revno is not None:
84
74
# TODO: otherwise, it should depend on how I was built -
85
75
# if it's in_history(branch), then check revision_history(),
86
76
# if it's in_store(branch), do the check below
87
77
return self.branch.repository.has_revision(self.rev_id)
89
__nonzero__ = __bool__
94
82
def __getitem__(self, index):
83
if index == 0: return self.revno
84
if index == 1: return self.rev_id
99
85
raise IndexError(index)
104
90
def __eq__(self, other):
105
91
if type(other) not in (tuple, list, type(self)):
107
if isinstance(other, type(self)) and self.branch is not other.branch:
93
if type(other) is type(self) and self.branch is not other.branch:
109
95
return tuple(self) == tuple(other)
111
97
def __repr__(self):
112
return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
98
return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
113
99
self.revno, self.rev_id, self.branch)
116
def from_revision_id(branch, revision_id):
102
def from_revision_id(branch, revision_id, revs):
117
103
"""Construct a RevisionInfo given just the id.
119
105
Use this if you don't know or care what the revno is.
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):
301
312
"""Run the lookup and see what we can get."""
303
314
# First, see if it's a revno
304
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:
306
320
return self._try_spectype(RevisionSpec_revno, branch)
307
321
except RevisionSpec_revno.dwim_catchable_exceptions:
310
324
# Next see what has been registered
311
for objgetter in self._possible_revspecs:
312
rs_class = objgetter.get_obj()
325
for rs_class in dwim_revspecs:
314
327
return self._try_spectype(rs_class, branch)
315
328
except rs_class.dwim_catchable_exceptions:
320
333
# really relevant.
321
334
raise errors.InvalidRevisionSpec(self.spec, branch)
324
def append_possible_revspec(cls, revspec):
325
"""Append a possible DWIM revspec.
327
:param revspec: Revision spec to try.
329
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
332
def append_possible_lazy_revspec(cls, module_name, member_name):
333
"""Append a possible lazily loaded DWIM revspec.
335
:param module_name: Name of the module with the revspec
336
:param member_name: Name of the revspec within the module
338
cls._possible_revspecs.append(
339
registry._LazyObjectGetter(module_name, member_name))
342
337
class RevisionSpec_revno(RevisionSpec):
343
338
"""Selects a revision using a number."""
361
356
your history is very long.
363
358
prefix = 'revno:'
359
wants_revision_history = False
365
361
def _match_on(self, branch, revs):
366
362
"""Lookup a revision by revision number"""
367
branch, revno, revision_id = self._lookup(branch)
363
branch, revno, revision_id = self._lookup(branch, revs)
368
364
return RevisionInfo(branch, revno, revision_id)
370
def _lookup(self, branch):
366
def _lookup(self, branch, revs_or_none):
371
367
loc = self.spec.find(':')
373
369
revno_spec = self.spec
374
370
branch_spec = None
376
372
revno_spec = self.spec[:loc]
377
branch_spec = self.spec[loc + 1:]
373
branch_spec = self.spec[loc+1:]
379
375
if revno_spec == '':
380
376
if not branch_spec:
381
377
raise errors.InvalidRevisionSpec(self.user_spec,
382
branch, 'cannot have an empty revno and no branch')
378
branch, 'cannot have an empty revno and no branch')
390
386
# but the from_string method is a little primitive
391
387
# right now - RBC 20060928
393
match_revno = tuple((int(number)
394
for number in revno_spec.split('.')))
395
except ValueError as e:
389
match_revno = tuple((int(number) for number in revno_spec.split('.')))
390
except ValueError, e:
396
391
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
401
# the user has overriden the branch to look in.
402
branch = _mod_branch.Branch.open(branch_spec)
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)
406
405
revision_id = branch.dotted_revno_to_revision_id(match_revno,
408
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
407
except errors.NoSuchRevision:
409
408
raise errors.InvalidRevisionSpec(self.user_spec, branch)
411
410
# there is no traditional 'revno' for dotted-decimal revnos.
412
# so for API compatibility we return None.
411
# so for API compatability we return None.
413
412
return branch, None, revision_id
415
414
last_revno, last_revision_id = branch.last_revision_info()
422
421
revno = last_revno + revno + 1
424
revision_id = branch.get_rev_id(revno)
425
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
423
revision_id = branch.get_rev_id(revno, revs_or_none)
424
except errors.NoSuchRevision:
426
425
raise errors.InvalidRevisionSpec(self.user_spec, branch)
427
426
return branch, revno, revision_id
429
428
def _as_revision_id(self, context_branch):
430
429
# We would have the revno here, but we don't really care
431
branch, revno, revision_id = self._lookup(context_branch)
430
branch, revno, revision_id = self._lookup(context_branch, None)
432
431
return revision_id
434
433
def needs_branch(self):
468
460
prefix = 'revid:'
470
def _as_revision_id(self, context_branch):
462
def _match_on(self, branch, revs):
471
463
# self.spec comes straight from parsing the command line arguments,
472
464
# so we expect it to be a Unicode string. Switch it to the internal
473
465
# representation.
474
if isinstance(self.spec, text_type):
475
return cache_utf8.encode(self.spec)
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)
479
474
class RevisionSpec_last(RevisionSpec):
515
510
revno = last_revno - offset + 1
517
revision_id = context_branch.get_rev_id(revno)
518
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
512
revision_id = context_branch.get_rev_id(revno, revs_or_none)
513
except errors.NoSuchRevision:
519
514
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
520
515
return revno, revision_id
522
517
def _as_revision_id(self, context_branch):
523
518
# We compute the revno as part of the process, but we don't really care
525
revno, revision_id = self._revno_and_revision_id(context_branch)
520
revno, revision_id = self._revno_and_revision_id(context_branch, None)
526
521
return revision_id
529
525
class RevisionSpec_before(RevisionSpec):
530
526
"""Selects the parent of the revision specified."""
554
550
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
556
552
raise errors.InvalidRevisionSpec(self.user_spec, branch,
557
'cannot go before the null: revision')
553
'cannot go before the null: revision')
558
554
if r.revno is None:
559
555
# We need to use the repository history here
560
556
rev = branch.repository.get_revision(r.rev_id)
561
557
if not rev.parent_ids:
562
559
revision_id = revision.NULL_REVISION
564
561
revision_id = rev.parent_ids[0]
563
revno = revs.index(revision_id) + 1
567
567
revno = r.revno - 1
569
569
revision_id = branch.get_rev_id(revno, revs)
570
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
570
except errors.NoSuchRevision:
571
571
raise errors.InvalidRevisionSpec(self.user_spec,
573
573
return RevisionInfo(branch, revno, revision_id)
575
575
def _as_revision_id(self, context_branch):
576
base_revision_id = RevisionSpec.from_string(
577
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)
578
578
if base_revision_id == revision.NULL_REVISION:
579
579
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
580
'cannot go before the null: revision')
580
'cannot go before the null: revision')
581
581
context_repo = context_branch.repository
582
with context_repo.lock_read():
582
context_repo.lock_read()
583
584
parent_map = context_repo.get_parent_map([base_revision_id])
586
context_repo.unlock()
584
587
if base_revision_id not in parent_map:
585
588
# Ghost, or unknown revision id
586
589
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
587
'cannot find the matching revision')
590
'cannot find the matching revision')
588
591
parents = parent_map[base_revision_id]
589
592
if len(parents) < 1:
590
593
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
591
'No parents for revision.')
594
'No parents for revision.')
592
595
return parents[0]
595
599
class RevisionSpec_tag(RevisionSpec):
596
600
"""Select a revision identified by tag name"""
606
610
def _match_on(self, branch, revs):
607
611
# Can raise tags not supported, NoSuchTag, etc
608
612
return RevisionInfo.from_revision_id(branch,
609
branch.tags.lookup_tag(self.spec))
613
branch.tags.lookup_tag(self.spec),
611
616
def _as_revision_id(self, context_branch):
612
617
return context_branch.tags.lookup_tag(self.spec)
615
621
class _RevListToTimestamps(object):
616
622
"""This takes a list of revisions, and allows you to bisect by date"""
618
__slots__ = ['branch']
624
__slots__ = ['revs', 'branch']
620
def __init__(self, branch):
626
def __init__(self, revs, branch):
621
628
self.branch = branch
623
630
def __getitem__(self, index):
624
631
"""Get the date of the index'd item"""
625
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
632
r = self.branch.repository.get_revision(self.revs[index])
626
633
# TODO: Handle timezone.
627
634
return datetime.datetime.fromtimestamp(r.timestamp)
629
636
def __len__(self):
630
return self.branch.revno()
637
return len(self.revs)
633
640
class RevisionSpec_date(RevisionSpec):
702
hour, minute, second = 0, 0, 0
708
hour, minute, second = 0,0,0
703
709
except ValueError:
704
710
raise errors.InvalidRevisionSpec(self.user_spec,
705
711
branch, 'invalid date')
707
713
dt = datetime.datetime(year=year, month=month, day=day,
708
hour=hour, minute=minute, second=second)
709
with branch.lock_read():
710
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
711
if rev == branch.revno():
714
hour=hour, minute=minute, second=second)
717
rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
712
721
raise errors.InvalidRevisionSpec(self.user_spec, branch)
713
return RevisionInfo(branch, rev)
723
return RevisionInfo(branch, rev + 1)
716
727
class RevisionSpec_ancestor(RevisionSpec):
746
757
def _find_revision_info(branch, other_location):
747
758
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
749
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)
752
767
def _find_revision_id(branch, other_location):
753
from .branch import Branch
768
from bzrlib.branch import Branch
755
with branch.lock_read():
756
772
revision_a = revision.ensure_null(branch.last_revision())
757
773
if revision_a == revision.NULL_REVISION:
758
774
raise errors.NoCommits(branch)
759
775
if other_location == '':
760
776
other_location = branch.get_parent()
761
777
other_branch = Branch.open(other_location)
762
with other_branch.lock_read():
778
other_branch.lock_read()
763
780
revision_b = revision.ensure_null(other_branch.last_revision())
764
781
if revision_b == revision.NULL_REVISION:
765
782
raise errors.NoCommits(other_branch)
766
783
graph = branch.repository.get_graph(other_branch.repository)
767
784
rev_id = graph.find_unique_lca(revision_a, revision_b)
786
other_branch.unlock()
768
787
if rev_id == revision.NULL_REVISION:
769
788
raise errors.NoCommonAncestor(revision_a, revision_b)
773
796
class RevisionSpec_branch(RevisionSpec):
798
821
branch.fetch(other_branch, revision_b)
799
822
except errors.ReadOnlyError:
800
823
branch = other_branch
801
return RevisionInfo(branch, None, revision_b)
825
revno = branch.revision_id_to_revno(revision_b)
826
except errors.NoSuchRevision:
828
return RevisionInfo(branch, revno, revision_b)
803
830
def _as_revision_id(self, context_branch):
804
from .branch import Branch
831
from bzrlib.branch import Branch
805
832
other_branch = Branch.open(self.spec)
806
833
last_revision = other_branch.last_revision()
807
834
last_revision = revision.ensure_null(last_revision)
855
883
location_type = 'parent branch'
856
884
if submit_location is None:
857
885
raise errors.NoSubmitBranch(branch)
858
trace.note(gettext('Using {0} {1}').format(location_type,
886
trace.note('Using %s %s', location_type, submit_location)
860
887
return submit_location
862
889
def _match_on(self, branch, revs):
863
890
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
864
891
return self._find_revision_info(branch,
865
self._get_submit_location(branch))
892
self._get_submit_location(branch))
867
894
def _as_revision_id(self, context_branch):
868
895
return self._find_revision_id(context_branch,
869
self._get_submit_location(context_branch))
872
class RevisionSpec_annotate(RevisionIDSpec):
876
help_txt = """Select the revision that last modified the specified line.
878
Select the revision that last modified the specified line. Line is
879
specified as path:number. Path is a relative path to the file. Numbers
880
start at 1, and are relative to the current version, not the last-
881
committed version of the file.
884
def _raise_invalid(self, numstring, context_branch):
885
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
886
'No such line: %s' % numstring)
888
def _as_revision_id(self, context_branch):
889
path, numstring = self.spec.rsplit(':', 1)
891
index = int(numstring) - 1
893
self._raise_invalid(numstring, context_branch)
894
tree, file_path = workingtree.WorkingTree.open_containing(path)
895
with tree.lock_read():
896
if not tree.has_filename(file_path):
897
raise errors.InvalidRevisionSpec(self.user_spec,
898
context_branch, "File '%s' is not versioned." %
900
revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
902
revision_id = revision_ids[index]
904
self._raise_invalid(numstring, context_branch)
905
if revision_id == revision.CURRENT_REVISION:
906
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
907
'Line %s has not been committed.' % numstring)
911
class RevisionSpec_mainline(RevisionIDSpec):
913
help_txt = """Select mainline revision that merged the specified revision.
915
Select the revision that merged the specified revision into mainline.
920
def _as_revision_id(self, context_branch):
921
revspec = RevisionSpec.from_string(self.spec)
922
if revspec.get_branch() is None:
923
spec_branch = context_branch
925
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
926
revision_id = revspec.as_revision_id(spec_branch)
927
graph = context_branch.repository.get_graph()
928
result = graph.find_lefthand_merger(revision_id,
929
context_branch.last_revision())
931
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
896
self._get_submit_location(context_branch))
935
899
# The order in which we want to DWIM a revision spec without any prefix.
936
900
# revno is always tried first and isn't listed here, this is used by
937
901
# RevisionSpec_dwim._match_on
938
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
939
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
940
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
941
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
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
943
910
revspec_registry = registry.Registry()
946
911
def _register_revspec(revspec):
947
912
revspec_registry.register(revspec.prefix, revspec)
950
914
_register_revspec(RevisionSpec_revno)
951
915
_register_revspec(RevisionSpec_revid)
952
916
_register_revspec(RevisionSpec_last)