147
166
return RevisionSpec(None, _internal=True)
149
assert isinstance(spec, basestring), \
150
"You should only supply strings not %s" % (type(spec),)
152
for spectype in SPEC_TYPES:
153
if spec.startswith(spectype.prefix):
154
trace.mutter('Returning RevisionSpec %s for %s',
155
spectype.__name__, spec)
156
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)
158
# RevisionSpec_revno is special cased, because it is the only
159
# one that directly handles plain integers
160
# TODO: This should not be special cased rather it should be
161
# a method invocation on spectype.canparse()
163
if _revno_regex is None:
164
_revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
165
if _revno_regex.match(spec) is not None:
166
return RevisionSpec_revno(spec, _internal=True)
168
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)
170
178
def __init__(self, spec, _internal=False):
171
179
"""Create a RevisionSpec referring to the Null revision.
220
241
# will do what you expect.
221
242
in_store = in_history
222
243
in_branch = in_store
245
def as_revision_id(self, context_branch):
246
"""Return just the revision_id for this revisions spec.
248
Some revision specs require a context_branch to be able to determine
249
their value. Not all specs will make use of it.
251
return self._as_revision_id(context_branch)
253
def _as_revision_id(self, context_branch):
254
"""Implementation of as_revision_id()
256
Classes should override this function to provide appropriate
257
functionality. The default is to just call '.in_history().rev_id'
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)
224
280
def __repr__(self):
225
281
# this is mostly for helping with testing
226
282
return '<%s %s>' % (self.__class__.__name__,
229
285
def needs_branch(self):
230
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))
246
373
class RevisionSpec_revno(RevisionSpec):
247
374
"""Selects a revision using a number."""
249
376
help_txt = """Selects a revision using a number.
251
378
Use an integer to specify a revision in the history of the branch.
252
Optionally a branch can be specified. The 'revno:' prefix is optional.
253
A negative number will count from the end of the branch (-1 is the
254
last revision, -2 the previous one). If the negative number is larger
255
than the branch's history, the first revision is returned.
257
revno:1 -> return the first revision
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.
385
revno:1 -> return the first revision of this branch
258
386
revno:3:/path/to/branch -> return the 3rd revision of
259
387
the branch '/path/to/branch'
260
388
revno:-1 -> The last revision in a branch.
299
# the user has override the branch to look in.
300
# we need to refresh the revision_history map and
302
from bzrlib.branch import Branch
303
branch = Branch.open(branch_spec)
304
# Need to use a new revision history
305
# because we are using a specific branch
306
revs = branch.revision_history()
431
# the user has overriden the branch to look in.
432
branch = _mod_branch.Branch.open(branch_spec)
311
last_rev = branch.last_revision()
312
merge_sorted_revisions = tsort.merge_sort(
313
branch.repository.get_revision_graph(last_rev),
317
return item[3] == match_revno
318
revisions = filter(match, merge_sorted_revisions)
321
if len(revisions) != 1:
322
return RevisionInfo(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)
324
441
# there is no traditional 'revno' for dotted-decimal revnos.
325
# so for API compatability we return None.
326
return RevisionInfo(branch, None, revisions[0][1])
442
# so for API compatibility we return None.
443
return branch, None, revision_id
445
last_revno, last_revision_id = branch.last_revision_info()
329
447
# if get_rev_id supported negative revnos, there would not be a
330
448
# need for this special case.
331
if (-revno) >= len(revs):
449
if (-revno) >= last_revno:
334
revno = len(revs) + revno + 1
452
revno = last_revno + revno + 1
336
revision_id = branch.get_rev_id(revno, revs)
454
revision_id = branch.get_rev_id(revno)
337
455
except errors.NoSuchRevision:
338
456
raise errors.InvalidRevisionSpec(self.user_spec, branch)
339
return RevisionInfo(branch, revno, revision_id)
457
return branch, revno, revision_id
459
def _as_revision_id(self, context_branch):
460
# We would have the revno here, but we don't really care
461
branch, revno, revision_id = self._lookup(context_branch)
341
464
def needs_branch(self):
342
465
return self.spec.find(':') == -1
348
471
return self.spec[self.spec.find(':')+1:]
351
474
RevisionSpec_int = RevisionSpec_revno
353
SPEC_TYPES.append(RevisionSpec_revno)
356
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):
357
485
"""Selects a revision using the revision id."""
359
487
help_txt = """Selects a revision using the revision id.
361
489
Supply a specific revision id, that can be used to specify any
362
revision id in the ancestry of the branch.
490
revision id in the ancestry of the branch.
363
491
Including merges, and pending merges.
365
494
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
367
497
prefix = 'revid:'
369
def _match_on(self, branch, revs):
371
revno = revs.index(self.spec) + 1
374
return RevisionInfo(branch, revno, self.spec)
499
def _as_revision_id(self, context_branch):
500
# self.spec comes straight from parsing the command line arguments,
501
# so we expect it to be a Unicode string. Switch it to the internal
503
return osutils.safe_revision_id(self.spec, warn=False)
376
SPEC_TYPES.append(RevisionSpec_revid)
379
507
class RevisionSpec_last(RevisionSpec):
384
512
Supply a positive number to get the nth revision from the end.
385
513
This is the same as supplying negative numbers to the 'revno:' spec.
387
516
last:1 -> return the last revision
388
517
last:3 -> return the revision 2 before the end.
393
522
def _match_on(self, branch, revs):
523
revno, revision_id = self._revno_and_revision_id(branch)
524
return RevisionInfo(branch, revno, revision_id)
526
def _revno_and_revision_id(self, context_branch):
527
last_revno, last_revision_id = context_branch.last_revision_info()
394
529
if self.spec == '':
396
raise errors.NoCommits(branch)
397
return RevisionInfo(branch, len(revs), revs[-1])
531
raise errors.NoCommits(context_branch)
532
return last_revno, last_revision_id
400
535
offset = int(self.spec)
401
536
except ValueError, e:
402
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
537
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
405
raise errors.InvalidRevisionSpec(self.user_spec, branch,
540
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
406
541
'you must supply a positive value')
407
revno = len(revs) - offset + 1
543
revno = last_revno - offset + 1
409
revision_id = branch.get_rev_id(revno, revs)
545
revision_id = context_branch.get_rev_id(revno)
410
546
except errors.NoSuchRevision:
411
raise errors.InvalidRevisionSpec(self.user_spec, branch)
412
return RevisionInfo(branch, revno, revision_id)
414
SPEC_TYPES.append(RevisionSpec_last)
547
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
548
return revno, revision_id
550
def _as_revision_id(self, context_branch):
551
# We compute the revno as part of the process, but we don't really care
553
revno, revision_id = self._revno_and_revision_id(context_branch)
417
558
class RevisionSpec_before(RevisionSpec):
462
602
return RevisionInfo(branch, revno, revision_id)
464
SPEC_TYPES.append(RevisionSpec_before)
604
def _as_revision_id(self, context_branch):
605
base_revision_id = RevisionSpec.from_string(self.spec)._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.')
467
627
class RevisionSpec_tag(RevisionSpec):
468
"""To be implemented."""
470
help_txt = """To be implemented."""
628
"""Select a revision identified by tag name"""
630
help_txt = """Selects a revision identified by a tag name.
632
Tags are stored in the branch and created by the 'tag' command.
636
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
474
638
def _match_on(self, branch, revs):
475
raise errors.InvalidRevisionSpec(self.user_spec, branch,
476
'tag: namespace registered,'
477
' but not implemented')
479
SPEC_TYPES.append(RevisionSpec_tag)
639
# Can raise tags not supported, NoSuchTag, etc
640
return RevisionInfo.from_revision_id(branch,
641
branch.tags.lookup_tag(self.spec))
643
def _as_revision_id(self, context_branch):
644
return context_branch.tags.lookup_tag(self.spec)
482
648
class _RevListToTimestamps(object):
483
649
"""This takes a list of revisions, and allows you to bisect by date"""
485
__slots__ = ['revs', 'branch']
651
__slots__ = ['branch']
487
def __init__(self, revs, branch):
653
def __init__(self, branch):
489
654
self.branch = branch
491
656
def __getitem__(self, index):
492
657
"""Get the date of the index'd item"""
493
r = self.branch.repository.get_revision(self.revs[index])
658
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
494
659
# TODO: Handle timezone.
495
660
return datetime.datetime.fromtimestamp(r.timestamp)
497
662
def __len__(self):
498
return len(self.revs)
663
return self.branch.revno()
501
666
class RevisionSpec_date(RevisionSpec):
599
764
that your branch introduces, while excluding the changes that you
600
765
have not merged from the remote branch.
603
769
ancestor:/path/to/branch
604
770
$ bzr diff -r ancestor:../../mainline/branch
606
772
prefix = 'ancestor:'
608
774
def _match_on(self, branch, revs):
775
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
776
return self._find_revision_info(branch, self.spec)
778
def _as_revision_id(self, context_branch):
779
return self._find_revision_id(context_branch, self.spec)
782
def _find_revision_info(branch, other_location):
783
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
785
return RevisionInfo(branch, None, revision_id)
788
def _find_revision_id(branch, other_location):
609
789
from bzrlib.branch import Branch
611
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
612
other_branch = Branch.open(self.spec)
613
revision_a = branch.last_revision()
614
revision_b = other_branch.last_revision()
615
for r, b in ((revision_a, branch), (revision_b, other_branch)):
616
if r in (None, revision.NULL_REVISION):
617
raise errors.NoCommits(b)
618
revision_source = revision.MultipleRevisionSources(
619
branch.repository, other_branch.repository)
620
rev_id = revision.common_ancestor(revision_a, revision_b,
623
revno = branch.revision_id_to_revno(rev_id)
624
except errors.NoSuchRevision:
626
return RevisionInfo(branch, revno, rev_id)
628
SPEC_TYPES.append(RevisionSpec_ancestor)
793
revision_a = revision.ensure_null(branch.last_revision())
794
if revision_a == revision.NULL_REVISION:
795
raise errors.NoCommits(branch)
796
if other_location == '':
797
other_location = branch.get_parent()
798
other_branch = Branch.open(other_location)
799
other_branch.lock_read()
801
revision_b = revision.ensure_null(other_branch.last_revision())
802
if revision_b == revision.NULL_REVISION:
803
raise errors.NoCommits(other_branch)
804
graph = branch.repository.get_graph(other_branch.repository)
805
rev_id = graph.find_unique_lca(revision_a, revision_b)
807
other_branch.unlock()
808
if rev_id == revision.NULL_REVISION:
809
raise errors.NoCommonAncestor(revision_a, revision_b)
631
817
class RevisionSpec_branch(RevisionSpec):
646
834
revision_b = other_branch.last_revision()
647
835
if revision_b in (None, revision.NULL_REVISION):
648
836
raise errors.NoCommits(other_branch)
649
# pull in the remote revisions so we can diff
650
branch.fetch(other_branch, revision_b)
652
revno = branch.revision_id_to_revno(revision_b)
653
except errors.NoSuchRevision:
655
return RevisionInfo(branch, revno, revision_b)
657
SPEC_TYPES.append(RevisionSpec_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)
847
def _as_revision_id(self, context_branch):
848
from bzrlib.branch import Branch
849
other_branch = Branch.open(self.spec)
850
last_revision = other_branch.last_revision()
851
last_revision = revision.ensure_null(last_revision)
852
context_branch.fetch(other_branch, last_revision)
853
if last_revision == revision.NULL_REVISION:
854
raise errors.NoCommits(other_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):
874
class RevisionSpec_submit(RevisionSpec_ancestor):
875
"""Selects a common ancestor with a submit branch."""
877
help_txt = """Selects a common ancestor with the submit branch.
879
Diffing against this shows all the changes that were made in this branch,
880
and is a good predictor of what merge will do. The submit branch is
881
used by the bundle and merge directive commands. If no submit branch
882
is specified, the parent branch is used instead.
884
The common ancestor is the last revision that existed in both
885
branches. Usually this is the branch point, but it could also be
886
a revision that was merged.
890
$ bzr diff -r submit:
895
def _get_submit_location(self, branch):
896
submit_location = branch.get_submit_branch()
897
location_type = 'submit branch'
898
if submit_location is None:
899
submit_location = branch.get_parent()
900
location_type = 'parent branch'
901
if submit_location is None:
902
raise errors.NoSubmitBranch(branch)
903
trace.note(gettext('Using {0} {1}').format(location_type,
905
return submit_location
907
def _match_on(self, branch, revs):
908
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
909
return self._find_revision_info(branch,
910
self._get_submit_location(branch))
912
def _as_revision_id(self, context_branch):
913
return self._find_revision_id(context_branch,
914
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)