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
18
from .lazy_import import lazy_import
19
lazy_import(globals(), """
24
branch as _mod_branch,
29
from breezy.i18n import gettext
40
class RevisionInfo(object):
41
"""The results of applying a revision specification to a branch."""
43
help_txt = """The results of applying a revision specification to a branch.
45
An instance has two useful attributes: revno, and rev_id.
47
They can also be accessed as spec[0] and spec[1] respectively,
48
so that you can write code like:
49
revno, rev_id = RevisionSpec(branch, spec)
50
although this is probably going to be deprecated later.
52
This class exists mostly to be the return value of a RevisionSpec,
53
so that you can access the member you're interested in (number or id)
54
or treat the result as a tuple.
57
def __init__(self, branch, revno=None, rev_id=None):
59
self._has_revno = (revno is not None)
62
if self.rev_id is None and self._revno is not None:
63
# allow caller to be lazy
64
self.rev_id = branch.get_rev_id(self._revno)
68
if not self._has_revno and self.rev_id is not None:
70
self._revno = self.branch.revision_id_to_revno(self.rev_id)
71
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
73
self._has_revno = True
77
if self.rev_id is None:
79
# TODO: otherwise, it should depend on how I was built -
80
# if it's in_history(branch), then check revision_history(),
81
# if it's in_store(branch), do the check below
82
return self.branch.repository.has_revision(self.rev_id)
84
__nonzero__ = __bool__
89
def __getitem__(self, index):
94
raise IndexError(index)
97
return self.branch.repository.get_revision(self.rev_id)
99
def __eq__(self, other):
100
if type(other) not in (tuple, list, type(self)):
102
if isinstance(other, type(self)) and self.branch is not other.branch:
104
return tuple(self) == tuple(other)
107
return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
108
self.revno, self.rev_id, self.branch)
111
def from_revision_id(branch, revision_id):
112
"""Construct a RevisionInfo given just the id.
114
Use this if you don't know or care what the revno is.
116
return RevisionInfo(branch, revno=None, rev_id=revision_id)
119
class RevisionSpec(object):
120
"""A parsed revision specification."""
122
help_txt = """A parsed revision specification.
124
A revision specification is a string, which may be unambiguous about
125
what it represents by giving a prefix like 'date:' or 'revid:' etc,
126
or it may have no prefix, in which case it's tried against several
127
specifier types in sequence to determine what the user meant.
129
Revision specs are an UI element, and they have been moved out
130
of the branch class to leave "back-end" classes unaware of such
131
details. Code that gets a revno or rev_id from other code should
132
not be using revision specs - revnos and revision ids are the
133
accepted ways to refer to revisions internally.
135
(Equivalent to the old Branch method get_revision_info())
139
dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
140
"""Exceptions that RevisionSpec_dwim._match_on will catch.
142
If the revspec is part of ``dwim_revspecs``, it may be tried with an
143
invalid revspec and raises some exception. The exceptions mentioned here
144
will not be reported to the user but simply ignored without stopping the
149
def from_string(spec):
150
"""Parse a revision spec string into a RevisionSpec object.
152
:param spec: A string specified by the user
153
:return: A RevisionSpec object that understands how to parse the
157
return RevisionSpec(None, _internal=True)
158
if not isinstance(spec, str):
159
raise TypeError("revision spec needs to be text")
160
match = revspec_registry.get_prefix(spec)
161
if match is not None:
162
spectype, specsuffix = match
163
trace.mutter('Returning RevisionSpec %s for %s',
164
spectype.__name__, spec)
165
return spectype(spec, _internal=True)
167
# Otherwise treat it as a DWIM, build the RevisionSpec object and
168
# wait for _match_on to be called.
169
return RevisionSpec_dwim(spec, _internal=True)
171
def __init__(self, spec, _internal=False):
172
"""Create a RevisionSpec referring to the Null revision.
174
:param spec: The original spec supplied by the user
175
:param _internal: Used to ensure that RevisionSpec is not being
176
called directly. Only from RevisionSpec.from_string()
179
raise AssertionError(
180
'Creating a RevisionSpec directly is not supported. '
181
'Use RevisionSpec.from_string() instead.')
182
self.user_spec = spec
183
if self.prefix and spec.startswith(self.prefix):
184
spec = spec[len(self.prefix):]
187
def _match_on(self, branch, revs):
188
trace.mutter('Returning RevisionSpec._match_on: None')
189
return RevisionInfo(branch, None, None)
191
def _match_on_and_check(self, branch, revs):
192
info = self._match_on(branch, revs)
195
elif info == (None, None):
196
# special case - nothing supplied
199
raise errors.InvalidRevisionSpec(self.user_spec, branch)
201
raise errors.InvalidRevisionSpec(self.spec, branch)
203
def in_history(self, branch):
204
return self._match_on_and_check(branch, revs=None)
206
# FIXME: in_history is somewhat broken,
207
# it will return non-history revisions in many
208
# circumstances. The expected facility is that
209
# in_history only returns revision-history revs,
210
# in_store returns any rev. RBC 20051010
211
# aliases for now, when we fix the core logic, then they
212
# will do what you expect.
213
in_store = in_history
216
def as_revision_id(self, context_branch):
217
"""Return just the revision_id for this revisions spec.
219
Some revision specs require a context_branch to be able to determine
220
their value. Not all specs will make use of it.
222
return self._as_revision_id(context_branch)
224
def _as_revision_id(self, context_branch):
225
"""Implementation of as_revision_id()
227
Classes should override this function to provide appropriate
228
functionality. The default is to just call '.in_history().rev_id'
230
return self.in_history(context_branch).rev_id
232
def as_tree(self, context_branch):
233
"""Return the tree object for this revisions spec.
235
Some revision specs require a context_branch to be able to determine
236
the revision id and access the repository. Not all specs will make
239
return self._as_tree(context_branch)
241
def _as_tree(self, context_branch):
242
"""Implementation of as_tree().
244
Classes should override this function to provide appropriate
245
functionality. The default is to just call '.as_revision_id()'
246
and get the revision tree from context_branch's repository.
248
revision_id = self.as_revision_id(context_branch)
249
return context_branch.repository.revision_tree(revision_id)
252
# this is mostly for helping with testing
253
return '<%s %s>' % (self.__class__.__name__,
256
def needs_branch(self):
257
"""Whether this revision spec needs a branch.
259
Set this to False the branch argument of _match_on is not used.
263
def get_branch(self):
264
"""When the revision specifier contains a branch location, return it.
266
Otherwise, return None.
273
class RevisionSpec_dwim(RevisionSpec):
274
"""Provides a DWIMish revision specifier lookup.
276
Note that this does not go in the revspec_registry because by definition
277
there is no prefix to identify it. It's solely called from
278
RevisionSpec.from_string() because the DWIMification happen when _match_on
279
is called so the string describing the revision is kept here until needed.
284
_revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
286
# The revspecs to try
287
_possible_revspecs = []
289
def _try_spectype(self, rstype, branch):
290
rs = rstype(self.spec, _internal=True)
291
# Hit in_history to find out if it exists, or we need to try the
293
return rs.in_history(branch)
295
def _match_on(self, branch, revs):
296
"""Run the lookup and see what we can get."""
298
# First, see if it's a revno
299
if self._revno_regex.match(self.spec) is not None:
301
return self._try_spectype(RevisionSpec_revno, branch)
302
except RevisionSpec_revno.dwim_catchable_exceptions:
305
# Next see what has been registered
306
for objgetter in self._possible_revspecs:
307
rs_class = objgetter.get_obj()
309
return self._try_spectype(rs_class, branch)
310
except rs_class.dwim_catchable_exceptions:
313
# Well, I dunno what it is. Note that we don't try to keep track of the
314
# first of last exception raised during the DWIM tries as none seems
316
raise errors.InvalidRevisionSpec(self.spec, branch)
319
def append_possible_revspec(cls, revspec):
320
"""Append a possible DWIM revspec.
322
:param revspec: Revision spec to try.
324
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
327
def append_possible_lazy_revspec(cls, module_name, member_name):
328
"""Append a possible lazily loaded DWIM revspec.
330
:param module_name: Name of the module with the revspec
331
:param member_name: Name of the revspec within the module
333
cls._possible_revspecs.append(
334
registry._LazyObjectGetter(module_name, member_name))
337
class RevisionSpec_revno(RevisionSpec):
338
"""Selects a revision using a number."""
340
help_txt = """Selects a revision using a number.
342
Use an integer to specify a revision in the history of the branch.
343
Optionally a branch can be specified. A negative number will count
344
from the end of the branch (-1 is the last revision, -2 the previous
345
one). If the negative number is larger than the branch's history, the
346
first revision is returned.
349
revno:1 -> return the first revision of this branch
350
revno:3:/path/to/branch -> return the 3rd revision of
351
the branch '/path/to/branch'
352
revno:-1 -> The last revision in a branch.
353
-2:http://other/branch -> The second to last revision in the
355
-1000000 -> Most likely the first revision, unless
356
your history is very long.
360
def _match_on(self, branch, revs):
361
"""Lookup a revision by revision number"""
362
branch, revno, revision_id = self._lookup(branch)
363
return RevisionInfo(branch, revno, revision_id)
365
def _lookup(self, branch):
366
loc = self.spec.find(':')
368
revno_spec = self.spec
371
revno_spec = self.spec[:loc]
372
branch_spec = self.spec[loc + 1:]
376
raise errors.InvalidRevisionSpec(self.user_spec,
377
branch, 'cannot have an empty revno and no branch')
381
revno = int(revno_spec)
384
# dotted decimal. This arguably should not be here
385
# but the from_string method is a little primitive
386
# right now - RBC 20060928
388
match_revno = tuple((int(number)
389
for number in revno_spec.split('.')))
390
except ValueError as e:
391
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
396
# the user has overriden the branch to look in.
397
branch = _mod_branch.Branch.open(branch_spec)
401
revision_id = branch.dotted_revno_to_revision_id(match_revno,
403
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
404
raise errors.InvalidRevisionSpec(self.user_spec, branch)
406
# there is no traditional 'revno' for dotted-decimal revnos.
407
# so for API compatibility we return None.
408
return branch, None, revision_id
410
last_revno, last_revision_id = branch.last_revision_info()
412
# if get_rev_id supported negative revnos, there would not be a
413
# need for this special case.
414
if (-revno) >= last_revno:
417
revno = last_revno + revno + 1
419
revision_id = branch.get_rev_id(revno)
420
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
421
raise errors.InvalidRevisionSpec(self.user_spec, branch)
422
return branch, revno, revision_id
424
def _as_revision_id(self, context_branch):
425
# We would have the revno here, but we don't really care
426
branch, revno, revision_id = self._lookup(context_branch)
429
def needs_branch(self):
430
return self.spec.find(':') == -1
432
def get_branch(self):
433
if self.spec.find(':') == -1:
436
return self.spec[self.spec.find(':') + 1:]
440
RevisionSpec_int = RevisionSpec_revno
443
class RevisionIDSpec(RevisionSpec):
445
def _match_on(self, branch, revs):
446
revision_id = self.as_revision_id(branch)
447
return RevisionInfo.from_revision_id(branch, revision_id)
450
class RevisionSpec_revid(RevisionIDSpec):
451
"""Selects a revision using the revision id."""
453
help_txt = """Selects a revision using the revision id.
455
Supply a specific revision id, that can be used to specify any
456
revision id in the ancestry of the branch.
457
Including merges, and pending merges.
460
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
465
def _as_revision_id(self, context_branch):
466
# self.spec comes straight from parsing the command line arguments,
467
# so we expect it to be a Unicode string. Switch it to the internal
469
if isinstance(self.spec, str):
470
return cache_utf8.encode(self.spec)
474
class RevisionSpec_last(RevisionSpec):
475
"""Selects the nth revision from the end."""
477
help_txt = """Selects the nth revision from the end.
479
Supply a positive number to get the nth revision from the end.
480
This is the same as supplying negative numbers to the 'revno:' spec.
483
last:1 -> return the last revision
484
last:3 -> return the revision 2 before the end.
489
def _match_on(self, branch, revs):
490
revno, revision_id = self._revno_and_revision_id(branch)
491
return RevisionInfo(branch, revno, revision_id)
493
def _revno_and_revision_id(self, context_branch):
494
last_revno, last_revision_id = context_branch.last_revision_info()
498
raise errors.NoCommits(context_branch)
499
return last_revno, last_revision_id
502
offset = int(self.spec)
503
except ValueError as e:
504
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
507
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
508
'you must supply a positive value')
510
revno = last_revno - offset + 1
512
revision_id = context_branch.get_rev_id(revno)
513
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
514
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
515
return revno, revision_id
517
def _as_revision_id(self, context_branch):
518
# 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)
524
class RevisionSpec_before(RevisionSpec):
525
"""Selects the parent of the revision specified."""
527
help_txt = """Selects the parent of the revision specified.
529
Supply any revision spec to return the parent of that revision. This is
530
mostly useful when inspecting revisions that are not in the revision history
533
It is an error to request the parent of the null revision (before:0).
537
before:1913 -> Return the parent of revno 1913 (revno 1912)
538
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
540
bzr diff -r before:1913..1913
541
-> Find the changes between revision 1913 and its parent (1912).
542
(What changes did revision 1913 introduce).
543
This is equivalent to: bzr diff -c 1913
548
def _match_on(self, branch, revs):
549
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
551
raise errors.InvalidRevisionSpec(self.user_spec, branch,
552
'cannot go before the null: revision')
554
# We need to use the repository history here
555
rev = branch.repository.get_revision(r.rev_id)
556
if not rev.parent_ids:
557
revision_id = revision.NULL_REVISION
559
revision_id = rev.parent_ids[0]
564
revision_id = branch.get_rev_id(revno, revs)
565
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
566
raise errors.InvalidRevisionSpec(self.user_spec,
568
return RevisionInfo(branch, revno, revision_id)
570
def _as_revision_id(self, context_branch):
571
base_revision_id = RevisionSpec.from_string(
572
self.spec)._as_revision_id(context_branch)
573
if base_revision_id == revision.NULL_REVISION:
574
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
575
'cannot go before the null: revision')
576
context_repo = context_branch.repository
577
with context_repo.lock_read():
578
parent_map = context_repo.get_parent_map([base_revision_id])
579
if base_revision_id not in parent_map:
580
# Ghost, or unknown revision id
581
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
582
'cannot find the matching revision')
583
parents = parent_map[base_revision_id]
585
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
586
'No parents for revision.')
590
class RevisionSpec_tag(RevisionSpec):
591
"""Select a revision identified by tag name"""
593
help_txt = """Selects a revision identified by a tag name.
595
Tags are stored in the branch and created by the 'tag' command.
599
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
601
def _match_on(self, branch, revs):
602
# Can raise tags not supported, NoSuchTag, etc
603
return RevisionInfo.from_revision_id(branch,
604
branch.tags.lookup_tag(self.spec))
606
def _as_revision_id(self, context_branch):
607
return context_branch.tags.lookup_tag(self.spec)
610
class _RevListToTimestamps(object):
611
"""This takes a list of revisions, and allows you to bisect by date"""
613
__slots__ = ['branch']
615
def __init__(self, branch):
618
def __getitem__(self, index):
619
"""Get the date of the index'd item"""
620
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
621
# TODO: Handle timezone.
622
return datetime.datetime.fromtimestamp(r.timestamp)
625
return self.branch.revno()
628
class RevisionSpec_date(RevisionSpec):
629
"""Selects a revision on the basis of a datestamp."""
631
help_txt = """Selects a revision on the basis of a datestamp.
633
Supply a datestamp to select the first revision that matches the date.
634
Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
635
Matches the first entry after a given date (either at midnight or
636
at a specified time).
638
One way to display all the changes since yesterday would be::
640
brz log -r date:yesterday..
644
date:yesterday -> select the first revision since yesterday
645
date:2006-08-14,17:10:14 -> select the first revision after
646
August 14th, 2006 at 5:10pm.
649
_date_regex = lazy_regex.lazy_compile(
650
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
652
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
655
def _match_on(self, branch, revs):
656
"""Spec for date revisions:
658
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
659
matches the first entry after a given date (either at midnight or
660
at a specified time).
662
# XXX: This doesn't actually work
663
# So the proper way of saying 'give me all entries for today' is:
664
# -r date:yesterday..date:today
665
today = datetime.datetime.fromordinal(
666
datetime.date.today().toordinal())
667
if self.spec.lower() == 'yesterday':
668
dt = today - datetime.timedelta(days=1)
669
elif self.spec.lower() == 'today':
671
elif self.spec.lower() == 'tomorrow':
672
dt = today + datetime.timedelta(days=1)
674
m = self._date_regex.match(self.spec)
675
if not m or (not m.group('date') and not m.group('time')):
676
raise errors.InvalidRevisionSpec(self.user_spec,
677
branch, 'invalid date')
681
year = int(m.group('year'))
682
month = int(m.group('month'))
683
day = int(m.group('day'))
690
hour = int(m.group('hour'))
691
minute = int(m.group('minute'))
692
if m.group('second'):
693
second = int(m.group('second'))
697
hour, minute, second = 0, 0, 0
699
raise errors.InvalidRevisionSpec(self.user_spec,
700
branch, 'invalid date')
702
dt = datetime.datetime(year=year, month=month, day=day,
703
hour=hour, minute=minute, second=second)
704
with branch.lock_read():
705
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
706
if rev == branch.revno():
707
raise errors.InvalidRevisionSpec(self.user_spec, branch)
708
return RevisionInfo(branch, rev)
711
class RevisionSpec_ancestor(RevisionSpec):
712
"""Selects a common ancestor with a second branch."""
714
help_txt = """Selects a common ancestor with a second branch.
716
Supply the path to a branch to select the common ancestor.
718
The common ancestor is the last revision that existed in both
719
branches. Usually this is the branch point, but it could also be
720
a revision that was merged.
722
This is frequently used with 'diff' to return all of the changes
723
that your branch introduces, while excluding the changes that you
724
have not merged from the remote branch.
728
ancestor:/path/to/branch
729
$ bzr diff -r ancestor:../../mainline/branch
733
def _match_on(self, branch, revs):
734
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
735
return self._find_revision_info(branch, self.spec)
737
def _as_revision_id(self, context_branch):
738
return self._find_revision_id(context_branch, self.spec)
741
def _find_revision_info(branch, other_location):
742
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
744
return RevisionInfo(branch, None, revision_id)
747
def _find_revision_id(branch, other_location):
748
from .branch import Branch
750
with branch.lock_read():
751
revision_a = revision.ensure_null(branch.last_revision())
752
if revision_a == revision.NULL_REVISION:
753
raise errors.NoCommits(branch)
754
if other_location == '':
755
other_location = branch.get_parent()
756
other_branch = Branch.open(other_location)
757
with other_branch.lock_read():
758
revision_b = revision.ensure_null(other_branch.last_revision())
759
if revision_b == revision.NULL_REVISION:
760
raise errors.NoCommits(other_branch)
761
graph = branch.repository.get_graph(other_branch.repository)
762
rev_id = graph.find_unique_lca(revision_a, revision_b)
763
if rev_id == revision.NULL_REVISION:
764
raise errors.NoCommonAncestor(revision_a, revision_b)
768
class RevisionSpec_branch(RevisionSpec):
769
"""Selects the last revision of a specified branch."""
771
help_txt = """Selects the last revision of a specified branch.
773
Supply the path to a branch to select its last revision.
777
branch:/path/to/branch
780
dwim_catchable_exceptions = (errors.NotBranchError,)
782
def _match_on(self, branch, revs):
783
from .branch import Branch
784
other_branch = Branch.open(self.spec)
785
revision_b = other_branch.last_revision()
786
if revision_b in (None, revision.NULL_REVISION):
787
raise errors.NoCommits(other_branch)
789
branch = other_branch
792
# pull in the remote revisions so we can diff
793
branch.fetch(other_branch, revision_b)
794
except errors.ReadOnlyError:
795
branch = other_branch
796
return RevisionInfo(branch, None, revision_b)
798
def _as_revision_id(self, context_branch):
799
from .branch import Branch
800
other_branch = Branch.open(self.spec)
801
last_revision = other_branch.last_revision()
802
last_revision = revision.ensure_null(last_revision)
803
context_branch.fetch(other_branch, last_revision)
804
if last_revision == revision.NULL_REVISION:
805
raise errors.NoCommits(other_branch)
808
def _as_tree(self, context_branch):
809
from .branch import Branch
810
other_branch = Branch.open(self.spec)
811
last_revision = other_branch.last_revision()
812
last_revision = revision.ensure_null(last_revision)
813
if last_revision == revision.NULL_REVISION:
814
raise errors.NoCommits(other_branch)
815
return other_branch.repository.revision_tree(last_revision)
817
def needs_branch(self):
820
def get_branch(self):
824
class RevisionSpec_submit(RevisionSpec_ancestor):
825
"""Selects a common ancestor with a submit branch."""
827
help_txt = """Selects a common ancestor with the submit branch.
829
Diffing against this shows all the changes that were made in this branch,
830
and is a good predictor of what merge will do. The submit branch is
831
used by the bundle and merge directive commands. If no submit branch
832
is specified, the parent branch is used instead.
834
The common ancestor is the last revision that existed in both
835
branches. Usually this is the branch point, but it could also be
836
a revision that was merged.
840
$ bzr diff -r submit:
845
def _get_submit_location(self, branch):
846
submit_location = branch.get_submit_branch()
847
location_type = 'submit branch'
848
if submit_location is None:
849
submit_location = branch.get_parent()
850
location_type = 'parent branch'
851
if submit_location is None:
852
raise errors.NoSubmitBranch(branch)
853
trace.note(gettext('Using {0} {1}').format(location_type,
855
return submit_location
857
def _match_on(self, branch, revs):
858
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
859
return self._find_revision_info(branch,
860
self._get_submit_location(branch))
862
def _as_revision_id(self, context_branch):
863
return self._find_revision_id(context_branch,
864
self._get_submit_location(context_branch))
867
class RevisionSpec_annotate(RevisionIDSpec):
871
help_txt = """Select the revision that last modified the specified line.
873
Select the revision that last modified the specified line. Line is
874
specified as path:number. Path is a relative path to the file. Numbers
875
start at 1, and are relative to the current version, not the last-
876
committed version of the file.
879
def _raise_invalid(self, numstring, context_branch):
880
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
881
'No such line: %s' % numstring)
883
def _as_revision_id(self, context_branch):
884
path, numstring = self.spec.rsplit(':', 1)
886
index = int(numstring) - 1
888
self._raise_invalid(numstring, context_branch)
889
tree, file_path = workingtree.WorkingTree.open_containing(path)
890
with tree.lock_read():
891
if not tree.has_filename(file_path):
892
raise errors.InvalidRevisionSpec(self.user_spec,
893
context_branch, "File '%s' is not versioned." %
895
revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
897
revision_id = revision_ids[index]
899
self._raise_invalid(numstring, context_branch)
900
if revision_id == revision.CURRENT_REVISION:
901
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
902
'Line %s has not been committed.' % numstring)
906
class RevisionSpec_mainline(RevisionIDSpec):
908
help_txt = """Select mainline revision that merged the specified revision.
910
Select the revision that merged the specified revision into mainline.
915
def _as_revision_id(self, context_branch):
916
revspec = RevisionSpec.from_string(self.spec)
917
if revspec.get_branch() is None:
918
spec_branch = context_branch
920
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
921
revision_id = revspec.as_revision_id(spec_branch)
922
graph = context_branch.repository.get_graph()
923
result = graph.find_lefthand_merger(revision_id,
924
context_branch.last_revision())
926
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
930
# The order in which we want to DWIM a revision spec without any prefix.
931
# revno is always tried first and isn't listed here, this is used by
932
# RevisionSpec_dwim._match_on
933
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
934
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
935
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
936
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
938
revspec_registry = registry.Registry()
941
def _register_revspec(revspec):
942
revspec_registry.register(revspec.prefix, revspec)
945
_register_revspec(RevisionSpec_revno)
946
_register_revspec(RevisionSpec_revid)
947
_register_revspec(RevisionSpec_last)
948
_register_revspec(RevisionSpec_before)
949
_register_revspec(RevisionSpec_tag)
950
_register_revspec(RevisionSpec_date)
951
_register_revspec(RevisionSpec_ancestor)
952
_register_revspec(RevisionSpec_branch)
953
_register_revspec(RevisionSpec_submit)
954
_register_revspec(RevisionSpec_annotate)
955
_register_revspec(RevisionSpec_mainline)