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(
523
self.user_spec, context_branch,
524
'you must supply a positive value')
526
revno = last_revno - offset + 1
528
revision_id = context_branch.get_rev_id(revno)
529
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
530
raise InvalidRevisionSpec(self.user_spec, context_branch)
531
return revno, revision_id
533
def _as_revision_id(self, context_branch):
534
# We compute the revno as part of the process, but we don't really care
536
revno, revision_id = self._revno_and_revision_id(context_branch)
540
class RevisionSpec_before(RevisionSpec):
541
"""Selects the parent of the revision specified."""
543
help_txt = """Selects the parent of the revision specified.
545
Supply any revision spec to return the parent of that revision. This is
546
mostly useful when inspecting revisions that are not in the revision history
549
It is an error to request the parent of the null revision (before:0).
553
before:1913 -> Return the parent of revno 1913 (revno 1912)
554
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
556
bzr diff -r before:1913..1913
557
-> Find the changes between revision 1913 and its parent (1912).
558
(What changes did revision 1913 introduce).
559
This is equivalent to: bzr diff -c 1913
564
def _match_on(self, branch, revs):
565
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
567
raise InvalidRevisionSpec(
568
self.user_spec, branch,
569
'cannot go before the null: revision')
571
# We need to use the repository history here
572
rev = branch.repository.get_revision(r.rev_id)
573
if not rev.parent_ids:
574
revision_id = revision.NULL_REVISION
576
revision_id = rev.parent_ids[0]
581
revision_id = branch.get_rev_id(revno, revs)
582
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
583
raise InvalidRevisionSpec(self.user_spec, branch)
584
return RevisionInfo(branch, revno, revision_id)
586
def _as_revision_id(self, context_branch):
587
base_revision_id = RevisionSpec.from_string(
588
self.spec)._as_revision_id(context_branch)
589
if base_revision_id == revision.NULL_REVISION:
590
raise InvalidRevisionSpec(
591
self.user_spec, context_branch,
592
'cannot go before the null: revision')
593
context_repo = context_branch.repository
594
with context_repo.lock_read():
595
parent_map = context_repo.get_parent_map([base_revision_id])
596
if base_revision_id not in parent_map:
597
# Ghost, or unknown revision id
598
raise InvalidRevisionSpec(
599
self.user_spec, context_branch, 'cannot find the matching revision')
600
parents = parent_map[base_revision_id]
602
raise errors.InvalidRevisionSpec(
603
self.user_spec, context_branch, 'No parents for revision.')
607
class RevisionSpec_tag(RevisionSpec):
608
"""Select a revision identified by tag name"""
610
help_txt = """Selects a revision identified by a tag name.
612
Tags are stored in the branch and created by the 'tag' command.
616
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
618
def _match_on(self, branch, revs):
619
# Can raise tags not supported, NoSuchTag, etc
620
return RevisionInfo.from_revision_id(branch,
621
branch.tags.lookup_tag(self.spec))
623
def _as_revision_id(self, context_branch):
624
return context_branch.tags.lookup_tag(self.spec)
627
class _RevListToTimestamps(object):
628
"""This takes a list of revisions, and allows you to bisect by date"""
630
__slots__ = ['branch']
632
def __init__(self, branch):
635
def __getitem__(self, index):
636
"""Get the date of the index'd item"""
637
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
638
# TODO: Handle timezone.
639
return datetime.datetime.fromtimestamp(r.timestamp)
642
return self.branch.revno()
645
class RevisionSpec_date(RevisionSpec):
646
"""Selects a revision on the basis of a datestamp."""
648
help_txt = """Selects a revision on the basis of a datestamp.
650
Supply a datestamp to select the first revision that matches the date.
651
Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
652
Matches the first entry after a given date (either at midnight or
653
at a specified time).
655
One way to display all the changes since yesterday would be::
657
brz log -r date:yesterday..
661
date:yesterday -> select the first revision since yesterday
662
date:2006-08-14,17:10:14 -> select the first revision after
663
August 14th, 2006 at 5:10pm.
666
_date_regex = lazy_regex.lazy_compile(
667
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
669
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
672
def _match_on(self, branch, revs):
673
"""Spec for date revisions:
675
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
676
matches the first entry after a given date (either at midnight or
677
at a specified time).
679
# XXX: This doesn't actually work
680
# So the proper way of saying 'give me all entries for today' is:
681
# -r date:yesterday..date:today
682
today = datetime.datetime.fromordinal(
683
datetime.date.today().toordinal())
684
if self.spec.lower() == 'yesterday':
685
dt = today - datetime.timedelta(days=1)
686
elif self.spec.lower() == 'today':
688
elif self.spec.lower() == 'tomorrow':
689
dt = today + datetime.timedelta(days=1)
691
m = self._date_regex.match(self.spec)
692
if not m or (not m.group('date') and not m.group('time')):
693
raise InvalidRevisionSpec(
694
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(
717
self.user_spec, branch, 'invalid date')
719
dt = datetime.datetime(year=year, month=month, day=day,
720
hour=hour, minute=minute, second=second)
721
with branch.lock_read():
722
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
723
if rev == branch.revno():
724
raise InvalidRevisionSpec(self.user_spec, branch)
725
return RevisionInfo(branch, rev)
728
class RevisionSpec_ancestor(RevisionSpec):
729
"""Selects a common ancestor with a second branch."""
731
help_txt = """Selects a common ancestor with a second branch.
733
Supply the path to a branch to select the common ancestor.
735
The common ancestor is the last revision that existed in both
736
branches. Usually this is the branch point, but it could also be
737
a revision that was merged.
739
This is frequently used with 'diff' to return all of the changes
740
that your branch introduces, while excluding the changes that you
741
have not merged from the remote branch.
745
ancestor:/path/to/branch
746
$ bzr diff -r ancestor:../../mainline/branch
750
def _match_on(self, branch, revs):
751
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
752
return self._find_revision_info(branch, self.spec)
754
def _as_revision_id(self, context_branch):
755
return self._find_revision_id(context_branch, self.spec)
758
def _find_revision_info(branch, other_location):
759
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
761
return RevisionInfo(branch, None, revision_id)
764
def _find_revision_id(branch, other_location):
765
from .branch import Branch
767
with branch.lock_read():
768
revision_a = revision.ensure_null(branch.last_revision())
769
if revision_a == revision.NULL_REVISION:
770
raise errors.NoCommits(branch)
771
if other_location == '':
772
other_location = branch.get_parent()
773
other_branch = Branch.open(other_location)
774
with other_branch.lock_read():
775
revision_b = revision.ensure_null(other_branch.last_revision())
776
if revision_b == revision.NULL_REVISION:
777
raise errors.NoCommits(other_branch)
778
graph = branch.repository.get_graph(other_branch.repository)
779
rev_id = graph.find_unique_lca(revision_a, revision_b)
780
if rev_id == revision.NULL_REVISION:
781
raise errors.NoCommonAncestor(revision_a, revision_b)
785
class RevisionSpec_branch(RevisionSpec):
786
"""Selects the last revision of a specified branch."""
788
help_txt = """Selects the last revision of a specified branch.
790
Supply the path to a branch to select its last revision.
794
branch:/path/to/branch
797
dwim_catchable_exceptions = (errors.NotBranchError,)
799
def _match_on(self, branch, revs):
800
from .branch import Branch
801
other_branch = Branch.open(self.spec)
802
revision_b = other_branch.last_revision()
803
if revision_b in (None, revision.NULL_REVISION):
804
raise errors.NoCommits(other_branch)
806
branch = other_branch
809
# pull in the remote revisions so we can diff
810
branch.fetch(other_branch, revision_b)
811
except errors.ReadOnlyError:
812
branch = other_branch
813
return RevisionInfo(branch, None, revision_b)
815
def _as_revision_id(self, context_branch):
816
from .branch import Branch
817
other_branch = Branch.open(self.spec)
818
last_revision = other_branch.last_revision()
819
last_revision = revision.ensure_null(last_revision)
820
context_branch.fetch(other_branch, last_revision)
821
if last_revision == revision.NULL_REVISION:
822
raise errors.NoCommits(other_branch)
825
def _as_tree(self, context_branch):
826
from .branch import Branch
827
other_branch = Branch.open(self.spec)
828
last_revision = other_branch.last_revision()
829
last_revision = revision.ensure_null(last_revision)
830
if last_revision == revision.NULL_REVISION:
831
raise errors.NoCommits(other_branch)
832
return other_branch.repository.revision_tree(last_revision)
834
def needs_branch(self):
837
def get_branch(self):
841
class RevisionSpec_submit(RevisionSpec_ancestor):
842
"""Selects a common ancestor with a submit branch."""
844
help_txt = """Selects a common ancestor with the submit branch.
846
Diffing against this shows all the changes that were made in this branch,
847
and is a good predictor of what merge will do. The submit branch is
848
used by the bundle and merge directive commands. If no submit branch
849
is specified, the parent branch is used instead.
851
The common ancestor is the last revision that existed in both
852
branches. Usually this is the branch point, but it could also be
853
a revision that was merged.
857
$ bzr diff -r submit:
862
def _get_submit_location(self, branch):
863
submit_location = branch.get_submit_branch()
864
location_type = 'submit branch'
865
if submit_location is None:
866
submit_location = branch.get_parent()
867
location_type = 'parent branch'
868
if submit_location is None:
869
raise errors.NoSubmitBranch(branch)
870
trace.note(gettext('Using {0} {1}').format(location_type,
872
return submit_location
874
def _match_on(self, branch, revs):
875
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
876
return self._find_revision_info(branch,
877
self._get_submit_location(branch))
879
def _as_revision_id(self, context_branch):
880
return self._find_revision_id(context_branch,
881
self._get_submit_location(context_branch))
884
class RevisionSpec_annotate(RevisionIDSpec):
888
help_txt = """Select the revision that last modified the specified line.
890
Select the revision that last modified the specified line. Line is
891
specified as path:number. Path is a relative path to the file. Numbers
892
start at 1, and are relative to the current version, not the last-
893
committed version of the file.
896
def _raise_invalid(self, numstring, context_branch):
897
raise InvalidRevisionSpec(
898
self.user_spec, context_branch,
899
'No such line: %s' % numstring)
901
def _as_revision_id(self, context_branch):
902
path, numstring = self.spec.rsplit(':', 1)
904
index = int(numstring) - 1
906
self._raise_invalid(numstring, context_branch)
907
tree, file_path = workingtree.WorkingTree.open_containing(path)
908
with tree.lock_read():
909
if not tree.has_filename(file_path):
910
raise InvalidRevisionSpec(
911
self.user_spec, context_branch,
912
"File '%s' is not versioned." % file_path)
913
revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
915
revision_id = revision_ids[index]
917
self._raise_invalid(numstring, context_branch)
918
if revision_id == revision.CURRENT_REVISION:
919
raise InvalidRevisionSpec(
920
self.user_spec, context_branch,
921
'Line %s has not been committed.' % numstring)
925
class RevisionSpec_mainline(RevisionIDSpec):
927
help_txt = """Select mainline revision that merged the specified revision.
929
Select the revision that merged the specified revision into mainline.
934
def _as_revision_id(self, context_branch):
935
revspec = RevisionSpec.from_string(self.spec)
936
if revspec.get_branch() is None:
937
spec_branch = context_branch
939
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
940
revision_id = revspec.as_revision_id(spec_branch)
941
graph = context_branch.repository.get_graph()
942
result = graph.find_lefthand_merger(revision_id,
943
context_branch.last_revision())
945
raise InvalidRevisionSpec(self.user_spec, context_branch)
949
# The order in which we want to DWIM a revision spec without any prefix.
950
# revno is always tried first and isn't listed here, this is used by
951
# RevisionSpec_dwim._match_on
952
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
953
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
954
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
955
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
957
revspec_registry = registry.Registry()
960
def _register_revspec(revspec):
961
revspec_registry.register(revspec.prefix, revspec)
964
_register_revspec(RevisionSpec_revno)
965
_register_revspec(RevisionSpec_revid)
966
_register_revspec(RevisionSpec_last)
967
_register_revspec(RevisionSpec_before)
968
_register_revspec(RevisionSpec_tag)
969
_register_revspec(RevisionSpec_date)
970
_register_revspec(RevisionSpec_ancestor)
971
_register_revspec(RevisionSpec_branch)
972
_register_revspec(RevisionSpec_submit)
973
_register_revspec(RevisionSpec_annotate)
974
_register_revspec(RevisionSpec_mainline)