13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
from .lazy_import import lazy_import
19
lazy_import(globals(), """
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
branch as _mod_branch,
29
from breezy.i18n import gettext
40
class InvalidRevisionSpec(errors.BzrError):
42
_fmt = ("Requested revision: '%(spec)s' does not exist in branch:"
43
" %(branch_url)s%(extra)s")
45
def __init__(self, spec, branch, extra=None):
46
errors.BzrError.__init__(self, branch=branch, spec=spec)
47
self.branch_url = getattr(branch, 'user_url', str(branch))
49
self.extra = '\n' + str(extra)
54
class InvalidRevisionSpec(errors.BzrError):
56
_fmt = ("Requested revision: '%(spec)s' does not exist in branch:"
57
" %(branch_url)s%(extra)s")
59
def __init__(self, spec, branch, extra=None):
60
errors.BzrError.__init__(self, branch=branch, spec=spec)
61
self.branch_url = getattr(branch, 'user_url', str(branch))
63
self.extra = '\n' + str(extra)
68
35
class RevisionInfo(object):
82
49
or treat the result as a tuple.
85
def __init__(self, branch, revno=None, rev_id=None):
52
def __init__(self, branch, revno, rev_id=_marker):
86
53
self.branch = branch
87
self._has_revno = (revno is not None)
90
if self.rev_id is None and self._revno is not None:
91
56
# allow caller to be lazy
92
self.rev_id = branch.get_rev_id(self._revno)
96
if not self._has_revno and self.rev_id is not None:
98
self._revno = self.branch.revision_id_to_revno(self.rev_id)
99
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
101
self._has_revno = True
57
if self.revno is None:
60
self.rev_id = branch.get_rev_id(self.revno)
64
def __nonzero__(self):
65
# first the easy ones...
105
66
if self.rev_id is None:
68
if self.revno is not None:
107
70
# TODO: otherwise, it should depend on how I was built -
108
71
# if it's in_history(branch), then check revision_history(),
109
72
# if it's in_store(branch), do the check below
110
73
return self.branch.repository.has_revision(self.rev_id)
112
__nonzero__ = __bool__
114
75
def __len__(self):
117
78
def __getitem__(self, index):
79
if index == 0: return self.revno
80
if index == 1: return self.rev_id
122
81
raise IndexError(index)
127
86
def __eq__(self, other):
128
87
if type(other) not in (tuple, list, type(self)):
130
if isinstance(other, type(self)) and self.branch is not other.branch:
89
if type(other) is type(self) and self.branch is not other.branch:
132
91
return tuple(self) == tuple(other)
134
93
def __repr__(self):
135
return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
94
return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
136
95
self.revno, self.rev_id, self.branch)
139
def from_revision_id(branch, revision_id):
98
def from_revision_id(branch, revision_id, revs):
140
99
"""Construct a RevisionInfo given just the id.
142
101
Use this if you don't know or care what the revno is.
144
return RevisionInfo(branch, revno=None, rev_id=revision_id)
104
revno = revs.index(revision_id) + 1
107
return RevisionInfo(branch, revno, revision_id)
110
# classes in this list should have a "prefix" attribute, against which
111
# string specs are matched
147
116
class RevisionSpec(object):
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
137
def __new__(cls, spec, _internal=False):
139
return object.__new__(cls, spec, _internal=_internal)
141
symbol_versioning.warn('Creating a RevisionSpec directly has'
142
' been deprecated in version 0.11. Use'
143
' RevisionSpec.from_string()'
145
DeprecationWarning, stacklevel=2)
146
return RevisionSpec.from_string(spec)
177
149
def from_string(spec):
181
153
:return: A RevisionSpec object that understands how to parse the
182
154
supplied notation.
156
if not isinstance(spec, (type(None), basestring)):
157
raise TypeError('error')
185
160
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)
162
assert isinstance(spec, basestring), \
163
"You should only supply strings not %s" % (type(spec),)
165
for spectype in SPEC_TYPES:
166
if spec.startswith(spectype.prefix):
167
trace.mutter('Returning RevisionSpec %s for %s',
168
spectype.__name__, spec)
169
return spectype(spec, _internal=True)
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)
171
# RevisionSpec_revno is special cased, because it is the only
172
# one that directly handles plain integers
173
# TODO: This should not be special cased rather it should be
174
# a method invocation on spectype.canparse()
176
if _revno_regex is None:
177
_revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
178
if _revno_regex.match(spec) is not None:
179
return RevisionSpec_revno(spec, _internal=True)
181
raise errors.NoSuchRevisionSpec(spec)
199
183
def __init__(self, spec, _internal=False):
200
184
"""Create a RevisionSpec referring to the Null revision.
215
202
def _match_on(self, branch, revs):
216
203
trace.mutter('Returning RevisionSpec._match_on: None')
217
return RevisionInfo(branch, None, None)
204
return RevisionInfo(branch, 0, None)
219
206
def _match_on_and_check(self, branch, revs):
220
207
info = self._match_on(branch, revs)
223
elif info == (None, None):
224
# special case - nothing supplied
210
elif info == (0, None):
211
# special case - the empty tree
226
213
elif self.prefix:
227
raise InvalidRevisionSpec(self.user_spec, branch)
214
raise errors.InvalidRevisionSpec(self.user_spec, branch)
229
raise InvalidRevisionSpec(self.spec, branch)
216
raise errors.InvalidRevisionSpec(self.spec, branch)
231
218
def in_history(self, branch):
232
return self._match_on_and_check(branch, revs=None)
220
revs = branch.revision_history()
222
# this should never trigger.
223
# TODO: make it a deprecated code path. RBC 20060928
225
return self._match_on_and_check(branch, revs)
234
227
# FIXME: in_history is somewhat broken,
235
228
# it will return non-history revisions in many
240
233
# will do what you expect.
241
234
in_store = in_history
242
235
in_branch = in_store
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)
279
237
def __repr__(self):
280
238
# this is mostly for helping with testing
281
239
return '<%s %s>' % (self.__class__.__name__,
284
242
def needs_branch(self):
285
243
"""Whether this revision spec needs a branch.
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)
323
def _match_on(self, branch, revs):
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))
365
259
class RevisionSpec_revno(RevisionSpec):
366
260
"""Selects a revision using a number."""
368
262
help_txt = """Selects a revision using a number.
370
264
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
265
Optionally a branch can be specified. The 'revno:' prefix is optional.
266
A negative number will count from the end of the branch (-1 is the
267
last revision, -2 the previous one). If the negative number is larger
268
than the branch's history, the first revision is returned.
270
revno:1 -> return the first revision
378
271
revno:3:/path/to/branch -> return the 3rd revision of
379
272
the branch '/path/to/branch'
380
273
revno:-1 -> The last revision in a branch.
388
281
def _match_on(self, branch, revs):
389
282
"""Lookup a revision by revision number"""
390
branch, revno, revision_id = self._lookup(branch)
391
return RevisionInfo(branch, revno, revision_id)
393
def _lookup(self, branch):
394
283
loc = self.spec.find(':')
396
285
revno_spec = self.spec
397
286
branch_spec = None
399
288
revno_spec = self.spec[:loc]
400
branch_spec = self.spec[loc + 1:]
289
branch_spec = self.spec[loc+1:]
402
291
if revno_spec == '':
403
292
if not branch_spec:
404
raise InvalidRevisionSpec(
405
self.user_spec, branch,
406
'cannot have an empty revno and no branch')
293
raise errors.InvalidRevisionSpec(self.user_spec,
294
branch, 'cannot have an empty revno and no branch')
412
300
except ValueError:
413
301
# dotted decimal. This arguably should not be here
414
# but the from_string method is a little primitive
302
# but the from_string method is a little primitive
415
303
# 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)
305
match_revno = tuple((int(number) for number in revno_spec.split('.')))
306
except ValueError, e:
307
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
425
# the user has overriden the branch to look in.
426
branch = _mod_branch.Branch.open(branch_spec)
312
# the user has override the branch to look in.
313
# we need to refresh the revision_history map and
315
from bzrlib.branch import Branch
316
branch = Branch.open(branch_spec)
317
# Need to use a new revision history
318
# because we are using a specific branch
319
revs = branch.revision_history()
430
revision_id = branch.dotted_revno_to_revision_id(match_revno,
432
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
433
raise InvalidRevisionSpec(self.user_spec, branch)
324
last_rev = branch.last_revision()
325
merge_sorted_revisions = tsort.merge_sort(
326
branch.repository.get_revision_graph(last_rev),
330
return item[3] == match_revno
331
revisions = filter(match, merge_sorted_revisions)
334
if len(revisions) != 1:
335
return RevisionInfo(branch, None, None)
435
337
# there is no traditional 'revno' for dotted-decimal revnos.
436
# so for API compatibility we return None.
437
return branch, None, revision_id
338
# so for API compatability we return None.
339
return RevisionInfo(branch, None, revisions[0][1])
439
last_revno, last_revision_id = branch.last_revision_info()
441
342
# if get_rev_id supported negative revnos, there would not be a
442
343
# need for this special case.
443
if (-revno) >= last_revno:
344
if (-revno) >= len(revs):
446
revno = last_revno + revno + 1
347
revno = len(revs) + 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)
349
revision_id = branch.get_rev_id(revno, revs)
350
except errors.NoSuchRevision:
351
raise errors.InvalidRevisionSpec(self.user_spec, branch)
352
return RevisionInfo(branch, revno, revision_id)
458
354
def needs_branch(self):
459
355
return self.spec.find(':') == -1
462
358
if self.spec.find(':') == -1:
465
return self.spec[self.spec.find(':') + 1:]
361
return self.spec[self.spec.find(':')+1:]
469
364
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):
366
SPEC_TYPES.append(RevisionSpec_revno)
369
class RevisionSpec_revid(RevisionSpec):
480
370
"""Selects a revision using the revision id."""
482
372
help_txt = """Selects a revision using the revision id.
484
374
Supply a specific revision id, that can be used to specify any
485
revision id in the ancestry of the branch.
375
revision id in the ancestry of the branch.
486
376
Including merges, and pending merges.
489
378
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
492
380
prefix = 'revid:'
494
def _as_revision_id(self, context_branch):
382
def _match_on(self, branch, revs):
495
383
# self.spec comes straight from parsing the command line arguments,
496
384
# so we expect it to be a Unicode string. Switch it to the internal
497
385
# representation.
498
if isinstance(self.spec, str):
499
return cache_utf8.encode(self.spec)
386
revision_id = osutils.safe_revision_id(self.spec, warn=False)
387
return RevisionInfo.from_revision_id(branch, revision_id, revs)
389
SPEC_TYPES.append(RevisionSpec_revid)
503
392
class RevisionSpec_last(RevisionSpec):
508
397
Supply a positive number to get the nth revision from the end.
509
398
This is the same as supplying negative numbers to the 'revno:' spec.
512
400
last:1 -> return the last revision
513
401
last:3 -> return the revision 2 before the end.
518
406
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()
525
407
if self.spec == '':
527
raise errors.NoCommits(context_branch)
528
return last_revno, last_revision_id
409
raise errors.NoCommits(branch)
410
return RevisionInfo(branch, len(revs), revs[-1])
531
413
offset = int(self.spec)
532
except ValueError as e:
533
raise InvalidRevisionSpec(self.user_spec, context_branch, e)
414
except ValueError, e:
415
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
536
raise InvalidRevisionSpec(
537
self.user_spec, context_branch,
538
'you must supply a positive value')
540
revno = last_revno - offset + 1
418
raise errors.InvalidRevisionSpec(self.user_spec, branch,
419
'you must supply a positive value')
420
revno = len(revs) - 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
422
revision_id = branch.get_rev_id(revno, revs)
423
except errors.NoSuchRevision:
424
raise errors.InvalidRevisionSpec(self.user_spec, branch)
425
return RevisionInfo(branch, 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)
427
SPEC_TYPES.append(RevisionSpec_last)
554
430
class RevisionSpec_before(RevisionSpec):
557
433
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
435
Supply any revision spec to return the parent of that revision.
563
436
It is an error to request the parent of the null revision (before:0).
437
This is mostly useful when inspecting revisions that are not in the
438
revision history of a branch.
567
441
before:1913 -> Return the parent of revno 1913 (revno 1912)
568
442
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
569
443
aaaa@bbbb-1234567890
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
444
bzr diff -r before:revid:aaaa..revid:aaaa
445
-> Find the changes between revision 'aaaa' and its parent.
446
(what changes did 'aaaa' introduce)
576
449
prefix = 'before:'
578
451
def _match_on(self, branch, revs):
579
452
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')
454
raise errors.InvalidRevisionSpec(self.user_spec, branch,
455
'cannot go before the null: revision')
584
456
if r.revno is None:
585
457
# We need to use the repository history here
586
458
rev = branch.repository.get_revision(r.rev_id)
587
459
if not rev.parent_ids:
588
revision_id = revision.NULL_REVISION
590
463
revision_id = rev.parent_ids[0]
465
revno = revs.index(revision_id) + 1
593
469
revno = r.revno - 1
595
471
revision_id = branch.get_rev_id(revno, revs)
596
except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
597
raise InvalidRevisionSpec(self.user_spec, branch)
472
except errors.NoSuchRevision:
473
raise errors.InvalidRevisionSpec(self.user_spec,
598
475
return RevisionInfo(branch, revno, revision_id)
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.')
477
SPEC_TYPES.append(RevisionSpec_before)
621
480
class RevisionSpec_tag(RevisionSpec):
630
dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
632
490
def _match_on(self, branch, revs):
633
491
# Can raise tags not supported, NoSuchTag, etc
634
492
return RevisionInfo.from_revision_id(branch,
635
branch.tags.lookup_tag(self.spec))
493
branch.tags.lookup_tag(self.spec),
637
def _as_revision_id(self, context_branch):
638
return context_branch.tags.lookup_tag(self.spec)
496
SPEC_TYPES.append(RevisionSpec_tag)
641
499
class _RevListToTimestamps(object):
642
500
"""This takes a list of revisions, and allows you to bisect by date"""
644
__slots__ = ['branch']
502
__slots__ = ['revs', 'branch']
646
def __init__(self, branch):
504
def __init__(self, revs, branch):
647
506
self.branch = branch
649
508
def __getitem__(self, index):
650
509
"""Get the date of the index'd item"""
651
r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
510
r = self.branch.repository.get_revision(self.revs[index])
652
511
# TODO: Handle timezone.
653
512
return datetime.datetime.fromtimestamp(r.timestamp)
655
514
def __len__(self):
656
return self.branch.revno()
515
return len(self.revs)
659
518
class RevisionSpec_date(RevisionSpec):
666
525
Matches the first entry after a given date (either at midnight or
667
526
at a specified time).
669
One way to display all the changes since yesterday would be::
671
brz log -r date:yesterday..
528
One way to display all the changes since yesterday would be:
529
bzr log -r date:yesterday..-1
675
532
date:yesterday -> select the first revision since yesterday
676
533
date:2006-08-14,17:10:14 -> select the first revision after
677
534
August 14th, 2006 at 5:10pm.
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))?)?'
537
_date_re = re.compile(
538
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
540
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
686
543
def _match_on(self, branch, revs):
728
hour, minute, second = 0, 0, 0
584
hour, minute, second = 0,0,0
729
585
except ValueError:
730
raise InvalidRevisionSpec(
731
self.user_spec, branch, 'invalid date')
586
raise errors.InvalidRevisionSpec(self.user_spec,
587
branch, 'invalid date')
733
589
dt = datetime.datetime(year=year, month=month, day=day,
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)
590
hour=hour, minute=minute, second=second)
593
rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
597
return RevisionInfo(branch, None)
599
return RevisionInfo(branch, rev + 1)
601
SPEC_TYPES.append(RevisionSpec_date)
742
604
class RevisionSpec_ancestor(RevisionSpec):
765
626
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
766
627
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
630
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)
631
from bzrlib.branch import Branch
633
other_branch = Branch.open(other_location)
634
revision_a = branch.last_revision()
635
revision_b = other_branch.last_revision()
636
for r, b in ((revision_a, branch), (revision_b, other_branch)):
637
if r in (None, revision.NULL_REVISION):
638
raise errors.NoCommits(b)
639
revision_source = revision.MultipleRevisionSources(
640
branch.repository, other_branch.repository)
641
rev_id = revision.common_ancestor(revision_a, revision_b,
644
revno = branch.revision_id_to_revno(rev_id)
645
except errors.NoSuchRevision:
647
return RevisionInfo(branch, revno, rev_id)
650
SPEC_TYPES.append(RevisionSpec_ancestor)
799
653
class RevisionSpec_branch(RevisionSpec):
804
658
Supply the path to a branch to select its last revision.
808
661
branch:/path/to/branch
810
663
prefix = 'branch:'
811
dwim_catchable_exceptions = (errors.NotBranchError,)
813
665
def _match_on(self, branch, revs):
814
from .branch import Branch
666
from bzrlib.branch import Branch
815
667
other_branch = Branch.open(self.spec)
816
668
revision_b = other_branch.last_revision()
817
669
if revision_b in (None, revision.NULL_REVISION):
818
670
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):
671
# pull in the remote revisions so we can diff
672
branch.fetch(other_branch, revision_b)
674
revno = branch.revision_id_to_revno(revision_b)
675
except errors.NoSuchRevision:
677
return RevisionInfo(branch, revno, revision_b)
679
SPEC_TYPES.append(RevisionSpec_branch)
855
682
class RevisionSpec_submit(RevisionSpec_ancestor):
860
687
Diffing against this shows all the changes that were made in this branch,
861
688
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
689
used by the bundle and merge directive comands. If no submit branch
863
690
is specified, the parent branch is used instead.
865
692
The common ancestor is the last revision that existed in both
866
693
branches. Usually this is the branch point, but it could also be
867
694
a revision that was merged.
871
697
$ bzr diff -r submit:
874
700
prefix = 'submit:'
876
def _get_submit_location(self, branch):
702
def _match_on(self, branch, revs):
703
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
877
704
submit_location = branch.get_submit_branch()
878
705
location_type = 'submit branch'
879
706
if submit_location is None:
881
708
location_type = 'parent branch'
882
709
if submit_location is None:
883
710
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)
711
trace.note('Using %s %s', location_type, submit_location)
712
return self._find_revision_info(branch, submit_location)
715
SPEC_TYPES.append(RevisionSpec_submit)