118
107
return RevisionInfo(branch, revno, revision_id)
110
# classes in this list should have a "prefix" attribute, against which
111
# string specs are matched
121
116
class RevisionSpec(object):
122
117
"""A parsed revision specification."""
124
119
help_txt = """A parsed revision specification.
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.
121
A revision specification can be an integer, in which case it is
122
assumed to be a revno (though this will translate negative values
123
into positive ones); or it can be a string, in which case it is
124
parsed for something like 'date:' or 'revid:' etc.
131
126
Revision specs are an UI element, and they have been moved out
132
127
of the branch class to leave "back-end" classes unaware of such
141
wants_revision_history = True
142
dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
143
"""Exceptions that RevisionSpec_dwim._match_on will catch.
145
If the revspec is part of ``dwim_revspecs``, it may be tried with an
146
invalid revspec and raises some exception. The exceptions mentioned here
147
will not be reported to the user but simply ignored without stopping the
137
def __new__(cls, spec, _internal=False):
139
return object.__new__(cls, spec, _internal=_internal)
141
symbol_versioning.warn('Creating a RevisionSpec directly has'
142
' been deprecated in version 0.11. Use'
143
' RevisionSpec.from_string()'
145
DeprecationWarning, stacklevel=2)
146
return RevisionSpec.from_string(spec)
152
149
def from_string(spec):
163
160
return RevisionSpec(None, _internal=True)
164
match = revspec_registry.get_prefix(spec)
165
if match is not None:
166
spectype, specsuffix = match
167
trace.mutter('Returning RevisionSpec %s for %s',
168
spectype.__name__, spec)
169
return spectype(spec, _internal=True)
162
assert isinstance(spec, basestring), \
163
"You should only supply strings not %s" % (type(spec),)
165
for spectype in SPEC_TYPES:
166
if spec.startswith(spectype.prefix):
167
trace.mutter('Returning RevisionSpec %s for %s',
168
spectype.__name__, spec)
169
return spectype(spec, _internal=True)
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)
171
# RevisionSpec_revno is special cased, because it is the only
172
# one that directly handles plain integers
173
# TODO: This should not be special cased rather it should be
174
# a method invocation on spectype.canparse()
176
if _revno_regex is None:
177
_revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
178
if _revno_regex.match(spec) is not None:
179
return RevisionSpec_revno(spec, _internal=True)
181
raise errors.NoSuchRevisionSpec(spec)
175
183
def __init__(self, spec, _internal=False):
176
184
"""Create a RevisionSpec referring to the Null revision.
227
233
# will do what you expect.
228
234
in_store = in_history
229
235
in_branch = in_store
231
def as_revision_id(self, context_branch):
232
"""Return just the revision_id for this revisions spec.
234
Some revision specs require a context_branch to be able to determine
235
their value. Not all specs will make use of it.
237
return self._as_revision_id(context_branch)
239
def _as_revision_id(self, context_branch):
240
"""Implementation of as_revision_id()
242
Classes should override this function to provide appropriate
243
functionality. The default is to just call '.in_history().rev_id'
245
return self.in_history(context_branch).rev_id
247
def as_tree(self, context_branch):
248
"""Return the tree object for this revisions spec.
250
Some revision specs require a context_branch to be able to determine
251
the revision id and access the repository. Not all specs will make
254
return self._as_tree(context_branch)
256
def _as_tree(self, context_branch):
257
"""Implementation of as_tree().
259
Classes should override this function to provide appropriate
260
functionality. The default is to just call '.as_revision_id()'
261
and get the revision tree from context_branch's repository.
263
revision_id = self.as_revision_id(context_branch)
264
return context_branch.repository.revision_tree(revision_id)
266
237
def __repr__(self):
267
238
# this is mostly for helping with testing
268
239
return '<%s %s>' % (self.__class__.__name__,
271
242
def needs_branch(self):
272
243
"""Whether this revision spec needs a branch.
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))
362
259
class RevisionSpec_revno(RevisionSpec):
363
260
"""Selects a revision using a number."""
365
262
help_txt = """Selects a revision using a number.
367
264
Use an integer to specify a revision in the history of the branch.
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.
265
Optionally a branch can be specified. The 'revno:' prefix is optional.
266
A negative number will count from the end of the branch (-1 is the
267
last revision, -2 the previous one). If the negative number is larger
268
than the branch's history, the first revision is returned.
374
revno:1 -> return the first revision of this branch
271
revno:1 -> return the first revision
375
272
revno:3:/path/to/branch -> return the 3rd revision of
376
273
the branch '/path/to/branch'
377
274
revno:-1 -> The last revision in a branch.
423
315
# the branch object.
424
316
from bzrlib.branch import Branch
425
317
branch = Branch.open(branch_spec)
318
# Need to use a new revision history
319
# because we are using a specific branch
320
revs = branch.revision_history()
430
revision_id = branch.dotted_revno_to_revision_id(match_revno,
432
except errors.NoSuchRevision:
433
raise errors.InvalidRevisionSpec(self.user_spec, branch)
325
revision_id_to_revno = branch.get_revision_id_to_revno_map()
326
revisions = [revision_id for revision_id, revno
327
in revision_id_to_revno.iteritems()
328
if revno == match_revno]
331
if len(revisions) != 1:
332
return RevisionInfo(branch, None, None)
435
334
# there is no traditional 'revno' for dotted-decimal revnos.
436
335
# so for API compatability we return None.
437
return branch, None, revision_id
336
return RevisionInfo(branch, None, revisions[0])
439
last_revno, last_revision_id = branch.last_revision_info()
441
339
# if get_rev_id supported negative revnos, there would not be a
442
340
# need for this special case.
443
if (-revno) >= last_revno:
341
if (-revno) >= len(revs):
446
revno = last_revno + revno + 1
344
revno = len(revs) + revno + 1
448
revision_id = branch.get_rev_id(revno, revs_or_none)
346
revision_id = branch.get_rev_id(revno, revs)
449
347
except errors.NoSuchRevision:
450
348
raise errors.InvalidRevisionSpec(self.user_spec, branch)
451
return branch, revno, revision_id
453
def _as_revision_id(self, context_branch):
454
# We would have the revno here, but we don't really care
455
branch, revno, revision_id = self._lookup(context_branch, None)
349
return RevisionInfo(branch, revno, revision_id)
458
351
def needs_branch(self):
459
352
return self.spec.find(':') == -1
465
358
return self.spec[self.spec.find(':')+1:]
468
361
RevisionSpec_int = RevisionSpec_revno
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):
363
SPEC_TYPES.append(RevisionSpec_revno)
366
class RevisionSpec_revid(RevisionSpec):
480
367
"""Selects a revision using the revision id."""
482
369
help_txt = """Selects a revision using the revision id.
484
371
Supply a specific revision id, that can be used to specify any
485
revision id in the ancestry of the branch.
372
revision id in the ancestry of the branch.
486
373
Including merges, and pending merges.
489
376
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
492
378
prefix = 'revid:'
494
def _as_revision_id(self, context_branch):
380
def _match_on(self, branch, revs):
495
381
# self.spec comes straight from parsing the command line arguments,
496
382
# so we expect it to be a Unicode string. Switch it to the internal
497
383
# representation.
498
return osutils.safe_revision_id(self.spec, warn=False)
384
revision_id = osutils.safe_revision_id(self.spec, warn=False)
385
return RevisionInfo.from_revision_id(branch, revision_id, revs)
387
SPEC_TYPES.append(RevisionSpec_revid)
502
390
class RevisionSpec_last(RevisionSpec):
511
399
last:1 -> return the last revision
512
400
last:3 -> return the revision 2 before the end.
517
405
def _match_on(self, branch, revs):
518
revno, revision_id = self._revno_and_revision_id(branch, revs)
519
return RevisionInfo(branch, revno, revision_id)
521
def _revno_and_revision_id(self, context_branch, revs_or_none):
522
last_revno, last_revision_id = context_branch.last_revision_info()
524
406
if self.spec == '':
526
raise errors.NoCommits(context_branch)
527
return last_revno, last_revision_id
408
raise errors.NoCommits(branch)
409
return RevisionInfo(branch, len(revs), revs[-1])
530
412
offset = int(self.spec)
531
413
except ValueError, e:
532
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
414
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
535
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
417
raise errors.InvalidRevisionSpec(self.user_spec, branch,
536
418
'you must supply a positive value')
538
revno = last_revno - offset + 1
419
revno = len(revs) - offset + 1
540
revision_id = context_branch.get_rev_id(revno, revs_or_none)
421
revision_id = branch.get_rev_id(revno, revs)
541
422
except errors.NoSuchRevision:
542
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
543
return revno, revision_id
545
def _as_revision_id(self, context_branch):
546
# We compute the revno as part of the process, but we don't really care
548
revno, revision_id = self._revno_and_revision_id(context_branch, None)
423
raise errors.InvalidRevisionSpec(self.user_spec, branch)
424
return RevisionInfo(branch, revno, revision_id)
426
SPEC_TYPES.append(RevisionSpec_last)
553
429
class RevisionSpec_before(RevisionSpec):
556
432
help_txt = """Selects the parent of the revision specified.
558
Supply any revision spec to return the parent of that revision. This is
559
mostly useful when inspecting revisions that are not in the revision history
434
Supply any revision spec to return the parent of that revision.
562
435
It is an error to request the parent of the null revision (before:0).
436
This is mostly useful when inspecting revisions that are not in the
437
revision history of a branch.
566
441
before:1913 -> Return the parent of revno 1913 (revno 1912)
567
442
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
568
443
aaaa@bbbb-1234567890
569
bzr diff -r before:1913..1913
570
-> Find the changes between revision 1913 and its parent (1912).
571
(What changes did revision 1913 introduce).
572
This is equivalent to: bzr diff -c 1913
444
bzr diff -r before:revid:aaaa..revid:aaaa
445
-> Find the changes between revision 'aaaa' and its parent.
446
(what changes did 'aaaa' introduce)
575
449
prefix = 'before:'
577
451
def _match_on(self, branch, revs):
578
452
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
601
475
return RevisionInfo(branch, revno, revision_id)
603
def _as_revision_id(self, context_branch):
604
base_revspec = RevisionSpec.from_string(self.spec)
605
base_revision_id = base_revspec.as_revision_id(context_branch)
606
if base_revision_id == revision.NULL_REVISION:
607
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
608
'cannot go before the null: revision')
609
context_repo = context_branch.repository
610
context_repo.lock_read()
612
parent_map = context_repo.get_parent_map([base_revision_id])
614
context_repo.unlock()
615
if base_revision_id not in parent_map:
616
# Ghost, or unknown revision id
617
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
618
'cannot find the matching revision')
619
parents = parent_map[base_revision_id]
621
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
622
'No parents for revision.')
477
SPEC_TYPES.append(RevisionSpec_before)
627
480
class RevisionSpec_tag(RevisionSpec):
778
629
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
779
630
return self._find_revision_info(branch, self.spec)
781
def _as_revision_id(self, context_branch):
782
return self._find_revision_id(context_branch, self.spec)
785
633
def _find_revision_info(branch, other_location):
786
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
789
revno = branch.revision_id_to_revno(revision_id)
790
except errors.NoSuchRevision:
792
return RevisionInfo(branch, revno, revision_id)
795
def _find_revision_id(branch, other_location):
796
634
from bzrlib.branch import Branch
800
revision_a = revision.ensure_null(branch.last_revision())
801
if revision_a == revision.NULL_REVISION:
802
raise errors.NoCommits(branch)
803
if other_location == '':
804
other_location = branch.get_parent()
805
other_branch = Branch.open(other_location)
806
other_branch.lock_read()
808
revision_b = revision.ensure_null(other_branch.last_revision())
809
if revision_b == revision.NULL_REVISION:
810
raise errors.NoCommits(other_branch)
811
graph = branch.repository.get_graph(other_branch.repository)
812
rev_id = graph.find_unique_lca(revision_a, revision_b)
814
other_branch.unlock()
636
other_branch = Branch.open(other_location)
637
revision_a = branch.last_revision()
638
revision_b = other_branch.last_revision()
639
for r, b in ((revision_a, branch), (revision_b, other_branch)):
640
if r in (None, revision.NULL_REVISION):
641
raise errors.NoCommits(b)
642
revision_source = revision.MultipleRevisionSources(
643
branch.repository, other_branch.repository)
644
graph = branch.repository.get_graph(other_branch.repository)
645
revision_a = revision.ensure_null(revision_a)
646
revision_b = revision.ensure_null(revision_b)
647
if revision.NULL_REVISION in (revision_a, revision_b):
648
rev_id = revision.NULL_REVISION
650
rev_id = graph.find_unique_lca(revision_a, revision_b)
815
651
if rev_id == revision.NULL_REVISION:
816
652
raise errors.NoCommonAncestor(revision_a, revision_b)
654
revno = branch.revision_id_to_revno(rev_id)
655
except errors.NoSuchRevision:
657
return RevisionInfo(branch, revno, rev_id)
660
SPEC_TYPES.append(RevisionSpec_ancestor)
824
663
class RevisionSpec_branch(RevisionSpec):
841
679
revision_b = other_branch.last_revision()
842
680
if revision_b in (None, revision.NULL_REVISION):
843
681
raise errors.NoCommits(other_branch)
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
682
# pull in the remote revisions so we can diff
683
branch.fetch(other_branch, revision_b)
853
685
revno = branch.revision_id_to_revno(revision_b)
854
686
except errors.NoSuchRevision:
856
688
return RevisionInfo(branch, revno, revision_b)
858
def _as_revision_id(self, context_branch):
859
from bzrlib.branch import Branch
860
other_branch = Branch.open(self.spec)
861
last_revision = other_branch.last_revision()
862
last_revision = revision.ensure_null(last_revision)
863
context_branch.fetch(other_branch, last_revision)
864
if last_revision == revision.NULL_REVISION:
865
raise errors.NoCommits(other_branch)
868
def _as_tree(self, context_branch):
869
from bzrlib.branch import Branch
870
other_branch = Branch.open(self.spec)
871
last_revision = other_branch.last_revision()
872
last_revision = revision.ensure_null(last_revision)
873
if last_revision == revision.NULL_REVISION:
874
raise errors.NoCommits(other_branch)
875
return other_branch.repository.revision_tree(last_revision)
877
def needs_branch(self):
880
def get_branch(self):
690
SPEC_TYPES.append(RevisionSpec_branch)
885
693
class RevisionSpec_submit(RevisionSpec_ancestor):
911
720
location_type = 'parent branch'
912
721
if submit_location is None:
913
722
raise errors.NoSubmitBranch(branch)
914
trace.note(gettext('Using {0} {1}').format(location_type,
916
return submit_location
918
def _match_on(self, branch, revs):
919
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
920
return self._find_revision_info(branch,
921
self._get_submit_location(branch))
923
def _as_revision_id(self, context_branch):
924
return self._find_revision_id(context_branch,
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)
1006
revspec_registry = registry.Registry()
1007
def _register_revspec(revspec):
1008
revspec_registry.register(revspec.prefix, revspec)
1010
_register_revspec(RevisionSpec_revno)
1011
_register_revspec(RevisionSpec_revid)
1012
_register_revspec(RevisionSpec_last)
1013
_register_revspec(RevisionSpec_before)
1014
_register_revspec(RevisionSpec_tag)
1015
_register_revspec(RevisionSpec_date)
1016
_register_revspec(RevisionSpec_ancestor)
1017
_register_revspec(RevisionSpec_branch)
1018
_register_revspec(RevisionSpec_submit)
1019
_register_revspec(RevisionSpec_annotate)
1020
_register_revspec(RevisionSpec_mainline)
723
trace.note('Using %s %s', location_type, submit_location)
724
return self._find_revision_info(branch, submit_location)
727
SPEC_TYPES.append(RevisionSpec_submit)