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