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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
20
from bzrlib.lazy_import import lazy_import
21
21
lazy_import(globals(), """
25
26
from bzrlib import (
26
branch as _mod_branch,
32
from bzrlib.i18n import gettext
43
38
class RevisionInfo(object):
44
39
"""The results of applying a revision specification to a branch."""
57
52
or treat the result as a tuple.
60
def __init__(self, branch, revno=None, rev_id=None):
55
def __init__(self, branch, revno, rev_id=_marker):
61
56
self.branch = branch
62
self._has_revno = (revno is not None)
65
if self.rev_id is None and self._revno is not None:
66
59
# allow caller to be lazy
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
60
if self.revno is None:
63
self.rev_id = branch.get_rev_id(self.revno)
79
67
def __nonzero__(self):
68
# first the easy ones...
80
69
if self.rev_id is None:
71
if self.revno is not None:
82
73
# TODO: otherwise, it should depend on how I was built -
83
74
# if it's in_history(branch), then check revision_history(),
84
75
# if it's in_store(branch), do the check below
107
98
self.revno, self.rev_id, self.branch)
110
def from_revision_id(branch, revision_id, revs=symbol_versioning.DEPRECATED_PARAMETER):
101
def from_revision_id(branch, revision_id, revs):
111
102
"""Construct a RevisionInfo given just the id.
113
104
Use this if you don't know or care what the revno is.
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)
106
if revision_id == revision.NULL_REVISION:
107
return RevisionInfo(branch, 0, revision_id)
109
revno = revs.index(revision_id) + 1
112
return RevisionInfo(branch, revno, revision_id)
115
# classes in this list should have a "prefix" attribute, against which
116
# string specs are matched
123
121
class RevisionSpec(object):
126
124
help_txt = """A parsed revision specification.
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.
126
A revision specification can be an integer, in which case it is
127
assumed to be a revno (though this will translate negative values
128
into positive ones); or it can be a string, in which case it is
129
parsed for something like 'date:' or 'revid:' etc.
133
131
Revision specs are an UI element, and they have been moved out
134
132
of the branch class to leave "back-end" classes unaware of such
166
155
return RevisionSpec(None, _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)
156
for spectype in SPEC_TYPES:
157
if spec.startswith(spectype.prefix):
158
trace.mutter('Returning RevisionSpec %s for %s',
159
spectype.__name__, spec)
160
return spectype(spec, _internal=True)
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)
162
# RevisionSpec_revno is special cased, because it is the only
163
# one that directly handles plain integers
164
# TODO: This should not be special cased rather it should be
165
# a method invocation on spectype.canparse()
167
if _revno_regex is None:
168
_revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
169
if _revno_regex.match(spec) is not None:
170
return RevisionSpec_revno(spec, _internal=True)
172
raise errors.NoSuchRevisionSpec(spec)
178
174
def __init__(self, spec, _internal=False):
179
175
"""Create a RevisionSpec referring to the Null revision.
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))
373
288
class RevisionSpec_revno(RevisionSpec):
374
289
"""Selects a revision using a number."""
376
291
help_txt = """Selects a revision using a number.
378
293
Use an integer to specify a revision in the history of the branch.
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.
294
Optionally a branch can be specified. The 'revno:' prefix is optional.
295
A negative number will count from the end of the branch (-1 is the
296
last revision, -2 the previous one). If the negative number is larger
297
than the branch's history, the first revision is returned.
385
300
revno:1 -> return the first revision of this branch
392
307
your history is very long.
394
309
prefix = 'revno:'
310
wants_revision_history = False
396
312
def _match_on(self, branch, revs):
397
313
"""Lookup a revision by revision number"""
398
branch, revno, revision_id = self._lookup(branch)
314
branch, revno, revision_id = self._lookup(branch, revs)
399
315
return RevisionInfo(branch, revno, revision_id)
401
def _lookup(self, branch):
317
def _lookup(self, branch, revs_or_none):
402
318
loc = self.spec.find(':')
404
320
revno_spec = self.spec
431
# the user has overriden the branch to look in.
432
branch = _mod_branch.Branch.open(branch_spec)
347
# the user has override the branch to look in.
348
# we need to refresh the revision_history map and
350
from bzrlib.branch import Branch
351
branch = Branch.open(branch_spec)
436
revision_id = branch.dotted_revno_to_revision_id(match_revno,
438
except errors.NoSuchRevision:
357
revision_id_to_revno = branch.get_revision_id_to_revno_map()
358
revisions = [revision_id for revision_id, revno
359
in revision_id_to_revno.iteritems()
360
if revno == match_revno]
363
if len(revisions) != 1:
439
364
raise errors.InvalidRevisionSpec(self.user_spec, branch)
441
366
# there is no traditional 'revno' for dotted-decimal revnos.
442
# so for API compatibility we return None.
443
return branch, None, revision_id
367
# so for API compatability we return None.
368
return branch, None, revisions[0]
445
370
last_revno, last_revision_id = branch.last_revision_info()
452
377
revno = last_revno + revno + 1
454
revision_id = branch.get_rev_id(revno)
379
revision_id = branch.get_rev_id(revno, revs_or_none)
455
380
except errors.NoSuchRevision:
456
381
raise errors.InvalidRevisionSpec(self.user_spec, branch)
457
382
return branch, revno, revision_id
459
384
def _as_revision_id(self, context_branch):
460
385
# We would have the revno here, but we don't really care
461
branch, revno, revision_id = self._lookup(context_branch)
386
branch, revno, revision_id = self._lookup(context_branch, None)
462
387
return revision_id
464
389
def needs_branch(self):
471
396
return self.spec[self.spec.find(':')+1:]
474
399
RevisionSpec_int = RevisionSpec_revno
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):
401
SPEC_TYPES.append(RevisionSpec_revno)
404
class RevisionSpec_revid(RevisionSpec):
485
405
"""Selects a revision using the revision id."""
487
407
help_txt = """Selects a revision using the revision id.
489
409
Supply a specific revision id, that can be used to specify any
490
revision id in the ancestry of the branch.
410
revision id in the ancestry of the branch.
491
411
Including merges, and pending merges.
497
417
prefix = 'revid:'
499
def _as_revision_id(self, context_branch):
419
def _match_on(self, branch, revs):
500
420
# self.spec comes straight from parsing the command line arguments,
501
421
# so we expect it to be a Unicode string. Switch it to the internal
502
422
# representation.
423
revision_id = osutils.safe_revision_id(self.spec, warn=False)
424
return RevisionInfo.from_revision_id(branch, revision_id, revs)
426
def _as_revision_id(self, context_branch):
503
427
return osutils.safe_revision_id(self.spec, warn=False)
429
SPEC_TYPES.append(RevisionSpec_revid)
507
432
class RevisionSpec_last(RevisionSpec):
522
447
def _match_on(self, branch, revs):
523
revno, revision_id = self._revno_and_revision_id(branch)
448
revno, revision_id = self._revno_and_revision_id(branch, revs)
524
449
return RevisionInfo(branch, revno, revision_id)
526
def _revno_and_revision_id(self, context_branch):
451
def _revno_and_revision_id(self, context_branch, revs_or_none):
527
452
last_revno, last_revision_id = context_branch.last_revision_info()
529
454
if self.spec == '':
636
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
638
569
def _match_on(self, branch, revs):
639
570
# Can raise tags not supported, NoSuchTag, etc
640
571
return RevisionInfo.from_revision_id(branch,
641
branch.tags.lookup_tag(self.spec))
572
branch.tags.lookup_tag(self.spec),
643
575
def _as_revision_id(self, context_branch):
644
576
return context_branch.tags.lookup_tag(self.spec)
578
SPEC_TYPES.append(RevisionSpec_tag)
648
581
class _RevListToTimestamps(object):
649
582
"""This takes a list of revisions, and allows you to bisect by date"""
651
__slots__ = ['branch']
584
__slots__ = ['revs', 'branch']
653
def __init__(self, branch):
586
def __init__(self, revs, branch):
654
588
self.branch = branch
656
590
def __getitem__(self, index):
657
591
"""Get the date of the index'd item"""
658
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
592
r = self.branch.repository.get_revision(self.revs[index])
659
593
# TODO: Handle timezone.
660
594
return datetime.datetime.fromtimestamp(r.timestamp)
662
596
def __len__(self):
663
return self.branch.revno()
597
return len(self.revs)
666
600
class RevisionSpec_date(RevisionSpec):
740
674
hour=hour, minute=minute, second=second)
741
675
branch.lock_read()
743
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
677
rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
746
if rev == branch.revno():
747
681
raise errors.InvalidRevisionSpec(self.user_spec, branch)
748
return RevisionInfo(branch, rev)
683
return RevisionInfo(branch, rev + 1)
685
SPEC_TYPES.append(RevisionSpec_date)
752
688
class RevisionSpec_ancestor(RevisionSpec):
834
772
revision_b = other_branch.last_revision()
835
773
if revision_b in (None, revision.NULL_REVISION):
836
774
raise errors.NoCommits(other_branch)
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)
775
# pull in the remote revisions so we can diff
776
branch.fetch(other_branch, revision_b)
778
revno = branch.revision_id_to_revno(revision_b)
779
except errors.NoSuchRevision:
781
return RevisionInfo(branch, revno, revision_b)
847
783
def _as_revision_id(self, context_branch):
848
784
from bzrlib.branch import Branch
914
844
self._get_submit_location(context_branch))
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)
847
SPEC_TYPES.append(RevisionSpec_submit)