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
20
from bzrlib.lazy_import import lazy_import
21
lazy_import(globals(), """
31
branch as _mod_branch,
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, rev_id=_marker):
66
# allow caller to be lazy
67
if self.revno is None:
70
self.rev_id = branch.get_rev_id(self.revno)
74
def __nonzero__(self):
75
# first the easy ones...
76
if self.rev_id is None:
78
if self.revno is not None:
80
# TODO: otherwise, it should depend on how I was built -
81
# if it's in_history(branch), then check revision_history(),
82
# if it's in_store(branch), do the check below
83
return self.branch.repository.has_revision(self.rev_id)
88
def __getitem__(self, index):
89
if index == 0: return self.revno
90
if index == 1: return self.rev_id
91
raise IndexError(index)
94
return self.branch.repository.get_revision(self.rev_id)
96
def __eq__(self, other):
97
if type(other) not in (tuple, list, type(self)):
99
if type(other) is type(self) and self.branch is not other.branch:
101
return tuple(self) == tuple(other)
104
return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
105
self.revno, self.rev_id, self.branch)
108
def from_revision_id(branch, revision_id, revs):
109
"""Construct a RevisionInfo given just the id.
111
Use this if you don't know or care what the revno is.
113
if revision_id == revision.NULL_REVISION:
114
return RevisionInfo(branch, 0, revision_id)
116
revno = revs.index(revision_id) + 1
119
return RevisionInfo(branch, revno, revision_id)
125
class RevisionSpec(object):
126
"""A parsed revision specification."""
128
help_txt = """A parsed revision specification.
130
A revision specification is a string, which may be unambiguous about
131
what it represents by giving a prefix like 'date:' or 'revid:' etc,
132
or it may have no prefix, in which case it's tried against several
133
specifier types in sequence to determine what the user meant.
135
Revision specs are an UI element, and they have been moved out
136
of the branch class to leave "back-end" classes unaware of such
137
details. Code that gets a revno or rev_id from other code should
138
not be using revision specs - revnos and revision ids are the
139
accepted ways to refer to revisions internally.
141
(Equivalent to the old Branch method get_revision_info())
145
wants_revision_history = True
146
dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
147
"""Exceptions that RevisionSpec_dwim._match_on will catch.
149
If the revspec is part of ``dwim_revspecs``, it may be tried with an
150
invalid revspec and raises some exception. The exceptions mentioned here
151
will not be reported to the user but simply ignored without stopping the
156
def from_string(spec):
157
"""Parse a revision spec string into a RevisionSpec object.
159
:param spec: A string specified by the user
160
:return: A RevisionSpec object that understands how to parse the
163
if not isinstance(spec, (type(None), basestring)):
164
raise TypeError('error')
167
return RevisionSpec(None, _internal=True)
168
match = revspec_registry.get_prefix(spec)
169
if match is not None:
170
spectype, specsuffix = match
171
trace.mutter('Returning RevisionSpec %s for %s',
172
spectype.__name__, spec)
173
return spectype(spec, _internal=True)
175
# Otherwise treat it as a DWIM, build the RevisionSpec object and
176
# wait for _match_on to be called.
177
return RevisionSpec_dwim(spec, _internal=True)
179
def __init__(self, spec, _internal=False):
180
"""Create a RevisionSpec referring to the Null revision.
182
:param spec: The original spec supplied by the user
183
:param _internal: Used to ensure that RevisionSpec is not being
184
called directly. Only from RevisionSpec.from_string()
187
symbol_versioning.warn('Creating a RevisionSpec directly has'
188
' been deprecated in version 0.11. Use'
189
' RevisionSpec.from_string()'
191
DeprecationWarning, stacklevel=2)
192
self.user_spec = spec
193
if self.prefix and spec.startswith(self.prefix):
194
spec = spec[len(self.prefix):]
197
def _match_on(self, branch, revs):
198
trace.mutter('Returning RevisionSpec._match_on: None')
199
return RevisionInfo(branch, None, None)
201
def _match_on_and_check(self, branch, revs):
202
info = self._match_on(branch, revs)
205
elif info == (None, None):
206
# special case - nothing supplied
209
raise errors.InvalidRevisionSpec(self.user_spec, branch)
211
raise errors.InvalidRevisionSpec(self.spec, branch)
213
def in_history(self, branch):
215
if self.wants_revision_history:
216
revs = branch.revision_history()
220
# this should never trigger.
221
# TODO: make it a deprecated code path. RBC 20060928
223
return self._match_on_and_check(branch, revs)
225
# FIXME: in_history is somewhat broken,
226
# it will return non-history revisions in many
227
# circumstances. The expected facility is that
228
# in_history only returns revision-history revs,
229
# in_store returns any rev. RBC 20051010
230
# aliases for now, when we fix the core logic, then they
231
# will do what you expect.
232
in_store = in_history
235
def as_revision_id(self, context_branch):
236
"""Return just the revision_id for this revisions spec.
238
Some revision specs require a context_branch to be able to determine
239
their value. Not all specs will make use of it.
241
return self._as_revision_id(context_branch)
243
def _as_revision_id(self, context_branch):
244
"""Implementation of as_revision_id()
246
Classes should override this function to provide appropriate
247
functionality. The default is to just call '.in_history().rev_id'
249
return self.in_history(context_branch).rev_id
251
def as_tree(self, context_branch):
252
"""Return the tree object for this revisions spec.
254
Some revision specs require a context_branch to be able to determine
255
the revision id and access the repository. Not all specs will make
258
return self._as_tree(context_branch)
260
def _as_tree(self, context_branch):
261
"""Implementation of as_tree().
263
Classes should override this function to provide appropriate
264
functionality. The default is to just call '.as_revision_id()'
265
and get the revision tree from context_branch's repository.
267
revision_id = self.as_revision_id(context_branch)
268
return context_branch.repository.revision_tree(revision_id)
271
# this is mostly for helping with testing
272
return '<%s %s>' % (self.__class__.__name__,
275
def needs_branch(self):
276
"""Whether this revision spec needs a branch.
278
Set this to False the branch argument of _match_on is not used.
282
def get_branch(self):
283
"""When the revision specifier contains a branch location, return it.
285
Otherwise, return None.
292
class RevisionSpec_dwim(RevisionSpec):
293
"""Provides a DWIMish revision specifier lookup.
295
Note that this does not go in the revspec_registry because by definition
296
there is no prefix to identify it. It's solely called from
297
RevisionSpec.from_string() because the DWIMification happen when _match_on
298
is called so the string describing the revision is kept here until needed.
302
# We don't need to build the revision history ourself, that's delegated to
303
# each revspec we try.
304
wants_revision_history = False
306
def _try_spectype(self, rstype, branch):
307
rs = rstype(self.spec, _internal=True)
308
# Hit in_history to find out if it exists, or we need to try the
310
return rs.in_history(branch)
312
def _match_on(self, branch, revs):
313
"""Run the lookup and see what we can get."""
315
# First, see if it's a revno
317
if _revno_regex is None:
318
_revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
319
if _revno_regex.match(self.spec) is not None:
321
return self._try_spectype(RevisionSpec_revno, branch)
322
except RevisionSpec_revno.dwim_catchable_exceptions:
325
# Next see what has been registered
326
for rs_class in dwim_revspecs:
328
return self._try_spectype(rs_class, branch)
329
except rs_class.dwim_catchable_exceptions:
332
# Well, I dunno what it is. Note that we don't try to keep track of the
333
# first of last exception raised during the DWIM tries as none seems
335
raise errors.InvalidRevisionSpec(self.spec, branch)
338
class RevisionSpec_revno(RevisionSpec):
339
"""Selects a revision using a number."""
341
help_txt = """Selects a revision using a number.
343
Use an integer to specify a revision in the history of the branch.
344
Optionally a branch can be specified. A negative number will count
345
from the end of the branch (-1 is the last revision, -2 the previous
346
one). If the negative number is larger than the branch's history, the
347
first revision is returned.
350
revno:1 -> return the first revision of this branch
351
revno:3:/path/to/branch -> return the 3rd revision of
352
the branch '/path/to/branch'
353
revno:-1 -> The last revision in a branch.
354
-2:http://other/branch -> The second to last revision in the
356
-1000000 -> Most likely the first revision, unless
357
your history is very long.
360
wants_revision_history = False
362
def _match_on(self, branch, revs):
363
"""Lookup a revision by revision number"""
364
branch, revno, revision_id = self._lookup(branch, revs)
365
return RevisionInfo(branch, revno, revision_id)
367
def _lookup(self, branch, revs_or_none):
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, e:
392
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
397
# the user has override the branch to look in.
398
# we need to refresh the revision_history map and
400
from bzrlib.branch import Branch
401
branch = Branch.open(branch_spec)
406
revision_id = branch.dotted_revno_to_revision_id(match_revno,
408
except errors.NoSuchRevision:
409
raise errors.InvalidRevisionSpec(self.user_spec, branch)
411
# there is no traditional 'revno' for dotted-decimal revnos.
412
# so for API compatability 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, revs_or_none)
425
except errors.NoSuchRevision:
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, None)
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:]
444
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, revs)
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
return osutils.safe_revision_id(self.spec, warn=False)
478
class RevisionSpec_last(RevisionSpec):
479
"""Selects the nth revision from the end."""
481
help_txt = """Selects the nth revision from the end.
483
Supply a positive number to get the nth revision from the end.
484
This is the same as supplying negative numbers to the 'revno:' spec.
487
last:1 -> return the last revision
488
last:3 -> return the revision 2 before the end.
493
def _match_on(self, branch, revs):
494
revno, revision_id = self._revno_and_revision_id(branch, revs)
495
return RevisionInfo(branch, revno, revision_id)
497
def _revno_and_revision_id(self, context_branch, revs_or_none):
498
last_revno, last_revision_id = context_branch.last_revision_info()
502
raise errors.NoCommits(context_branch)
503
return last_revno, last_revision_id
506
offset = int(self.spec)
507
except ValueError, e:
508
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
511
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
512
'you must supply a positive value')
514
revno = last_revno - offset + 1
516
revision_id = context_branch.get_rev_id(revno, revs_or_none)
517
except errors.NoSuchRevision:
518
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
519
return revno, revision_id
521
def _as_revision_id(self, context_branch):
522
# We compute the revno as part of the process, but we don't really care
524
revno, revision_id = self._revno_and_revision_id(context_branch, None)
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:
563
revision_id = revision.NULL_REVISION
565
revision_id = rev.parent_ids[0]
567
revno = revs.index(revision_id) + 1
573
revision_id = branch.get_rev_id(revno, revs)
574
except errors.NoSuchRevision:
575
raise errors.InvalidRevisionSpec(self.user_spec,
577
return RevisionInfo(branch, revno, revision_id)
579
def _as_revision_id(self, context_branch):
580
base_revspec = RevisionSpec.from_string(self.spec)
581
base_revision_id = base_revspec.as_revision_id(context_branch)
582
if base_revision_id == revision.NULL_REVISION:
583
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
584
'cannot go before the null: revision')
585
context_repo = context_branch.repository
586
context_repo.lock_read()
588
parent_map = context_repo.get_parent_map([base_revision_id])
590
context_repo.unlock()
591
if base_revision_id not in parent_map:
592
# Ghost, or unknown revision id
593
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
594
'cannot find the matching revision')
595
parents = parent_map[base_revision_id]
597
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
598
'No parents for revision.')
603
class RevisionSpec_tag(RevisionSpec):
604
"""Select a revision identified by tag name"""
606
help_txt = """Selects a revision identified by a tag name.
608
Tags are stored in the branch and created by the 'tag' command.
612
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
614
def _match_on(self, branch, revs):
615
# Can raise tags not supported, NoSuchTag, etc
616
return RevisionInfo.from_revision_id(branch,
617
branch.tags.lookup_tag(self.spec),
620
def _as_revision_id(self, context_branch):
621
return context_branch.tags.lookup_tag(self.spec)
625
class _RevListToTimestamps(object):
626
"""This takes a list of revisions, and allows you to bisect by date"""
628
__slots__ = ['revs', 'branch']
630
def __init__(self, revs, branch):
634
def __getitem__(self, index):
635
"""Get the date of the index'd item"""
636
r = self.branch.repository.get_revision(self.revs[index])
637
# TODO: Handle timezone.
638
return datetime.datetime.fromtimestamp(r.timestamp)
641
return len(self.revs)
644
class RevisionSpec_date(RevisionSpec):
645
"""Selects a revision on the basis of a datestamp."""
647
help_txt = """Selects a revision on the basis of a datestamp.
649
Supply a datestamp to select the first revision that matches the date.
650
Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
651
Matches the first entry after a given date (either at midnight or
652
at a specified time).
654
One way to display all the changes since yesterday would be::
656
bzr log -r date:yesterday..
660
date:yesterday -> select the first revision since yesterday
661
date:2006-08-14,17:10:14 -> select the first revision after
662
August 14th, 2006 at 5:10pm.
665
_date_re = re.compile(
666
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
668
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
671
def _match_on(self, branch, revs):
672
"""Spec for date revisions:
674
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
675
matches the first entry after a given date (either at midnight or
676
at a specified time).
678
# XXX: This doesn't actually work
679
# So the proper way of saying 'give me all entries for today' is:
680
# -r date:yesterday..date:today
681
today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
682
if self.spec.lower() == 'yesterday':
683
dt = today - datetime.timedelta(days=1)
684
elif self.spec.lower() == 'today':
686
elif self.spec.lower() == 'tomorrow':
687
dt = today + datetime.timedelta(days=1)
689
m = self._date_re.match(self.spec)
690
if not m or (not m.group('date') and not m.group('time')):
691
raise errors.InvalidRevisionSpec(self.user_spec,
692
branch, 'invalid date')
696
year = int(m.group('year'))
697
month = int(m.group('month'))
698
day = int(m.group('day'))
705
hour = int(m.group('hour'))
706
minute = int(m.group('minute'))
707
if m.group('second'):
708
second = int(m.group('second'))
712
hour, minute, second = 0,0,0
714
raise errors.InvalidRevisionSpec(self.user_spec,
715
branch, 'invalid date')
717
dt = datetime.datetime(year=year, month=month, day=day,
718
hour=hour, minute=minute, second=second)
721
rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
725
raise errors.InvalidRevisionSpec(self.user_spec, branch)
727
return RevisionInfo(branch, rev + 1)
731
class RevisionSpec_ancestor(RevisionSpec):
732
"""Selects a common ancestor with a second branch."""
734
help_txt = """Selects a common ancestor with a second branch.
736
Supply the path to a branch to select the common ancestor.
738
The common ancestor is the last revision that existed in both
739
branches. Usually this is the branch point, but it could also be
740
a revision that was merged.
742
This is frequently used with 'diff' to return all of the changes
743
that your branch introduces, while excluding the changes that you
744
have not merged from the remote branch.
748
ancestor:/path/to/branch
749
$ bzr diff -r ancestor:../../mainline/branch
753
def _match_on(self, branch, revs):
754
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
755
return self._find_revision_info(branch, self.spec)
757
def _as_revision_id(self, context_branch):
758
return self._find_revision_id(context_branch, self.spec)
761
def _find_revision_info(branch, other_location):
762
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
765
revno = branch.revision_id_to_revno(revision_id)
766
except errors.NoSuchRevision:
768
return RevisionInfo(branch, revno, revision_id)
771
def _find_revision_id(branch, other_location):
772
from bzrlib.branch import Branch
776
revision_a = revision.ensure_null(branch.last_revision())
777
if revision_a == revision.NULL_REVISION:
778
raise errors.NoCommits(branch)
779
if other_location == '':
780
other_location = branch.get_parent()
781
other_branch = Branch.open(other_location)
782
other_branch.lock_read()
784
revision_b = revision.ensure_null(other_branch.last_revision())
785
if revision_b == revision.NULL_REVISION:
786
raise errors.NoCommits(other_branch)
787
graph = branch.repository.get_graph(other_branch.repository)
788
rev_id = graph.find_unique_lca(revision_a, revision_b)
790
other_branch.unlock()
791
if rev_id == revision.NULL_REVISION:
792
raise errors.NoCommonAncestor(revision_a, revision_b)
800
class RevisionSpec_branch(RevisionSpec):
801
"""Selects the last revision of a specified branch."""
803
help_txt = """Selects the last revision of a specified branch.
805
Supply the path to a branch to select its last revision.
809
branch:/path/to/branch
812
dwim_catchable_exceptions = (errors.NotBranchError,)
814
def _match_on(self, branch, revs):
815
from bzrlib.branch import Branch
816
other_branch = Branch.open(self.spec)
817
revision_b = other_branch.last_revision()
818
if revision_b in (None, revision.NULL_REVISION):
819
raise errors.NoCommits(other_branch)
821
branch = other_branch
824
# pull in the remote revisions so we can diff
825
branch.fetch(other_branch, revision_b)
826
except errors.ReadOnlyError:
827
branch = other_branch
829
revno = branch.revision_id_to_revno(revision_b)
830
except errors.NoSuchRevision:
832
return RevisionInfo(branch, revno, revision_b)
834
def _as_revision_id(self, context_branch):
835
from bzrlib.branch import Branch
836
other_branch = Branch.open(self.spec)
837
last_revision = other_branch.last_revision()
838
last_revision = revision.ensure_null(last_revision)
839
context_branch.fetch(other_branch, last_revision)
840
if last_revision == revision.NULL_REVISION:
841
raise errors.NoCommits(other_branch)
844
def _as_tree(self, context_branch):
845
from bzrlib.branch import Branch
846
other_branch = Branch.open(self.spec)
847
last_revision = other_branch.last_revision()
848
last_revision = revision.ensure_null(last_revision)
849
if last_revision == revision.NULL_REVISION:
850
raise errors.NoCommits(other_branch)
851
return other_branch.repository.revision_tree(last_revision)
853
def needs_branch(self):
856
def get_branch(self):
861
class RevisionSpec_submit(RevisionSpec_ancestor):
862
"""Selects a common ancestor with a submit branch."""
864
help_txt = """Selects a common ancestor with the submit branch.
866
Diffing against this shows all the changes that were made in this branch,
867
and is a good predictor of what merge will do. The submit branch is
868
used by the bundle and merge directive commands. If no submit branch
869
is specified, the parent branch is used instead.
871
The common ancestor is the last revision that existed in both
872
branches. Usually this is the branch point, but it could also be
873
a revision that was merged.
877
$ bzr diff -r submit:
882
def _get_submit_location(self, branch):
883
submit_location = branch.get_submit_branch()
884
location_type = 'submit branch'
885
if submit_location is None:
886
submit_location = branch.get_parent()
887
location_type = 'parent branch'
888
if submit_location is None:
889
raise errors.NoSubmitBranch(branch)
890
trace.note('Using %s %s', location_type, submit_location)
891
return submit_location
893
def _match_on(self, branch, revs):
894
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
895
return self._find_revision_info(branch,
896
self._get_submit_location(branch))
898
def _as_revision_id(self, context_branch):
899
return self._find_revision_id(context_branch,
900
self._get_submit_location(context_branch))
903
class RevisionSpec_annotate(RevisionIDSpec):
907
help_txt = """Select the revision that last modified the specified line.
909
Select the revision that last modified the specified line. Line is
910
specified as path:number. Path is a relative path to the file. Numbers
911
start at 1, and are relative to the current version, not the last-
912
committed version of the file.
915
def _raise_invalid(self, numstring, context_branch):
916
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
917
'No such line: %s' % numstring)
919
def _as_revision_id(self, context_branch):
920
path, numstring = self.spec.rsplit(':', 1)
922
index = int(numstring) - 1
924
self._raise_invalid(numstring, context_branch)
925
tree, file_path = workingtree.WorkingTree.open_containing(path)
928
file_id = tree.path2id(file_path)
930
raise errors.InvalidRevisionSpec(self.user_spec,
931
context_branch, "File '%s' is not versioned." %
933
revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
937
revision_id = revision_ids[index]
939
self._raise_invalid(numstring, context_branch)
940
if revision_id == revision.CURRENT_REVISION:
941
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
942
'Line %s has not been committed.' % numstring)
946
class RevisionSpec_mainline(RevisionIDSpec):
948
help_txt = """Select mainline revision that merged the specified revision.
950
Select the revision that merged the specified revision into mainline.
955
def _as_revision_id(self, context_branch):
956
revspec = RevisionSpec.from_string(self.spec)
957
if revspec.get_branch() is None:
958
spec_branch = context_branch
960
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
961
revision_id = revspec.as_revision_id(spec_branch)
962
graph = context_branch.repository.get_graph()
963
result = graph.find_lefthand_merger(revision_id,
964
context_branch.last_revision())
966
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
970
# The order in which we want to DWIM a revision spec without any prefix.
971
# revno is always tried first and isn't listed here, this is used by
972
# RevisionSpec_dwim._match_on
974
RevisionSpec_tag, # Let's try for a tag
975
RevisionSpec_revid, # Maybe it's a revid?
976
RevisionSpec_date, # Perhaps a date?
977
RevisionSpec_branch, # OK, last try, maybe it's a branch
981
revspec_registry = registry.Registry()
982
def _register_revspec(revspec):
983
revspec_registry.register(revspec.prefix, revspec)
985
_register_revspec(RevisionSpec_revno)
986
_register_revspec(RevisionSpec_revid)
987
_register_revspec(RevisionSpec_last)
988
_register_revspec(RevisionSpec_before)
989
_register_revspec(RevisionSpec_tag)
990
_register_revspec(RevisionSpec_date)
991
_register_revspec(RevisionSpec_ancestor)
992
_register_revspec(RevisionSpec_branch)
993
_register_revspec(RevisionSpec_submit)
994
_register_revspec(RevisionSpec_annotate)
995
_register_revspec(RevisionSpec_mainline)