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
42
class RevisionInfo(object):
43
"""The results of applying a revision specification to a branch."""
45
help_txt = """The results of applying a revision specification to a branch.
47
An instance has two useful attributes: revno, and rev_id.
49
They can also be accessed as spec[0] and spec[1] respectively,
50
so that you can write code like:
51
revno, rev_id = RevisionSpec(branch, spec)
52
although this is probably going to be deprecated later.
54
This class exists mostly to be the return value of a RevisionSpec,
55
so that you can access the member you're interested in (number or id)
56
or treat the result as a tuple.
59
def __init__(self, branch, revno=None, rev_id=None):
61
self._has_revno = (revno is not None)
64
if self.rev_id is None and self._revno is not None:
65
# allow caller to be lazy
66
self.rev_id = branch.get_rev_id(self._revno)
70
if not self._has_revno and self.rev_id is not None:
72
self._revno = self.branch.revision_id_to_revno(self.rev_id)
73
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
75
self._has_revno = True
79
if self.rev_id is None:
81
# TODO: otherwise, it should depend on how I was built -
82
# if it's in_history(branch), then check revision_history(),
83
# if it's in_store(branch), do the check below
84
return self.branch.repository.has_revision(self.rev_id)
86
__nonzero__ = __bool__
91
def __getitem__(self, index):
96
raise IndexError(index)
99
return self.branch.repository.get_revision(self.rev_id)
101
def __eq__(self, other):
102
if type(other) not in (tuple, list, type(self)):
104
if isinstance(other, type(self)) and self.branch is not other.branch:
106
return tuple(self) == tuple(other)
109
return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
110
self.revno, self.rev_id, self.branch)
113
def from_revision_id(branch, revision_id):
114
"""Construct a RevisionInfo given just the id.
116
Use this if you don't know or care what the revno is.
118
return RevisionInfo(branch, revno=None, rev_id=revision_id)
121
class RevisionSpec(object):
122
"""A parsed revision specification."""
124
help_txt = """A parsed revision specification.
126
A revision specification is a string, which may be unambiguous about
127
what it represents by giving a prefix like 'date:' or 'revid:' etc,
128
or it may have no prefix, in which case it's tried against several
129
specifier types in sequence to determine what the user meant.
131
Revision specs are an UI element, and they have been moved out
132
of the branch class to leave "back-end" classes unaware of such
133
details. Code that gets a revno or rev_id from other code should
134
not be using revision specs - revnos and revision ids are the
135
accepted ways to refer to revisions internally.
137
(Equivalent to the old Branch method get_revision_info())
141
dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
142
"""Exceptions that RevisionSpec_dwim._match_on will catch.
144
If the revspec is part of ``dwim_revspecs``, it may be tried with an
145
invalid revspec and raises some exception. The exceptions mentioned here
146
will not be reported to the user but simply ignored without stopping the
151
def from_string(spec):
152
"""Parse a revision spec string into a RevisionSpec object.
154
:param spec: A string specified by the user
155
:return: A RevisionSpec object that understands how to parse the
159
return RevisionSpec(None, _internal=True)
160
if not isinstance(spec, str):
161
raise TypeError("revision spec needs to be text")
162
match = revspec_registry.get_prefix(spec)
163
if match is not None:
164
spectype, specsuffix = match
165
trace.mutter('Returning RevisionSpec %s for %s',
166
spectype.__name__, spec)
167
return spectype(spec, _internal=True)
169
# Otherwise treat it as a DWIM, build the RevisionSpec object and
170
# wait for _match_on to be called.
171
return RevisionSpec_dwim(spec, _internal=True)
173
def __init__(self, spec, _internal=False):
174
"""Create a RevisionSpec referring to the Null revision.
176
:param spec: The original spec supplied by the user
177
:param _internal: Used to ensure that RevisionSpec is not being
178
called directly. Only from RevisionSpec.from_string()
181
raise AssertionError(
182
'Creating a RevisionSpec directly is not supported. '
183
'Use RevisionSpec.from_string() instead.')
184
self.user_spec = spec
185
if self.prefix and spec.startswith(self.prefix):
186
spec = spec[len(self.prefix):]
189
def _match_on(self, branch, revs):
190
trace.mutter('Returning RevisionSpec._match_on: None')
191
return RevisionInfo(branch, None, None)
193
def _match_on_and_check(self, branch, revs):
194
info = self._match_on(branch, revs)
197
elif info == (None, None):
198
# special case - nothing supplied
201
raise errors.InvalidRevisionSpec(self.user_spec, branch)
203
raise errors.InvalidRevisionSpec(self.spec, branch)
205
def in_history(self, branch):
206
return self._match_on_and_check(branch, revs=None)
208
# FIXME: in_history is somewhat broken,
209
# it will return non-history revisions in many
210
# circumstances. The expected facility is that
211
# in_history only returns revision-history revs,
212
# in_store returns any rev. RBC 20051010
213
# aliases for now, when we fix the core logic, then they
214
# will do what you expect.
215
in_store = in_history
218
def as_revision_id(self, context_branch):
219
"""Return just the revision_id for this revisions spec.
221
Some revision specs require a context_branch to be able to determine
222
their value. Not all specs will make use of it.
224
return self._as_revision_id(context_branch)
226
def _as_revision_id(self, context_branch):
227
"""Implementation of as_revision_id()
229
Classes should override this function to provide appropriate
230
functionality. The default is to just call '.in_history().rev_id'
232
return self.in_history(context_branch).rev_id
234
def as_tree(self, context_branch):
235
"""Return the tree object for this revisions spec.
237
Some revision specs require a context_branch to be able to determine
238
the revision id and access the repository. Not all specs will make
241
return self._as_tree(context_branch)
243
def _as_tree(self, context_branch):
244
"""Implementation of as_tree().
246
Classes should override this function to provide appropriate
247
functionality. The default is to just call '.as_revision_id()'
248
and get the revision tree from context_branch's repository.
250
revision_id = self.as_revision_id(context_branch)
251
return context_branch.repository.revision_tree(revision_id)
254
# this is mostly for helping with testing
255
return '<%s %s>' % (self.__class__.__name__,
258
def needs_branch(self):
259
"""Whether this revision spec needs a branch.
261
Set this to False the branch argument of _match_on is not used.
265
def get_branch(self):
266
"""When the revision specifier contains a branch location, return it.
268
Otherwise, return None.
275
class RevisionSpec_dwim(RevisionSpec):
276
"""Provides a DWIMish revision specifier lookup.
278
Note that this does not go in the revspec_registry because by definition
279
there is no prefix to identify it. It's solely called from
280
RevisionSpec.from_string() because the DWIMification happen when _match_on
281
is called so the string describing the revision is kept here until needed.
286
_revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
288
# The revspecs to try
289
_possible_revspecs = []
291
def _try_spectype(self, rstype, branch):
292
rs = rstype(self.spec, _internal=True)
293
# Hit in_history to find out if it exists, or we need to try the
295
return rs.in_history(branch)
297
def _match_on(self, branch, revs):
298
"""Run the lookup and see what we can get."""
300
# First, see if it's a revno
301
if self._revno_regex.match(self.spec) is not None:
303
return self._try_spectype(RevisionSpec_revno, branch)
304
except RevisionSpec_revno.dwim_catchable_exceptions:
307
# Next see what has been registered
308
for objgetter in self._possible_revspecs:
309
rs_class = objgetter.get_obj()
311
return self._try_spectype(rs_class, branch)
312
except rs_class.dwim_catchable_exceptions:
315
# Well, I dunno what it is. Note that we don't try to keep track of the
316
# first of last exception raised during the DWIM tries as none seems
318
raise errors.InvalidRevisionSpec(self.spec, branch)
321
def append_possible_revspec(cls, revspec):
322
"""Append a possible DWIM revspec.
324
:param revspec: Revision spec to try.
326
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
329
def append_possible_lazy_revspec(cls, module_name, member_name):
330
"""Append a possible lazily loaded DWIM revspec.
332
:param module_name: Name of the module with the revspec
333
:param member_name: Name of the revspec within the module
335
cls._possible_revspecs.append(
336
registry._LazyObjectGetter(module_name, member_name))
339
class RevisionSpec_revno(RevisionSpec):
340
"""Selects a revision using a number."""
342
help_txt = """Selects a revision using a number.
344
Use an integer to specify a revision in the history of the branch.
345
Optionally a branch can be specified. A negative number will count
346
from the end of the branch (-1 is the last revision, -2 the previous
347
one). If the negative number is larger than the branch's history, the
348
first revision is returned.
351
revno:1 -> return the first revision of this branch
352
revno:3:/path/to/branch -> return the 3rd revision of
353
the branch '/path/to/branch'
354
revno:-1 -> The last revision in a branch.
355
-2:http://other/branch -> The second to last revision in the
357
-1000000 -> Most likely the first revision, unless
358
your history is very long.
362
def _match_on(self, branch, revs):
363
"""Lookup a revision by revision number"""
364
branch, revno, revision_id = self._lookup(branch)
365
return RevisionInfo(branch, revno, revision_id)
367
def _lookup(self, branch):
368
loc = self.spec.find(':')
370
revno_spec = self.spec
373
revno_spec = self.spec[:loc]
374
branch_spec = self.spec[loc + 1:]
378
raise errors.InvalidRevisionSpec(self.user_spec,
379
branch, 'cannot have an empty revno and no branch')
383
revno = int(revno_spec)
386
# dotted decimal. This arguably should not be here
387
# but the from_string method is a little primitive
388
# right now - RBC 20060928
390
match_revno = tuple((int(number)
391
for number in revno_spec.split('.')))
392
except ValueError as e:
393
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
398
# the user has overriden the branch to look in.
399
branch = _mod_branch.Branch.open(branch_spec)
403
revision_id = branch.dotted_revno_to_revision_id(match_revno,
405
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
406
raise errors.InvalidRevisionSpec(self.user_spec, branch)
408
# there is no traditional 'revno' for dotted-decimal revnos.
409
# so for API compatibility we return None.
410
return branch, None, revision_id
412
last_revno, last_revision_id = branch.last_revision_info()
414
# if get_rev_id supported negative revnos, there would not be a
415
# need for this special case.
416
if (-revno) >= last_revno:
419
revno = last_revno + revno + 1
421
revision_id = branch.get_rev_id(revno)
422
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
423
raise errors.InvalidRevisionSpec(self.user_spec, branch)
424
return branch, revno, revision_id
426
def _as_revision_id(self, context_branch):
427
# We would have the revno here, but we don't really care
428
branch, revno, revision_id = self._lookup(context_branch)
431
def needs_branch(self):
432
return self.spec.find(':') == -1
434
def get_branch(self):
435
if self.spec.find(':') == -1:
438
return self.spec[self.spec.find(':') + 1:]
442
RevisionSpec_int = RevisionSpec_revno
445
class RevisionIDSpec(RevisionSpec):
447
def _match_on(self, branch, revs):
448
revision_id = self.as_revision_id(branch)
449
return RevisionInfo.from_revision_id(branch, revision_id)
452
class RevisionSpec_revid(RevisionIDSpec):
453
"""Selects a revision using the revision id."""
455
help_txt = """Selects a revision using the revision id.
457
Supply a specific revision id, that can be used to specify any
458
revision id in the ancestry of the branch.
459
Including merges, and pending merges.
462
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
467
def _as_revision_id(self, context_branch):
468
# self.spec comes straight from parsing the command line arguments,
469
# so we expect it to be a Unicode string. Switch it to the internal
471
if isinstance(self.spec, str):
472
return cache_utf8.encode(self.spec)
476
class RevisionSpec_last(RevisionSpec):
477
"""Selects the nth revision from the end."""
479
help_txt = """Selects the nth revision from the end.
481
Supply a positive number to get the nth revision from the end.
482
This is the same as supplying negative numbers to the 'revno:' spec.
485
last:1 -> return the last revision
486
last:3 -> return the revision 2 before the end.
491
def _match_on(self, branch, revs):
492
revno, revision_id = self._revno_and_revision_id(branch)
493
return RevisionInfo(branch, revno, revision_id)
495
def _revno_and_revision_id(self, context_branch):
496
last_revno, last_revision_id = context_branch.last_revision_info()
500
raise errors.NoCommits(context_branch)
501
return last_revno, last_revision_id
504
offset = int(self.spec)
505
except ValueError as e:
506
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
509
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
510
'you must supply a positive value')
512
revno = last_revno - offset + 1
514
revision_id = context_branch.get_rev_id(revno)
515
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
516
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
517
return revno, revision_id
519
def _as_revision_id(self, context_branch):
520
# We compute the revno as part of the process, but we don't really care
522
revno, revision_id = self._revno_and_revision_id(context_branch)
526
class RevisionSpec_before(RevisionSpec):
527
"""Selects the parent of the revision specified."""
529
help_txt = """Selects the parent of the revision specified.
531
Supply any revision spec to return the parent of that revision. This is
532
mostly useful when inspecting revisions that are not in the revision history
535
It is an error to request the parent of the null revision (before:0).
539
before:1913 -> Return the parent of revno 1913 (revno 1912)
540
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
542
bzr diff -r before:1913..1913
543
-> Find the changes between revision 1913 and its parent (1912).
544
(What changes did revision 1913 introduce).
545
This is equivalent to: bzr diff -c 1913
550
def _match_on(self, branch, revs):
551
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
553
raise errors.InvalidRevisionSpec(self.user_spec, branch,
554
'cannot go before the null: revision')
556
# We need to use the repository history here
557
rev = branch.repository.get_revision(r.rev_id)
558
if not rev.parent_ids:
559
revision_id = revision.NULL_REVISION
561
revision_id = rev.parent_ids[0]
566
revision_id = branch.get_rev_id(revno, revs)
567
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
568
raise errors.InvalidRevisionSpec(self.user_spec,
570
return RevisionInfo(branch, revno, revision_id)
572
def _as_revision_id(self, context_branch):
573
base_revision_id = RevisionSpec.from_string(
574
self.spec)._as_revision_id(context_branch)
575
if base_revision_id == revision.NULL_REVISION:
576
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
577
'cannot go before the null: revision')
578
context_repo = context_branch.repository
579
with context_repo.lock_read():
580
parent_map = context_repo.get_parent_map([base_revision_id])
581
if base_revision_id not in parent_map:
582
# Ghost, or unknown revision id
583
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
584
'cannot find the matching revision')
585
parents = parent_map[base_revision_id]
587
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
588
'No parents for revision.')
592
class RevisionSpec_tag(RevisionSpec):
593
"""Select a revision identified by tag name"""
595
help_txt = """Selects a revision identified by a tag name.
597
Tags are stored in the branch and created by the 'tag' command.
601
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
603
def _match_on(self, branch, revs):
604
# Can raise tags not supported, NoSuchTag, etc
605
return RevisionInfo.from_revision_id(branch,
606
branch.tags.lookup_tag(self.spec))
608
def _as_revision_id(self, context_branch):
609
return context_branch.tags.lookup_tag(self.spec)
612
class _RevListToTimestamps(object):
613
"""This takes a list of revisions, and allows you to bisect by date"""
615
__slots__ = ['branch']
617
def __init__(self, branch):
620
def __getitem__(self, index):
621
"""Get the date of the index'd item"""
622
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
623
# TODO: Handle timezone.
624
return datetime.datetime.fromtimestamp(r.timestamp)
627
return self.branch.revno()
630
class RevisionSpec_date(RevisionSpec):
631
"""Selects a revision on the basis of a datestamp."""
633
help_txt = """Selects a revision on the basis of a datestamp.
635
Supply a datestamp to select the first revision that matches the date.
636
Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
637
Matches the first entry after a given date (either at midnight or
638
at a specified time).
640
One way to display all the changes since yesterday would be::
642
brz log -r date:yesterday..
646
date:yesterday -> select the first revision since yesterday
647
date:2006-08-14,17:10:14 -> select the first revision after
648
August 14th, 2006 at 5:10pm.
651
_date_regex = lazy_regex.lazy_compile(
652
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
654
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
657
def _match_on(self, branch, revs):
658
"""Spec for date revisions:
660
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
661
matches the first entry after a given date (either at midnight or
662
at a specified time).
664
# XXX: This doesn't actually work
665
# So the proper way of saying 'give me all entries for today' is:
666
# -r date:yesterday..date:today
667
today = datetime.datetime.fromordinal(
668
datetime.date.today().toordinal())
669
if self.spec.lower() == 'yesterday':
670
dt = today - datetime.timedelta(days=1)
671
elif self.spec.lower() == 'today':
673
elif self.spec.lower() == 'tomorrow':
674
dt = today + datetime.timedelta(days=1)
676
m = self._date_regex.match(self.spec)
677
if not m or (not m.group('date') and not m.group('time')):
678
raise errors.InvalidRevisionSpec(self.user_spec,
679
branch, 'invalid date')
683
year = int(m.group('year'))
684
month = int(m.group('month'))
685
day = int(m.group('day'))
692
hour = int(m.group('hour'))
693
minute = int(m.group('minute'))
694
if m.group('second'):
695
second = int(m.group('second'))
699
hour, minute, second = 0, 0, 0
701
raise errors.InvalidRevisionSpec(self.user_spec,
702
branch, 'invalid date')
704
dt = datetime.datetime(year=year, month=month, day=day,
705
hour=hour, minute=minute, second=second)
706
with branch.lock_read():
707
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
708
if rev == branch.revno():
709
raise errors.InvalidRevisionSpec(self.user_spec, branch)
710
return RevisionInfo(branch, rev)
713
class RevisionSpec_ancestor(RevisionSpec):
714
"""Selects a common ancestor with a second branch."""
716
help_txt = """Selects a common ancestor with a second branch.
718
Supply the path to a branch to select the common ancestor.
720
The common ancestor is the last revision that existed in both
721
branches. Usually this is the branch point, but it could also be
722
a revision that was merged.
724
This is frequently used with 'diff' to return all of the changes
725
that your branch introduces, while excluding the changes that you
726
have not merged from the remote branch.
730
ancestor:/path/to/branch
731
$ bzr diff -r ancestor:../../mainline/branch
735
def _match_on(self, branch, revs):
736
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
737
return self._find_revision_info(branch, self.spec)
739
def _as_revision_id(self, context_branch):
740
return self._find_revision_id(context_branch, self.spec)
743
def _find_revision_info(branch, other_location):
744
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
746
return RevisionInfo(branch, None, revision_id)
749
def _find_revision_id(branch, other_location):
750
from .branch import Branch
752
with branch.lock_read():
753
revision_a = revision.ensure_null(branch.last_revision())
754
if revision_a == revision.NULL_REVISION:
755
raise errors.NoCommits(branch)
756
if other_location == '':
757
other_location = branch.get_parent()
758
other_branch = Branch.open(other_location)
759
with other_branch.lock_read():
760
revision_b = revision.ensure_null(other_branch.last_revision())
761
if revision_b == revision.NULL_REVISION:
762
raise errors.NoCommits(other_branch)
763
graph = branch.repository.get_graph(other_branch.repository)
764
rev_id = graph.find_unique_lca(revision_a, revision_b)
765
if rev_id == revision.NULL_REVISION:
766
raise errors.NoCommonAncestor(revision_a, revision_b)
770
class RevisionSpec_branch(RevisionSpec):
771
"""Selects the last revision of a specified branch."""
773
help_txt = """Selects the last revision of a specified branch.
775
Supply the path to a branch to select its last revision.
779
branch:/path/to/branch
782
dwim_catchable_exceptions = (errors.NotBranchError,)
784
def _match_on(self, branch, revs):
785
from .branch import Branch
786
other_branch = Branch.open(self.spec)
787
revision_b = other_branch.last_revision()
788
if revision_b in (None, revision.NULL_REVISION):
789
raise errors.NoCommits(other_branch)
791
branch = other_branch
794
# pull in the remote revisions so we can diff
795
branch.fetch(other_branch, revision_b)
796
except errors.ReadOnlyError:
797
branch = other_branch
798
return RevisionInfo(branch, None, revision_b)
800
def _as_revision_id(self, context_branch):
801
from .branch import Branch
802
other_branch = Branch.open(self.spec)
803
last_revision = other_branch.last_revision()
804
last_revision = revision.ensure_null(last_revision)
805
context_branch.fetch(other_branch, last_revision)
806
if last_revision == revision.NULL_REVISION:
807
raise errors.NoCommits(other_branch)
810
def _as_tree(self, context_branch):
811
from .branch import Branch
812
other_branch = Branch.open(self.spec)
813
last_revision = other_branch.last_revision()
814
last_revision = revision.ensure_null(last_revision)
815
if last_revision == revision.NULL_REVISION:
816
raise errors.NoCommits(other_branch)
817
return other_branch.repository.revision_tree(last_revision)
819
def needs_branch(self):
822
def get_branch(self):
826
class RevisionSpec_submit(RevisionSpec_ancestor):
827
"""Selects a common ancestor with a submit branch."""
829
help_txt = """Selects a common ancestor with the submit branch.
831
Diffing against this shows all the changes that were made in this branch,
832
and is a good predictor of what merge will do. The submit branch is
833
used by the bundle and merge directive commands. If no submit branch
834
is specified, the parent branch is used instead.
836
The common ancestor is the last revision that existed in both
837
branches. Usually this is the branch point, but it could also be
838
a revision that was merged.
842
$ bzr diff -r submit:
847
def _get_submit_location(self, branch):
848
submit_location = branch.get_submit_branch()
849
location_type = 'submit branch'
850
if submit_location is None:
851
submit_location = branch.get_parent()
852
location_type = 'parent branch'
853
if submit_location is None:
854
raise errors.NoSubmitBranch(branch)
855
trace.note(gettext('Using {0} {1}').format(location_type,
857
return submit_location
859
def _match_on(self, branch, revs):
860
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
861
return self._find_revision_info(branch,
862
self._get_submit_location(branch))
864
def _as_revision_id(self, context_branch):
865
return self._find_revision_id(context_branch,
866
self._get_submit_location(context_branch))
869
class RevisionSpec_annotate(RevisionIDSpec):
873
help_txt = """Select the revision that last modified the specified line.
875
Select the revision that last modified the specified line. Line is
876
specified as path:number. Path is a relative path to the file. Numbers
877
start at 1, and are relative to the current version, not the last-
878
committed version of the file.
881
def _raise_invalid(self, numstring, context_branch):
882
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
883
'No such line: %s' % numstring)
885
def _as_revision_id(self, context_branch):
886
path, numstring = self.spec.rsplit(':', 1)
888
index = int(numstring) - 1
890
self._raise_invalid(numstring, context_branch)
891
tree, file_path = workingtree.WorkingTree.open_containing(path)
892
with tree.lock_read():
893
if not tree.has_filename(file_path):
894
raise errors.InvalidRevisionSpec(self.user_spec,
895
context_branch, "File '%s' is not versioned." %
897
revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
899
revision_id = revision_ids[index]
901
self._raise_invalid(numstring, context_branch)
902
if revision_id == revision.CURRENT_REVISION:
903
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
904
'Line %s has not been committed.' % numstring)
908
class RevisionSpec_mainline(RevisionIDSpec):
910
help_txt = """Select mainline revision that merged the specified revision.
912
Select the revision that merged the specified revision into mainline.
917
def _as_revision_id(self, context_branch):
918
revspec = RevisionSpec.from_string(self.spec)
919
if revspec.get_branch() is None:
920
spec_branch = context_branch
922
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
923
revision_id = revspec.as_revision_id(spec_branch)
924
graph = context_branch.repository.get_graph()
925
result = graph.find_lefthand_merger(revision_id,
926
context_branch.last_revision())
928
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
932
# The order in which we want to DWIM a revision spec without any prefix.
933
# revno is always tried first and isn't listed here, this is used by
934
# RevisionSpec_dwim._match_on
935
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
936
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
937
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
938
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
940
revspec_registry = registry.Registry()
943
def _register_revspec(revspec):
944
revspec_registry.register(revspec.prefix, revspec)
947
_register_revspec(RevisionSpec_revno)
948
_register_revspec(RevisionSpec_revid)
949
_register_revspec(RevisionSpec_last)
950
_register_revspec(RevisionSpec_before)
951
_register_revspec(RevisionSpec_tag)
952
_register_revspec(RevisionSpec_date)
953
_register_revspec(RevisionSpec_ancestor)
954
_register_revspec(RevisionSpec_branch)
955
_register_revspec(RevisionSpec_submit)
956
_register_revspec(RevisionSpec_annotate)
957
_register_revspec(RevisionSpec_mainline)