1
# Copyright (C) 2005-2010 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
20
from .lazy_import import lazy_import
21
lazy_import(globals(), """
26
branch as _mod_branch,
31
from breezy.i18n import gettext
45
class RevisionInfo(object):
46
"""The results of applying a revision specification to a branch."""
48
help_txt = """The results of applying a revision specification to a branch.
50
An instance has two useful attributes: revno, and rev_id.
52
They can also be accessed as spec[0] and spec[1] respectively,
53
so that you can write code like:
54
revno, rev_id = RevisionSpec(branch, spec)
55
although this is probably going to be deprecated later.
57
This class exists mostly to be the return value of a RevisionSpec,
58
so that you can access the member you're interested in (number or id)
59
or treat the result as a tuple.
62
def __init__(self, branch, revno=None, rev_id=None):
64
self._has_revno = (revno is not None)
67
if self.rev_id is None and self._revno is not None:
68
# 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
82
if self.rev_id is None:
84
# TODO: otherwise, it should depend on how I was built -
85
# if it's in_history(branch), then check revision_history(),
86
# if it's in_store(branch), do the check below
87
return self.branch.repository.has_revision(self.rev_id)
89
__nonzero__ = __bool__
94
def __getitem__(self, index):
99
raise IndexError(index)
102
return self.branch.repository.get_revision(self.rev_id)
104
def __eq__(self, other):
105
if type(other) not in (tuple, list, type(self)):
107
if isinstance(other, type(self)) and self.branch is not other.branch:
109
return tuple(self) == tuple(other)
112
return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
113
self.revno, self.rev_id, self.branch)
116
def from_revision_id(branch, revision_id):
117
"""Construct a RevisionInfo given just the id.
119
Use this if you don't know or care what the revno is.
121
return RevisionInfo(branch, revno=None, rev_id=revision_id)
124
class RevisionSpec(object):
125
"""A parsed revision specification."""
127
help_txt = """A parsed revision specification.
129
A revision specification is a string, which may be unambiguous about
130
what it represents by giving a prefix like 'date:' or 'revid:' etc,
131
or it may have no prefix, in which case it's tried against several
132
specifier types in sequence to determine what the user meant.
134
Revision specs are an UI element, and they have been moved out
135
of the branch class to leave "back-end" classes unaware of such
136
details. Code that gets a revno or rev_id from other code should
137
not be using revision specs - revnos and revision ids are the
138
accepted ways to refer to revisions internally.
140
(Equivalent to the old Branch method get_revision_info())
144
dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
145
"""Exceptions that RevisionSpec_dwim._match_on will catch.
147
If the revspec is part of ``dwim_revspecs``, it may be tried with an
148
invalid revspec and raises some exception. The exceptions mentioned here
149
will not be reported to the user but simply ignored without stopping the
154
def from_string(spec):
155
"""Parse a revision spec string into a RevisionSpec object.
157
:param spec: A string specified by the user
158
:return: A RevisionSpec object that understands how to parse the
162
return RevisionSpec(None, _internal=True)
163
if not isinstance(spec, (str, text_type)):
164
raise TypeError("revision spec needs to be text")
165
match = revspec_registry.get_prefix(spec)
166
if match is not None:
167
spectype, specsuffix = match
168
trace.mutter('Returning RevisionSpec %s for %s',
169
spectype.__name__, spec)
170
return spectype(spec, _internal=True)
172
# Otherwise treat it as a DWIM, build the RevisionSpec object and
173
# wait for _match_on to be called.
174
return RevisionSpec_dwim(spec, _internal=True)
176
def __init__(self, spec, _internal=False):
177
"""Create a RevisionSpec referring to the Null revision.
179
:param spec: The original spec supplied by the user
180
:param _internal: Used to ensure that RevisionSpec is not being
181
called directly. Only from RevisionSpec.from_string()
184
raise AssertionError(
185
'Creating a RevisionSpec directly is not supported. '
186
'Use RevisionSpec.from_string() instead.')
187
self.user_spec = spec
188
if self.prefix and spec.startswith(self.prefix):
189
spec = spec[len(self.prefix):]
192
def _match_on(self, branch, revs):
193
trace.mutter('Returning RevisionSpec._match_on: None')
194
return RevisionInfo(branch, None, None)
196
def _match_on_and_check(self, branch, revs):
197
info = self._match_on(branch, revs)
200
elif info == (None, None):
201
# special case - nothing supplied
204
raise errors.InvalidRevisionSpec(self.user_spec, branch)
206
raise errors.InvalidRevisionSpec(self.spec, branch)
208
def in_history(self, branch):
209
return self._match_on_and_check(branch, revs=None)
211
# FIXME: in_history is somewhat broken,
212
# it will return non-history revisions in many
213
# circumstances. The expected facility is that
214
# in_history only returns revision-history revs,
215
# in_store returns any rev. RBC 20051010
216
# aliases for now, when we fix the core logic, then they
217
# will do what you expect.
218
in_store = in_history
221
def as_revision_id(self, context_branch):
222
"""Return just the revision_id for this revisions spec.
224
Some revision specs require a context_branch to be able to determine
225
their value. Not all specs will make use of it.
227
return self._as_revision_id(context_branch)
229
def _as_revision_id(self, context_branch):
230
"""Implementation of as_revision_id()
232
Classes should override this function to provide appropriate
233
functionality. The default is to just call '.in_history().rev_id'
235
return self.in_history(context_branch).rev_id
237
def as_tree(self, context_branch):
238
"""Return the tree object for this revisions spec.
240
Some revision specs require a context_branch to be able to determine
241
the revision id and access the repository. Not all specs will make
244
return self._as_tree(context_branch)
246
def _as_tree(self, context_branch):
247
"""Implementation of as_tree().
249
Classes should override this function to provide appropriate
250
functionality. The default is to just call '.as_revision_id()'
251
and get the revision tree from context_branch's repository.
253
revision_id = self.as_revision_id(context_branch)
254
return context_branch.repository.revision_tree(revision_id)
257
# this is mostly for helping with testing
258
return '<%s %s>' % (self.__class__.__name__,
261
def needs_branch(self):
262
"""Whether this revision spec needs a branch.
264
Set this to False the branch argument of _match_on is not used.
268
def get_branch(self):
269
"""When the revision specifier contains a branch location, return it.
271
Otherwise, return None.
278
class RevisionSpec_dwim(RevisionSpec):
279
"""Provides a DWIMish revision specifier lookup.
281
Note that this does not go in the revspec_registry because by definition
282
there is no prefix to identify it. It's solely called from
283
RevisionSpec.from_string() because the DWIMification happen when _match_on
284
is called so the string describing the revision is kept here until needed.
289
_revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
291
# The revspecs to try
292
_possible_revspecs = []
294
def _try_spectype(self, rstype, branch):
295
rs = rstype(self.spec, _internal=True)
296
# Hit in_history to find out if it exists, or we need to try the
298
return rs.in_history(branch)
300
def _match_on(self, branch, revs):
301
"""Run the lookup and see what we can get."""
303
# First, see if it's a revno
304
if self._revno_regex.match(self.spec) is not None:
306
return self._try_spectype(RevisionSpec_revno, branch)
307
except RevisionSpec_revno.dwim_catchable_exceptions:
310
# Next see what has been registered
311
for objgetter in self._possible_revspecs:
312
rs_class = objgetter.get_obj()
314
return self._try_spectype(rs_class, branch)
315
except rs_class.dwim_catchable_exceptions:
318
# Well, I dunno what it is. Note that we don't try to keep track of the
319
# first of last exception raised during the DWIM tries as none seems
321
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
class RevisionSpec_revno(RevisionSpec):
343
"""Selects a revision using a number."""
345
help_txt = """Selects a revision using a number.
347
Use an integer to specify a revision in the history of the branch.
348
Optionally a branch can be specified. A negative number will count
349
from the end of the branch (-1 is the last revision, -2 the previous
350
one). If the negative number is larger than the branch's history, the
351
first revision is returned.
354
revno:1 -> return the first revision of this branch
355
revno:3:/path/to/branch -> return the 3rd revision of
356
the branch '/path/to/branch'
357
revno:-1 -> The last revision in a branch.
358
-2:http://other/branch -> The second to last revision in the
360
-1000000 -> Most likely the first revision, unless
361
your history is very long.
365
def _match_on(self, branch, revs):
366
"""Lookup a revision by revision number"""
367
branch, revno, revision_id = self._lookup(branch)
368
return RevisionInfo(branch, revno, revision_id)
370
def _lookup(self, branch):
371
loc = self.spec.find(':')
373
revno_spec = self.spec
376
revno_spec = self.spec[:loc]
377
branch_spec = self.spec[loc + 1:]
381
raise errors.InvalidRevisionSpec(self.user_spec,
382
branch, 'cannot have an empty revno and no branch')
386
revno = int(revno_spec)
389
# dotted decimal. This arguably should not be here
390
# but the from_string method is a little primitive
391
# right now - RBC 20060928
393
match_revno = tuple((int(number)
394
for number in revno_spec.split('.')))
395
except ValueError as e:
396
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)
406
revision_id = branch.dotted_revno_to_revision_id(match_revno,
408
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
409
raise errors.InvalidRevisionSpec(self.user_spec, branch)
411
# there is no traditional 'revno' for dotted-decimal revnos.
412
# so for API compatibility we return None.
413
return branch, None, revision_id
415
last_revno, last_revision_id = branch.last_revision_info()
417
# if get_rev_id supported negative revnos, there would not be a
418
# need for this special case.
419
if (-revno) >= last_revno:
422
revno = last_revno + revno + 1
424
revision_id = branch.get_rev_id(revno)
425
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
426
raise errors.InvalidRevisionSpec(self.user_spec, branch)
427
return branch, revno, revision_id
429
def _as_revision_id(self, context_branch):
430
# We would have the revno here, but we don't really care
431
branch, revno, revision_id = self._lookup(context_branch)
434
def needs_branch(self):
435
return self.spec.find(':') == -1
437
def get_branch(self):
438
if self.spec.find(':') == -1:
441
return self.spec[self.spec.find(':') + 1:]
445
RevisionSpec_int = RevisionSpec_revno
448
class RevisionIDSpec(RevisionSpec):
450
def _match_on(self, branch, revs):
451
revision_id = self.as_revision_id(branch)
452
return RevisionInfo.from_revision_id(branch, revision_id)
455
class RevisionSpec_revid(RevisionIDSpec):
456
"""Selects a revision using the revision id."""
458
help_txt = """Selects a revision using the revision id.
460
Supply a specific revision id, that can be used to specify any
461
revision id in the ancestry of the branch.
462
Including merges, and pending merges.
465
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
470
def _as_revision_id(self, context_branch):
471
# self.spec comes straight from parsing the command line arguments,
472
# so we expect it to be a Unicode string. Switch it to the internal
474
if isinstance(self.spec, text_type):
475
return cache_utf8.encode(self.spec)
479
class RevisionSpec_last(RevisionSpec):
480
"""Selects the nth revision from the end."""
482
help_txt = """Selects the nth revision from the end.
484
Supply a positive number to get the nth revision from the end.
485
This is the same as supplying negative numbers to the 'revno:' spec.
488
last:1 -> return the last revision
489
last:3 -> return the revision 2 before the end.
494
def _match_on(self, branch, revs):
495
revno, revision_id = self._revno_and_revision_id(branch)
496
return RevisionInfo(branch, revno, revision_id)
498
def _revno_and_revision_id(self, context_branch):
499
last_revno, last_revision_id = context_branch.last_revision_info()
503
raise errors.NoCommits(context_branch)
504
return last_revno, last_revision_id
507
offset = int(self.spec)
508
except ValueError as e:
509
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
512
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
513
'you must supply a positive value')
515
revno = last_revno - offset + 1
517
revision_id = context_branch.get_rev_id(revno)
518
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
519
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
520
return revno, revision_id
522
def _as_revision_id(self, context_branch):
523
# 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)
529
class RevisionSpec_before(RevisionSpec):
530
"""Selects the parent of the revision specified."""
532
help_txt = """Selects the parent of the revision specified.
534
Supply any revision spec to return the parent of that revision. This is
535
mostly useful when inspecting revisions that are not in the revision history
538
It is an error to request the parent of the null revision (before:0).
542
before:1913 -> Return the parent of revno 1913 (revno 1912)
543
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
545
bzr diff -r before:1913..1913
546
-> Find the changes between revision 1913 and its parent (1912).
547
(What changes did revision 1913 introduce).
548
This is equivalent to: bzr diff -c 1913
553
def _match_on(self, branch, revs):
554
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
556
raise errors.InvalidRevisionSpec(self.user_spec, branch,
557
'cannot go before the null: revision')
559
# We need to use the repository history here
560
rev = branch.repository.get_revision(r.rev_id)
561
if not rev.parent_ids:
562
revision_id = revision.NULL_REVISION
564
revision_id = rev.parent_ids[0]
569
revision_id = branch.get_rev_id(revno, revs)
570
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
571
raise errors.InvalidRevisionSpec(self.user_spec,
573
return RevisionInfo(branch, revno, revision_id)
575
def _as_revision_id(self, context_branch):
576
base_revision_id = RevisionSpec.from_string(
577
self.spec)._as_revision_id(context_branch)
578
if base_revision_id == revision.NULL_REVISION:
579
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
580
'cannot go before the null: revision')
581
context_repo = context_branch.repository
582
with context_repo.lock_read():
583
parent_map = context_repo.get_parent_map([base_revision_id])
584
if base_revision_id not in parent_map:
585
# Ghost, or unknown revision id
586
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
587
'cannot find the matching revision')
588
parents = parent_map[base_revision_id]
590
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
591
'No parents for revision.')
595
class RevisionSpec_tag(RevisionSpec):
596
"""Select a revision identified by tag name"""
598
help_txt = """Selects a revision identified by a tag name.
600
Tags are stored in the branch and created by the 'tag' command.
604
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
606
def _match_on(self, branch, revs):
607
# Can raise tags not supported, NoSuchTag, etc
608
return RevisionInfo.from_revision_id(branch,
609
branch.tags.lookup_tag(self.spec))
611
def _as_revision_id(self, context_branch):
612
return context_branch.tags.lookup_tag(self.spec)
615
class _RevListToTimestamps(object):
616
"""This takes a list of revisions, and allows you to bisect by date"""
618
__slots__ = ['branch']
620
def __init__(self, branch):
623
def __getitem__(self, index):
624
"""Get the date of the index'd item"""
625
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
626
# TODO: Handle timezone.
627
return datetime.datetime.fromtimestamp(r.timestamp)
630
return self.branch.revno()
633
class RevisionSpec_date(RevisionSpec):
634
"""Selects a revision on the basis of a datestamp."""
636
help_txt = """Selects a revision on the basis of a datestamp.
638
Supply a datestamp to select the first revision that matches the date.
639
Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
640
Matches the first entry after a given date (either at midnight or
641
at a specified time).
643
One way to display all the changes since yesterday would be::
645
brz log -r date:yesterday..
649
date:yesterday -> select the first revision since yesterday
650
date:2006-08-14,17:10:14 -> select the first revision after
651
August 14th, 2006 at 5:10pm.
654
_date_regex = lazy_regex.lazy_compile(
655
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
657
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
660
def _match_on(self, branch, revs):
661
"""Spec for date revisions:
663
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
664
matches the first entry after a given date (either at midnight or
665
at a specified time).
667
# XXX: This doesn't actually work
668
# So the proper way of saying 'give me all entries for today' is:
669
# -r date:yesterday..date:today
670
today = datetime.datetime.fromordinal(
671
datetime.date.today().toordinal())
672
if self.spec.lower() == 'yesterday':
673
dt = today - datetime.timedelta(days=1)
674
elif self.spec.lower() == 'today':
676
elif self.spec.lower() == 'tomorrow':
677
dt = today + datetime.timedelta(days=1)
679
m = self._date_regex.match(self.spec)
680
if not m or (not m.group('date') and not m.group('time')):
681
raise errors.InvalidRevisionSpec(self.user_spec,
682
branch, 'invalid date')
686
year = int(m.group('year'))
687
month = int(m.group('month'))
688
day = int(m.group('day'))
695
hour = int(m.group('hour'))
696
minute = int(m.group('minute'))
697
if m.group('second'):
698
second = int(m.group('second'))
702
hour, minute, second = 0, 0, 0
704
raise errors.InvalidRevisionSpec(self.user_spec,
705
branch, 'invalid date')
707
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():
712
raise errors.InvalidRevisionSpec(self.user_spec, branch)
713
return RevisionInfo(branch, rev)
716
class RevisionSpec_ancestor(RevisionSpec):
717
"""Selects a common ancestor with a second branch."""
719
help_txt = """Selects a common ancestor with a second branch.
721
Supply the path to a branch to select the common ancestor.
723
The common ancestor is the last revision that existed in both
724
branches. Usually this is the branch point, but it could also be
725
a revision that was merged.
727
This is frequently used with 'diff' to return all of the changes
728
that your branch introduces, while excluding the changes that you
729
have not merged from the remote branch.
733
ancestor:/path/to/branch
734
$ bzr diff -r ancestor:../../mainline/branch
738
def _match_on(self, branch, revs):
739
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
740
return self._find_revision_info(branch, self.spec)
742
def _as_revision_id(self, context_branch):
743
return self._find_revision_id(context_branch, self.spec)
746
def _find_revision_info(branch, other_location):
747
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
749
return RevisionInfo(branch, None, revision_id)
752
def _find_revision_id(branch, other_location):
753
from .branch import Branch
755
with branch.lock_read():
756
revision_a = revision.ensure_null(branch.last_revision())
757
if revision_a == revision.NULL_REVISION:
758
raise errors.NoCommits(branch)
759
if other_location == '':
760
other_location = branch.get_parent()
761
other_branch = Branch.open(other_location)
762
with other_branch.lock_read():
763
revision_b = revision.ensure_null(other_branch.last_revision())
764
if revision_b == revision.NULL_REVISION:
765
raise errors.NoCommits(other_branch)
766
graph = branch.repository.get_graph(other_branch.repository)
767
rev_id = graph.find_unique_lca(revision_a, revision_b)
768
if rev_id == revision.NULL_REVISION:
769
raise errors.NoCommonAncestor(revision_a, revision_b)
773
class RevisionSpec_branch(RevisionSpec):
774
"""Selects the last revision of a specified branch."""
776
help_txt = """Selects the last revision of a specified branch.
778
Supply the path to a branch to select its last revision.
782
branch:/path/to/branch
785
dwim_catchable_exceptions = (errors.NotBranchError,)
787
def _match_on(self, branch, revs):
788
from .branch import Branch
789
other_branch = Branch.open(self.spec)
790
revision_b = other_branch.last_revision()
791
if revision_b in (None, revision.NULL_REVISION):
792
raise errors.NoCommits(other_branch)
794
branch = other_branch
797
# pull in the remote revisions so we can diff
798
branch.fetch(other_branch, revision_b)
799
except errors.ReadOnlyError:
800
branch = other_branch
801
return RevisionInfo(branch, None, revision_b)
803
def _as_revision_id(self, context_branch):
804
from .branch import Branch
805
other_branch = Branch.open(self.spec)
806
last_revision = other_branch.last_revision()
807
last_revision = revision.ensure_null(last_revision)
808
context_branch.fetch(other_branch, last_revision)
809
if last_revision == revision.NULL_REVISION:
810
raise errors.NoCommits(other_branch)
813
def _as_tree(self, context_branch):
814
from .branch import Branch
815
other_branch = Branch.open(self.spec)
816
last_revision = other_branch.last_revision()
817
last_revision = revision.ensure_null(last_revision)
818
if last_revision == revision.NULL_REVISION:
819
raise errors.NoCommits(other_branch)
820
return other_branch.repository.revision_tree(last_revision)
822
def needs_branch(self):
825
def get_branch(self):
829
class RevisionSpec_submit(RevisionSpec_ancestor):
830
"""Selects a common ancestor with a submit branch."""
832
help_txt = """Selects a common ancestor with the submit branch.
834
Diffing against this shows all the changes that were made in this branch,
835
and is a good predictor of what merge will do. The submit branch is
836
used by the bundle and merge directive commands. If no submit branch
837
is specified, the parent branch is used instead.
839
The common ancestor is the last revision that existed in both
840
branches. Usually this is the branch point, but it could also be
841
a revision that was merged.
845
$ bzr diff -r submit:
850
def _get_submit_location(self, branch):
851
submit_location = branch.get_submit_branch()
852
location_type = 'submit branch'
853
if submit_location is None:
854
submit_location = branch.get_parent()
855
location_type = 'parent branch'
856
if submit_location is None:
857
raise errors.NoSubmitBranch(branch)
858
trace.note(gettext('Using {0} {1}').format(location_type,
860
return submit_location
862
def _match_on(self, branch, revs):
863
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
864
return self._find_revision_info(branch,
865
self._get_submit_location(branch))
867
def _as_revision_id(self, context_branch):
868
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)
935
# The order in which we want to DWIM a revision spec without any prefix.
936
# revno is always tried first and isn't listed here, this is used by
937
# 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)
943
revspec_registry = registry.Registry()
946
def _register_revspec(revspec):
947
revspec_registry.register(revspec.prefix, revspec)
950
_register_revspec(RevisionSpec_revno)
951
_register_revspec(RevisionSpec_revid)
952
_register_revspec(RevisionSpec_last)
953
_register_revspec(RevisionSpec_before)
954
_register_revspec(RevisionSpec_tag)
955
_register_revspec(RevisionSpec_date)
956
_register_revspec(RevisionSpec_ancestor)
957
_register_revspec(RevisionSpec_branch)
958
_register_revspec(RevisionSpec_submit)
959
_register_revspec(RevisionSpec_annotate)
960
_register_revspec(RevisionSpec_mainline)