144
dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
145
"""Exceptions that RevisionSpec_dwim._match_on will catch.
147
If the revspec is part of ``dwim_revspecs``, it may be tried with an
148
invalid revspec and raises some exception. The exceptions mentioned here
149
will not be reported to the user but simply ignored without stopping the
154
def from_string(spec):
155
"""Parse a revision spec string into a RevisionSpec object.
157
:param spec: A string specified by the user
158
:return: A RevisionSpec object that understands how to parse the
108
def __new__(cls, spec, foo=_marker):
109
"""Parse a revision specifier.
162
return RevisionSpec(None, _internal=True)
163
if not isinstance(spec, (str, text_type)):
164
raise TypeError("revision spec needs to be text")
165
match = revspec_registry.get_prefix(spec)
166
if match is not None:
167
spectype, specsuffix = match
168
trace.mutter('Returning RevisionSpec %s for %s',
169
spectype.__name__, spec)
170
return spectype(spec, _internal=True)
112
return object.__new__(RevisionSpec, spec)
119
if isinstance(spec, int):
120
return object.__new__(RevisionSpec_int, spec)
121
elif isinstance(spec, basestring):
122
for spectype in SPEC_TYPES:
123
if spec.startswith(spectype.prefix):
124
return object.__new__(spectype, spec)
126
raise BzrError('No namespace registered for string: %r' %
172
# Otherwise treat it as a DWIM, build the RevisionSpec object and
173
# wait for _match_on to be called.
174
return RevisionSpec_dwim(spec, _internal=True)
176
def __init__(self, spec, _internal=False):
177
"""Create a RevisionSpec referring to the Null revision.
179
:param spec: The original spec supplied by the user
180
:param _internal: Used to ensure that RevisionSpec is not being
181
called directly. Only from RevisionSpec.from_string()
184
raise AssertionError(
185
'Creating a RevisionSpec directly is not supported. '
186
'Use RevisionSpec.from_string() instead.')
187
self.user_spec = spec
129
raise TypeError('Unhandled revision type %s' % spec)
131
def __init__(self, spec):
188
132
if self.prefix and spec.startswith(self.prefix):
189
133
spec = spec[len(self.prefix):]
192
136
def _match_on(self, branch, revs):
193
trace.mutter('Returning RevisionSpec._match_on: None')
194
return RevisionInfo(branch, None, None)
137
return RevisionInfo(branch, 0, None)
196
139
def _match_on_and_check(self, branch, revs):
197
140
info = self._match_on(branch, revs)
200
elif info == (None, None):
201
# special case - nothing supplied
143
elif info == (0, None):
144
# special case - the empty tree
203
146
elif self.prefix:
204
raise errors.InvalidRevisionSpec(self.user_spec, branch)
147
raise NoSuchRevision(branch, self.prefix + str(self.spec))
206
raise errors.InvalidRevisionSpec(self.spec, branch)
149
raise NoSuchRevision(branch, str(self.spec))
208
151
def in_history(self, branch):
209
return self._match_on_and_check(branch, revs=None)
152
revs = branch.revision_history()
153
return self._match_on_and_check(branch, revs)
211
155
# FIXME: in_history is somewhat broken,
212
156
# it will return non-history revisions in many
217
161
# will do what you expect.
218
162
in_store = in_history
219
163
in_branch = in_store
221
def as_revision_id(self, context_branch):
222
"""Return just the revision_id for this revisions spec.
224
Some revision specs require a context_branch to be able to determine
225
their value. Not all specs will make use of it.
227
return self._as_revision_id(context_branch)
229
def _as_revision_id(self, context_branch):
230
"""Implementation of as_revision_id()
232
Classes should override this function to provide appropriate
233
functionality. The default is to just call '.in_history().rev_id'
235
return self.in_history(context_branch).rev_id
237
def as_tree(self, context_branch):
238
"""Return the tree object for this revisions spec.
240
Some revision specs require a context_branch to be able to determine
241
the revision id and access the repository. Not all specs will make
244
return self._as_tree(context_branch)
246
def _as_tree(self, context_branch):
247
"""Implementation of as_tree().
249
Classes should override this function to provide appropriate
250
functionality. The default is to just call '.as_revision_id()'
251
and get the revision tree from context_branch's repository.
253
revision_id = self.as_revision_id(context_branch)
254
return context_branch.repository.revision_tree(revision_id)
256
165
def __repr__(self):
257
166
# this is mostly for helping with testing
258
return '<%s %s>' % (self.__class__.__name__,
261
def needs_branch(self):
262
"""Whether this revision spec needs a branch.
264
Set this to False the branch argument of _match_on is not used.
268
def get_branch(self):
269
"""When the revision specifier contains a branch location, return it.
271
Otherwise, return None.
167
return '<%s %s%s>' % (self.__class__.__name__,
278
class RevisionSpec_dwim(RevisionSpec):
279
"""Provides a DWIMish revision specifier lookup.
281
Note that this does not go in the revspec_registry because by definition
282
there is no prefix to identify it. It's solely called from
283
RevisionSpec.from_string() because the DWIMification happen when _match_on
284
is called so the string describing the revision is kept here until needed.
289
_revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
291
# The revspecs to try
292
_possible_revspecs = []
294
def _try_spectype(self, rstype, branch):
295
rs = rstype(self.spec, _internal=True)
296
# Hit in_history to find out if it exists, or we need to try the
298
return rs.in_history(branch)
174
class RevisionSpec_int(RevisionSpec):
175
"""Spec is a number. Special case."""
176
def __init__(self, spec):
177
self.spec = int(spec)
300
179
def _match_on(self, branch, revs):
301
"""Run the lookup and see what we can get."""
303
# First, see if it's a revno
304
if self._revno_regex.match(self.spec) is not None:
306
return self._try_spectype(RevisionSpec_revno, branch)
307
except RevisionSpec_revno.dwim_catchable_exceptions:
310
# Next see what has been registered
311
for objgetter in self._possible_revspecs:
312
rs_class = objgetter.get_obj()
314
return self._try_spectype(rs_class, branch)
315
except rs_class.dwim_catchable_exceptions:
318
# Well, I dunno what it is. Note that we don't try to keep track of the
319
# first of last exception raised during the DWIM tries as none seems
321
raise errors.InvalidRevisionSpec(self.spec, branch)
324
def append_possible_revspec(cls, revspec):
325
"""Append a possible DWIM revspec.
327
:param revspec: Revision spec to try.
329
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
332
def append_possible_lazy_revspec(cls, module_name, member_name):
333
"""Append a possible lazily loaded DWIM revspec.
335
:param module_name: Name of the module with the revspec
336
:param member_name: Name of the revspec within the module
338
cls._possible_revspecs.append(
339
registry._LazyObjectGetter(module_name, member_name))
181
revno = len(revs) + self.spec + 1
184
rev_id = branch.get_rev_id(revno, revs)
185
return RevisionInfo(branch, revno, rev_id)
342
188
class RevisionSpec_revno(RevisionSpec):
343
"""Selects a revision using a number."""
345
help_txt = """Selects a revision using a number.
347
Use an integer to specify a revision in the history of the branch.
348
Optionally a branch can be specified. A negative number will count
349
from the end of the branch (-1 is the last revision, -2 the previous
350
one). If the negative number is larger than the branch's history, the
351
first revision is returned.
354
revno:1 -> return the first revision of this branch
355
revno:3:/path/to/branch -> return the 3rd revision of
356
the branch '/path/to/branch'
357
revno:-1 -> The last revision in a branch.
358
-2:http://other/branch -> The second to last revision in the
360
-1000000 -> Most likely the first revision, unless
361
your history is very long.
363
189
prefix = 'revno:'
365
191
def _match_on(self, branch, revs):
366
192
"""Lookup a revision by revision number"""
367
branch, revno, revision_id = self._lookup(branch)
368
return RevisionInfo(branch, revno, revision_id)
370
def _lookup(self, branch):
371
loc = self.spec.find(':')
373
revno_spec = self.spec
376
revno_spec = self.spec[:loc]
377
branch_spec = self.spec[loc + 1:]
381
raise errors.InvalidRevisionSpec(self.user_spec,
382
branch, 'cannot have an empty revno and no branch')
386
revno = int(revno_spec)
389
# dotted decimal. This arguably should not be here
390
# but the from_string method is a little primitive
391
# right now - RBC 20060928
393
match_revno = tuple((int(number)
394
for number in revno_spec.split('.')))
395
except ValueError as e:
396
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
401
# the user has overriden the branch to look in.
402
branch = _mod_branch.Branch.open(branch_spec)
406
revision_id = branch.dotted_revno_to_revision_id(match_revno,
408
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
409
raise errors.InvalidRevisionSpec(self.user_spec, branch)
411
# there is no traditional 'revno' for dotted-decimal revnos.
412
# so for API compatibility we return None.
413
return branch, None, revision_id
415
last_revno, last_revision_id = branch.last_revision_info()
417
# if get_rev_id supported negative revnos, there would not be a
418
# need for this special case.
419
if (-revno) >= last_revno:
422
revno = last_revno + revno + 1
424
revision_id = branch.get_rev_id(revno)
425
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
426
raise errors.InvalidRevisionSpec(self.user_spec, branch)
427
return branch, revno, revision_id
429
def _as_revision_id(self, context_branch):
430
# We would have the revno here, but we don't really care
431
branch, revno, revision_id = self._lookup(context_branch)
434
def needs_branch(self):
435
return self.spec.find(':') == -1
437
def get_branch(self):
438
if self.spec.find(':') == -1:
441
return self.spec[self.spec.find(':') + 1:]
445
RevisionSpec_int = RevisionSpec_revno
448
class RevisionIDSpec(RevisionSpec):
450
def _match_on(self, branch, revs):
451
revision_id = self.as_revision_id(branch)
452
return RevisionInfo.from_revision_id(branch, revision_id)
455
class RevisionSpec_revid(RevisionIDSpec):
456
"""Selects a revision using the revision id."""
458
help_txt = """Selects a revision using the revision id.
460
Supply a specific revision id, that can be used to specify any
461
revision id in the ancestry of the branch.
462
Including merges, and pending merges.
465
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
194
return RevisionInfo(branch, int(self.spec))
196
return RevisionInfo(branch, None)
198
SPEC_TYPES.append(RevisionSpec_revno)
201
class RevisionSpec_revid(RevisionSpec):
468
202
prefix = 'revid:'
470
def _as_revision_id(self, context_branch):
471
# self.spec comes straight from parsing the command line arguments,
472
# so we expect it to be a Unicode string. Switch it to the internal
474
if isinstance(self.spec, text_type):
475
return cache_utf8.encode(self.spec)
204
def _match_on(self, branch, revs):
206
return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
208
return RevisionInfo(branch, None, self.spec)
210
SPEC_TYPES.append(RevisionSpec_revid)
479
213
class RevisionSpec_last(RevisionSpec):
480
"""Selects the nth revision from the end."""
482
help_txt = """Selects the nth revision from the end.
484
Supply a positive number to get the nth revision from the end.
485
This is the same as supplying negative numbers to the 'revno:' spec.
488
last:1 -> return the last revision
489
last:3 -> return the revision 2 before the end.
494
217
def _match_on(self, branch, revs):
495
revno, revision_id = self._revno_and_revision_id(branch)
496
return RevisionInfo(branch, revno, revision_id)
498
def _revno_and_revision_id(self, context_branch):
499
last_revno, last_revision_id = context_branch.last_revision_info()
503
raise errors.NoCommits(context_branch)
504
return last_revno, last_revision_id
507
219
offset = int(self.spec)
508
except ValueError as e:
509
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
512
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
513
'you must supply a positive value')
515
revno = last_revno - offset + 1
517
revision_id = context_branch.get_rev_id(revno)
518
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
519
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
520
return revno, revision_id
522
def _as_revision_id(self, context_branch):
523
# We compute the revno as part of the process, but we don't really care
525
revno, revision_id = self._revno_and_revision_id(context_branch)
221
return RevisionInfo(branch, None)
224
raise BzrError('You must supply a positive value for --revision last:XXX')
225
return RevisionInfo(branch, len(revs) - offset + 1)
227
SPEC_TYPES.append(RevisionSpec_last)
529
230
class RevisionSpec_before(RevisionSpec):
530
"""Selects the parent of the revision specified."""
532
help_txt = """Selects the parent of the revision specified.
534
Supply any revision spec to return the parent of that revision. This is
535
mostly useful when inspecting revisions that are not in the revision history
538
It is an error to request the parent of the null revision (before:0).
542
before:1913 -> Return the parent of revno 1913 (revno 1912)
543
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
545
bzr diff -r before:1913..1913
546
-> Find the changes between revision 1913 and its parent (1912).
547
(What changes did revision 1913 introduce).
548
This is equivalent to: bzr diff -c 1913
551
232
prefix = 'before:'
553
234
def _match_on(self, branch, revs):
554
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
556
raise errors.InvalidRevisionSpec(self.user_spec, branch,
557
'cannot go before the null: revision')
559
# We need to use the repository history here
560
rev = branch.repository.get_revision(r.rev_id)
561
if not rev.parent_ids:
562
revision_id = revision.NULL_REVISION
564
revision_id = rev.parent_ids[0]
569
revision_id = branch.get_rev_id(revno, revs)
570
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
571
raise errors.InvalidRevisionSpec(self.user_spec,
573
return RevisionInfo(branch, revno, revision_id)
235
r = RevisionSpec(self.spec)._match_on(branch, revs)
236
if (r.revno is None) or (r.revno == 0):
238
return RevisionInfo(branch, r.revno - 1)
575
def _as_revision_id(self, context_branch):
576
base_revision_id = RevisionSpec.from_string(
577
self.spec)._as_revision_id(context_branch)
578
if base_revision_id == revision.NULL_REVISION:
579
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
580
'cannot go before the null: revision')
581
context_repo = context_branch.repository
582
with context_repo.lock_read():
583
parent_map = context_repo.get_parent_map([base_revision_id])
584
if base_revision_id not in parent_map:
585
# Ghost, or unknown revision id
586
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
587
'cannot find the matching revision')
588
parents = parent_map[base_revision_id]
590
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
591
'No parents for revision.')
240
SPEC_TYPES.append(RevisionSpec_before)
595
243
class RevisionSpec_tag(RevisionSpec):
596
"""Select a revision identified by tag name"""
598
help_txt = """Selects a revision identified by a tag name.
600
Tags are stored in the branch and created by the 'tag' command.
604
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
606
246
def _match_on(self, branch, revs):
607
# Can raise tags not supported, NoSuchTag, etc
608
return RevisionInfo.from_revision_id(branch,
609
branch.tags.lookup_tag(self.spec))
611
def _as_revision_id(self, context_branch):
612
return context_branch.tags.lookup_tag(self.spec)
615
class _RevListToTimestamps(object):
616
"""This takes a list of revisions, and allows you to bisect by date"""
618
__slots__ = ['branch']
620
def __init__(self, branch):
247
raise BzrError('tag: namespace registered, but not implemented.')
249
SPEC_TYPES.append(RevisionSpec_tag)
252
class RevisionSpec_revs:
253
def __init__(self, revs, branch):
621
255
self.branch = branch
623
256
def __getitem__(self, index):
624
"""Get the date of the index'd item"""
625
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
257
r = self.branch.repository.get_revision(self.revs[index])
626
258
# TODO: Handle timezone.
627
259
return datetime.datetime.fromtimestamp(r.timestamp)
629
260
def __len__(self):
630
return self.branch.revno()
261
return len(self.revs)
633
264
class RevisionSpec_date(RevisionSpec):
634
"""Selects a revision on the basis of a datestamp."""
636
help_txt = """Selects a revision on the basis of a datestamp.
638
Supply a datestamp to select the first revision that matches the date.
639
Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
640
Matches the first entry after a given date (either at midnight or
641
at a specified time).
643
One way to display all the changes since yesterday would be::
645
brz log -r date:yesterday..
649
date:yesterday -> select the first revision since yesterday
650
date:2006-08-14,17:10:14 -> select the first revision after
651
August 14th, 2006 at 5:10pm.
654
_date_regex = lazy_regex.lazy_compile(
655
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
657
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
266
_date_re = re.compile(
267
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
269
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
660
272
def _match_on(self, branch, revs):
661
"""Spec for date revisions:
274
Spec for date revisions:
663
276
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
664
277
matches the first entry after a given date (either at midnight or
665
278
at a specified time).
280
So the proper way of saying 'give me all entries for today' is:
281
-r date:today..date:tomorrow
667
# XXX: This doesn't actually work
668
# So the proper way of saying 'give me all entries for today' is:
669
# -r date:yesterday..date:today
670
today = datetime.datetime.fromordinal(
671
datetime.date.today().toordinal())
283
today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
672
284
if self.spec.lower() == 'yesterday':
673
285
dt = today - datetime.timedelta(days=1)
674
286
elif self.spec.lower() == 'today':
676
288
elif self.spec.lower() == 'tomorrow':
677
289
dt = today + datetime.timedelta(days=1)
679
m = self._date_regex.match(self.spec)
291
m = self._date_re.match(self.spec)
680
292
if not m or (not m.group('date') and not m.group('time')):
681
raise errors.InvalidRevisionSpec(self.user_spec,
682
branch, 'invalid date')
686
year = int(m.group('year'))
687
month = int(m.group('month'))
688
day = int(m.group('day'))
695
hour = int(m.group('hour'))
696
minute = int(m.group('minute'))
697
if m.group('second'):
698
second = int(m.group('second'))
702
hour, minute, second = 0, 0, 0
704
raise errors.InvalidRevisionSpec(self.user_spec,
705
branch, 'invalid date')
293
raise BzrError('Invalid revision date %r' % self.spec)
296
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
298
year, month, day = today.year, today.month, today.day
300
hour = int(m.group('hour'))
301
minute = int(m.group('minute'))
302
if m.group('second'):
303
second = int(m.group('second'))
307
hour, minute, second = 0,0,0
707
309
dt = datetime.datetime(year=year, month=month, day=day,
708
hour=hour, minute=minute, second=second)
709
with branch.lock_read():
710
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
711
if rev == branch.revno():
712
raise errors.InvalidRevisionSpec(self.user_spec, branch)
713
return RevisionInfo(branch, rev)
310
hour=hour, minute=minute, second=second)
313
rev = bisect.bisect(RevisionSpec_revs(revs, branch), dt)
317
return RevisionInfo(branch, None)
319
return RevisionInfo(branch, rev + 1)
321
SPEC_TYPES.append(RevisionSpec_date)
716
324
class RevisionSpec_ancestor(RevisionSpec):
717
"""Selects a common ancestor with a second branch."""
719
help_txt = """Selects a common ancestor with a second branch.
721
Supply the path to a branch to select the common ancestor.
723
The common ancestor is the last revision that existed in both
724
branches. Usually this is the branch point, but it could also be
725
a revision that was merged.
727
This is frequently used with 'diff' to return all of the changes
728
that your branch introduces, while excluding the changes that you
729
have not merged from the remote branch.
733
ancestor:/path/to/branch
734
$ bzr diff -r ancestor:../../mainline/branch
736
325
prefix = 'ancestor:'
738
327
def _match_on(self, branch, revs):
739
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
740
return self._find_revision_info(branch, self.spec)
742
def _as_revision_id(self, context_branch):
743
return self._find_revision_id(context_branch, self.spec)
746
def _find_revision_info(branch, other_location):
747
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
749
return RevisionInfo(branch, None, revision_id)
752
def _find_revision_id(branch, other_location):
753
from .branch import Branch
755
with branch.lock_read():
756
revision_a = revision.ensure_null(branch.last_revision())
757
if revision_a == revision.NULL_REVISION:
758
raise errors.NoCommits(branch)
759
if other_location == '':
760
other_location = branch.get_parent()
761
other_branch = Branch.open(other_location)
762
with other_branch.lock_read():
763
revision_b = revision.ensure_null(other_branch.last_revision())
764
if revision_b == revision.NULL_REVISION:
765
raise errors.NoCommits(other_branch)
766
graph = branch.repository.get_graph(other_branch.repository)
767
rev_id = graph.find_unique_lca(revision_a, revision_b)
768
if rev_id == revision.NULL_REVISION:
769
raise errors.NoCommonAncestor(revision_a, revision_b)
328
from branch import Branch
329
from revision import common_ancestor, MultipleRevisionSources
330
other_branch = Branch.open_containing(self.spec)[0]
331
revision_a = branch.last_revision()
332
revision_b = other_branch.last_revision()
333
for r, b in ((revision_a, branch), (revision_b, other_branch)):
336
revision_source = MultipleRevisionSources(branch.repository,
337
other_branch.repository)
338
rev_id = common_ancestor(revision_a, revision_b, revision_source)
340
revno = branch.revision_id_to_revno(rev_id)
341
except NoSuchRevision:
343
return RevisionInfo(branch, revno, rev_id)
345
SPEC_TYPES.append(RevisionSpec_ancestor)
773
347
class RevisionSpec_branch(RevisionSpec):
774
"""Selects the last revision of a specified branch."""
776
help_txt = """Selects the last revision of a specified branch.
778
Supply the path to a branch to select its last revision.
782
branch:/path/to/branch
348
"""A branch: revision specifier.
350
This takes the path to a branch and returns its tip revision id.
784
352
prefix = 'branch:'
785
dwim_catchable_exceptions = (errors.NotBranchError,)
787
354
def _match_on(self, branch, revs):
788
from .branch import Branch
789
other_branch = Branch.open(self.spec)
355
from branch import Branch
356
other_branch = Branch.open_containing(self.spec)[0]
790
357
revision_b = other_branch.last_revision()
791
if revision_b in (None, revision.NULL_REVISION):
792
raise errors.NoCommits(other_branch)
794
branch = other_branch
797
# pull in the remote revisions so we can diff
798
branch.fetch(other_branch, revision_b)
799
except errors.ReadOnlyError:
800
branch = other_branch
801
return RevisionInfo(branch, None, revision_b)
803
def _as_revision_id(self, context_branch):
804
from .branch import Branch
805
other_branch = Branch.open(self.spec)
806
last_revision = other_branch.last_revision()
807
last_revision = revision.ensure_null(last_revision)
808
context_branch.fetch(other_branch, last_revision)
809
if last_revision == revision.NULL_REVISION:
810
raise errors.NoCommits(other_branch)
813
def _as_tree(self, context_branch):
814
from .branch import Branch
815
other_branch = Branch.open(self.spec)
816
last_revision = other_branch.last_revision()
817
last_revision = revision.ensure_null(last_revision)
818
if last_revision == revision.NULL_REVISION:
819
raise errors.NoCommits(other_branch)
820
return other_branch.repository.revision_tree(last_revision)
822
def needs_branch(self):
825
def get_branch(self):
829
class RevisionSpec_submit(RevisionSpec_ancestor):
830
"""Selects a common ancestor with a submit branch."""
832
help_txt = """Selects a common ancestor with the submit branch.
834
Diffing against this shows all the changes that were made in this branch,
835
and is a good predictor of what merge will do. The submit branch is
836
used by the bundle and merge directive commands. If no submit branch
837
is specified, the parent branch is used instead.
839
The common ancestor is the last revision that existed in both
840
branches. Usually this is the branch point, but it could also be
841
a revision that was merged.
845
$ bzr diff -r submit:
850
def _get_submit_location(self, branch):
851
submit_location = branch.get_submit_branch()
852
location_type = 'submit branch'
853
if submit_location is None:
854
submit_location = branch.get_parent()
855
location_type = 'parent branch'
856
if submit_location is None:
857
raise errors.NoSubmitBranch(branch)
858
trace.note(gettext('Using {0} {1}').format(location_type,
860
return submit_location
862
def _match_on(self, branch, revs):
863
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
864
return self._find_revision_info(branch,
865
self._get_submit_location(branch))
867
def _as_revision_id(self, context_branch):
868
return self._find_revision_id(context_branch,
869
self._get_submit_location(context_branch))
872
class RevisionSpec_annotate(RevisionIDSpec):
876
help_txt = """Select the revision that last modified the specified line.
878
Select the revision that last modified the specified line. Line is
879
specified as path:number. Path is a relative path to the file. Numbers
880
start at 1, and are relative to the current version, not the last-
881
committed version of the file.
884
def _raise_invalid(self, numstring, context_branch):
885
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
886
'No such line: %s' % numstring)
888
def _as_revision_id(self, context_branch):
889
path, numstring = self.spec.rsplit(':', 1)
891
index = int(numstring) - 1
893
self._raise_invalid(numstring, context_branch)
894
tree, file_path = workingtree.WorkingTree.open_containing(path)
895
with tree.lock_read():
896
if not tree.has_filename(file_path):
897
raise errors.InvalidRevisionSpec(self.user_spec,
898
context_branch, "File '%s' is not versioned." %
900
revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
902
revision_id = revision_ids[index]
904
self._raise_invalid(numstring, context_branch)
905
if revision_id == revision.CURRENT_REVISION:
906
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
907
'Line %s has not been committed.' % numstring)
911
class RevisionSpec_mainline(RevisionIDSpec):
913
help_txt = """Select mainline revision that merged the specified revision.
915
Select the revision that merged the specified revision into mainline.
920
def _as_revision_id(self, context_branch):
921
revspec = RevisionSpec.from_string(self.spec)
922
if revspec.get_branch() is None:
923
spec_branch = context_branch
925
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
926
revision_id = revspec.as_revision_id(spec_branch)
927
graph = context_branch.repository.get_graph()
928
result = graph.find_lefthand_merger(revision_id,
929
context_branch.last_revision())
931
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
935
# The order in which we want to DWIM a revision spec without any prefix.
936
# revno is always tried first and isn't listed here, this is used by
937
# RevisionSpec_dwim._match_on
938
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
939
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
940
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
941
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
943
revspec_registry = registry.Registry()
946
def _register_revspec(revspec):
947
revspec_registry.register(revspec.prefix, revspec)
950
_register_revspec(RevisionSpec_revno)
951
_register_revspec(RevisionSpec_revid)
952
_register_revspec(RevisionSpec_last)
953
_register_revspec(RevisionSpec_before)
954
_register_revspec(RevisionSpec_tag)
955
_register_revspec(RevisionSpec_date)
956
_register_revspec(RevisionSpec_ancestor)
957
_register_revspec(RevisionSpec_branch)
958
_register_revspec(RevisionSpec_submit)
959
_register_revspec(RevisionSpec_annotate)
960
_register_revspec(RevisionSpec_mainline)
358
if revision_b is None:
359
raise NoCommits(other_branch)
360
# pull in the remote revisions so we can diff
361
branch.fetch(other_branch, revision_b)
363
revno = branch.revision_id_to_revno(revision_b)
364
except NoSuchRevision:
366
return RevisionInfo(branch, revno, revision_b)
368
SPEC_TYPES.append(RevisionSpec_branch)