95
108
self.revno, self.rev_id, self.branch)
98
def from_revision_id(branch, revision_id, revs):
111
def from_revision_id(branch, revision_id, revs=symbol_versioning.DEPRECATED_PARAMETER):
99
112
"""Construct a RevisionInfo given just the id.
101
114
Use this if you don't know or care what the revno is.
104
revno = revs.index(revision_id) + 1
107
return RevisionInfo(branch, revno, revision_id)
110
# classes in this list should have a "prefix" attribute, against which
111
# string specs are matched
116
if symbol_versioning.deprecated_passed(revs):
117
symbol_versioning.warn(
118
'RevisionInfo.from_revision_id(revs) was deprecated in 2.5.',
121
return RevisionInfo(branch, revno=None, rev_id=revision_id)
116
124
class RevisionSpec(object):
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)
144
# wants_revision_history has been deprecated in 2.5.
145
wants_revision_history = False
146
dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
147
"""Exceptions that RevisionSpec_dwim._match_on will catch.
149
If the revspec is part of ``dwim_revspecs``, it may be tried with an
150
invalid revspec and raises some exception. The exceptions mentioned here
151
will not be reported to the user but simply ignored without stopping the
149
156
def from_string(spec):
160
167
return RevisionSpec(None, _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)
168
match = revspec_registry.get_prefix(spec)
169
if match is not None:
170
spectype, specsuffix = match
171
trace.mutter('Returning RevisionSpec %s for %s',
172
spectype.__name__, spec)
173
return spectype(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
# Otherwise treat it as a DWIM, build the RevisionSpec object and
176
# wait for _match_on to be called.
177
return RevisionSpec_dwim(spec, _internal=True)
183
179
def __init__(self, spec, _internal=False):
184
180
"""Create a RevisionSpec referring to the Null revision.
233
242
# will do what you expect.
234
243
in_store = in_history
235
244
in_branch = in_store
246
def as_revision_id(self, context_branch):
247
"""Return just the revision_id for this revisions spec.
249
Some revision specs require a context_branch to be able to determine
250
their value. Not all specs will make use of it.
252
return self._as_revision_id(context_branch)
254
def _as_revision_id(self, context_branch):
255
"""Implementation of as_revision_id()
257
Classes should override this function to provide appropriate
258
functionality. The default is to just call '.in_history().rev_id'
260
return self.in_history(context_branch).rev_id
262
def as_tree(self, context_branch):
263
"""Return the tree object for this revisions spec.
265
Some revision specs require a context_branch to be able to determine
266
the revision id and access the repository. Not all specs will make
269
return self._as_tree(context_branch)
271
def _as_tree(self, context_branch):
272
"""Implementation of as_tree().
274
Classes should override this function to provide appropriate
275
functionality. The default is to just call '.as_revision_id()'
276
and get the revision tree from context_branch's repository.
278
revision_id = self.as_revision_id(context_branch)
279
return context_branch.repository.revision_tree(revision_id)
237
281
def __repr__(self):
238
282
# this is mostly for helping with testing
239
283
return '<%s %s>' % (self.__class__.__name__,
242
286
def needs_branch(self):
243
287
"""Whether this revision spec needs a branch.
303
class RevisionSpec_dwim(RevisionSpec):
304
"""Provides a DWIMish revision specifier lookup.
306
Note that this does not go in the revspec_registry because by definition
307
there is no prefix to identify it. It's solely called from
308
RevisionSpec.from_string() because the DWIMification happen when _match_on
309
is called so the string describing the revision is kept here until needed.
314
_revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
316
# The revspecs to try
317
_possible_revspecs = []
319
def _try_spectype(self, rstype, branch):
320
rs = rstype(self.spec, _internal=True)
321
# Hit in_history to find out if it exists, or we need to try the
323
return rs.in_history(branch)
325
def _match_on(self, branch, revs):
326
"""Run the lookup and see what we can get."""
328
# First, see if it's a revno
329
if self._revno_regex.match(self.spec) is not None:
331
return self._try_spectype(RevisionSpec_revno, branch)
332
except RevisionSpec_revno.dwim_catchable_exceptions:
335
# Next see what has been registered
336
for objgetter in self._possible_revspecs:
337
rs_class = objgetter.get_obj()
339
return self._try_spectype(rs_class, branch)
340
except rs_class.dwim_catchable_exceptions:
343
# Try the old (deprecated) dwim list:
344
for rs_class in dwim_revspecs:
346
return self._try_spectype(rs_class, branch)
347
except rs_class.dwim_catchable_exceptions:
350
# Well, I dunno what it is. Note that we don't try to keep track of the
351
# first of last exception raised during the DWIM tries as none seems
353
raise errors.InvalidRevisionSpec(self.spec, branch)
356
def append_possible_revspec(cls, revspec):
357
"""Append a possible DWIM revspec.
359
:param revspec: Revision spec to try.
361
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
364
def append_possible_lazy_revspec(cls, module_name, member_name):
365
"""Append a possible lazily loaded DWIM revspec.
367
:param module_name: Name of the module with the revspec
368
:param member_name: Name of the revspec within the module
370
cls._possible_revspecs.append(
371
registry._LazyObjectGetter(module_name, member_name))
259
374
class RevisionSpec_revno(RevisionSpec):
260
375
"""Selects a revision using a number."""
262
377
help_txt = """Selects a revision using a number.
264
379
Use an integer to specify a revision in the history of the branch.
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.
270
revno:1 -> return the first revision
380
Optionally a branch can be specified. A negative number will count
381
from the end of the branch (-1 is the last revision, -2 the previous
382
one). If the negative number is larger than the branch's history, the
383
first revision is returned.
386
revno:1 -> return the first revision of this branch
271
387
revno:3:/path/to/branch -> return the 3rd revision of
272
388
the branch '/path/to/branch'
273
389
revno:-1 -> The last revision in a branch.
312
# the user has override the branch to look in.
313
# we need to refresh the revision_history map and
315
from bzrlib.branch import Branch
316
branch = Branch.open(branch_spec)
317
# Need to use a new revision history
318
# because we are using a specific branch
319
revs = branch.revision_history()
432
# the user has overriden the branch to look in.
433
branch = _mod_branch.Branch.open(branch_spec)
324
revision_id_to_revno = branch.get_revision_id_to_revno_map()
325
revisions = [revision_id for revision_id, revno
326
in revision_id_to_revno.iteritems()
327
if revno == match_revno]
330
if len(revisions) != 1:
331
return RevisionInfo(branch, None, None)
437
revision_id = branch.dotted_revno_to_revision_id(match_revno,
439
except errors.NoSuchRevision:
440
raise errors.InvalidRevisionSpec(self.user_spec, branch)
333
442
# there is no traditional 'revno' for dotted-decimal revnos.
334
# so for API compatability we return None.
335
return RevisionInfo(branch, None, revisions[0])
443
# so for API compatibility we return None.
444
return branch, None, revision_id
446
last_revno, last_revision_id = branch.last_revision_info()
338
448
# if get_rev_id supported negative revnos, there would not be a
339
449
# need for this special case.
340
if (-revno) >= len(revs):
450
if (-revno) >= last_revno:
343
revno = len(revs) + revno + 1
453
revno = last_revno + revno + 1
345
revision_id = branch.get_rev_id(revno, revs)
455
revision_id = branch.get_rev_id(revno)
346
456
except errors.NoSuchRevision:
347
457
raise errors.InvalidRevisionSpec(self.user_spec, branch)
348
return RevisionInfo(branch, revno, revision_id)
458
return branch, revno, revision_id
460
def _as_revision_id(self, context_branch):
461
# We would have the revno here, but we don't really care
462
branch, revno, revision_id = self._lookup(context_branch)
350
465
def needs_branch(self):
351
466
return self.spec.find(':') == -1
357
472
return self.spec[self.spec.find(':')+1:]
360
475
RevisionSpec_int = RevisionSpec_revno
362
SPEC_TYPES.append(RevisionSpec_revno)
365
class RevisionSpec_revid(RevisionSpec):
478
class RevisionIDSpec(RevisionSpec):
480
def _match_on(self, branch, revs):
481
revision_id = self.as_revision_id(branch)
482
return RevisionInfo.from_revision_id(branch, revision_id)
485
class RevisionSpec_revid(RevisionIDSpec):
366
486
"""Selects a revision using the revision id."""
368
488
help_txt = """Selects a revision using the revision id.
370
490
Supply a specific revision id, that can be used to specify any
371
revision id in the ancestry of the branch.
491
revision id in the ancestry of the branch.
372
492
Including merges, and pending merges.
374
495
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
376
498
prefix = 'revid:'
378
def _match_on(self, branch, revs):
500
def _as_revision_id(self, context_branch):
379
501
# self.spec comes straight from parsing the command line arguments,
380
502
# so we expect it to be a Unicode string. Switch it to the internal
381
503
# representation.
382
revision_id = osutils.safe_revision_id(self.spec, warn=False)
383
return RevisionInfo.from_revision_id(branch, revision_id, revs)
504
return osutils.safe_revision_id(self.spec, warn=False)
385
SPEC_TYPES.append(RevisionSpec_revid)
388
508
class RevisionSpec_last(RevisionSpec):
393
513
Supply a positive number to get the nth revision from the end.
394
514
This is the same as supplying negative numbers to the 'revno:' spec.
396
517
last:1 -> return the last revision
397
518
last:3 -> return the revision 2 before the end.
402
523
def _match_on(self, branch, revs):
524
revno, revision_id = self._revno_and_revision_id(branch)
525
return RevisionInfo(branch, revno, revision_id)
527
def _revno_and_revision_id(self, context_branch):
528
last_revno, last_revision_id = context_branch.last_revision_info()
403
530
if self.spec == '':
405
raise errors.NoCommits(branch)
406
return RevisionInfo(branch, len(revs), revs[-1])
532
raise errors.NoCommits(context_branch)
533
return last_revno, last_revision_id
409
536
offset = int(self.spec)
410
537
except ValueError, e:
411
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
538
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
414
raise errors.InvalidRevisionSpec(self.user_spec, branch,
541
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
415
542
'you must supply a positive value')
416
revno = len(revs) - offset + 1
544
revno = last_revno - offset + 1
418
revision_id = branch.get_rev_id(revno, revs)
546
revision_id = context_branch.get_rev_id(revno)
419
547
except errors.NoSuchRevision:
420
raise errors.InvalidRevisionSpec(self.user_spec, branch)
421
return RevisionInfo(branch, revno, revision_id)
423
SPEC_TYPES.append(RevisionSpec_last)
548
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
549
return revno, revision_id
551
def _as_revision_id(self, context_branch):
552
# We compute the revno as part of the process, but we don't really care
554
revno, revision_id = self._revno_and_revision_id(context_branch)
426
559
class RevisionSpec_before(RevisionSpec):
429
562
help_txt = """Selects the parent of the revision specified.
431
Supply any revision spec to return the parent of that revision.
564
Supply any revision spec to return the parent of that revision. This is
565
mostly useful when inspecting revisions that are not in the revision history
432
568
It is an error to request the parent of the null revision (before:0).
433
This is mostly useful when inspecting revisions that are not in the
434
revision history of a branch.
437
572
before:1913 -> Return the parent of revno 1913 (revno 1912)
438
573
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
439
574
aaaa@bbbb-1234567890
440
bzr diff -r before:revid:aaaa..revid:aaaa
441
-> Find the changes between revision 'aaaa' and its parent.
442
(what changes did 'aaaa' introduce)
575
bzr diff -r before:1913..1913
576
-> Find the changes between revision 1913 and its parent (1912).
577
(What changes did revision 1913 introduce).
578
This is equivalent to: bzr diff -c 1913
445
581
prefix = 'before:'
447
583
def _match_on(self, branch, revs):
448
584
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
471
603
return RevisionInfo(branch, revno, revision_id)
473
SPEC_TYPES.append(RevisionSpec_before)
605
def _as_revision_id(self, context_branch):
606
base_revision_id = RevisionSpec.from_string(self.spec)._as_revision_id(context_branch)
607
if base_revision_id == revision.NULL_REVISION:
608
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
609
'cannot go before the null: revision')
610
context_repo = context_branch.repository
611
context_repo.lock_read()
613
parent_map = context_repo.get_parent_map([base_revision_id])
615
context_repo.unlock()
616
if base_revision_id not in parent_map:
617
# Ghost, or unknown revision id
618
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
619
'cannot find the matching revision')
620
parents = parent_map[base_revision_id]
622
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
623
'No parents for revision.')
476
628
class RevisionSpec_tag(RevisionSpec):
637
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
486
639
def _match_on(self, branch, revs):
487
640
# Can raise tags not supported, NoSuchTag, etc
488
641
return RevisionInfo.from_revision_id(branch,
489
branch.tags.lookup_tag(self.spec),
492
SPEC_TYPES.append(RevisionSpec_tag)
642
branch.tags.lookup_tag(self.spec))
644
def _as_revision_id(self, context_branch):
645
return context_branch.tags.lookup_tag(self.spec)
495
649
class _RevListToTimestamps(object):
496
650
"""This takes a list of revisions, and allows you to bisect by date"""
498
__slots__ = ['revs', 'branch']
652
__slots__ = ['branch']
500
def __init__(self, revs, branch):
654
def __init__(self, branch):
502
655
self.branch = branch
504
657
def __getitem__(self, index):
505
658
"""Get the date of the index'd item"""
506
r = self.branch.repository.get_revision(self.revs[index])
659
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
507
660
# TODO: Handle timezone.
508
661
return datetime.datetime.fromtimestamp(r.timestamp)
510
663
def __len__(self):
511
return len(self.revs)
664
return self.branch.revno()
514
667
class RevisionSpec_date(RevisionSpec):
622
776
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
623
777
return self._find_revision_info(branch, self.spec)
779
def _as_revision_id(self, context_branch):
780
return self._find_revision_id(context_branch, self.spec)
626
783
def _find_revision_info(branch, other_location):
784
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
786
return RevisionInfo(branch, None, revision_id)
789
def _find_revision_id(branch, other_location):
627
790
from bzrlib.branch import Branch
629
other_branch = Branch.open(other_location)
630
revision_a = branch.last_revision()
631
revision_b = other_branch.last_revision()
632
for r, b in ((revision_a, branch), (revision_b, other_branch)):
633
if r in (None, revision.NULL_REVISION):
634
raise errors.NoCommits(b)
635
revision_source = revision.MultipleRevisionSources(
636
branch.repository, other_branch.repository)
637
graph = branch.repository.get_graph(other_branch.repository)
638
revision_a = revision.ensure_null(revision_a)
639
revision_b = revision.ensure_null(revision_b)
640
if revision.NULL_REVISION in (revision_a, revision_b):
641
rev_id = revision.NULL_REVISION
643
rev_id = graph.find_unique_lca(revision_a, revision_b)
794
revision_a = revision.ensure_null(branch.last_revision())
795
if revision_a == revision.NULL_REVISION:
796
raise errors.NoCommits(branch)
797
if other_location == '':
798
other_location = branch.get_parent()
799
other_branch = Branch.open(other_location)
800
other_branch.lock_read()
802
revision_b = revision.ensure_null(other_branch.last_revision())
803
if revision_b == revision.NULL_REVISION:
804
raise errors.NoCommits(other_branch)
805
graph = branch.repository.get_graph(other_branch.repository)
806
rev_id = graph.find_unique_lca(revision_a, revision_b)
808
other_branch.unlock()
644
809
if rev_id == revision.NULL_REVISION:
645
810
raise errors.NoCommonAncestor(revision_a, revision_b)
647
revno = branch.revision_id_to_revno(rev_id)
648
except errors.NoSuchRevision:
650
return RevisionInfo(branch, revno, rev_id)
653
SPEC_TYPES.append(RevisionSpec_ancestor)
656
818
class RevisionSpec_branch(RevisionSpec):
671
835
revision_b = other_branch.last_revision()
672
836
if revision_b in (None, revision.NULL_REVISION):
673
837
raise errors.NoCommits(other_branch)
674
# pull in the remote revisions so we can diff
675
branch.fetch(other_branch, revision_b)
677
revno = branch.revision_id_to_revno(revision_b)
678
except errors.NoSuchRevision:
680
return RevisionInfo(branch, revno, revision_b)
682
SPEC_TYPES.append(RevisionSpec_branch)
839
branch = other_branch
842
# pull in the remote revisions so we can diff
843
branch.fetch(other_branch, revision_b)
844
except errors.ReadOnlyError:
845
branch = other_branch
846
return RevisionInfo(branch, None, revision_b)
848
def _as_revision_id(self, context_branch):
849
from bzrlib.branch import Branch
850
other_branch = Branch.open(self.spec)
851
last_revision = other_branch.last_revision()
852
last_revision = revision.ensure_null(last_revision)
853
context_branch.fetch(other_branch, last_revision)
854
if last_revision == revision.NULL_REVISION:
855
raise errors.NoCommits(other_branch)
858
def _as_tree(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
if last_revision == revision.NULL_REVISION:
864
raise errors.NoCommits(other_branch)
865
return other_branch.repository.revision_tree(last_revision)
867
def needs_branch(self):
870
def get_branch(self):
685
875
class RevisionSpec_submit(RevisionSpec_ancestor):
690
880
Diffing against this shows all the changes that were made in this branch,
691
881
and is a good predictor of what merge will do. The submit branch is
692
used by the bundle and merge directive comands. If no submit branch
882
used by the bundle and merge directive commands. If no submit branch
693
883
is specified, the parent branch is used instead.
695
885
The common ancestor is the last revision that existed in both
696
886
branches. Usually this is the branch point, but it could also be
697
887
a revision that was merged.
700
891
$ bzr diff -r submit:
703
894
prefix = 'submit:'
705
def _match_on(self, branch, revs):
706
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
896
def _get_submit_location(self, branch):
707
897
submit_location = branch.get_submit_branch()
708
898
location_type = 'submit branch'
709
899
if submit_location is None:
711
901
location_type = 'parent branch'
712
902
if submit_location is None:
713
903
raise errors.NoSubmitBranch(branch)
714
trace.note('Using %s %s', location_type, submit_location)
715
return self._find_revision_info(branch, submit_location)
718
SPEC_TYPES.append(RevisionSpec_submit)
904
trace.note(gettext('Using {0} {1}').format(location_type,
906
return submit_location
908
def _match_on(self, branch, revs):
909
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
910
return self._find_revision_info(branch,
911
self._get_submit_location(branch))
913
def _as_revision_id(self, context_branch):
914
return self._find_revision_id(context_branch,
915
self._get_submit_location(context_branch))
918
class RevisionSpec_annotate(RevisionIDSpec):
922
help_txt = """Select the revision that last modified the specified line.
924
Select the revision that last modified the specified line. Line is
925
specified as path:number. Path is a relative path to the file. Numbers
926
start at 1, and are relative to the current version, not the last-
927
committed version of the file.
930
def _raise_invalid(self, numstring, context_branch):
931
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
932
'No such line: %s' % numstring)
934
def _as_revision_id(self, context_branch):
935
path, numstring = self.spec.rsplit(':', 1)
937
index = int(numstring) - 1
939
self._raise_invalid(numstring, context_branch)
940
tree, file_path = workingtree.WorkingTree.open_containing(path)
943
file_id = tree.path2id(file_path)
945
raise errors.InvalidRevisionSpec(self.user_spec,
946
context_branch, "File '%s' is not versioned." %
948
revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
952
revision_id = revision_ids[index]
954
self._raise_invalid(numstring, context_branch)
955
if revision_id == revision.CURRENT_REVISION:
956
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
957
'Line %s has not been committed.' % numstring)
961
class RevisionSpec_mainline(RevisionIDSpec):
963
help_txt = """Select mainline revision that merged the specified revision.
965
Select the revision that merged the specified revision into mainline.
970
def _as_revision_id(self, context_branch):
971
revspec = RevisionSpec.from_string(self.spec)
972
if revspec.get_branch() is None:
973
spec_branch = context_branch
975
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
976
revision_id = revspec.as_revision_id(spec_branch)
977
graph = context_branch.repository.get_graph()
978
result = graph.find_lefthand_merger(revision_id,
979
context_branch.last_revision())
981
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
985
# The order in which we want to DWIM a revision spec without any prefix.
986
# revno is always tried first and isn't listed here, this is used by
987
# RevisionSpec_dwim._match_on
988
dwim_revspecs = symbol_versioning.deprecated_list(
989
symbol_versioning.deprecated_in((2, 4, 0)), "dwim_revspecs", [])
991
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
992
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
993
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
994
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
996
revspec_registry = registry.Registry()
997
def _register_revspec(revspec):
998
revspec_registry.register(revspec.prefix, revspec)
1000
_register_revspec(RevisionSpec_revno)
1001
_register_revspec(RevisionSpec_revid)
1002
_register_revspec(RevisionSpec_last)
1003
_register_revspec(RevisionSpec_before)
1004
_register_revspec(RevisionSpec_tag)
1005
_register_revspec(RevisionSpec_date)
1006
_register_revspec(RevisionSpec_ancestor)
1007
_register_revspec(RevisionSpec_branch)
1008
_register_revspec(RevisionSpec_submit)
1009
_register_revspec(RevisionSpec_annotate)
1010
_register_revspec(RevisionSpec_mainline)