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
18
from .lazy_import import lazy_import
19
lazy_import(globals(), """
24
branch as _mod_branch,
29
from breezy.i18n import gettext
40
class InvalidRevisionSpec(errors.BzrError):
42
_fmt = ("Requested revision: '%(spec)s' does not exist in branch:"
43
" %(branch_url)s%(extra)s")
45
def __init__(self, spec, branch, extra=None):
46
errors.BzrError.__init__(self, branch=branch, spec=spec)
47
self.branch_url = getattr(branch, 'user_url', str(branch))
49
self.extra = '\n' + str(extra)
54
class RevisionInfo(object):
55
"""The results of applying a revision specification to a branch."""
57
help_txt = """The results of applying a revision specification to a branch.
59
An instance has two useful attributes: revno, and rev_id.
61
They can also be accessed as spec[0] and spec[1] respectively,
62
so that you can write code like:
63
revno, rev_id = RevisionSpec(branch, spec)
64
although this is probably going to be deprecated later.
66
This class exists mostly to be the return value of a RevisionSpec,
67
so that you can access the member you're interested in (number or id)
68
or treat the result as a tuple.
71
def __init__(self, branch, revno=None, rev_id=None):
73
self._has_revno = (revno is not None)
76
if self.rev_id is None and self._revno is not None:
77
# allow caller to be lazy
78
self.rev_id = branch.get_rev_id(self._revno)
82
if not self._has_revno and self.rev_id is not None:
84
self._revno = self.branch.revision_id_to_revno(self.rev_id)
85
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
87
self._has_revno = True
91
if self.rev_id is None:
93
# TODO: otherwise, it should depend on how I was built -
94
# if it's in_history(branch), then check revision_history(),
95
# if it's in_store(branch), do the check below
96
return self.branch.repository.has_revision(self.rev_id)
98
__nonzero__ = __bool__
103
def __getitem__(self, index):
108
raise IndexError(index)
111
return self.branch.repository.get_revision(self.rev_id)
113
def __eq__(self, other):
114
if type(other) not in (tuple, list, type(self)):
116
if isinstance(other, type(self)) and self.branch is not other.branch:
118
return tuple(self) == tuple(other)
121
return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
122
self.revno, self.rev_id, self.branch)
125
def from_revision_id(branch, revision_id):
126
"""Construct a RevisionInfo given just the id.
128
Use this if you don't know or care what the revno is.
130
return RevisionInfo(branch, revno=None, rev_id=revision_id)
133
class RevisionSpec(object):
134
"""A parsed revision specification."""
136
help_txt = """A parsed revision specification.
138
A revision specification is a string, which may be unambiguous about
139
what it represents by giving a prefix like 'date:' or 'revid:' etc,
140
or it may have no prefix, in which case it's tried against several
141
specifier types in sequence to determine what the user meant.
143
Revision specs are an UI element, and they have been moved out
144
of the branch class to leave "back-end" classes unaware of such
145
details. Code that gets a revno or rev_id from other code should
146
not be using revision specs - revnos and revision ids are the
147
accepted ways to refer to revisions internally.
149
(Equivalent to the old Branch method get_revision_info())
153
dwim_catchable_exceptions = (InvalidRevisionSpec,)
154
"""Exceptions that RevisionSpec_dwim._match_on will catch.
156
If the revspec is part of ``dwim_revspecs``, it may be tried with an
157
invalid revspec and raises some exception. The exceptions mentioned here
158
will not be reported to the user but simply ignored without stopping the
163
def from_string(spec):
164
"""Parse a revision spec string into a RevisionSpec object.
166
:param spec: A string specified by the user
167
:return: A RevisionSpec object that understands how to parse the
171
return RevisionSpec(None, _internal=True)
172
if not isinstance(spec, str):
173
raise TypeError("revision spec needs to be text")
174
match = revspec_registry.get_prefix(spec)
175
if match is not None:
176
spectype, specsuffix = match
177
trace.mutter('Returning RevisionSpec %s for %s',
178
spectype.__name__, spec)
179
return spectype(spec, _internal=True)
181
# Otherwise treat it as a DWIM, build the RevisionSpec object and
182
# wait for _match_on to be called.
183
return RevisionSpec_dwim(spec, _internal=True)
185
def __init__(self, spec, _internal=False):
186
"""Create a RevisionSpec referring to the Null revision.
188
:param spec: The original spec supplied by the user
189
:param _internal: Used to ensure that RevisionSpec is not being
190
called directly. Only from RevisionSpec.from_string()
193
raise AssertionError(
194
'Creating a RevisionSpec directly is not supported. '
195
'Use RevisionSpec.from_string() instead.')
196
self.user_spec = spec
197
if self.prefix and spec.startswith(self.prefix):
198
spec = spec[len(self.prefix):]
201
def _match_on(self, branch, revs):
202
trace.mutter('Returning RevisionSpec._match_on: None')
203
return RevisionInfo(branch, None, None)
205
def _match_on_and_check(self, branch, revs):
206
info = self._match_on(branch, revs)
209
elif info == (None, None):
210
# special case - nothing supplied
213
raise InvalidRevisionSpec(self.user_spec, branch)
215
raise InvalidRevisionSpec(self.spec, branch)
217
def in_history(self, branch):
218
return self._match_on_and_check(branch, revs=None)
220
# FIXME: in_history is somewhat broken,
221
# it will return non-history revisions in many
222
# circumstances. The expected facility is that
223
# in_history only returns revision-history revs,
224
# in_store returns any rev. RBC 20051010
225
# aliases for now, when we fix the core logic, then they
226
# will do what you expect.
227
in_store = in_history
230
def as_revision_id(self, context_branch):
231
"""Return just the revision_id for this revisions spec.
233
Some revision specs require a context_branch to be able to determine
234
their value. Not all specs will make use of it.
236
return self._as_revision_id(context_branch)
238
def _as_revision_id(self, context_branch):
239
"""Implementation of as_revision_id()
241
Classes should override this function to provide appropriate
242
functionality. The default is to just call '.in_history().rev_id'
244
return self.in_history(context_branch).rev_id
246
def as_tree(self, context_branch):
247
"""Return the tree object for this revisions spec.
249
Some revision specs require a context_branch to be able to determine
250
the revision id and access the repository. Not all specs will make
253
return self._as_tree(context_branch)
255
def _as_tree(self, context_branch):
256
"""Implementation of as_tree().
258
Classes should override this function to provide appropriate
259
functionality. The default is to just call '.as_revision_id()'
260
and get the revision tree from context_branch's repository.
262
revision_id = self.as_revision_id(context_branch)
263
return context_branch.repository.revision_tree(revision_id)
266
# this is mostly for helping with testing
267
return '<%s %s>' % (self.__class__.__name__,
270
def needs_branch(self):
271
"""Whether this revision spec needs a branch.
273
Set this to False the branch argument of _match_on is not used.
277
def get_branch(self):
278
"""When the revision specifier contains a branch location, return it.
280
Otherwise, return None.
287
class RevisionSpec_dwim(RevisionSpec):
288
"""Provides a DWIMish revision specifier lookup.
290
Note that this does not go in the revspec_registry because by definition
291
there is no prefix to identify it. It's solely called from
292
RevisionSpec.from_string() because the DWIMification happen when _match_on
293
is called so the string describing the revision is kept here until needed.
298
_revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
300
# The revspecs to try
301
_possible_revspecs = []
303
def _try_spectype(self, rstype, branch):
304
rs = rstype(self.spec, _internal=True)
305
# Hit in_history to find out if it exists, or we need to try the
307
return rs.in_history(branch)
309
def _match_on(self, branch, revs):
310
"""Run the lookup and see what we can get."""
312
# First, see if it's a revno
313
if self._revno_regex.match(self.spec) is not None:
315
return self._try_spectype(RevisionSpec_revno, branch)
316
except RevisionSpec_revno.dwim_catchable_exceptions:
319
# Next see what has been registered
320
for objgetter in self._possible_revspecs:
321
rs_class = objgetter.get_obj()
323
return self._try_spectype(rs_class, branch)
324
except rs_class.dwim_catchable_exceptions:
327
# Well, I dunno what it is. Note that we don't try to keep track of the
328
# first of last exception raised during the DWIM tries as none seems
330
raise InvalidRevisionSpec(self.spec, branch)
333
def append_possible_revspec(cls, revspec):
334
"""Append a possible DWIM revspec.
336
:param revspec: Revision spec to try.
338
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
341
def append_possible_lazy_revspec(cls, module_name, member_name):
342
"""Append a possible lazily loaded DWIM revspec.
344
:param module_name: Name of the module with the revspec
345
:param member_name: Name of the revspec within the module
347
cls._possible_revspecs.append(
348
registry._LazyObjectGetter(module_name, member_name))
351
class RevisionSpec_revno(RevisionSpec):
352
"""Selects a revision using a number."""
354
help_txt = """Selects a revision using a number.
356
Use an integer to specify a revision in the history of the branch.
357
Optionally a branch can be specified. A negative number will count
358
from the end of the branch (-1 is the last revision, -2 the previous
359
one). If the negative number is larger than the branch's history, the
360
first revision is returned.
363
revno:1 -> return the first revision of this branch
364
revno:3:/path/to/branch -> return the 3rd revision of
365
the branch '/path/to/branch'
366
revno:-1 -> The last revision in a branch.
367
-2:http://other/branch -> The second to last revision in the
369
-1000000 -> Most likely the first revision, unless
370
your history is very long.
374
def _match_on(self, branch, revs):
375
"""Lookup a revision by revision number"""
376
branch, revno, revision_id = self._lookup(branch)
377
return RevisionInfo(branch, revno, revision_id)
379
def _lookup(self, branch):
380
loc = self.spec.find(':')
382
revno_spec = self.spec
385
revno_spec = self.spec[:loc]
386
branch_spec = self.spec[loc + 1:]
390
raise InvalidRevisionSpec(
391
self.user_spec, branch,
392
'cannot have an empty revno and no branch')
396
revno = int(revno_spec)
399
# dotted decimal. This arguably should not be here
400
# but the from_string method is a little primitive
401
# right now - RBC 20060928
403
match_revno = tuple((int(number)
404
for number in revno_spec.split('.')))
405
except ValueError as e:
406
raise InvalidRevisionSpec(self.user_spec, branch, e)
411
# the user has overriden the branch to look in.
412
branch = _mod_branch.Branch.open(branch_spec)
416
revision_id = branch.dotted_revno_to_revision_id(match_revno,
418
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
419
raise InvalidRevisionSpec(self.user_spec, branch)
421
# there is no traditional 'revno' for dotted-decimal revnos.
422
# so for API compatibility we return None.
423
return branch, None, revision_id
425
last_revno, last_revision_id = branch.last_revision_info()
427
# if get_rev_id supported negative revnos, there would not be a
428
# need for this special case.
429
if (-revno) >= last_revno:
432
revno = last_revno + revno + 1
434
revision_id = branch.get_rev_id(revno)
435
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
436
raise InvalidRevisionSpec(self.user_spec, branch)
437
return branch, revno, revision_id
439
def _as_revision_id(self, context_branch):
440
# We would have the revno here, but we don't really care
441
branch, revno, revision_id = self._lookup(context_branch)
444
def needs_branch(self):
445
return self.spec.find(':') == -1
447
def get_branch(self):
448
if self.spec.find(':') == -1:
451
return self.spec[self.spec.find(':') + 1:]
455
RevisionSpec_int = RevisionSpec_revno
458
class RevisionIDSpec(RevisionSpec):
460
def _match_on(self, branch, revs):
461
revision_id = self.as_revision_id(branch)
462
return RevisionInfo.from_revision_id(branch, revision_id)
465
class RevisionSpec_revid(RevisionIDSpec):
466
"""Selects a revision using the revision id."""
468
help_txt = """Selects a revision using the revision id.
470
Supply a specific revision id, that can be used to specify any
471
revision id in the ancestry of the branch.
472
Including merges, and pending merges.
475
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
480
def _as_revision_id(self, context_branch):
481
# self.spec comes straight from parsing the command line arguments,
482
# so we expect it to be a Unicode string. Switch it to the internal
484
if isinstance(self.spec, str):
485
return cache_utf8.encode(self.spec)
489
class RevisionSpec_last(RevisionSpec):
490
"""Selects the nth revision from the end."""
492
help_txt = """Selects the nth revision from the end.
494
Supply a positive number to get the nth revision from the end.
495
This is the same as supplying negative numbers to the 'revno:' spec.
498
last:1 -> return the last revision
499
last:3 -> return the revision 2 before the end.
504
def _match_on(self, branch, revs):
505
revno, revision_id = self._revno_and_revision_id(branch)
506
return RevisionInfo(branch, revno, revision_id)
508
def _revno_and_revision_id(self, context_branch):
509
last_revno, last_revision_id = context_branch.last_revision_info()
513
raise errors.NoCommits(context_branch)
514
return last_revno, last_revision_id
517
offset = int(self.spec)
518
except ValueError as e:
519
raise InvalidRevisionSpec(self.user_spec, context_branch, e)
522
raise InvalidRevisionSpec(self.user_spec, context_branch,
523
'you must supply a positive value')
525
revno = last_revno - offset + 1
527
revision_id = context_branch.get_rev_id(revno)
528
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
529
raise InvalidRevisionSpec(self.user_spec, context_branch)
530
return revno, revision_id
532
def _as_revision_id(self, context_branch):
533
# We compute the revno as part of the process, but we don't really care
535
revno, revision_id = self._revno_and_revision_id(context_branch)
539
class RevisionSpec_before(RevisionSpec):
540
"""Selects the parent of the revision specified."""
542
help_txt = """Selects the parent of the revision specified.
544
Supply any revision spec to return the parent of that revision. This is
545
mostly useful when inspecting revisions that are not in the revision history
548
It is an error to request the parent of the null revision (before:0).
552
before:1913 -> Return the parent of revno 1913 (revno 1912)
553
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
555
bzr diff -r before:1913..1913
556
-> Find the changes between revision 1913 and its parent (1912).
557
(What changes did revision 1913 introduce).
558
This is equivalent to: bzr diff -c 1913
563
def _match_on(self, branch, revs):
564
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
566
raise InvalidRevisionSpec(
567
self.user_spec, branch,
568
'cannot go before the null: revision')
570
# We need to use the repository history here
571
rev = branch.repository.get_revision(r.rev_id)
572
if not rev.parent_ids:
573
revision_id = revision.NULL_REVISION
575
revision_id = rev.parent_ids[0]
580
revision_id = branch.get_rev_id(revno, revs)
581
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
582
raise InvalidRevisionSpec(self.user_spec, branch)
583
return RevisionInfo(branch, revno, revision_id)
585
def _as_revision_id(self, context_branch):
586
base_revision_id = RevisionSpec.from_string(
587
self.spec)._as_revision_id(context_branch)
588
if base_revision_id == revision.NULL_REVISION:
589
raise InvalidRevisionSpec(
590
self.user_spec, context_branch,
591
'cannot go before the null: revision')
592
context_repo = context_branch.repository
593
with context_repo.lock_read():
594
parent_map = context_repo.get_parent_map([base_revision_id])
595
if base_revision_id not in parent_map:
596
# Ghost, or unknown revision id
597
raise InvalidRevisionSpec(
598
self.user_spec, context_branch, 'cannot find the matching revision')
599
parents = parent_map[base_revision_id]
601
raise errors.InvalidRevisionSpec(
602
self.user_spec, context_branch, 'No parents for revision.')
606
class RevisionSpec_tag(RevisionSpec):
607
"""Select a revision identified by tag name"""
609
help_txt = """Selects a revision identified by a tag name.
611
Tags are stored in the branch and created by the 'tag' command.
615
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
617
def _match_on(self, branch, revs):
618
# Can raise tags not supported, NoSuchTag, etc
619
return RevisionInfo.from_revision_id(branch,
620
branch.tags.lookup_tag(self.spec))
622
def _as_revision_id(self, context_branch):
623
return context_branch.tags.lookup_tag(self.spec)
626
class _RevListToTimestamps(object):
627
"""This takes a list of revisions, and allows you to bisect by date"""
629
__slots__ = ['branch']
631
def __init__(self, branch):
634
def __getitem__(self, index):
635
"""Get the date of the index'd item"""
636
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
637
# TODO: Handle timezone.
638
return datetime.datetime.fromtimestamp(r.timestamp)
641
return self.branch.revno()
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
brz 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_regex = lazy_regex.lazy_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(
682
datetime.date.today().toordinal())
683
if self.spec.lower() == 'yesterday':
684
dt = today - datetime.timedelta(days=1)
685
elif self.spec.lower() == 'today':
687
elif self.spec.lower() == 'tomorrow':
688
dt = today + datetime.timedelta(days=1)
690
m = self._date_regex.match(self.spec)
691
if not m or (not m.group('date') and not m.group('time')):
692
raise InvalidRevisionSpec(
693
self.user_spec, branch, 'invalid date')
697
year = int(m.group('year'))
698
month = int(m.group('month'))
699
day = int(m.group('day'))
706
hour = int(m.group('hour'))
707
minute = int(m.group('minute'))
708
if m.group('second'):
709
second = int(m.group('second'))
713
hour, minute, second = 0, 0, 0
715
raise InvalidRevisionSpec(
716
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(
897
self.user_spec, context_branch,
898
'No such line: %s' % numstring)
900
def _as_revision_id(self, context_branch):
901
path, numstring = self.spec.rsplit(':', 1)
903
index = int(numstring) - 1
905
self._raise_invalid(numstring, context_branch)
906
tree, file_path = workingtree.WorkingTree.open_containing(path)
907
with tree.lock_read():
908
if not tree.has_filename(file_path):
909
raise InvalidRevisionSpec(
910
self.user_spec, context_branch,
911
"File '%s' is not versioned." % file_path)
912
revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
914
revision_id = revision_ids[index]
916
self._raise_invalid(numstring, context_branch)
917
if revision_id == revision.CURRENT_REVISION:
918
raise InvalidRevisionSpec(
919
self.user_spec, context_branch,
920
'Line %s has not been committed.' % numstring)
924
class RevisionSpec_mainline(RevisionIDSpec):
926
help_txt = """Select mainline revision that merged the specified revision.
928
Select the revision that merged the specified revision into mainline.
933
def _as_revision_id(self, context_branch):
934
revspec = RevisionSpec.from_string(self.spec)
935
if revspec.get_branch() is None:
936
spec_branch = context_branch
938
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
939
revision_id = revspec.as_revision_id(spec_branch)
940
graph = context_branch.repository.get_graph()
941
result = graph.find_lefthand_merger(revision_id,
942
context_branch.last_revision())
944
raise InvalidRevisionSpec(self.user_spec, context_branch)
948
# The order in which we want to DWIM a revision spec without any prefix.
949
# revno is always tried first and isn't listed here, this is used by
950
# RevisionSpec_dwim._match_on
951
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
952
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
953
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
954
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
956
revspec_registry = registry.Registry()
959
def _register_revspec(revspec):
960
revspec_registry.register(revspec.prefix, revspec)
963
_register_revspec(RevisionSpec_revno)
964
_register_revspec(RevisionSpec_revid)
965
_register_revspec(RevisionSpec_last)
966
_register_revspec(RevisionSpec_before)
967
_register_revspec(RevisionSpec_tag)
968
_register_revspec(RevisionSpec_date)
969
_register_revspec(RevisionSpec_ancestor)
970
_register_revspec(RevisionSpec_branch)
971
_register_revspec(RevisionSpec_submit)
972
_register_revspec(RevisionSpec_annotate)
973
_register_revspec(RevisionSpec_mainline)