13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
20
from bzrlib.lazy_import import lazy_import
21
lazy_import(globals(), """
22
25
from bzrlib import (
26
branch as _mod_branch,
32
from bzrlib.i18n import gettext
35
43
class RevisionInfo(object):
36
44
"""The results of applying a revision specification to a branch."""
49
57
or treat the result as a tuple.
52
def __init__(self, branch, revno, rev_id=_marker):
60
def __init__(self, branch, revno=None, rev_id=None):
53
61
self.branch = branch
62
self._has_revno = (revno is not None)
65
if self.rev_id is None and self._revno is not None:
56
66
# allow caller to be lazy
57
if self.revno is None:
60
self.rev_id = branch.get_rev_id(self.revno)
67
self.rev_id = branch.get_rev_id(self._revno)
71
if not self._has_revno and self.rev_id is not None:
73
self._revno = self.branch.revision_id_to_revno(self.rev_id)
74
except errors.NoSuchRevision:
76
self._has_revno = True
64
79
def __nonzero__(self):
65
# first the easy ones...
66
80
if self.rev_id is None:
68
if self.revno is not None:
70
82
# TODO: otherwise, it should depend on how I was built -
71
83
# if it's in_history(branch), then check revision_history(),
72
84
# if it's in_store(branch), do the check below
95
107
self.revno, self.rev_id, self.branch)
98
def from_revision_id(branch, revision_id, revs):
110
def from_revision_id(branch, revision_id, revs=symbol_versioning.DEPRECATED_PARAMETER):
99
111
"""Construct a RevisionInfo given just the id.
101
113
Use this if you don't know or care what the revno is.
103
if revision_id == revision.NULL_REVISION:
104
return RevisionInfo(branch, 0, revision_id)
106
revno = revs.index(revision_id) + 1
109
return RevisionInfo(branch, revno, revision_id)
112
# classes in this list should have a "prefix" attribute, against which
113
# string specs are matched
115
if symbol_versioning.deprecated_passed(revs):
116
symbol_versioning.warn(
117
'RevisionInfo.from_revision_id(revs) was deprecated in 2.5.',
120
return RevisionInfo(branch, revno=None, rev_id=revision_id)
118
123
class RevisionSpec(object):
121
126
help_txt = """A parsed revision specification.
123
A revision specification can be an integer, in which case it is
124
assumed to be a revno (though this will translate negative values
125
into positive ones); or it can be a string, in which case it is
126
parsed for something like 'date:' or 'revid:' etc.
128
A revision specification is a string, which may be unambiguous about
129
what it represents by giving a prefix like 'date:' or 'revid:' etc,
130
or it may have no prefix, in which case it's tried against several
131
specifier types in sequence to determine what the user meant.
128
133
Revision specs are an UI element, and they have been moved out
129
134
of the branch class to leave "back-end" classes unaware of such
138
wants_revision_history = True
140
def __new__(cls, spec, _internal=False):
142
return object.__new__(cls, spec, _internal=_internal)
144
symbol_versioning.warn('Creating a RevisionSpec directly has'
145
' been deprecated in version 0.11. Use'
146
' RevisionSpec.from_string()'
148
DeprecationWarning, stacklevel=2)
149
return RevisionSpec.from_string(spec)
143
# wants_revision_history has been deprecated in 2.5.
144
wants_revision_history = False
145
dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
146
"""Exceptions that RevisionSpec_dwim._match_on will catch.
148
If the revspec is part of ``dwim_revspecs``, it may be tried with an
149
invalid revspec and raises some exception. The exceptions mentioned here
150
will not be reported to the user but simply ignored without stopping the
152
155
def from_string(spec):
163
166
return RevisionSpec(None, _internal=True)
164
for spectype in SPEC_TYPES:
165
if spec.startswith(spectype.prefix):
166
trace.mutter('Returning RevisionSpec %s for %s',
167
spectype.__name__, spec)
168
return spectype(spec, _internal=True)
167
match = revspec_registry.get_prefix(spec)
168
if match is not None:
169
spectype, specsuffix = match
170
trace.mutter('Returning RevisionSpec %s for %s',
171
spectype.__name__, spec)
172
return spectype(spec, _internal=True)
170
# RevisionSpec_revno is special cased, because it is the only
171
# one that directly handles plain integers
172
# TODO: This should not be special cased rather it should be
173
# a method invocation on spectype.canparse()
175
if _revno_regex is None:
176
_revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
177
if _revno_regex.match(spec) is not None:
178
return RevisionSpec_revno(spec, _internal=True)
180
raise errors.NoSuchRevisionSpec(spec)
174
# Otherwise treat it as a DWIM, build the RevisionSpec object and
175
# wait for _match_on to be called.
176
return RevisionSpec_dwim(spec, _internal=True)
182
178
def __init__(self, spec, _internal=False):
183
179
"""Create a RevisionSpec referring to the Null revision.
253
259
return self.in_history(context_branch).rev_id
261
def as_tree(self, context_branch):
262
"""Return the tree object for this revisions spec.
264
Some revision specs require a context_branch to be able to determine
265
the revision id and access the repository. Not all specs will make
268
return self._as_tree(context_branch)
270
def _as_tree(self, context_branch):
271
"""Implementation of as_tree().
273
Classes should override this function to provide appropriate
274
functionality. The default is to just call '.as_revision_id()'
275
and get the revision tree from context_branch's repository.
277
revision_id = self.as_revision_id(context_branch)
278
return context_branch.repository.revision_tree(revision_id)
255
280
def __repr__(self):
256
281
# this is mostly for helping with testing
257
282
return '<%s %s>' % (self.__class__.__name__,
260
285
def needs_branch(self):
261
286
"""Whether this revision spec needs a branch.
302
class RevisionSpec_dwim(RevisionSpec):
303
"""Provides a DWIMish revision specifier lookup.
305
Note that this does not go in the revspec_registry because by definition
306
there is no prefix to identify it. It's solely called from
307
RevisionSpec.from_string() because the DWIMification happen when _match_on
308
is called so the string describing the revision is kept here until needed.
313
_revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
315
# The revspecs to try
316
_possible_revspecs = []
318
def _try_spectype(self, rstype, branch):
319
rs = rstype(self.spec, _internal=True)
320
# Hit in_history to find out if it exists, or we need to try the
322
return rs.in_history(branch)
324
def _match_on(self, branch, revs):
325
"""Run the lookup and see what we can get."""
327
# First, see if it's a revno
328
if self._revno_regex.match(self.spec) is not None:
330
return self._try_spectype(RevisionSpec_revno, branch)
331
except RevisionSpec_revno.dwim_catchable_exceptions:
334
# Next see what has been registered
335
for objgetter in self._possible_revspecs:
336
rs_class = objgetter.get_obj()
338
return self._try_spectype(rs_class, branch)
339
except rs_class.dwim_catchable_exceptions:
342
# Try the old (deprecated) dwim list:
343
for rs_class in dwim_revspecs:
345
return self._try_spectype(rs_class, branch)
346
except rs_class.dwim_catchable_exceptions:
349
# Well, I dunno what it is. Note that we don't try to keep track of the
350
# first of last exception raised during the DWIM tries as none seems
352
raise errors.InvalidRevisionSpec(self.spec, branch)
355
def append_possible_revspec(cls, revspec):
356
"""Append a possible DWIM revspec.
358
:param revspec: Revision spec to try.
360
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
363
def append_possible_lazy_revspec(cls, module_name, member_name):
364
"""Append a possible lazily loaded DWIM revspec.
366
:param module_name: Name of the module with the revspec
367
:param member_name: Name of the revspec within the module
369
cls._possible_revspecs.append(
370
registry._LazyObjectGetter(module_name, member_name))
277
373
class RevisionSpec_revno(RevisionSpec):
278
374
"""Selects a revision using a number."""
280
376
help_txt = """Selects a revision using a number.
282
378
Use an integer to specify a revision in the history of the branch.
283
Optionally a branch can be specified. The 'revno:' prefix is optional.
284
A negative number will count from the end of the branch (-1 is the
285
last revision, -2 the previous one). If the negative number is larger
286
than the branch's history, the first revision is returned.
379
Optionally a branch can be specified. A negative number will count
380
from the end of the branch (-1 is the last revision, -2 the previous
381
one). If the negative number is larger than the branch's history, the
382
first revision is returned.
289
revno:1 -> return the first revision
385
revno:1 -> return the first revision of this branch
290
386
revno:3:/path/to/branch -> return the 3rd revision of
291
387
the branch '/path/to/branch'
292
388
revno:-1 -> The last revision in a branch.
296
392
your history is very long.
298
394
prefix = 'revno:'
299
wants_revision_history = False
301
396
def _match_on(self, branch, revs):
302
397
"""Lookup a revision by revision number"""
303
branch, revno, revision_id = self._lookup(branch, revs)
398
branch, revno, revision_id = self._lookup(branch)
304
399
return RevisionInfo(branch, revno, revision_id)
306
def _lookup(self, branch, revs_or_none):
401
def _lookup(self, branch):
307
402
loc = self.spec.find(':')
309
404
revno_spec = self.spec
336
# the user has override the branch to look in.
337
# we need to refresh the revision_history map and
339
from bzrlib.branch import Branch
340
branch = Branch.open(branch_spec)
431
# the user has overriden the branch to look in.
432
branch = _mod_branch.Branch.open(branch_spec)
346
revision_id_to_revno = branch.get_revision_id_to_revno_map()
347
revisions = [revision_id for revision_id, revno
348
in revision_id_to_revno.iteritems()
349
if revno == match_revno]
352
if len(revisions) != 1:
353
return branch, None, None
436
revision_id = branch.dotted_revno_to_revision_id(match_revno,
438
except errors.NoSuchRevision:
439
raise errors.InvalidRevisionSpec(self.user_spec, branch)
355
441
# there is no traditional 'revno' for dotted-decimal revnos.
356
# so for API compatability we return None.
357
return branch, None, revisions[0]
442
# so for API compatibility we return None.
443
return branch, None, revision_id
359
445
last_revno, last_revision_id = branch.last_revision_info()
366
452
revno = last_revno + revno + 1
368
revision_id = branch.get_rev_id(revno, revs_or_none)
454
revision_id = branch.get_rev_id(revno)
369
455
except errors.NoSuchRevision:
370
456
raise errors.InvalidRevisionSpec(self.user_spec, branch)
371
457
return branch, revno, revision_id
373
459
def _as_revision_id(self, context_branch):
374
460
# We would have the revno here, but we don't really care
375
branch, revno, revision_id = self._lookup(context_branch, None)
461
branch, revno, revision_id = self._lookup(context_branch)
376
462
return revision_id
378
464
def needs_branch(self):
385
471
return self.spec[self.spec.find(':')+1:]
388
474
RevisionSpec_int = RevisionSpec_revno
390
SPEC_TYPES.append(RevisionSpec_revno)
393
class RevisionSpec_revid(RevisionSpec):
477
class RevisionIDSpec(RevisionSpec):
479
def _match_on(self, branch, revs):
480
revision_id = self.as_revision_id(branch)
481
return RevisionInfo.from_revision_id(branch, revision_id)
484
class RevisionSpec_revid(RevisionIDSpec):
394
485
"""Selects a revision using the revision id."""
396
487
help_txt = """Selects a revision using the revision id.
398
489
Supply a specific revision id, that can be used to specify any
399
revision id in the ancestry of the branch.
490
revision id in the ancestry of the branch.
400
491
Including merges, and pending merges.
406
497
prefix = 'revid:'
408
def _match_on(self, branch, revs):
499
def _as_revision_id(self, context_branch):
409
500
# self.spec comes straight from parsing the command line arguments,
410
501
# so we expect it to be a Unicode string. Switch it to the internal
411
502
# representation.
412
revision_id = osutils.safe_revision_id(self.spec, warn=False)
413
return RevisionInfo.from_revision_id(branch, revision_id, revs)
415
def _as_revision_id(self, context_branch):
416
503
return osutils.safe_revision_id(self.spec, warn=False)
418
SPEC_TYPES.append(RevisionSpec_revid)
421
507
class RevisionSpec_last(RevisionSpec):
436
522
def _match_on(self, branch, revs):
437
revno, revision_id = self._revno_and_revision_id(branch, revs)
523
revno, revision_id = self._revno_and_revision_id(branch)
438
524
return RevisionInfo(branch, revno, revision_id)
440
def _revno_and_revision_id(self, context_branch, revs_or_none):
526
def _revno_and_revision_id(self, context_branch):
441
527
last_revno, last_revision_id = context_branch.last_revision_info()
443
529
if self.spec == '':
476
561
help_txt = """Selects the parent of the revision specified.
478
Supply any revision spec to return the parent of that revision.
563
Supply any revision spec to return the parent of that revision. This is
564
mostly useful when inspecting revisions that are not in the revision history
479
567
It is an error to request the parent of the null revision (before:0).
480
This is mostly useful when inspecting revisions that are not in the
481
revision history of a branch.
485
571
before:1913 -> Return the parent of revno 1913 (revno 1912)
486
572
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
487
573
aaaa@bbbb-1234567890
488
bzr diff -r before:revid:aaaa..revid:aaaa
489
-> Find the changes between revision 'aaaa' and its parent.
490
(what changes did 'aaaa' introduce)
574
bzr diff -r before:1913..1913
575
-> Find the changes between revision 1913 and its parent (1912).
576
(What changes did revision 1913 introduce).
577
This is equivalent to: bzr diff -c 1913
493
580
prefix = 'before:'
495
582
def _match_on(self, branch, revs):
496
583
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
636
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
556
638
def _match_on(self, branch, revs):
557
639
# Can raise tags not supported, NoSuchTag, etc
558
640
return RevisionInfo.from_revision_id(branch,
559
branch.tags.lookup_tag(self.spec),
641
branch.tags.lookup_tag(self.spec))
562
643
def _as_revision_id(self, context_branch):
563
644
return context_branch.tags.lookup_tag(self.spec)
565
SPEC_TYPES.append(RevisionSpec_tag)
568
648
class _RevListToTimestamps(object):
569
649
"""This takes a list of revisions, and allows you to bisect by date"""
571
__slots__ = ['revs', 'branch']
651
__slots__ = ['branch']
573
def __init__(self, revs, branch):
653
def __init__(self, branch):
575
654
self.branch = branch
577
656
def __getitem__(self, index):
578
657
"""Get the date of the index'd item"""
579
r = self.branch.repository.get_revision(self.revs[index])
658
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
580
659
# TODO: Handle timezone.
581
660
return datetime.datetime.fromtimestamp(r.timestamp)
583
662
def __len__(self):
584
return len(self.revs)
663
return self.branch.revno()
587
666
class RevisionSpec_date(RevisionSpec):
661
740
hour=hour, minute=minute, second=second)
662
741
branch.lock_read()
664
rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
743
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
746
if rev == branch.revno():
668
747
raise errors.InvalidRevisionSpec(self.user_spec, branch)
670
return RevisionInfo(branch, rev + 1)
748
return RevisionInfo(branch, rev)
672
SPEC_TYPES.append(RevisionSpec_date)
675
752
class RevisionSpec_ancestor(RevisionSpec):
759
834
revision_b = other_branch.last_revision()
760
835
if revision_b in (None, revision.NULL_REVISION):
761
836
raise errors.NoCommits(other_branch)
762
# pull in the remote revisions so we can diff
763
branch.fetch(other_branch, revision_b)
765
revno = branch.revision_id_to_revno(revision_b)
766
except errors.NoSuchRevision:
768
return RevisionInfo(branch, revno, revision_b)
838
branch = other_branch
841
# pull in the remote revisions so we can diff
842
branch.fetch(other_branch, revision_b)
843
except errors.ReadOnlyError:
844
branch = other_branch
845
return RevisionInfo(branch, None, revision_b)
770
847
def _as_revision_id(self, context_branch):
771
848
from bzrlib.branch import Branch
777
854
raise errors.NoCommits(other_branch)
778
855
return last_revision
780
SPEC_TYPES.append(RevisionSpec_branch)
857
def _as_tree(self, context_branch):
858
from bzrlib.branch import Branch
859
other_branch = Branch.open(self.spec)
860
last_revision = other_branch.last_revision()
861
last_revision = revision.ensure_null(last_revision)
862
if last_revision == revision.NULL_REVISION:
863
raise errors.NoCommits(other_branch)
864
return other_branch.repository.revision_tree(last_revision)
866
def needs_branch(self):
869
def get_branch(self):
783
874
class RevisionSpec_submit(RevisionSpec_ancestor):
822
914
self._get_submit_location(context_branch))
825
SPEC_TYPES.append(RevisionSpec_submit)
917
class RevisionSpec_annotate(RevisionIDSpec):
921
help_txt = """Select the revision that last modified the specified line.
923
Select the revision that last modified the specified line. Line is
924
specified as path:number. Path is a relative path to the file. Numbers
925
start at 1, and are relative to the current version, not the last-
926
committed version of the file.
929
def _raise_invalid(self, numstring, context_branch):
930
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
931
'No such line: %s' % numstring)
933
def _as_revision_id(self, context_branch):
934
path, numstring = self.spec.rsplit(':', 1)
936
index = int(numstring) - 1
938
self._raise_invalid(numstring, context_branch)
939
tree, file_path = workingtree.WorkingTree.open_containing(path)
942
file_id = tree.path2id(file_path)
944
raise errors.InvalidRevisionSpec(self.user_spec,
945
context_branch, "File '%s' is not versioned." %
947
revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
951
revision_id = revision_ids[index]
953
self._raise_invalid(numstring, context_branch)
954
if revision_id == revision.CURRENT_REVISION:
955
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
956
'Line %s has not been committed.' % numstring)
960
class RevisionSpec_mainline(RevisionIDSpec):
962
help_txt = """Select mainline revision that merged the specified revision.
964
Select the revision that merged the specified revision into mainline.
969
def _as_revision_id(self, context_branch):
970
revspec = RevisionSpec.from_string(self.spec)
971
if revspec.get_branch() is None:
972
spec_branch = context_branch
974
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
975
revision_id = revspec.as_revision_id(spec_branch)
976
graph = context_branch.repository.get_graph()
977
result = graph.find_lefthand_merger(revision_id,
978
context_branch.last_revision())
980
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
984
# The order in which we want to DWIM a revision spec without any prefix.
985
# revno is always tried first and isn't listed here, this is used by
986
# RevisionSpec_dwim._match_on
987
dwim_revspecs = symbol_versioning.deprecated_list(
988
symbol_versioning.deprecated_in((2, 4, 0)), "dwim_revspecs", [])
990
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
991
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
992
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
993
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
995
revspec_registry = registry.Registry()
996
def _register_revspec(revspec):
997
revspec_registry.register(revspec.prefix, revspec)
999
_register_revspec(RevisionSpec_revno)
1000
_register_revspec(RevisionSpec_revid)
1001
_register_revspec(RevisionSpec_last)
1002
_register_revspec(RevisionSpec_before)
1003
_register_revspec(RevisionSpec_tag)
1004
_register_revspec(RevisionSpec_date)
1005
_register_revspec(RevisionSpec_ancestor)
1006
_register_revspec(RevisionSpec_branch)
1007
_register_revspec(RevisionSpec_submit)
1008
_register_revspec(RevisionSpec_annotate)
1009
_register_revspec(RevisionSpec_mainline)