107
def __new__(cls, spec, foo=_marker):
108
"""Parse a revision specifier.
167
dwim_catchable_exceptions = (InvalidRevisionSpec,)
168
"""Exceptions that RevisionSpec_dwim._match_on will catch.
170
If the revspec is part of ``dwim_revspecs``, it may be tried with an
171
invalid revspec and raises some exception. The exceptions mentioned here
172
will not be reported to the user but simply ignored without stopping the
177
def from_string(spec):
178
"""Parse a revision spec string into a RevisionSpec object.
180
:param spec: A string specified by the user
181
:return: A RevisionSpec object that understands how to parse the
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' %
185
return RevisionSpec(None, _internal=True)
186
if not isinstance(spec, str):
187
raise TypeError("revision spec needs to be text")
188
match = revspec_registry.get_prefix(spec)
189
if match is not None:
190
spectype, specsuffix = match
191
trace.mutter('Returning RevisionSpec %s for %s',
192
spectype.__name__, spec)
193
return spectype(spec, _internal=True)
128
raise TypeError('Unhandled revision type %s' % spec)
130
def __init__(self, spec):
195
# Otherwise treat it as a DWIM, build the RevisionSpec object and
196
# wait for _match_on to be called.
197
return RevisionSpec_dwim(spec, _internal=True)
199
def __init__(self, spec, _internal=False):
200
"""Create a RevisionSpec referring to the Null revision.
202
:param spec: The original spec supplied by the user
203
:param _internal: Used to ensure that RevisionSpec is not being
204
called directly. Only from RevisionSpec.from_string()
207
raise AssertionError(
208
'Creating a RevisionSpec directly is not supported. '
209
'Use RevisionSpec.from_string() instead.')
210
self.user_spec = spec
131
211
if self.prefix and spec.startswith(self.prefix):
132
212
spec = spec[len(self.prefix):]
135
215
def _match_on(self, branch, revs):
136
return RevisionInfo(branch, 0, None)
216
trace.mutter('Returning RevisionSpec._match_on: None')
217
return RevisionInfo(branch, None, None)
138
219
def _match_on_and_check(self, branch, revs):
139
220
info = self._match_on(branch, revs)
142
elif info == (0, None):
143
# special case - the empty tree
223
elif info == (None, None):
224
# special case - nothing supplied
145
226
elif self.prefix:
146
raise NoSuchRevision(branch, self.prefix + str(self.spec))
227
raise InvalidRevisionSpec(self.user_spec, branch)
148
raise NoSuchRevision(branch, str(self.spec))
229
raise InvalidRevisionSpec(self.spec, branch)
150
231
def in_history(self, branch):
151
revs = branch.revision_history()
152
return self._match_on_and_check(branch, revs)
232
return self._match_on_and_check(branch, revs=None)
234
# FIXME: in_history is somewhat broken,
235
# it will return non-history revisions in many
236
# circumstances. The expected facility is that
237
# in_history only returns revision-history revs,
238
# in_store returns any rev. RBC 20051010
239
# aliases for now, when we fix the core logic, then they
240
# will do what you expect.
241
in_store = in_history
244
def as_revision_id(self, context_branch):
245
"""Return just the revision_id for this revisions spec.
247
Some revision specs require a context_branch to be able to determine
248
their value. Not all specs will make use of it.
250
return self._as_revision_id(context_branch)
252
def _as_revision_id(self, context_branch):
253
"""Implementation of as_revision_id()
255
Classes should override this function to provide appropriate
256
functionality. The default is to just call '.in_history().rev_id'
258
return self.in_history(context_branch).rev_id
260
def as_tree(self, context_branch):
261
"""Return the tree object for this revisions spec.
263
Some revision specs require a context_branch to be able to determine
264
the revision id and access the repository. Not all specs will make
267
return self._as_tree(context_branch)
269
def _as_tree(self, context_branch):
270
"""Implementation of as_tree().
272
Classes should override this function to provide appropriate
273
functionality. The default is to just call '.as_revision_id()'
274
and get the revision tree from context_branch's repository.
276
revision_id = self.as_revision_id(context_branch)
277
return context_branch.repository.revision_tree(revision_id)
154
279
def __repr__(self):
155
280
# this is mostly for helping with testing
156
return '<%s %s%s>' % (self.__class__.__name__,
281
return '<%s %s>' % (self.__class__.__name__,
284
def needs_branch(self):
285
"""Whether this revision spec needs a branch.
287
Set this to False the branch argument of _match_on is not used.
291
def get_branch(self):
292
"""When the revision specifier contains a branch location, return it.
294
Otherwise, return None.
163
class RevisionSpec_int(RevisionSpec):
164
"""Spec is a number. Special case."""
165
def __init__(self, spec):
166
self.spec = int(spec)
301
class RevisionSpec_dwim(RevisionSpec):
302
"""Provides a DWIMish revision specifier lookup.
304
Note that this does not go in the revspec_registry because by definition
305
there is no prefix to identify it. It's solely called from
306
RevisionSpec.from_string() because the DWIMification happen when _match_on
307
is called so the string describing the revision is kept here until needed.
312
_revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
314
# The revspecs to try
315
_possible_revspecs = []
317
def _try_spectype(self, rstype, branch):
318
rs = rstype(self.spec, _internal=True)
319
# Hit in_history to find out if it exists, or we need to try the
321
return rs.in_history(branch)
168
323
def _match_on(self, branch, revs):
170
revno = len(revs) + self.spec + 1
173
rev_id = branch.get_rev_id(revno, revs)
174
return RevisionInfo(branch, revno, rev_id)
324
"""Run the lookup and see what we can get."""
326
# First, see if it's a revno
327
if self._revno_regex.match(self.spec) is not None:
329
return self._try_spectype(RevisionSpec_revno, branch)
330
except RevisionSpec_revno.dwim_catchable_exceptions:
333
# Next see what has been registered
334
for objgetter in self._possible_revspecs:
335
rs_class = objgetter.get_obj()
337
return self._try_spectype(rs_class, branch)
338
except rs_class.dwim_catchable_exceptions:
341
# Well, I dunno what it is. Note that we don't try to keep track of the
342
# first of last exception raised during the DWIM tries as none seems
344
raise InvalidRevisionSpec(self.spec, branch)
347
def append_possible_revspec(cls, revspec):
348
"""Append a possible DWIM revspec.
350
:param revspec: Revision spec to try.
352
cls._possible_revspecs.append(registry._ObjectGetter(revspec))
355
def append_possible_lazy_revspec(cls, module_name, member_name):
356
"""Append a possible lazily loaded DWIM revspec.
358
:param module_name: Name of the module with the revspec
359
:param member_name: Name of the revspec within the module
361
cls._possible_revspecs.append(
362
registry._LazyObjectGetter(module_name, member_name))
177
365
class RevisionSpec_revno(RevisionSpec):
366
"""Selects a revision using a number."""
368
help_txt = """Selects a revision using a number.
370
Use an integer to specify a revision in the history of the branch.
371
Optionally a branch can be specified. A negative number will count
372
from the end of the branch (-1 is the last revision, -2 the previous
373
one). If the negative number is larger than the branch's history, the
374
first revision is returned.
377
revno:1 -> return the first revision of this branch
378
revno:3:/path/to/branch -> return the 3rd revision of
379
the branch '/path/to/branch'
380
revno:-1 -> The last revision in a branch.
381
-2:http://other/branch -> The second to last revision in the
383
-1000000 -> Most likely the first revision, unless
384
your history is very long.
178
386
prefix = 'revno:'
180
388
def _match_on(self, branch, revs):
181
389
"""Lookup a revision by revision number"""
183
return RevisionInfo(branch, int(self.spec))
185
return RevisionInfo(branch, None)
187
SPEC_TYPES.append(RevisionSpec_revno)
190
class RevisionSpec_revid(RevisionSpec):
390
branch, revno, revision_id = self._lookup(branch)
391
return RevisionInfo(branch, revno, revision_id)
393
def _lookup(self, branch):
394
loc = self.spec.find(':')
396
revno_spec = self.spec
399
revno_spec = self.spec[:loc]
400
branch_spec = self.spec[loc + 1:]
404
raise InvalidRevisionSpec(
405
self.user_spec, branch,
406
'cannot have an empty revno and no branch')
410
revno = int(revno_spec)
413
# dotted decimal. This arguably should not be here
414
# but the from_string method is a little primitive
415
# right now - RBC 20060928
417
match_revno = tuple((int(number)
418
for number in revno_spec.split('.')))
419
except ValueError as e:
420
raise InvalidRevisionSpec(self.user_spec, branch, e)
425
# the user has overriden the branch to look in.
426
branch = _mod_branch.Branch.open(branch_spec)
430
revision_id = branch.dotted_revno_to_revision_id(match_revno,
432
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
433
raise InvalidRevisionSpec(self.user_spec, branch)
435
# there is no traditional 'revno' for dotted-decimal revnos.
436
# so for API compatibility we return None.
437
return branch, None, revision_id
439
last_revno, last_revision_id = branch.last_revision_info()
441
# if get_rev_id supported negative revnos, there would not be a
442
# need for this special case.
443
if (-revno) >= last_revno:
446
revno = last_revno + revno + 1
448
revision_id = branch.get_rev_id(revno)
449
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
450
raise 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)
458
def needs_branch(self):
459
return self.spec.find(':') == -1
461
def get_branch(self):
462
if self.spec.find(':') == -1:
465
return self.spec[self.spec.find(':') + 1:]
469
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)
479
class RevisionSpec_revid(RevisionIDSpec):
480
"""Selects a revision using the revision id."""
482
help_txt = """Selects a revision using the revision id.
484
Supply a specific revision id, that can be used to specify any
485
revision id in the ancestry of the branch.
486
Including merges, and pending merges.
489
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
191
492
prefix = 'revid:'
193
def _match_on(self, branch, revs):
195
return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
197
return RevisionInfo(branch, None)
199
SPEC_TYPES.append(RevisionSpec_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
if isinstance(self.spec, str):
499
return cache_utf8.encode(self.spec)
202
503
class RevisionSpec_last(RevisionSpec):
504
"""Selects the nth revision from the end."""
506
help_txt = """Selects the nth revision from the end.
508
Supply a positive number to get the nth revision from the end.
509
This is the same as supplying negative numbers to the 'revno:' spec.
512
last:1 -> return the last revision
513
last:3 -> return the revision 2 before the end.
205
518
def _match_on(self, branch, revs):
519
revno, revision_id = self._revno_and_revision_id(branch)
520
return RevisionInfo(branch, revno, revision_id)
522
def _revno_and_revision_id(self, context_branch):
523
last_revno, last_revision_id = context_branch.last_revision_info()
527
raise errors.NoCommits(context_branch)
528
return last_revno, last_revision_id
207
531
offset = int(self.spec)
209
return RevisionInfo(branch, None)
532
except ValueError as e:
533
raise InvalidRevisionSpec(self.user_spec, context_branch, e)
536
raise InvalidRevisionSpec(
537
self.user_spec, context_branch,
538
'you must supply a positive value')
540
revno = last_revno - offset + 1
542
revision_id = context_branch.get_rev_id(revno)
543
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
544
raise InvalidRevisionSpec(self.user_spec, context_branch)
545
return revno, revision_id
547
def _as_revision_id(self, context_branch):
548
# We compute the revno as part of the process, but we don't really care
550
revno, revision_id = self._revno_and_revision_id(context_branch)
554
class RevisionSpec_before(RevisionSpec):
555
"""Selects the parent of the revision specified."""
557
help_txt = """Selects the parent of the revision specified.
559
Supply any revision spec to return the parent of that revision. This is
560
mostly useful when inspecting revisions that are not in the revision history
563
It is an error to request the parent of the null revision (before:0).
567
before:1913 -> Return the parent of revno 1913 (revno 1912)
568
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
570
bzr diff -r before:1913..1913
571
-> Find the changes between revision 1913 and its parent (1912).
572
(What changes did revision 1913 introduce).
573
This is equivalent to: bzr diff -c 1913
578
def _match_on(self, branch, revs):
579
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
581
raise InvalidRevisionSpec(
582
self.user_spec, branch,
583
'cannot go before the null: revision')
585
# We need to use the repository history here
586
rev = branch.repository.get_revision(r.rev_id)
587
if not rev.parent_ids:
588
revision_id = revision.NULL_REVISION
590
revision_id = rev.parent_ids[0]
212
raise BzrError('You must supply a positive value for --revision last:XXX')
213
return RevisionInfo(branch, len(revs) - offset + 1)
595
revision_id = branch.get_rev_id(revno, revs)
596
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
597
raise InvalidRevisionSpec(self.user_spec, branch)
598
return RevisionInfo(branch, revno, revision_id)
215
SPEC_TYPES.append(RevisionSpec_last)
600
def _as_revision_id(self, context_branch):
601
base_revision_id = RevisionSpec.from_string(
602
self.spec)._as_revision_id(context_branch)
603
if base_revision_id == revision.NULL_REVISION:
604
raise InvalidRevisionSpec(
605
self.user_spec, context_branch,
606
'cannot go before the null: revision')
607
context_repo = context_branch.repository
608
with context_repo.lock_read():
609
parent_map = context_repo.get_parent_map([base_revision_id])
610
if base_revision_id not in parent_map:
611
# Ghost, or unknown revision id
612
raise InvalidRevisionSpec(
613
self.user_spec, context_branch, 'cannot find the matching revision')
614
parents = parent_map[base_revision_id]
616
raise InvalidRevisionSpec(
617
self.user_spec, context_branch, 'No parents for revision.')
218
621
class RevisionSpec_tag(RevisionSpec):
622
"""Select a revision identified by tag name"""
624
help_txt = """Selects a revision identified by a tag name.
626
Tags are stored in the branch and created by the 'tag' command.
630
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
221
632
def _match_on(self, branch, revs):
222
raise BzrError('tag: namespace registered, but not implemented.')
224
SPEC_TYPES.append(RevisionSpec_tag)
633
# Can raise tags not supported, NoSuchTag, etc
634
return RevisionInfo.from_revision_id(branch,
635
branch.tags.lookup_tag(self.spec))
637
def _as_revision_id(self, context_branch):
638
return context_branch.tags.lookup_tag(self.spec)
641
class _RevListToTimestamps(object):
642
"""This takes a list of revisions, and allows you to bisect by date"""
644
__slots__ = ['branch']
646
def __init__(self, branch):
649
def __getitem__(self, index):
650
"""Get the date of the index'd item"""
651
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
652
# TODO: Handle timezone.
653
return datetime.datetime.fromtimestamp(r.timestamp)
656
return self.branch.revno()
227
659
class RevisionSpec_date(RevisionSpec):
660
"""Selects a revision on the basis of a datestamp."""
662
help_txt = """Selects a revision on the basis of a datestamp.
664
Supply a datestamp to select the first revision that matches the date.
665
Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
666
Matches the first entry after a given date (either at midnight or
667
at a specified time).
669
One way to display all the changes since yesterday would be::
671
brz log -r date:yesterday..
675
date:yesterday -> select the first revision since yesterday
676
date:2006-08-14,17:10:14 -> select the first revision after
677
August 14th, 2006 at 5:10pm.
229
_date_re = re.compile(
230
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
232
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
680
_date_regex = lazy_regex.lazy_compile(
681
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
683
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
235
686
def _match_on(self, branch, revs):
237
Spec for date revisions:
687
"""Spec for date revisions:
239
689
value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
240
it can also start with a '+/-/='. '+' says match the first
241
entry after the given date. '-' is match the first entry before the date
242
'=' is match the first entry after, but still on the given date.
244
+2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
245
-2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
246
=2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
247
May 13th, 2005 at 0:00
249
So the proper way of saying 'give me all entries for today' is:
250
-r {date:+today}:{date:-tomorrow}
251
The default is '=' when not supplied
690
matches the first entry after a given date (either at midnight or
691
at a specified time).
254
if self.spec[:1] in ('+', '-', '='):
255
match_style = self.spec[:1]
256
self.spec = self.spec[1:]
258
# XXX: this should probably be using datetime.date instead
259
today = datetime.datetime.today().replace(hour=0, minute=0, second=0,
693
# XXX: This doesn't actually work
694
# So the proper way of saying 'give me all entries for today' is:
695
# -r date:yesterday..date:today
696
today = datetime.datetime.fromordinal(
697
datetime.date.today().toordinal())
261
698
if self.spec.lower() == 'yesterday':
262
699
dt = today - datetime.timedelta(days=1)
263
700
elif self.spec.lower() == 'today':
265
702
elif self.spec.lower() == 'tomorrow':
266
703
dt = today + datetime.timedelta(days=1)
268
m = self._date_re.match(self.spec)
705
m = self._date_regex.match(self.spec)
269
706
if not m or (not m.group('date') and not m.group('time')):
270
raise BzrError('Invalid revision date %r' % self.spec)
273
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
275
year, month, day = today.year, today.month, today.day
277
hour = int(m.group('hour'))
278
minute = int(m.group('minute'))
279
if m.group('second'):
280
second = int(m.group('second'))
284
hour, minute, second = 0,0,0
707
raise InvalidRevisionSpec(
708
self.user_spec, branch, 'invalid date')
712
year = int(m.group('year'))
713
month = int(m.group('month'))
714
day = int(m.group('day'))
721
hour = int(m.group('hour'))
722
minute = int(m.group('minute'))
723
if m.group('second'):
724
second = int(m.group('second'))
728
hour, minute, second = 0, 0, 0
730
raise InvalidRevisionSpec(
731
self.user_spec, branch, 'invalid date')
286
733
dt = datetime.datetime(year=year, month=month, day=day,
287
hour=hour, minute=minute, second=second)
291
if match_style == '-':
293
elif match_style == '=':
294
last = dt + datetime.timedelta(days=1)
297
for i in range(len(revs)-1, -1, -1):
298
r = branch.get_revision(revs[i])
299
# TODO: Handle timezone.
300
dt = datetime.datetime.fromtimestamp(r.timestamp)
301
if first >= dt and (last is None or dt >= last):
302
return RevisionInfo(branch, i+1,)
304
for i in range(len(revs)):
305
r = branch.get_revision(revs[i])
306
# TODO: Handle timezone.
307
dt = datetime.datetime.fromtimestamp(r.timestamp)
308
if first <= dt and (last is None or dt <= last):
309
return RevisionInfo(branch, i+1,)
311
SPEC_TYPES.append(RevisionSpec_date)
734
hour=hour, minute=minute, second=second)
735
with branch.lock_read():
736
rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
737
if rev == branch.revno():
738
raise InvalidRevisionSpec(self.user_spec, branch)
739
return RevisionInfo(branch, rev)
314
742
class RevisionSpec_ancestor(RevisionSpec):
743
"""Selects a common ancestor with a second branch."""
745
help_txt = """Selects a common ancestor with a second branch.
747
Supply the path to a branch to select the common ancestor.
749
The common ancestor is the last revision that existed in both
750
branches. Usually this is the branch point, but it could also be
751
a revision that was merged.
753
This is frequently used with 'diff' to return all of the changes
754
that your branch introduces, while excluding the changes that you
755
have not merged from the remote branch.
759
ancestor:/path/to/branch
760
$ bzr diff -r ancestor:../../mainline/branch
315
762
prefix = 'ancestor:'
317
764
def _match_on(self, branch, revs):
318
from branch import Branch
319
from revision import common_ancestor, MultipleRevisionSources
320
other_branch = Branch.open_containing(self.spec)
321
revision_a = branch.last_patch()
322
revision_b = other_branch.last_patch()
323
for r, b in ((revision_a, branch), (revision_b, other_branch)):
326
revision_source = MultipleRevisionSources(branch, other_branch)
327
rev_id = common_ancestor(revision_a, revision_b, revision_source)
329
revno = branch.revision_id_to_revno(rev_id)
330
except NoSuchRevision:
332
return RevisionInfo(branch, revno, rev_id)
334
SPEC_TYPES.append(RevisionSpec_ancestor)
765
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
766
return self._find_revision_info(branch, self.spec)
768
def _as_revision_id(self, context_branch):
769
return self._find_revision_id(context_branch, self.spec)
772
def _find_revision_info(branch, other_location):
773
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
775
return RevisionInfo(branch, None, revision_id)
778
def _find_revision_id(branch, other_location):
779
from .branch import Branch
781
with branch.lock_read():
782
revision_a = revision.ensure_null(branch.last_revision())
783
if revision_a == revision.NULL_REVISION:
784
raise errors.NoCommits(branch)
785
if other_location == '':
786
other_location = branch.get_parent()
787
other_branch = Branch.open(other_location)
788
with other_branch.lock_read():
789
revision_b = revision.ensure_null(other_branch.last_revision())
790
if revision_b == revision.NULL_REVISION:
791
raise errors.NoCommits(other_branch)
792
graph = branch.repository.get_graph(other_branch.repository)
793
rev_id = graph.find_unique_lca(revision_a, revision_b)
794
if rev_id == revision.NULL_REVISION:
795
raise errors.NoCommonAncestor(revision_a, revision_b)
799
class RevisionSpec_branch(RevisionSpec):
800
"""Selects the last revision of a specified branch."""
802
help_txt = """Selects the last revision of a specified branch.
804
Supply the path to a branch to select its last revision.
808
branch:/path/to/branch
811
dwim_catchable_exceptions = (errors.NotBranchError,)
813
def _match_on(self, branch, revs):
814
from .branch import Branch
815
other_branch = Branch.open(self.spec)
816
revision_b = other_branch.last_revision()
817
if revision_b in (None, revision.NULL_REVISION):
818
raise errors.NoCommits(other_branch)
820
branch = other_branch
823
# pull in the remote revisions so we can diff
824
branch.fetch(other_branch, revision_b)
825
except errors.ReadOnlyError:
826
branch = other_branch
827
return RevisionInfo(branch, None, revision_b)
829
def _as_revision_id(self, context_branch):
830
from .branch import Branch
831
other_branch = Branch.open(self.spec)
832
last_revision = other_branch.last_revision()
833
last_revision = revision.ensure_null(last_revision)
834
context_branch.fetch(other_branch, last_revision)
835
if last_revision == revision.NULL_REVISION:
836
raise errors.NoCommits(other_branch)
839
def _as_tree(self, context_branch):
840
from .branch import Branch
841
other_branch = Branch.open(self.spec)
842
last_revision = other_branch.last_revision()
843
last_revision = revision.ensure_null(last_revision)
844
if last_revision == revision.NULL_REVISION:
845
raise errors.NoCommits(other_branch)
846
return other_branch.repository.revision_tree(last_revision)
848
def needs_branch(self):
851
def get_branch(self):
855
class RevisionSpec_submit(RevisionSpec_ancestor):
856
"""Selects a common ancestor with a submit branch."""
858
help_txt = """Selects a common ancestor with the submit branch.
860
Diffing against this shows all the changes that were made in this branch,
861
and is a good predictor of what merge will do. The submit branch is
862
used by the bundle and merge directive commands. If no submit branch
863
is specified, the parent branch is used instead.
865
The common ancestor is the last revision that existed in both
866
branches. Usually this is the branch point, but it could also be
867
a revision that was merged.
871
$ bzr diff -r submit:
876
def _get_submit_location(self, branch):
877
submit_location = branch.get_submit_branch()
878
location_type = 'submit branch'
879
if submit_location is None:
880
submit_location = branch.get_parent()
881
location_type = 'parent branch'
882
if submit_location is None:
883
raise errors.NoSubmitBranch(branch)
884
trace.note(gettext('Using {0} {1}').format(location_type,
886
return submit_location
888
def _match_on(self, branch, revs):
889
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
890
return self._find_revision_info(branch,
891
self._get_submit_location(branch))
893
def _as_revision_id(self, context_branch):
894
return self._find_revision_id(context_branch,
895
self._get_submit_location(context_branch))
898
class RevisionSpec_annotate(RevisionIDSpec):
902
help_txt = """Select the revision that last modified the specified line.
904
Select the revision that last modified the specified line. Line is
905
specified as path:number. Path is a relative path to the file. Numbers
906
start at 1, and are relative to the current version, not the last-
907
committed version of the file.
910
def _raise_invalid(self, numstring, context_branch):
911
raise InvalidRevisionSpec(
912
self.user_spec, context_branch,
913
'No such line: %s' % numstring)
915
def _as_revision_id(self, context_branch):
916
path, numstring = self.spec.rsplit(':', 1)
918
index = int(numstring) - 1
920
self._raise_invalid(numstring, context_branch)
921
tree, file_path = workingtree.WorkingTree.open_containing(path)
922
with tree.lock_read():
923
if not tree.has_filename(file_path):
924
raise InvalidRevisionSpec(
925
self.user_spec, context_branch,
926
"File '%s' is not versioned." % file_path)
927
revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
929
revision_id = revision_ids[index]
931
self._raise_invalid(numstring, context_branch)
932
if revision_id == revision.CURRENT_REVISION:
933
raise InvalidRevisionSpec(
934
self.user_spec, context_branch,
935
'Line %s has not been committed.' % numstring)
939
class RevisionSpec_mainline(RevisionIDSpec):
941
help_txt = """Select mainline revision that merged the specified revision.
943
Select the revision that merged the specified revision into mainline.
948
def _as_revision_id(self, context_branch):
949
revspec = RevisionSpec.from_string(self.spec)
950
if revspec.get_branch() is None:
951
spec_branch = context_branch
953
spec_branch = _mod_branch.Branch.open(revspec.get_branch())
954
revision_id = revspec.as_revision_id(spec_branch)
955
graph = context_branch.repository.get_graph()
956
result = graph.find_lefthand_merger(revision_id,
957
context_branch.last_revision())
959
raise InvalidRevisionSpec(self.user_spec, context_branch)
963
# The order in which we want to DWIM a revision spec without any prefix.
964
# revno is always tried first and isn't listed here, this is used by
965
# RevisionSpec_dwim._match_on
966
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
967
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
968
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
969
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
971
revspec_registry = registry.Registry()
974
def _register_revspec(revspec):
975
revspec_registry.register(revspec.prefix, revspec)
978
_register_revspec(RevisionSpec_revno)
979
_register_revspec(RevisionSpec_revid)
980
_register_revspec(RevisionSpec_last)
981
_register_revspec(RevisionSpec_before)
982
_register_revspec(RevisionSpec_tag)
983
_register_revspec(RevisionSpec_date)
984
_register_revspec(RevisionSpec_ancestor)
985
_register_revspec(RevisionSpec_branch)
986
_register_revspec(RevisionSpec_submit)
987
_register_revspec(RevisionSpec_annotate)
988
_register_revspec(RevisionSpec_mainline)