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,
32
from breezy.i18n import gettext
43
class RevisionInfo(object):
44
"""The results of applying a revision specification to a branch."""
46
help_txt = """The results of applying a revision specification to a branch.
48
An instance has two useful attributes: revno, and rev_id.
50
They can also be accessed as spec[0] and spec[1] respectively,
51
so that you can write code like:
52
revno, rev_id = RevisionSpec(branch, spec)
53
although this is probably going to be deprecated later.
55
This class exists mostly to be the return value of a RevisionSpec,
56
so that you can access the member you're interested in (number or id)
57
or treat the result as a tuple.
60
def __init__(self, branch, revno=None, rev_id=None):
62
self._has_revno = (revno is not None)
65
if self.rev_id is None and self._revno is not None:
66
# allow caller to be lazy
67
self.rev_id = branch.get_rev_id(self._revno)
71
if not self._has_revno and self.rev_id is not None:
73
self._revno = self.branch.revision_id_to_revno(self.rev_id)
74
except errors.NoSuchRevision:
76
self._has_revno = True
80
if self.rev_id is None:
82
# TODO: otherwise, it should depend on how I was built -
83
# if it's in_history(branch), then check revision_history(),
84
# if it's in_store(branch), do the check below
85
return self.branch.repository.has_revision(self.rev_id)
87
__nonzero__ = __bool__
92
def __getitem__(self, index):
93
if index == 0: return self.revno
94
if index == 1: return self.rev_id
95
raise IndexError(index)
98
return self.branch.repository.get_revision(self.rev_id)
100
def __eq__(self, other):
101
if type(other) not in (tuple, list, type(self)):
103
if isinstance(other, type(self)) and self.branch is not other.branch:
105
return tuple(self) == tuple(other)
108
return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
109
self.revno, self.rev_id, self.branch)
112
def from_revision_id(branch, revision_id):
113
"""Construct a RevisionInfo given just the id.
115
Use this if you don't know or care what the revno is.
117
return RevisionInfo(branch, revno=None, rev_id=revision_id)
120
class RevisionSpec(object):
121
"""A parsed revision specification."""
123
help_txt = """A parsed revision specification.
125
A revision specification is a string, which may be unambiguous about
126
what it represents by giving a prefix like 'date:' or 'revid:' etc,
127
or it may have no prefix, in which case it's tried against several
128
specifier types in sequence to determine what the user meant.
130
Revision specs are an UI element, and they have been moved out
131
of the branch class to leave "back-end" classes unaware of such
132
details. Code that gets a revno or rev_id from other code should
133
not be using revision specs - revnos and revision ids are the
134
accepted ways to refer to revisions internally.
136
(Equivalent to the old Branch method get_revision_info())
140
dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
141
"""Exceptions that RevisionSpec_dwim._match_on will catch.
143
If the revspec is part of ``dwim_revspecs``, it may be tried with an
144
invalid revspec and raises some exception. The exceptions mentioned here
145
will not be reported to the user but simply ignored without stopping the
150
def from_string(spec):
151
"""Parse a revision spec string into a RevisionSpec object.
153
:param spec: A string specified by the user
154
:return: A RevisionSpec object that understands how to parse the
157
if not isinstance(spec, (type(None), basestring)):
158
raise TypeError('error')
161
return RevisionSpec(None, _internal=True)
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) for number in revno_spec.split('.')))
391
except ValueError as e:
392
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
397
# the user has overriden the branch to look in.
398
branch = _mod_branch.Branch.open(branch_spec)
402
revision_id = branch.dotted_revno_to_revision_id(match_revno,
404
except errors.NoSuchRevision:
405
raise errors.InvalidRevisionSpec(self.user_spec, branch)
407
# there is no traditional 'revno' for dotted-decimal revnos.
408
# so for API compatibility we return None.
409
return branch, None, revision_id
411
last_revno, last_revision_id = branch.last_revision_info()
413
# if get_rev_id supported negative revnos, there would not be a
414
# need for this special case.
415
if (-revno) >= last_revno:
418
revno = last_revno + revno + 1
420
revision_id = branch.get_rev_id(revno)
421
except errors.NoSuchRevision:
422
raise errors.InvalidRevisionSpec(self.user_spec, branch)
423
return branch, revno, revision_id
425
def _as_revision_id(self, context_branch):
426
# We would have the revno here, but we don't really care
427
branch, revno, revision_id = self._lookup(context_branch)
430
def needs_branch(self):
431
return self.spec.find(':') == -1
433
def get_branch(self):
434
if self.spec.find(':') == -1:
437
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, unicode):
470
return cache_utf8.encode(self.spec)
475
class RevisionSpec_last(RevisionSpec):
476
"""Selects the nth revision from the end."""
478
help_txt = """Selects the nth revision from the end.
480
Supply a positive number to get the nth revision from the end.
481
This is the same as supplying negative numbers to the 'revno:' spec.
484
last:1 -> return the last revision
485
last:3 -> return the revision 2 before the end.
490
def _match_on(self, branch, revs):
491
revno, revision_id = self._revno_and_revision_id(branch)
492
return RevisionInfo(branch, revno, revision_id)
494
def _revno_and_revision_id(self, context_branch):
495
last_revno, last_revision_id = context_branch.last_revision_info()
499
raise errors.NoCommits(context_branch)
500
return last_revno, last_revision_id
503
offset = int(self.spec)
504
except ValueError as e:
505
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
508
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
509
'you must supply a positive value')
511
revno = last_revno - offset + 1
513
revision_id = context_branch.get_rev_id(revno)
514
except errors.NoSuchRevision:
515
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
516
return revno, revision_id
518
def _as_revision_id(self, context_branch):
519
# We compute the revno as part of the process, but we don't really care
521
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:
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(self.spec)._as_revision_id(context_branch)
574
if base_revision_id == revision.NULL_REVISION:
575
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
576
'cannot go before the null: revision')
577
context_repo = context_branch.repository
578
context_repo.lock_read()
580
parent_map = context_repo.get_parent_map([base_revision_id])
582
context_repo.unlock()
583
if base_revision_id not in parent_map:
584
# Ghost, or unknown revision id
585
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
586
'cannot find the matching revision')
587
parents = parent_map[base_revision_id]
589
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
590
'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)
616
class _RevListToTimestamps(object):
617
"""This takes a list of revisions, and allows you to bisect by date"""
619
__slots__ = ['branch']
621
def __init__(self, branch):
624
def __getitem__(self, index):
625
"""Get the date of the index'd item"""
626
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
627
# TODO: Handle timezone.
628
return datetime.datetime.fromtimestamp(r.timestamp)
631
return self.branch.revno()
634
class RevisionSpec_date(RevisionSpec):
635
"""Selects a revision on the basis of a datestamp."""
637
help_txt = """Selects a revision on the basis of a datestamp.
639
Supply a datestamp to select the first revision that matches the date.
640
Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
641
Matches the first entry after a given date (either at midnight or
642
at a specified time).
644
One way to display all the changes since yesterday would be::
646
brz log -r date:yesterday..
650
date:yesterday -> select the first revision since yesterday
651
date:2006-08-14,17:10:14 -> select the first revision after
652
August 14th, 2006 at 5:10pm.
655
_date_regex = lazy_regex.lazy_compile(
656
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
658
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
661
def _match_on(self, branch, revs):
662
"""Spec for date revisions:
664
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
665
matches the first entry after a given date (either at midnight or
666
at a specified time).
668
# XXX: This doesn't actually work
669
# So the proper way of saying 'give me all entries for today' is:
670
# -r date:yesterday..date:today
671
today = datetime.datetime.fromordinal(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)
711
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
714
if rev == branch.revno():
715
raise errors.InvalidRevisionSpec(self.user_spec, branch)
716
return RevisionInfo(branch, rev)
720
class RevisionSpec_ancestor(RevisionSpec):
721
"""Selects a common ancestor with a second branch."""
723
help_txt = """Selects a common ancestor with a second branch.
725
Supply the path to a branch to select the common ancestor.
727
The common ancestor is the last revision that existed in both
728
branches. Usually this is the branch point, but it could also be
729
a revision that was merged.
731
This is frequently used with 'diff' to return all of the changes
732
that your branch introduces, while excluding the changes that you
733
have not merged from the remote branch.
737
ancestor:/path/to/branch
738
$ bzr diff -r ancestor:../../mainline/branch
742
def _match_on(self, branch, revs):
743
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
744
return self._find_revision_info(branch, self.spec)
746
def _as_revision_id(self, context_branch):
747
return self._find_revision_id(context_branch, self.spec)
750
def _find_revision_info(branch, other_location):
751
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
753
return RevisionInfo(branch, None, revision_id)
756
def _find_revision_id(branch, other_location):
757
from .branch import Branch
761
revision_a = revision.ensure_null(branch.last_revision())
762
if revision_a == revision.NULL_REVISION:
763
raise errors.NoCommits(branch)
764
if other_location == '':
765
other_location = branch.get_parent()
766
other_branch = Branch.open(other_location)
767
other_branch.lock_read()
769
revision_b = revision.ensure_null(other_branch.last_revision())
770
if revision_b == revision.NULL_REVISION:
771
raise errors.NoCommits(other_branch)
772
graph = branch.repository.get_graph(other_branch.repository)
773
rev_id = graph.find_unique_lca(revision_a, revision_b)
775
other_branch.unlock()
776
if rev_id == revision.NULL_REVISION:
777
raise errors.NoCommonAncestor(revision_a, revision_b)
785
class RevisionSpec_branch(RevisionSpec):
786
"""Selects the last revision of a specified branch."""
788
help_txt = """Selects the last revision of a specified branch.
790
Supply the path to a branch to select its last revision.
794
branch:/path/to/branch
797
dwim_catchable_exceptions = (errors.NotBranchError,)
799
def _match_on(self, branch, revs):
800
from .branch import Branch
801
other_branch = Branch.open(self.spec)
802
revision_b = other_branch.last_revision()
803
if revision_b in (None, revision.NULL_REVISION):
804
raise errors.NoCommits(other_branch)
806
branch = other_branch
809
# pull in the remote revisions so we can diff
810
branch.fetch(other_branch, revision_b)
811
except errors.ReadOnlyError:
812
branch = other_branch
813
return RevisionInfo(branch, None, revision_b)
815
def _as_revision_id(self, context_branch):
816
from .branch import Branch
817
other_branch = Branch.open(self.spec)
818
last_revision = other_branch.last_revision()
819
last_revision = revision.ensure_null(last_revision)
820
context_branch.fetch(other_branch, last_revision)
821
if last_revision == revision.NULL_REVISION:
822
raise errors.NoCommits(other_branch)
825
def _as_tree(self, context_branch):
826
from .branch import Branch
827
other_branch = Branch.open(self.spec)
828
last_revision = other_branch.last_revision()
829
last_revision = revision.ensure_null(last_revision)
830
if last_revision == revision.NULL_REVISION:
831
raise errors.NoCommits(other_branch)
832
return other_branch.repository.revision_tree(last_revision)
834
def needs_branch(self):
837
def get_branch(self):
842
class RevisionSpec_submit(RevisionSpec_ancestor):
843
"""Selects a common ancestor with a submit branch."""
845
help_txt = """Selects a common ancestor with the submit branch.
847
Diffing against this shows all the changes that were made in this branch,
848
and is a good predictor of what merge will do. The submit branch is
849
used by the bundle and merge directive commands. If no submit branch
850
is specified, the parent branch is used instead.
852
The common ancestor is the last revision that existed in both
853
branches. Usually this is the branch point, but it could also be
854
a revision that was merged.
858
$ bzr diff -r submit:
863
def _get_submit_location(self, branch):
864
submit_location = branch.get_submit_branch()
865
location_type = 'submit branch'
866
if submit_location is None:
867
submit_location = branch.get_parent()
868
location_type = 'parent branch'
869
if submit_location is None:
870
raise errors.NoSubmitBranch(branch)
871
trace.note(gettext('Using {0} {1}').format(location_type,
873
return submit_location
875
def _match_on(self, branch, revs):
876
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
877
return self._find_revision_info(branch,
878
self._get_submit_location(branch))
880
def _as_revision_id(self, context_branch):
881
return self._find_revision_id(context_branch,
882
self._get_submit_location(context_branch))
885
class RevisionSpec_annotate(RevisionIDSpec):
889
help_txt = """Select the revision that last modified the specified line.
891
Select the revision that last modified the specified line. Line is
892
specified as path:number. Path is a relative path to the file. Numbers
893
start at 1, and are relative to the current version, not the last-
894
committed version of the file.
897
def _raise_invalid(self, numstring, context_branch):
898
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
899
'No such line: %s' % numstring)
901
def _as_revision_id(self, context_branch):
902
path, numstring = self.spec.rsplit(':', 1)
904
index = int(numstring) - 1
906
self._raise_invalid(numstring, context_branch)
907
tree, file_path = workingtree.WorkingTree.open_containing(path)
910
file_id = tree.path2id(file_path)
912
raise errors.InvalidRevisionSpec(self.user_spec,
913
context_branch, "File '%s' is not versioned." %
915
revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
919
revision_id = revision_ids[index]
921
self._raise_invalid(numstring, context_branch)
922
if revision_id == revision.CURRENT_REVISION:
923
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
924
'Line %s has not been committed.' % numstring)
928
class RevisionSpec_mainline(RevisionIDSpec):
930
help_txt = """Select mainline revision that merged the specified revision.
932
Select the revision that merged the specified revision into mainline.
937
def _as_revision_id(self, context_branch):
938
revspec = RevisionSpec.from_string(self.spec)
939
if revspec.get_branch() is None:
940
spec_branch = context_branch
942
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
943
revision_id = revspec.as_revision_id(spec_branch)
944
graph = context_branch.repository.get_graph()
945
result = graph.find_lefthand_merger(revision_id,
946
context_branch.last_revision())
948
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
952
# The order in which we want to DWIM a revision spec without any prefix.
953
# revno is always tried first and isn't listed here, this is used by
954
# RevisionSpec_dwim._match_on
955
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
956
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
957
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
958
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
960
revspec_registry = registry.Registry()
961
def _register_revspec(revspec):
962
revspec_registry.register(revspec.prefix, revspec)
964
_register_revspec(RevisionSpec_revno)
965
_register_revspec(RevisionSpec_revid)
966
_register_revspec(RevisionSpec_last)
967
_register_revspec(RevisionSpec_before)
968
_register_revspec(RevisionSpec_tag)
969
_register_revspec(RevisionSpec_date)
970
_register_revspec(RevisionSpec_ancestor)
971
_register_revspec(RevisionSpec_branch)
972
_register_revspec(RevisionSpec_submit)
973
_register_revspec(RevisionSpec_annotate)
974
_register_revspec(RevisionSpec_mainline)