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 InvalidRevisionSpec(errors.BzrError):
47
_fmt = ("Requested revision: '%(spec)s' does not exist in branch:"
48
" %(branch_url)s%(extra)s")
50
def __init__(self, spec, branch, extra=None):
51
errors.BzrError.__init__(self, branch=branch, spec=spec)
52
self.branch_url = getattr(branch, 'user_url', str(branch))
54
self.extra = '\n' + str(extra)
59
class RevisionInfo(object):
60
"""The results of applying a revision specification to a branch."""
62
help_txt = """The results of applying a revision specification to a branch.
64
An instance has two useful attributes: revno, and rev_id.
66
They can also be accessed as spec[0] and spec[1] respectively,
67
so that you can write code like:
68
revno, rev_id = RevisionSpec(branch, spec)
69
although this is probably going to be deprecated later.
71
This class exists mostly to be the return value of a RevisionSpec,
72
so that you can access the member you're interested in (number or id)
73
or treat the result as a tuple.
76
def __init__(self, branch, revno=None, rev_id=None):
78
self._has_revno = (revno is not None)
81
if self.rev_id is None and self._revno is not None:
82
# allow caller to be lazy
83
self.rev_id = branch.get_rev_id(self._revno)
87
if not self._has_revno and self.rev_id is not None:
89
self._revno = self.branch.revision_id_to_revno(self.rev_id)
90
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
92
self._has_revno = True
96
if self.rev_id is None:
98
# TODO: otherwise, it should depend on how I was built -
99
# if it's in_history(branch), then check revision_history(),
100
# if it's in_store(branch), do the check below
101
return self.branch.repository.has_revision(self.rev_id)
103
__nonzero__ = __bool__
108
def __getitem__(self, index):
113
raise IndexError(index)
116
return self.branch.repository.get_revision(self.rev_id)
118
def __eq__(self, other):
119
if type(other) not in (tuple, list, type(self)):
121
if isinstance(other, type(self)) and self.branch is not other.branch:
123
return tuple(self) == tuple(other)
126
return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
127
self.revno, self.rev_id, self.branch)
130
def from_revision_id(branch, revision_id):
131
"""Construct a RevisionInfo given just the id.
133
Use this if you don't know or care what the revno is.
135
return RevisionInfo(branch, revno=None, rev_id=revision_id)
138
class RevisionSpec(object):
139
"""A parsed revision specification."""
141
help_txt = """A parsed revision specification.
143
A revision specification is a string, which may be unambiguous about
144
what it represents by giving a prefix like 'date:' or 'revid:' etc,
145
or it may have no prefix, in which case it's tried against several
146
specifier types in sequence to determine what the user meant.
148
Revision specs are an UI element, and they have been moved out
149
of the branch class to leave "back-end" classes unaware of such
150
details. Code that gets a revno or rev_id from other code should
151
not be using revision specs - revnos and revision ids are the
152
accepted ways to refer to revisions internally.
154
(Equivalent to the old Branch method get_revision_info())
158
dwim_catchable_exceptions = (InvalidRevisionSpec,)
159
"""Exceptions that RevisionSpec_dwim._match_on will catch.
161
If the revspec is part of ``dwim_revspecs``, it may be tried with an
162
invalid revspec and raises some exception. The exceptions mentioned here
163
will not be reported to the user but simply ignored without stopping the
168
def from_string(spec):
169
"""Parse a revision spec string into a RevisionSpec object.
171
:param spec: A string specified by the user
172
:return: A RevisionSpec object that understands how to parse the
176
return RevisionSpec(None, _internal=True)
177
if not isinstance(spec, (str, text_type)):
178
raise TypeError("revision spec needs to be text")
179
match = revspec_registry.get_prefix(spec)
180
if match is not None:
181
spectype, specsuffix = match
182
trace.mutter('Returning RevisionSpec %s for %s',
183
spectype.__name__, spec)
184
return spectype(spec, _internal=True)
186
# Otherwise treat it as a DWIM, build the RevisionSpec object and
187
# wait for _match_on to be called.
188
return RevisionSpec_dwim(spec, _internal=True)
190
def __init__(self, spec, _internal=False):
191
"""Create a RevisionSpec referring to the Null revision.
193
:param spec: The original spec supplied by the user
194
:param _internal: Used to ensure that RevisionSpec is not being
195
called directly. Only from RevisionSpec.from_string()
198
raise AssertionError(
199
'Creating a RevisionSpec directly is not supported. '
200
'Use RevisionSpec.from_string() instead.')
201
self.user_spec = spec
202
if self.prefix and spec.startswith(self.prefix):
203
spec = spec[len(self.prefix):]
206
def _match_on(self, branch, revs):
207
trace.mutter('Returning RevisionSpec._match_on: None')
208
return RevisionInfo(branch, None, None)
210
def _match_on_and_check(self, branch, revs):
211
info = self._match_on(branch, revs)
214
elif info == (None, None):
215
# special case - nothing supplied
218
raise InvalidRevisionSpec(self.user_spec, branch)
220
raise InvalidRevisionSpec(self.spec, branch)
222
def in_history(self, branch):
223
return self._match_on_and_check(branch, revs=None)
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.
303
_revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
305
# The revspecs to try
306
_possible_revspecs = []
308
def _try_spectype(self, rstype, branch):
309
rs = rstype(self.spec, _internal=True)
310
# Hit in_history to find out if it exists, or we need to try the
312
return rs.in_history(branch)
314
def _match_on(self, branch, revs):
315
"""Run the lookup and see what we can get."""
317
# First, see if it's a revno
318
if self._revno_regex.match(self.spec) is not None:
320
return self._try_spectype(RevisionSpec_revno, branch)
321
except RevisionSpec_revno.dwim_catchable_exceptions:
324
# Next see what has been registered
325
for objgetter in self._possible_revspecs:
326
rs_class = objgetter.get_obj()
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 InvalidRevisionSpec(self.spec, branch)
338
def append_possible_revspec(cls, revspec):
339
"""Append a possible DWIM revspec.
341
:param revspec: Revision spec to try.
343
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
346
def append_possible_lazy_revspec(cls, module_name, member_name):
347
"""Append a possible lazily loaded DWIM revspec.
349
:param module_name: Name of the module with the revspec
350
:param member_name: Name of the revspec within the module
352
cls._possible_revspecs.append(
353
registry._LazyObjectGetter(module_name, member_name))
356
class RevisionSpec_revno(RevisionSpec):
357
"""Selects a revision using a number."""
359
help_txt = """Selects a revision using a number.
361
Use an integer to specify a revision in the history of the branch.
362
Optionally a branch can be specified. A negative number will count
363
from the end of the branch (-1 is the last revision, -2 the previous
364
one). If the negative number is larger than the branch's history, the
365
first revision is returned.
368
revno:1 -> return the first revision of this branch
369
revno:3:/path/to/branch -> return the 3rd revision of
370
the branch '/path/to/branch'
371
revno:-1 -> The last revision in a branch.
372
-2:http://other/branch -> The second to last revision in the
374
-1000000 -> Most likely the first revision, unless
375
your history is very long.
379
def _match_on(self, branch, revs):
380
"""Lookup a revision by revision number"""
381
branch, revno, revision_id = self._lookup(branch)
382
return RevisionInfo(branch, revno, revision_id)
384
def _lookup(self, branch):
385
loc = self.spec.find(':')
387
revno_spec = self.spec
390
revno_spec = self.spec[:loc]
391
branch_spec = self.spec[loc + 1:]
395
raise InvalidRevisionSpec(
396
self.user_spec, branch, 'cannot have an empty revno and no branch')
400
revno = int(revno_spec)
403
# dotted decimal. This arguably should not be here
404
# but the from_string method is a little primitive
405
# right now - RBC 20060928
407
match_revno = tuple((int(number)
408
for number in revno_spec.split('.')))
409
except ValueError as e:
410
raise InvalidRevisionSpec(self.user_spec, branch, e)
415
# the user has overriden the branch to look in.
416
branch = _mod_branch.Branch.open(branch_spec)
420
revision_id = branch.dotted_revno_to_revision_id(match_revno,
422
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
423
raise InvalidRevisionSpec(self.user_spec, branch)
425
# there is no traditional 'revno' for dotted-decimal revnos.
426
# so for API compatibility we return None.
427
return branch, None, revision_id
429
last_revno, last_revision_id = branch.last_revision_info()
431
# if get_rev_id supported negative revnos, there would not be a
432
# need for this special case.
433
if (-revno) >= last_revno:
436
revno = last_revno + revno + 1
438
revision_id = branch.get_rev_id(revno)
439
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
440
raise InvalidRevisionSpec(self.user_spec, branch)
441
return branch, revno, revision_id
443
def _as_revision_id(self, context_branch):
444
# We would have the revno here, but we don't really care
445
branch, revno, revision_id = self._lookup(context_branch)
448
def needs_branch(self):
449
return self.spec.find(':') == -1
451
def get_branch(self):
452
if self.spec.find(':') == -1:
455
return self.spec[self.spec.find(':') + 1:]
459
RevisionSpec_int = RevisionSpec_revno
462
class RevisionIDSpec(RevisionSpec):
464
def _match_on(self, branch, revs):
465
revision_id = self.as_revision_id(branch)
466
return RevisionInfo.from_revision_id(branch, revision_id)
469
class RevisionSpec_revid(RevisionIDSpec):
470
"""Selects a revision using the revision id."""
472
help_txt = """Selects a revision using the revision id.
474
Supply a specific revision id, that can be used to specify any
475
revision id in the ancestry of the branch.
476
Including merges, and pending merges.
479
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
484
def _as_revision_id(self, context_branch):
485
# self.spec comes straight from parsing the command line arguments,
486
# so we expect it to be a Unicode string. Switch it to the internal
488
if isinstance(self.spec, text_type):
489
return cache_utf8.encode(self.spec)
493
class RevisionSpec_last(RevisionSpec):
494
"""Selects the nth revision from the end."""
496
help_txt = """Selects the nth revision from the end.
498
Supply a positive number to get the nth revision from the end.
499
This is the same as supplying negative numbers to the 'revno:' spec.
502
last:1 -> return the last revision
503
last:3 -> return the revision 2 before the end.
508
def _match_on(self, branch, revs):
509
revno, revision_id = self._revno_and_revision_id(branch)
510
return RevisionInfo(branch, revno, revision_id)
512
def _revno_and_revision_id(self, context_branch):
513
last_revno, last_revision_id = context_branch.last_revision_info()
517
raise errors.NoCommits(context_branch)
518
return last_revno, last_revision_id
521
offset = int(self.spec)
522
except ValueError as e:
523
raise InvalidRevisionSpec(self.user_spec, context_branch, e)
526
raise InvalidRevisionSpec(self.user_spec, context_branch,
527
'you must supply a positive value')
529
revno = last_revno - offset + 1
531
revision_id = context_branch.get_rev_id(revno)
532
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
533
raise InvalidRevisionSpec(self.user_spec, context_branch)
534
return revno, revision_id
536
def _as_revision_id(self, context_branch):
537
# We compute the revno as part of the process, but we don't really care
539
revno, revision_id = self._revno_and_revision_id(context_branch)
543
class RevisionSpec_before(RevisionSpec):
544
"""Selects the parent of the revision specified."""
546
help_txt = """Selects the parent of the revision specified.
548
Supply any revision spec to return the parent of that revision. This is
549
mostly useful when inspecting revisions that are not in the revision history
552
It is an error to request the parent of the null revision (before:0).
556
before:1913 -> Return the parent of revno 1913 (revno 1912)
557
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
559
bzr diff -r before:1913..1913
560
-> Find the changes between revision 1913 and its parent (1912).
561
(What changes did revision 1913 introduce).
562
This is equivalent to: bzr diff -c 1913
567
def _match_on(self, branch, revs):
568
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
570
raise InvalidRevisionSpec(self.user_spec, branch, 'cannot go before the null: revision')
572
# We need to use the repository history here
573
rev = branch.repository.get_revision(r.rev_id)
574
if not rev.parent_ids:
575
revision_id = revision.NULL_REVISION
577
revision_id = rev.parent_ids[0]
582
revision_id = branch.get_rev_id(revno, revs)
583
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
584
raise InvalidRevisionSpec(self.user_spec, branch)
585
return RevisionInfo(branch, revno, revision_id)
587
def _as_revision_id(self, context_branch):
588
base_revision_id = RevisionSpec.from_string(
589
self.spec)._as_revision_id(context_branch)
590
if base_revision_id == revision.NULL_REVISION:
591
raise InvalidRevisionSpec(
592
self.user_spec, context_branch,
593
'cannot go before the null: revision')
594
context_repo = context_branch.repository
595
with context_repo.lock_read():
596
parent_map = context_repo.get_parent_map([base_revision_id])
597
if base_revision_id not in parent_map:
598
# Ghost, or unknown revision id
599
raise InvalidRevisionSpec(
600
self.user_spec, context_branch, 'cannot find the matching revision')
601
parents = parent_map[base_revision_id]
603
raise InvalidRevisionSpec(
604
self.user_spec, context_branch, 'No parents for revision.')
608
class RevisionSpec_tag(RevisionSpec):
609
"""Select a revision identified by tag name"""
611
help_txt = """Selects a revision identified by a tag name.
613
Tags are stored in the branch and created by the 'tag' command.
617
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
619
def _match_on(self, branch, revs):
620
# Can raise tags not supported, NoSuchTag, etc
621
return RevisionInfo.from_revision_id(branch,
622
branch.tags.lookup_tag(self.spec))
624
def _as_revision_id(self, context_branch):
625
return context_branch.tags.lookup_tag(self.spec)
628
class _RevListToTimestamps(object):
629
"""This takes a list of revisions, and allows you to bisect by date"""
631
__slots__ = ['branch']
633
def __init__(self, branch):
636
def __getitem__(self, index):
637
"""Get the date of the index'd item"""
638
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
639
# TODO: Handle timezone.
640
return datetime.datetime.fromtimestamp(r.timestamp)
643
return self.branch.revno()
646
class RevisionSpec_date(RevisionSpec):
647
"""Selects a revision on the basis of a datestamp."""
649
help_txt = """Selects a revision on the basis of a datestamp.
651
Supply a datestamp to select the first revision that matches the date.
652
Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
653
Matches the first entry after a given date (either at midnight or
654
at a specified time).
656
One way to display all the changes since yesterday would be::
658
brz log -r date:yesterday..
662
date:yesterday -> select the first revision since yesterday
663
date:2006-08-14,17:10:14 -> select the first revision after
664
August 14th, 2006 at 5:10pm.
667
_date_regex = lazy_regex.lazy_compile(
668
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
670
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
673
def _match_on(self, branch, revs):
674
"""Spec for date revisions:
676
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
677
matches the first entry after a given date (either at midnight or
678
at a specified time).
680
# XXX: This doesn't actually work
681
# So the proper way of saying 'give me all entries for today' is:
682
# -r date:yesterday..date:today
683
today = datetime.datetime.fromordinal(
684
datetime.date.today().toordinal())
685
if self.spec.lower() == 'yesterday':
686
dt = today - datetime.timedelta(days=1)
687
elif self.spec.lower() == 'today':
689
elif self.spec.lower() == 'tomorrow':
690
dt = today + datetime.timedelta(days=1)
692
m = self._date_regex.match(self.spec)
693
if not m or (not m.group('date') and not m.group('time')):
694
raise InvalidRevisionSpec(self.user_spec, branch, 'invalid date')
698
year = int(m.group('year'))
699
month = int(m.group('month'))
700
day = int(m.group('day'))
707
hour = int(m.group('hour'))
708
minute = int(m.group('minute'))
709
if m.group('second'):
710
second = int(m.group('second'))
714
hour, minute, second = 0, 0, 0
716
raise InvalidRevisionSpec(self.user_spec, branch, 'invalid date')
718
dt = datetime.datetime(year=year, month=month, day=day,
719
hour=hour, minute=minute, second=second)
720
with branch.lock_read():
721
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
722
if rev == branch.revno():
723
raise InvalidRevisionSpec(self.user_spec, branch)
724
return RevisionInfo(branch, rev)
727
class RevisionSpec_ancestor(RevisionSpec):
728
"""Selects a common ancestor with a second branch."""
730
help_txt = """Selects a common ancestor with a second branch.
732
Supply the path to a branch to select the common ancestor.
734
The common ancestor is the last revision that existed in both
735
branches. Usually this is the branch point, but it could also be
736
a revision that was merged.
738
This is frequently used with 'diff' to return all of the changes
739
that your branch introduces, while excluding the changes that you
740
have not merged from the remote branch.
744
ancestor:/path/to/branch
745
$ bzr diff -r ancestor:../../mainline/branch
749
def _match_on(self, branch, revs):
750
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
751
return self._find_revision_info(branch, self.spec)
753
def _as_revision_id(self, context_branch):
754
return self._find_revision_id(context_branch, self.spec)
757
def _find_revision_info(branch, other_location):
758
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
760
return RevisionInfo(branch, None, revision_id)
763
def _find_revision_id(branch, other_location):
764
from .branch import Branch
766
with branch.lock_read():
767
revision_a = revision.ensure_null(branch.last_revision())
768
if revision_a == revision.NULL_REVISION:
769
raise errors.NoCommits(branch)
770
if other_location == '':
771
other_location = branch.get_parent()
772
other_branch = Branch.open(other_location)
773
with other_branch.lock_read():
774
revision_b = revision.ensure_null(other_branch.last_revision())
775
if revision_b == revision.NULL_REVISION:
776
raise errors.NoCommits(other_branch)
777
graph = branch.repository.get_graph(other_branch.repository)
778
rev_id = graph.find_unique_lca(revision_a, revision_b)
779
if rev_id == revision.NULL_REVISION:
780
raise errors.NoCommonAncestor(revision_a, revision_b)
784
class RevisionSpec_branch(RevisionSpec):
785
"""Selects the last revision of a specified branch."""
787
help_txt = """Selects the last revision of a specified branch.
789
Supply the path to a branch to select its last revision.
793
branch:/path/to/branch
796
dwim_catchable_exceptions = (errors.NotBranchError,)
798
def _match_on(self, branch, revs):
799
from .branch import Branch
800
other_branch = Branch.open(self.spec)
801
revision_b = other_branch.last_revision()
802
if revision_b in (None, revision.NULL_REVISION):
803
raise errors.NoCommits(other_branch)
805
branch = other_branch
808
# pull in the remote revisions so we can diff
809
branch.fetch(other_branch, revision_b)
810
except errors.ReadOnlyError:
811
branch = other_branch
812
return RevisionInfo(branch, None, revision_b)
814
def _as_revision_id(self, context_branch):
815
from .branch import Branch
816
other_branch = Branch.open(self.spec)
817
last_revision = other_branch.last_revision()
818
last_revision = revision.ensure_null(last_revision)
819
context_branch.fetch(other_branch, last_revision)
820
if last_revision == revision.NULL_REVISION:
821
raise errors.NoCommits(other_branch)
824
def _as_tree(self, context_branch):
825
from .branch import Branch
826
other_branch = Branch.open(self.spec)
827
last_revision = other_branch.last_revision()
828
last_revision = revision.ensure_null(last_revision)
829
if last_revision == revision.NULL_REVISION:
830
raise errors.NoCommits(other_branch)
831
return other_branch.repository.revision_tree(last_revision)
833
def needs_branch(self):
836
def get_branch(self):
840
class RevisionSpec_submit(RevisionSpec_ancestor):
841
"""Selects a common ancestor with a submit branch."""
843
help_txt = """Selects a common ancestor with the submit branch.
845
Diffing against this shows all the changes that were made in this branch,
846
and is a good predictor of what merge will do. The submit branch is
847
used by the bundle and merge directive commands. If no submit branch
848
is specified, the parent branch is used instead.
850
The common ancestor is the last revision that existed in both
851
branches. Usually this is the branch point, but it could also be
852
a revision that was merged.
856
$ bzr diff -r submit:
861
def _get_submit_location(self, branch):
862
submit_location = branch.get_submit_branch()
863
location_type = 'submit branch'
864
if submit_location is None:
865
submit_location = branch.get_parent()
866
location_type = 'parent branch'
867
if submit_location is None:
868
raise errors.NoSubmitBranch(branch)
869
trace.note(gettext('Using {0} {1}').format(location_type,
871
return submit_location
873
def _match_on(self, branch, revs):
874
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
875
return self._find_revision_info(branch,
876
self._get_submit_location(branch))
878
def _as_revision_id(self, context_branch):
879
return self._find_revision_id(context_branch,
880
self._get_submit_location(context_branch))
883
class RevisionSpec_annotate(RevisionIDSpec):
887
help_txt = """Select the revision that last modified the specified line.
889
Select the revision that last modified the specified line. Line is
890
specified as path:number. Path is a relative path to the file. Numbers
891
start at 1, and are relative to the current version, not the last-
892
committed version of the file.
895
def _raise_invalid(self, numstring, context_branch):
896
raise InvalidRevisionSpec(self.user_spec, context_branch,
897
'No such line: %s' % numstring)
899
def _as_revision_id(self, context_branch):
900
path, numstring = self.spec.rsplit(':', 1)
902
index = int(numstring) - 1
904
self._raise_invalid(numstring, context_branch)
905
tree, file_path = workingtree.WorkingTree.open_containing(path)
906
with tree.lock_read():
907
if not tree.has_filename(file_path):
908
raise InvalidRevisionSpec(
909
self.user_spec, context_branch,
910
"File '%s' is not versioned." % file_path)
911
revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
913
revision_id = revision_ids[index]
915
self._raise_invalid(numstring, context_branch)
916
if revision_id == revision.CURRENT_REVISION:
917
raise InvalidRevisionSpec(
918
self.user_spec, context_branch,
919
'Line %s has not been committed.' % numstring)
923
class RevisionSpec_mainline(RevisionIDSpec):
925
help_txt = """Select mainline revision that merged the specified revision.
927
Select the revision that merged the specified revision into mainline.
932
def _as_revision_id(self, context_branch):
933
revspec = RevisionSpec.from_string(self.spec)
934
if revspec.get_branch() is None:
935
spec_branch = context_branch
937
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
938
revision_id = revspec.as_revision_id(spec_branch)
939
graph = context_branch.repository.get_graph()
940
result = graph.find_lefthand_merger(revision_id,
941
context_branch.last_revision())
943
raise InvalidRevisionSpec(self.user_spec, context_branch)
947
# The order in which we want to DWIM a revision spec without any prefix.
948
# revno is always tried first and isn't listed here, this is used by
949
# RevisionSpec_dwim._match_on
950
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
951
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
952
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
953
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
955
revspec_registry = registry.Registry()
958
def _register_revspec(revspec):
959
revspec_registry.register(revspec.prefix, revspec)
962
_register_revspec(RevisionSpec_revno)
963
_register_revspec(RevisionSpec_revid)
964
_register_revspec(RevisionSpec_last)
965
_register_revspec(RevisionSpec_before)
966
_register_revspec(RevisionSpec_tag)
967
_register_revspec(RevisionSpec_date)
968
_register_revspec(RevisionSpec_ancestor)
969
_register_revspec(RevisionSpec_branch)
970
_register_revspec(RevisionSpec_submit)
971
_register_revspec(RevisionSpec_annotate)
972
_register_revspec(RevisionSpec_mainline)