113
118
return RevisionInfo(branch, revno, revision_id)
116
# classes in this list should have a "prefix" attribute, against which
117
# string specs are matched
121
121
class RevisionSpec(object):
122
122
"""A parsed revision specification."""
124
124
help_txt = """A parsed revision specification.
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.
126
A revision specification is a string, which may be unambiguous about
127
what it represents by giving a prefix like 'date:' or 'revid:' etc,
128
or it may have no prefix, in which case it's tried against several
129
specifier types in sequence to determine what the user meant.
131
131
Revision specs are an UI element, and they have been moved out
132
132
of the branch class to leave "back-end" classes unaware of such
160
168
spectype.__name__, spec)
161
169
return spectype(spec, _internal=True)
163
for spectype in SPEC_TYPES:
164
trace.mutter('Returning RevisionSpec %s for %s',
165
spectype.__name__, spec)
166
if spec.startswith(spectype.prefix):
167
return spectype(spec, _internal=True)
168
# RevisionSpec_revno is special cased, because it is the only
169
# one that directly handles plain integers
170
# TODO: This should not be special cased rather it should be
171
# a method invocation on spectype.canparse()
173
if _revno_regex is None:
174
_revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
175
if _revno_regex.match(spec) is not None:
176
return RevisionSpec_revno(spec, _internal=True)
178
raise errors.NoSuchRevisionSpec(spec)
171
# Otherwise treat it as a DWIM, build the RevisionSpec object and
172
# wait for _match_on to be called.
173
return RevisionSpec_dwim(spec, _internal=True)
180
175
def __init__(self, spec, _internal=False):
181
176
"""Create a RevisionSpec referring to the Null revision.
288
class RevisionSpec_dwim(RevisionSpec):
289
"""Provides a DWIMish revision specifier lookup.
291
Note that this does not go in the revspec_registry because by definition
292
there is no prefix to identify it. It's solely called from
293
RevisionSpec.from_string() because the DWIMification happen when _match_on
294
is called so the string describing the revision is kept here until needed.
298
# We don't need to build the revision history ourself, that's delegated to
299
# each revspec we try.
300
wants_revision_history = False
302
_revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
304
# The revspecs to try
305
_possible_revspecs = []
307
def _try_spectype(self, rstype, branch):
308
rs = rstype(self.spec, _internal=True)
309
# Hit in_history to find out if it exists, or we need to try the
311
return rs.in_history(branch)
313
def _match_on(self, branch, revs):
314
"""Run the lookup and see what we can get."""
316
# First, see if it's a revno
317
if self._revno_regex.match(self.spec) is not None:
319
return self._try_spectype(RevisionSpec_revno, branch)
320
except RevisionSpec_revno.dwim_catchable_exceptions:
323
# Next see what has been registered
324
for objgetter in self._possible_revspecs:
325
rs_class = objgetter.get_obj()
327
return self._try_spectype(rs_class, branch)
328
except rs_class.dwim_catchable_exceptions:
331
# Try the old (deprecated) dwim list:
332
for rs_class in dwim_revspecs:
334
return self._try_spectype(rs_class, branch)
335
except rs_class.dwim_catchable_exceptions:
338
# Well, I dunno what it is. Note that we don't try to keep track of the
339
# first of last exception raised during the DWIM tries as none seems
341
raise errors.InvalidRevisionSpec(self.spec, branch)
344
def append_possible_revspec(cls, revspec):
345
"""Append a possible DWIM revspec.
347
:param revspec: Revision spec to try.
349
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
352
def append_possible_lazy_revspec(cls, module_name, member_name):
353
"""Append a possible lazily loaded DWIM revspec.
355
:param module_name: Name of the module with the revspec
356
:param member_name: Name of the revspec within the module
358
cls._possible_revspecs.append(
359
registry._LazyObjectGetter(module_name, member_name))
294
362
class RevisionSpec_revno(RevisionSpec):
295
363
"""Selects a revision using a number."""
297
365
help_txt = """Selects a revision using a number.
299
367
Use an integer to specify a revision in the history of the branch.
300
Optionally a branch can be specified. The 'revno:' prefix is optional.
301
A negative number will count from the end of the branch (-1 is the
302
last revision, -2 the previous one). If the negative number is larger
303
than the branch's history, the first revision is returned.
368
Optionally a branch can be specified. A negative number will count
369
from the end of the branch (-1 is the last revision, -2 the previous
370
one). If the negative number is larger than the branch's history, the
371
first revision is returned.
306
374
revno:1 -> return the first revision of this branch
404
class RevisionSpec_revid(RevisionSpec):
472
class RevisionIDSpec(RevisionSpec):
474
def _match_on(self, branch, revs):
475
revision_id = self.as_revision_id(branch)
476
return RevisionInfo.from_revision_id(branch, revision_id, revs)
479
class RevisionSpec_revid(RevisionIDSpec):
405
480
"""Selects a revision using the revision id."""
407
482
help_txt = """Selects a revision using the revision id.
417
492
prefix = 'revid:'
419
def _match_on(self, branch, revs):
494
def _as_revision_id(self, context_branch):
420
495
# self.spec comes straight from parsing the command line arguments,
421
496
# so we expect it to be a Unicode string. Switch it to the internal
422
497
# 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):
427
498
return osutils.safe_revision_id(self.spec, warn=False)
768
841
revision_b = other_branch.last_revision()
769
842
if revision_b in (None, revision.NULL_REVISION):
770
843
raise errors.NoCommits(other_branch)
771
# pull in the remote revisions so we can diff
772
branch.fetch(other_branch, revision_b)
845
branch = other_branch
848
# pull in the remote revisions so we can diff
849
branch.fetch(other_branch, revision_b)
850
except errors.ReadOnlyError:
851
branch = other_branch
774
853
revno = branch.revision_id_to_revno(revision_b)
775
854
except errors.NoSuchRevision:
839
925
self._get_submit_location(context_branch))
928
class RevisionSpec_annotate(RevisionIDSpec):
932
help_txt = """Select the revision that last modified the specified line.
934
Select the revision that last modified the specified line. Line is
935
specified as path:number. Path is a relative path to the file. Numbers
936
start at 1, and are relative to the current version, not the last-
937
committed version of the file.
940
def _raise_invalid(self, numstring, context_branch):
941
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
942
'No such line: %s' % numstring)
944
def _as_revision_id(self, context_branch):
945
path, numstring = self.spec.rsplit(':', 1)
947
index = int(numstring) - 1
949
self._raise_invalid(numstring, context_branch)
950
tree, file_path = workingtree.WorkingTree.open_containing(path)
953
file_id = tree.path2id(file_path)
955
raise errors.InvalidRevisionSpec(self.user_spec,
956
context_branch, "File '%s' is not versioned." %
958
revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
962
revision_id = revision_ids[index]
964
self._raise_invalid(numstring, context_branch)
965
if revision_id == revision.CURRENT_REVISION:
966
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
967
'Line %s has not been committed.' % numstring)
971
class RevisionSpec_mainline(RevisionIDSpec):
973
help_txt = """Select mainline revision that merged the specified revision.
975
Select the revision that merged the specified revision into mainline.
980
def _as_revision_id(self, context_branch):
981
revspec = RevisionSpec.from_string(self.spec)
982
if revspec.get_branch() is None:
983
spec_branch = context_branch
985
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
986
revision_id = revspec.as_revision_id(spec_branch)
987
graph = context_branch.repository.get_graph()
988
result = graph.find_lefthand_merger(revision_id,
989
context_branch.last_revision())
991
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
995
# The order in which we want to DWIM a revision spec without any prefix.
996
# revno is always tried first and isn't listed here, this is used by
997
# RevisionSpec_dwim._match_on
998
dwim_revspecs = symbol_versioning.deprecated_list(
999
symbol_versioning.deprecated_in((2, 4, 0)), "dwim_revspecs", [])
1001
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
1002
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
1003
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
1004
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
842
1006
revspec_registry = registry.Registry()
843
1007
def _register_revspec(revspec):
844
1008
revspec_registry.register(revspec.prefix, revspec)