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
107
def __new__(cls, spec, foo=_marker):
108
"""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)
111
return object.__new__(RevisionSpec, spec)
118
if isinstance(spec, int):
119
return object.__new__(RevisionSpec_int, spec)
120
elif isinstance(spec, basestring):
121
for spectype in SPEC_TYPES:
122
if spec.startswith(spectype.prefix):
123
return object.__new__(spectype, spec)
125
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
128
raise TypeError('Unhandled revision type %s' % spec)
130
def __init__(self, spec):
188
131
if self.prefix and spec.startswith(self.prefix):
189
132
spec = spec[len(self.prefix):]
192
135
def _match_on(self, branch, revs):
193
trace.mutter('Returning RevisionSpec._match_on: None')
194
return RevisionInfo(branch, None, None)
136
return RevisionInfo(branch, 0, None)
196
138
def _match_on_and_check(self, branch, revs):
197
139
info = self._match_on(branch, revs)
200
elif info == (None, None):
201
# special case - nothing supplied
142
elif info == (0, None):
143
# special case - the empty tree
203
145
elif self.prefix:
204
raise errors.InvalidRevisionSpec(self.user_spec, branch)
146
raise NoSuchRevision(branch, self.prefix + str(self.spec))
206
raise errors.InvalidRevisionSpec(self.spec, branch)
148
raise NoSuchRevision(branch, str(self.spec))
208
150
def in_history(self, branch):
209
return self._match_on_and_check(branch, revs=None)
151
revs = branch.revision_history()
152
return self._match_on_and_check(branch, revs)
211
154
# FIXME: in_history is somewhat broken,
212
155
# it will return non-history revisions in many
217
160
# will do what you expect.
218
161
in_store = in_history
219
162
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
164
def __repr__(self):
257
165
# 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.
166
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)
173
class RevisionSpec_int(RevisionSpec):
174
"""Spec is a number. Special case."""
175
def __init__(self, spec):
176
self.spec = int(spec)
300
178
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))
180
revno = len(revs) + self.spec + 1
183
rev_id = branch.get_rev_id(revno, revs)
184
return RevisionInfo(branch, revno, rev_id)
342
187
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
188
prefix = 'revno:'
365
190
def _match_on(self, branch, revs):
366
191
"""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'
193
return RevisionInfo(branch, int(self.spec))
195
return RevisionInfo(branch, None)
197
SPEC_TYPES.append(RevisionSpec_revno)
200
class RevisionSpec_revid(RevisionSpec):
468
201
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)
203
def _match_on(self, branch, revs):
205
return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
207
return RevisionInfo(branch, None, self.spec)
209
SPEC_TYPES.append(RevisionSpec_revid)
479
212
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
216
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
218
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)
220
return RevisionInfo(branch, None)
223
raise BzrError('You must supply a positive value for --revision last:XXX')
224
return RevisionInfo(branch, len(revs) - offset + 1)
226
SPEC_TYPES.append(RevisionSpec_last)
529
229
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
231
prefix = 'before:'
553
233
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)
234
r = RevisionSpec(self.spec)._match_on(branch, revs)
235
if (r.revno is None) or (r.revno == 0):
237
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.')
239
SPEC_TYPES.append(RevisionSpec_before)
595
242
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
245
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):
623
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))
626
# TODO: Handle timezone.
627
return datetime.datetime.fromtimestamp(r.timestamp)
630
return self.branch.revno()
246
raise BzrError('tag: namespace registered, but not implemented.')
248
SPEC_TYPES.append(RevisionSpec_tag)
633
251
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))?)?'
253
_date_re = re.compile(
254
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
256
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
660
259
def _match_on(self, branch, revs):
661
"""Spec for date revisions:
261
Spec for date revisions:
663
263
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
664
264
matches the first entry after a given date (either at midnight or
665
265
at a specified time).
267
So the proper way of saying 'give me all entries for today' is:
268
-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())
270
today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
672
271
if self.spec.lower() == 'yesterday':
673
272
dt = today - datetime.timedelta(days=1)
674
273
elif self.spec.lower() == 'today':
676
275
elif self.spec.lower() == 'tomorrow':
677
276
dt = today + datetime.timedelta(days=1)
679
m = self._date_regex.match(self.spec)
278
m = self._date_re.match(self.spec)
680
279
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')
280
raise BzrError('Invalid revision date %r' % self.spec)
283
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
285
year, month, day = today.year, today.month, today.day
287
hour = int(m.group('hour'))
288
minute = int(m.group('minute'))
289
if m.group('second'):
290
second = int(m.group('second'))
294
hour, minute, second = 0,0,0
707
296
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)
297
hour=hour, minute=minute, second=second)
299
for i in range(len(revs)):
300
r = branch.repository.get_revision(revs[i])
301
# TODO: Handle timezone.
302
dt = datetime.datetime.fromtimestamp(r.timestamp)
304
return RevisionInfo(branch, i+1)
305
return RevisionInfo(branch, None)
307
SPEC_TYPES.append(RevisionSpec_date)
716
310
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
311
prefix = 'ancestor:'
738
313
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)
314
from branch import Branch
315
from revision import common_ancestor, MultipleRevisionSources
316
other_branch = Branch.open_containing(self.spec)[0]
317
revision_a = branch.last_revision()
318
revision_b = other_branch.last_revision()
319
for r, b in ((revision_a, branch), (revision_b, other_branch)):
322
revision_source = MultipleRevisionSources(branch.repository,
323
other_branch.repository)
324
rev_id = common_ancestor(revision_a, revision_b, revision_source)
326
revno = branch.revision_id_to_revno(rev_id)
327
except NoSuchRevision:
329
return RevisionInfo(branch, revno, rev_id)
331
SPEC_TYPES.append(RevisionSpec_ancestor)
773
333
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
334
"""A branch: revision specifier.
336
This takes the path to a branch and returns its tip revision id.
784
338
prefix = 'branch:'
785
dwim_catchable_exceptions = (errors.NotBranchError,)
787
340
def _match_on(self, branch, revs):
788
from .branch import Branch
789
other_branch = Branch.open(self.spec)
341
from branch import Branch
342
other_branch = Branch.open_containing(self.spec)[0]
790
343
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)
344
if revision_b is None:
345
raise NoCommits(other_branch)
346
# pull in the remote revisions so we can diff
347
branch.fetch(other_branch, revision_b)
349
revno = branch.revision_id_to_revno(revision_b)
350
except NoSuchRevision:
352
return RevisionInfo(branch, revno, revision_b)
354
SPEC_TYPES.append(RevisionSpec_branch)