139
def __new__(cls, spec, _internal=False):
141
return object.__new__(cls, spec, _internal=_internal)
143
symbol_versioning.warn('Creating a RevisionSpec directly has'
144
' been deprecated in version 0.11. Use'
145
' RevisionSpec.from_string()'
147
DeprecationWarning, stacklevel=2)
148
return RevisionSpec.from_string(spec)
151
def from_string(spec):
152
"""Parse a revision spec string into a RevisionSpec object.
154
:param spec: A string specified by the user
155
:return: A RevisionSpec object that understands how to parse the
108
def __new__(cls, spec, foo=_marker):
109
"""Parse a revision specifier.
158
if not isinstance(spec, (type(None), basestring)):
159
raise TypeError('error')
162
return RevisionSpec(None, _internal=True)
163
for spectype in SPEC_TYPES:
164
if spec.startswith(spectype.prefix):
165
trace.mutter('Returning RevisionSpec %s for %s',
166
spectype.__name__, spec)
167
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' %
169
# RevisionSpec_revno is special cased, because it is the only
170
# one that directly handles plain integers
171
# TODO: This should not be special cased rather it should be
172
# a method invocation on spectype.canparse()
174
if _revno_regex is None:
175
_revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
176
if _revno_regex.match(spec) is not None:
177
return RevisionSpec_revno(spec, _internal=True)
179
raise errors.NoSuchRevisionSpec(spec)
181
def __init__(self, spec, _internal=False):
182
"""Create a RevisionSpec referring to the Null revision.
184
:param spec: The original spec supplied by the user
185
:param _internal: Used to ensure that RevisionSpec is not being
186
called directly. Only from RevisionSpec.from_string()
189
# XXX: Update this after 0.10 is released
190
symbol_versioning.warn('Creating a RevisionSpec directly has'
191
' been deprecated in version 0.11. Use'
192
' RevisionSpec.from_string()'
194
DeprecationWarning, stacklevel=2)
195
self.user_spec = spec
129
raise TypeError('Unhandled revision type %s' % spec)
131
def __init__(self, spec):
196
132
if self.prefix and spec.startswith(self.prefix):
197
133
spec = spec[len(self.prefix):]
200
136
def _match_on(self, branch, revs):
201
trace.mutter('Returning RevisionSpec._match_on: None')
202
return RevisionInfo(branch, None, None)
137
return RevisionInfo(branch, 0, None)
204
139
def _match_on_and_check(self, branch, revs):
205
140
info = self._match_on(branch, revs)
208
elif info == (None, None):
209
# special case - nothing supplied
143
elif info == (0, None):
144
# special case - the empty tree
211
146
elif self.prefix:
212
raise errors.InvalidRevisionSpec(self.user_spec, branch)
147
raise NoSuchRevision(branch, self.prefix + str(self.spec))
214
raise errors.InvalidRevisionSpec(self.spec, branch)
149
raise NoSuchRevision(branch, str(self.spec))
216
151
def in_history(self, branch):
218
153
revs = branch.revision_history()
220
# this should never trigger.
221
# TODO: make it a deprecated code path. RBC 20060928
223
156
return self._match_on_and_check(branch, revs)
263
def get_branch(self):
264
"""When the revision specifier contains a branch location, return it.
266
Otherwise, return None.
183
class RevisionSpec_int(RevisionSpec):
184
"""Spec is a number. Special case."""
185
def __init__(self, spec):
186
self.spec = int(spec)
188
def _match_on(self, branch, revs):
190
revno = len(revs) + self.spec + 1
193
rev_id = branch.get_rev_id(revno, revs)
194
return RevisionInfo(branch, revno, rev_id)
273
197
class RevisionSpec_revno(RevisionSpec):
274
"""Selects a revision using a number."""
276
help_txt = """Selects a revision using a number.
278
Use an integer to specify a revision in the history of the branch.
279
Optionally a branch can be specified. The 'revno:' prefix is optional.
280
A negative number will count from the end of the branch (-1 is the
281
last revision, -2 the previous one). If the negative number is larger
282
than the branch's history, the first revision is returned.
285
revno:1 -> return the first revision
286
revno:3:/path/to/branch -> return the 3rd revision of
287
the branch '/path/to/branch'
288
revno:-1 -> The last revision in a branch.
289
-2:http://other/branch -> The second to last revision in the
291
-1000000 -> Most likely the first revision, unless
292
your history is very long.
294
198
prefix = 'revno:'
296
200
def _match_on(self, branch, revs):
297
201
"""Lookup a revision by revision number"""
298
branch, revno, revision_id = self._lookup(branch, revs)
299
return RevisionInfo(branch, revno, revision_id)
301
def _lookup(self, branch, revs_or_none):
302
loc = self.spec.find(':')
304
revno_spec = self.spec
307
revno_spec = self.spec[:loc]
308
branch_spec = self.spec[loc+1:]
312
raise errors.InvalidRevisionSpec(self.user_spec,
313
branch, 'cannot have an empty revno and no branch')
317
revno = int(revno_spec)
320
# dotted decimal. This arguably should not be here
321
# but the from_string method is a little primitive
322
# right now - RBC 20060928
324
match_revno = tuple((int(number) for number in revno_spec.split('.')))
325
except ValueError, e:
326
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
331
# the user has override the branch to look in.
332
# we need to refresh the revision_history map and
334
from bzrlib.branch import Branch
335
branch = Branch.open(branch_spec)
341
revision_id_to_revno = branch.get_revision_id_to_revno_map()
342
revisions = [revision_id for revision_id, revno
343
in revision_id_to_revno.iteritems()
344
if revno == match_revno]
347
if len(revisions) != 1:
348
return branch, None, None
350
# there is no traditional 'revno' for dotted-decimal revnos.
351
# so for API compatability we return None.
352
return branch, None, revisions[0]
354
last_revno, last_revision_id = branch.last_revision_info()
356
# if get_rev_id supported negative revnos, there would not be a
357
# need for this special case.
358
if (-revno) >= last_revno:
361
revno = last_revno + revno + 1
363
revision_id = branch.get_rev_id(revno, revs_or_none)
364
except errors.NoSuchRevision:
365
raise errors.InvalidRevisionSpec(self.user_spec, branch)
366
return branch, revno, revision_id
368
def _as_revision_id(self, context_branch):
369
# We would have the revno here, but we don't really care
370
branch, revno, revision_id = self._lookup(context_branch, None)
202
if self.spec.find(':') == -1:
204
return RevisionInfo(branch, int(self.spec))
206
return RevisionInfo(branch, None)
208
from branch import Branch
209
revname = self.spec[self.spec.find(':')+1:]
210
other_branch = Branch.open_containing(revname)[0]
212
revno = int(self.spec[:self.spec.find(':')])
214
return RevisionInfo(other_branch, None)
215
revid = other_branch.get_rev_id(revno)
216
return RevisionInfo(other_branch, revno)
373
218
def needs_branch(self):
374
219
return self.spec.find(':') == -1
376
def get_branch(self):
377
if self.spec.find(':') == -1:
380
return self.spec[self.spec.find(':')+1:]
383
RevisionSpec_int = RevisionSpec_revno
385
221
SPEC_TYPES.append(RevisionSpec_revno)
388
224
class RevisionSpec_revid(RevisionSpec):
389
"""Selects a revision using the revision id."""
391
help_txt = """Selects a revision using the revision id.
393
Supply a specific revision id, that can be used to specify any
394
revision id in the ancestry of the branch.
395
Including merges, and pending merges.
398
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
401
225
prefix = 'revid:'
403
227
def _match_on(self, branch, revs):
404
# self.spec comes straight from parsing the command line arguments,
405
# so we expect it to be a Unicode string. Switch it to the internal
407
revision_id = osutils.safe_revision_id(self.spec, warn=False)
408
return RevisionInfo.from_revision_id(branch, revision_id, revs)
410
def _as_revision_id(self, context_branch):
411
return osutils.safe_revision_id(self.spec, warn=False)
229
return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
231
return RevisionInfo(branch, None, self.spec)
413
233
SPEC_TYPES.append(RevisionSpec_revid)
416
236
class RevisionSpec_last(RevisionSpec):
417
"""Selects the nth revision from the end."""
419
help_txt = """Selects the nth revision from the end.
421
Supply a positive number to get the nth revision from the end.
422
This is the same as supplying negative numbers to the 'revno:' spec.
425
last:1 -> return the last revision
426
last:3 -> return the revision 2 before the end.
431
240
def _match_on(self, branch, revs):
432
revno, revision_id = self._revno_and_revision_id(branch, revs)
433
return RevisionInfo(branch, revno, revision_id)
435
def _revno_and_revision_id(self, context_branch, revs_or_none):
436
last_revno, last_revision_id = context_branch.last_revision_info()
440
raise errors.NoCommits(context_branch)
441
return last_revno, last_revision_id
444
242
offset = int(self.spec)
445
except ValueError, e:
446
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
449
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
450
'you must supply a positive value')
452
revno = last_revno - offset + 1
454
revision_id = context_branch.get_rev_id(revno, revs_or_none)
455
except errors.NoSuchRevision:
456
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
457
return revno, revision_id
459
def _as_revision_id(self, context_branch):
460
# We compute the revno as part of the process, but we don't really care
462
revno, revision_id = self._revno_and_revision_id(context_branch, None)
244
return RevisionInfo(branch, None)
247
raise BzrError('You must supply a positive value for --revision last:XXX')
248
return RevisionInfo(branch, len(revs) - offset + 1)
465
250
SPEC_TYPES.append(RevisionSpec_last)
468
253
class RevisionSpec_before(RevisionSpec):
469
"""Selects the parent of the revision specified."""
471
help_txt = """Selects the parent of the revision specified.
473
Supply any revision spec to return the parent of that revision.
474
It is an error to request the parent of the null revision (before:0).
475
This is mostly useful when inspecting revisions that are not in the
476
revision history of a branch.
480
before:1913 -> Return the parent of revno 1913 (revno 1912)
481
before:revid:aaaa@bbbb-1234567890 -> return the parent of revision
483
bzr diff -r before:revid:aaaa..revid:aaaa
484
-> Find the changes between revision 'aaaa' and its parent.
485
(what changes did 'aaaa' introduce)
488
255
prefix = 'before:'
490
257
def _match_on(self, branch, revs):
491
r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
493
raise errors.InvalidRevisionSpec(self.user_spec, branch,
494
'cannot go before the null: revision')
496
# We need to use the repository history here
497
rev = branch.repository.get_revision(r.rev_id)
498
if not rev.parent_ids:
500
revision_id = revision.NULL_REVISION
502
revision_id = rev.parent_ids[0]
504
revno = revs.index(revision_id) + 1
510
revision_id = branch.get_rev_id(revno, revs)
511
except errors.NoSuchRevision:
512
raise errors.InvalidRevisionSpec(self.user_spec,
514
return RevisionInfo(branch, revno, revision_id)
516
def _as_revision_id(self, context_branch):
517
base_revspec = RevisionSpec.from_string(self.spec)
518
base_revision_id = base_revspec.as_revision_id(context_branch)
519
if base_revision_id == revision.NULL_REVISION:
520
raise errors.InvalidRevisionSpec(self.user_spec, branch,
521
'cannot go before the null: revision')
522
context_repo = context_branch.repository
523
context_repo.lock_read()
525
parent_map = context_repo.get_parent_map([base_revision_id])
527
context_repo.unlock()
528
if base_revision_id not in parent_map:
529
# Ghost, or unknown revision id
530
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
531
'cannot find the matching revision')
532
parents = parent_map[base_revision_id]
534
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
535
'No parents for revision.')
258
r = RevisionSpec(self.spec)._match_on(branch, revs)
259
if (r.revno is None) or (r.revno == 0):
261
return RevisionInfo(branch, r.revno - 1)
538
263
SPEC_TYPES.append(RevisionSpec_before)
541
266
class RevisionSpec_tag(RevisionSpec):
542
"""Select a revision identified by tag name"""
544
help_txt = """Selects a revision identified by a tag name.
546
Tags are stored in the branch and created by the 'tag' command.
551
269
def _match_on(self, branch, revs):
552
# Can raise tags not supported, NoSuchTag, etc
553
return RevisionInfo.from_revision_id(branch,
554
branch.tags.lookup_tag(self.spec),
557
def _as_revision_id(self, context_branch):
558
return context_branch.tags.lookup_tag(self.spec)
270
raise BzrError('tag: namespace registered, but not implemented.')
560
272
SPEC_TYPES.append(RevisionSpec_tag)
563
class _RevListToTimestamps(object):
564
"""This takes a list of revisions, and allows you to bisect by date"""
566
__slots__ = ['revs', 'branch']
275
class RevisionSpec_revs:
568
276
def __init__(self, revs, branch):
570
278
self.branch = branch
572
279
def __getitem__(self, index):
573
"""Get the date of the index'd item"""
574
280
r = self.branch.repository.get_revision(self.revs[index])
575
281
# TODO: Handle timezone.
576
282
return datetime.datetime.fromtimestamp(r.timestamp)
578
283
def __len__(self):
579
284
return len(self.revs)
582
287
class RevisionSpec_date(RevisionSpec):
583
"""Selects a revision on the basis of a datestamp."""
585
help_txt = """Selects a revision on the basis of a datestamp.
587
Supply a datestamp to select the first revision that matches the date.
588
Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
589
Matches the first entry after a given date (either at midnight or
590
at a specified time).
592
One way to display all the changes since yesterday would be::
594
bzr log -r date:yesterday..-1
598
date:yesterday -> select the first revision since yesterday
599
date:2006-08-14,17:10:14 -> select the first revision after
600
August 14th, 2006 at 5:10pm.
603
289
_date_re = re.compile(
604
290
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
670
347
class RevisionSpec_ancestor(RevisionSpec):
671
"""Selects a common ancestor with a second branch."""
673
help_txt = """Selects a common ancestor with a second branch.
675
Supply the path to a branch to select the common ancestor.
677
The common ancestor is the last revision that existed in both
678
branches. Usually this is the branch point, but it could also be
679
a revision that was merged.
681
This is frequently used with 'diff' to return all of the changes
682
that your branch introduces, while excluding the changes that you
683
have not merged from the remote branch.
687
ancestor:/path/to/branch
688
$ bzr diff -r ancestor:../../mainline/branch
690
348
prefix = 'ancestor:'
692
350
def _match_on(self, branch, revs):
693
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
694
return self._find_revision_info(branch, self.spec)
696
def _as_revision_id(self, context_branch):
697
return self._find_revision_id(context_branch, self.spec)
700
def _find_revision_info(branch, other_location):
701
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
351
from branch import Branch
352
from revision import common_ancestor, MultipleRevisionSources
353
other_branch = Branch.open_containing(self.spec)[0]
354
revision_a = branch.last_revision()
355
revision_b = other_branch.last_revision()
356
for r, b in ((revision_a, branch), (revision_b, other_branch)):
359
revision_source = MultipleRevisionSources(branch.repository,
360
other_branch.repository)
361
rev_id = common_ancestor(revision_a, revision_b, revision_source)
704
revno = branch.revision_id_to_revno(revision_id)
705
except errors.NoSuchRevision:
363
revno = branch.revision_id_to_revno(rev_id)
364
except NoSuchRevision:
707
return RevisionInfo(branch, revno, revision_id)
710
def _find_revision_id(branch, other_location):
711
from bzrlib.branch import Branch
715
revision_a = revision.ensure_null(branch.last_revision())
716
if revision_a == revision.NULL_REVISION:
717
raise errors.NoCommits(branch)
718
other_branch = Branch.open(other_location)
719
other_branch.lock_read()
721
revision_b = revision.ensure_null(other_branch.last_revision())
722
if revision_b == revision.NULL_REVISION:
723
raise errors.NoCommits(other_branch)
724
graph = branch.repository.get_graph(other_branch.repository)
725
rev_id = graph.find_unique_lca(revision_a, revision_b)
727
other_branch.unlock()
728
if rev_id == revision.NULL_REVISION:
729
raise errors.NoCommonAncestor(revision_a, revision_b)
366
return RevisionInfo(branch, revno, rev_id)
735
368
SPEC_TYPES.append(RevisionSpec_ancestor)
738
370
class RevisionSpec_branch(RevisionSpec):
739
"""Selects the last revision of a specified branch."""
741
help_txt = """Selects the last revision of a specified branch.
743
Supply the path to a branch to select its last revision.
747
branch:/path/to/branch
371
"""A branch: revision specifier.
373
This takes the path to a branch and returns its tip revision id.
749
375
prefix = 'branch:'
751
377
def _match_on(self, branch, revs):
752
from bzrlib.branch import Branch
753
other_branch = Branch.open(self.spec)
378
from branch import Branch
379
other_branch = Branch.open_containing(self.spec)[0]
754
380
revision_b = other_branch.last_revision()
755
if revision_b in (None, revision.NULL_REVISION):
756
raise errors.NoCommits(other_branch)
381
if revision_b is None:
382
raise NoCommits(other_branch)
757
383
# pull in the remote revisions so we can diff
758
384
branch.fetch(other_branch, revision_b)
760
386
revno = branch.revision_id_to_revno(revision_b)
761
except errors.NoSuchRevision:
387
except NoSuchRevision:
763
389
return RevisionInfo(branch, revno, revision_b)
765
def _as_revision_id(self, context_branch):
766
from bzrlib.branch import Branch
767
other_branch = Branch.open(self.spec)
768
last_revision = other_branch.last_revision()
769
last_revision = revision.ensure_null(last_revision)
770
context_branch.fetch(other_branch, last_revision)
771
if last_revision == revision.NULL_REVISION:
772
raise errors.NoCommits(other_branch)
775
391
SPEC_TYPES.append(RevisionSpec_branch)
778
class RevisionSpec_submit(RevisionSpec_ancestor):
779
"""Selects a common ancestor with a submit branch."""
781
help_txt = """Selects a common ancestor with the submit branch.
783
Diffing against this shows all the changes that were made in this branch,
784
and is a good predictor of what merge will do. The submit branch is
785
used by the bundle and merge directive comands. If no submit branch
786
is specified, the parent branch is used instead.
788
The common ancestor is the last revision that existed in both
789
branches. Usually this is the branch point, but it could also be
790
a revision that was merged.
794
$ bzr diff -r submit:
799
def _get_submit_location(self, branch):
800
submit_location = branch.get_submit_branch()
801
location_type = 'submit branch'
802
if submit_location is None:
803
submit_location = branch.get_parent()
804
location_type = 'parent branch'
805
if submit_location is None:
806
raise errors.NoSubmitBranch(branch)
807
trace.note('Using %s %s', location_type, submit_location)
808
return submit_location
810
def _match_on(self, branch, revs):
811
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
812
return self._find_revision_info(branch,
813
self._get_submit_location(branch))
815
def _as_revision_id(self, context_branch):
816
return self._find_revision_id(context_branch,
817
self._get_submit_location(context_branch))
820
SPEC_TYPES.append(RevisionSpec_submit)