163
159
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)
161
assert isinstance(spec, basestring), \
162
"You should only supply strings not %s" % (type(spec),)
164
for spectype in SPEC_TYPES:
165
if spec.startswith(spectype.prefix):
166
trace.mutter('Returning RevisionSpec %s for %s',
167
spectype.__name__, spec)
168
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)
170
# RevisionSpec_revno is special cased, because it is the only
171
# one that directly handles plain integers
172
# TODO: This should not be special cased rather it should be
173
# a method invocation on spectype.canparse()
175
if _revno_regex is None:
176
_revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
177
if _revno_regex.match(spec) is not None:
178
return RevisionSpec_revno(spec, _internal=True)
180
raise errors.NoSuchRevisionSpec(spec)
175
182
def __init__(self, spec, _internal=False):
176
183
"""Create a RevisionSpec referring to the Null revision.
227
232
# will do what you expect.
228
233
in_store = in_history
229
234
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
236
def __repr__(self):
267
237
# this is mostly for helping with testing
268
238
return '<%s %s>' % (self.__class__.__name__,
271
241
def needs_branch(self):
272
242
"""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
258
class RevisionSpec_revno(RevisionSpec):
363
259
"""Selects a revision using a number."""
365
261
help_txt = """Selects a revision using a number.
367
263
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.
374
revno:1 -> return the first revision of this branch
264
Optionally a branch can be specified. The 'revno:' prefix is optional.
265
A negative number will count from the end of the branch (-1 is the
266
last revision, -2 the previous one). If the negative number is larger
267
than the branch's history, the first revision is returned.
269
revno:1 -> return the first revision
375
270
revno:3:/path/to/branch -> return the 3rd revision of
376
271
the branch '/path/to/branch'
377
272
revno:-1 -> The last revision in a branch.
423
313
# the branch object.
424
314
from bzrlib.branch import Branch
425
315
branch = Branch.open(branch_spec)
316
# Need to use a new revision history
317
# because we are using a specific branch
318
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)
323
last_rev = branch.last_revision()
324
merge_sorted_revisions = tsort.merge_sort(
325
branch.repository.get_revision_graph(last_rev),
329
return item[3] == match_revno
330
revisions = filter(match, merge_sorted_revisions)
333
if len(revisions) != 1:
334
return RevisionInfo(branch, None, None)
435
336
# there is no traditional 'revno' for dotted-decimal revnos.
436
337
# so for API compatability we return None.
437
return branch, None, revision_id
338
return RevisionInfo(branch, None, revisions[0][1])
439
last_revno, last_revision_id = branch.last_revision_info()
441
341
# if get_rev_id supported negative revnos, there would not be a
442
342
# need for this special case.
443
if (-revno) >= last_revno:
343
if (-revno) >= len(revs):
446
revno = last_revno + revno + 1
346
revno = len(revs) + revno + 1
448
revision_id = branch.get_rev_id(revno, revs_or_none)
348
revision_id = branch.get_rev_id(revno, revs)
449
349
except errors.NoSuchRevision:
450
350
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)
351
return RevisionInfo(branch, revno, revision_id)
458
353
def needs_branch(self):
459
354
return self.spec.find(':') == -1
465
360
return self.spec[self.spec.find(':')+1:]
468
363
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):
365
SPEC_TYPES.append(RevisionSpec_revno)
368
class RevisionSpec_revid(RevisionSpec):
480
369
"""Selects a revision using the revision id."""
482
371
help_txt = """Selects a revision using the revision id.
484
373
Supply a specific revision id, that can be used to specify any
485
revision id in the ancestry of the branch.
374
revision id in the ancestry of the branch.
486
375
Including merges, and pending merges.
489
377
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
492
379
prefix = 'revid:'
494
def _as_revision_id(self, context_branch):
495
# self.spec comes straight from parsing the command line arguments,
496
# so we expect it to be a Unicode string. Switch it to the internal
498
return osutils.safe_revision_id(self.spec, warn=False)
381
def _match_on(self, branch, revs):
382
return RevisionInfo.from_revision_id(branch, self.spec, revs)
384
SPEC_TYPES.append(RevisionSpec_revid)
502
387
class RevisionSpec_last(RevisionSpec):
507
392
Supply a positive number to get the nth revision from the end.
508
393
This is the same as supplying negative numbers to the 'revno:' spec.
511
395
last:1 -> return the last revision
512
396
last:3 -> return the revision 2 before the end.
517
401
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
402
if self.spec == '':
526
raise errors.NoCommits(context_branch)
527
return last_revno, last_revision_id
404
raise errors.NoCommits(branch)
405
return RevisionInfo(branch, len(revs), revs[-1])
530
408
offset = int(self.spec)
531
409
except ValueError, e:
532
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
410
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
535
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
413
raise errors.InvalidRevisionSpec(self.user_spec, branch,
536
414
'you must supply a positive value')
538
revno = last_revno - offset + 1
415
revno = len(revs) - offset + 1
540
revision_id = context_branch.get_rev_id(revno, revs_or_none)
417
revision_id = branch.get_rev_id(revno, revs)
541
418
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)
419
raise errors.InvalidRevisionSpec(self.user_spec, branch)
420
return RevisionInfo(branch, revno, revision_id)
422
SPEC_TYPES.append(RevisionSpec_last)
553
425
class RevisionSpec_before(RevisionSpec):
767
612
that your branch introduces, while excluding the changes that you
768
613
have not merged from the remote branch.
772
616
ancestor:/path/to/branch
773
617
$ bzr diff -r ancestor:../../mainline/branch
775
619
prefix = 'ancestor:'
777
621
def _match_on(self, branch, revs):
622
from bzrlib.branch import Branch
778
624
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
779
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
def _find_revision_info(branch, other_location):
786
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
625
other_branch = Branch.open(self.spec)
626
revision_a = branch.last_revision()
627
revision_b = other_branch.last_revision()
628
for r, b in ((revision_a, branch), (revision_b, other_branch)):
629
if r in (None, revision.NULL_REVISION):
630
raise errors.NoCommits(b)
631
revision_source = revision.MultipleRevisionSources(
632
branch.repository, other_branch.repository)
633
rev_id = revision.common_ancestor(revision_a, revision_b,
789
revno = branch.revision_id_to_revno(revision_id)
636
revno = branch.revision_id_to_revno(rev_id)
790
637
except errors.NoSuchRevision:
792
return RevisionInfo(branch, revno, revision_id)
795
def _find_revision_id(branch, other_location):
796
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()
815
if rev_id == revision.NULL_REVISION:
816
raise errors.NoCommonAncestor(revision_a, revision_b)
639
return RevisionInfo(branch, revno, rev_id)
641
SPEC_TYPES.append(RevisionSpec_ancestor)
824
644
class RevisionSpec_branch(RevisionSpec):
841
659
revision_b = other_branch.last_revision()
842
660
if revision_b in (None, revision.NULL_REVISION):
843
661
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
662
# pull in the remote revisions so we can diff
663
branch.fetch(other_branch, revision_b)
853
665
revno = branch.revision_id_to_revno(revision_b)
854
666
except errors.NoSuchRevision:
856
668
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):
885
class RevisionSpec_submit(RevisionSpec_ancestor):
886
"""Selects a common ancestor with a submit branch."""
888
help_txt = """Selects a common ancestor with the submit branch.
890
Diffing against this shows all the changes that were made in this branch,
891
and is a good predictor of what merge will do. The submit branch is
892
used by the bundle and merge directive commands. If no submit branch
893
is specified, the parent branch is used instead.
895
The common ancestor is the last revision that existed in both
896
branches. Usually this is the branch point, but it could also be
897
a revision that was merged.
901
$ bzr diff -r submit:
906
def _get_submit_location(self, branch):
907
submit_location = branch.get_submit_branch()
908
location_type = 'submit branch'
909
if submit_location is None:
910
submit_location = branch.get_parent()
911
location_type = 'parent branch'
912
if submit_location is None:
913
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)
670
SPEC_TYPES.append(RevisionSpec_branch)