94
93
return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
95
94
self.revno, self.rev_id, self.branch)
98
def from_revision_id(branch, revision_id, revs):
99
"""Construct a RevisionInfo given just the id.
101
Use this if you don't know or care what the revno is.
103
if revision_id == revision.NULL_REVISION:
104
return RevisionInfo(branch, 0, revision_id)
106
revno = revs.index(revision_id) + 1
109
return RevisionInfo(branch, revno, revision_id)
112
97
# classes in this list should have a "prefix" attribute, against which
113
98
# string specs are matched
163
147
return RevisionSpec(None, _internal=True)
149
assert isinstance(spec, basestring), \
150
"You should only supply strings not %s" % (type(spec),)
164
152
for spectype in SPEC_TYPES:
165
153
if spec.startswith(spectype.prefix):
166
154
trace.mutter('Returning RevisionSpec %s for %s',
201
189
def _match_on(self, branch, revs):
202
190
trace.mutter('Returning RevisionSpec._match_on: None')
203
return RevisionInfo(branch, None, None)
191
return RevisionInfo(branch, 0, None)
205
193
def _match_on_and_check(self, branch, revs):
206
194
info = self._match_on(branch, revs)
209
elif info == (None, None):
210
# special case - nothing supplied
197
elif info == (0, None):
198
# special case - the empty tree
212
200
elif self.prefix:
213
201
raise errors.InvalidRevisionSpec(self.user_spec, branch)
235
220
# will do what you expect.
236
221
in_store = in_history
237
222
in_branch = in_store
239
def as_revision_id(self, context_branch):
240
"""Return just the revision_id for this revisions spec.
242
Some revision specs require a context_branch to be able to determine
243
their value. Not all specs will make use of it.
245
return self._as_revision_id(context_branch)
247
def _as_revision_id(self, context_branch):
248
"""Implementation of as_revision_id()
250
Classes should override this function to provide appropriate
251
functionality. The default is to just call '.in_history().rev_id'
253
return self.in_history(context_branch).rev_id
255
224
def __repr__(self):
256
225
# this is mostly for helping with testing
257
226
return '<%s %s>' % (self.__class__.__name__,
284
253
A negative number will count from the end of the branch (-1 is the
285
254
last revision, -2 the previous one). If the negative number is larger
286
255
than the branch's history, the first revision is returned.
289
257
revno:1 -> return the first revision
290
258
revno:3:/path/to/branch -> return the 3rd revision of
291
259
the branch '/path/to/branch'
296
264
your history is very long.
298
266
prefix = 'revno:'
299
wants_revision_history = False
301
268
def _match_on(self, branch, revs):
302
269
"""Lookup a revision by revision number"""
303
branch, revno, revision_id = self._lookup(branch, revs)
304
return RevisionInfo(branch, revno, revision_id)
306
def _lookup(self, branch, revs_or_none):
307
270
loc = self.spec.find(':')
309
272
revno_spec = self.spec
338
301
# the branch object.
339
302
from bzrlib.branch import Branch
340
303
branch = Branch.open(branch_spec)
304
# Need to use a new revision history
305
# because we are using a specific branch
306
revs = branch.revision_history()
344
309
branch.lock_read()
346
revision_id_to_revno = branch.get_revision_id_to_revno_map()
347
revisions = [revision_id for revision_id, revno
348
in revision_id_to_revno.iteritems()
349
if revno == match_revno]
311
last_rev = branch.last_revision()
312
merge_sorted_revisions = tsort.merge_sort(
313
branch.repository.get_revision_graph(last_rev),
317
return item[3] == match_revno
318
revisions = filter(match, merge_sorted_revisions)
352
321
if len(revisions) != 1:
353
return branch, None, None
322
return RevisionInfo(branch, None, None)
355
324
# there is no traditional 'revno' for dotted-decimal revnos.
356
325
# so for API compatability we return None.
357
return branch, None, revisions[0]
326
return RevisionInfo(branch, None, revisions[0][1])
359
last_revno, last_revision_id = branch.last_revision_info()
361
329
# if get_rev_id supported negative revnos, there would not be a
362
330
# need for this special case.
363
if (-revno) >= last_revno:
331
if (-revno) >= len(revs):
366
revno = last_revno + revno + 1
334
revno = len(revs) + revno + 1
368
revision_id = branch.get_rev_id(revno, revs_or_none)
336
revision_id = branch.get_rev_id(revno, revs)
369
337
except errors.NoSuchRevision:
370
338
raise errors.InvalidRevisionSpec(self.user_spec, branch)
371
return branch, revno, revision_id
373
def _as_revision_id(self, context_branch):
374
# We would have the revno here, but we don't really care
375
branch, revno, revision_id = self._lookup(context_branch, None)
339
return RevisionInfo(branch, revno, revision_id)
378
341
def needs_branch(self):
379
342
return self.spec.find(':') == -1
398
361
Supply a specific revision id, that can be used to specify any
399
362
revision id in the ancestry of the branch.
400
363
Including merges, and pending merges.
403
365
revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
406
367
prefix = 'revid:'
408
369
def _match_on(self, branch, revs):
409
# self.spec comes straight from parsing the command line arguments,
410
# so we expect it to be a Unicode string. Switch it to the internal
412
revision_id = osutils.safe_revision_id(self.spec, warn=False)
413
return RevisionInfo.from_revision_id(branch, revision_id, revs)
415
def _as_revision_id(self, context_branch):
416
return osutils.safe_revision_id(self.spec, warn=False)
371
revno = revs.index(self.spec) + 1
374
return RevisionInfo(branch, revno, self.spec)
418
376
SPEC_TYPES.append(RevisionSpec_revid)
426
384
Supply a positive number to get the nth revision from the end.
427
385
This is the same as supplying negative numbers to the 'revno:' spec.
430
387
last:1 -> return the last revision
431
388
last:3 -> return the revision 2 before the end.
436
393
def _match_on(self, branch, revs):
437
revno, revision_id = self._revno_and_revision_id(branch, revs)
438
return RevisionInfo(branch, revno, revision_id)
440
def _revno_and_revision_id(self, context_branch, revs_or_none):
441
last_revno, last_revision_id = context_branch.last_revision_info()
443
394
if self.spec == '':
445
raise errors.NoCommits(context_branch)
446
return last_revno, last_revision_id
396
raise errors.NoCommits(branch)
397
return RevisionInfo(branch, len(revs), revs[-1])
449
400
offset = int(self.spec)
450
401
except ValueError, e:
451
raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
402
raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
454
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
405
raise errors.InvalidRevisionSpec(self.user_spec, branch,
455
406
'you must supply a positive value')
457
revno = last_revno - offset + 1
407
revno = len(revs) - offset + 1
459
revision_id = context_branch.get_rev_id(revno, revs_or_none)
409
revision_id = branch.get_rev_id(revno, revs)
460
410
except errors.NoSuchRevision:
461
raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
462
return revno, revision_id
464
def _as_revision_id(self, context_branch):
465
# We compute the revno as part of the process, but we don't really care
467
revno, revision_id = self._revno_and_revision_id(context_branch, None)
411
raise errors.InvalidRevisionSpec(self.user_spec, branch)
412
return RevisionInfo(branch, revno, revision_id)
470
414
SPEC_TYPES.append(RevisionSpec_last)
519
462
return RevisionInfo(branch, revno, revision_id)
521
def _as_revision_id(self, context_branch):
522
base_revspec = RevisionSpec.from_string(self.spec)
523
base_revision_id = base_revspec.as_revision_id(context_branch)
524
if base_revision_id == revision.NULL_REVISION:
525
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
526
'cannot go before the null: revision')
527
context_repo = context_branch.repository
528
context_repo.lock_read()
530
parent_map = context_repo.get_parent_map([base_revision_id])
532
context_repo.unlock()
533
if base_revision_id not in parent_map:
534
# Ghost, or unknown revision id
535
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
536
'cannot find the matching revision')
537
parents = parent_map[base_revision_id]
539
raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
540
'No parents for revision.')
543
464
SPEC_TYPES.append(RevisionSpec_before)
546
467
class RevisionSpec_tag(RevisionSpec):
547
"""Select a revision identified by tag name"""
549
help_txt = """Selects a revision identified by a tag name.
551
Tags are stored in the branch and created by the 'tag' command.
468
"""To be implemented."""
470
help_txt = """To be implemented."""
556
474
def _match_on(self, branch, revs):
557
# Can raise tags not supported, NoSuchTag, etc
558
return RevisionInfo.from_revision_id(branch,
559
branch.tags.lookup_tag(self.spec),
562
def _as_revision_id(self, context_branch):
563
return context_branch.tags.lookup_tag(self.spec)
475
raise errors.InvalidRevisionSpec(self.user_spec, branch,
476
'tag: namespace registered,'
477
' but not implemented')
565
479
SPEC_TYPES.append(RevisionSpec_tag)
594
508
Matches the first entry after a given date (either at midnight or
595
509
at a specified time).
597
One way to display all the changes since yesterday would be::
599
bzr log -r date:yesterday..
511
One way to display all the changes since yesterday would be:
512
bzr log -r date:yesterday..-1
603
515
date:yesterday -> select the first revision since yesterday
604
516
date:2006-08-14,17:10:14 -> select the first revision after
605
517
August 14th, 2006 at 5:10pm.
687
599
that your branch introduces, while excluding the changes that you
688
600
have not merged from the remote branch.
692
603
ancestor:/path/to/branch
693
604
$ bzr diff -r ancestor:../../mainline/branch
695
606
prefix = 'ancestor:'
697
608
def _match_on(self, branch, revs):
609
from bzrlib.branch import Branch
698
611
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
699
return self._find_revision_info(branch, self.spec)
701
def _as_revision_id(self, context_branch):
702
return self._find_revision_id(context_branch, self.spec)
705
def _find_revision_info(branch, other_location):
706
revision_id = RevisionSpec_ancestor._find_revision_id(branch,
612
other_branch = Branch.open(self.spec)
613
revision_a = branch.last_revision()
614
revision_b = other_branch.last_revision()
615
for r, b in ((revision_a, branch), (revision_b, other_branch)):
616
if r in (None, revision.NULL_REVISION):
617
raise errors.NoCommits(b)
618
revision_source = revision.MultipleRevisionSources(
619
branch.repository, other_branch.repository)
620
rev_id = revision.common_ancestor(revision_a, revision_b,
709
revno = branch.revision_id_to_revno(revision_id)
623
revno = branch.revision_id_to_revno(rev_id)
710
624
except errors.NoSuchRevision:
712
return RevisionInfo(branch, revno, revision_id)
715
def _find_revision_id(branch, other_location):
716
from bzrlib.branch import Branch
720
revision_a = revision.ensure_null(branch.last_revision())
721
if revision_a == revision.NULL_REVISION:
722
raise errors.NoCommits(branch)
723
other_branch = Branch.open(other_location)
724
other_branch.lock_read()
726
revision_b = revision.ensure_null(other_branch.last_revision())
727
if revision_b == revision.NULL_REVISION:
728
raise errors.NoCommits(other_branch)
729
graph = branch.repository.get_graph(other_branch.repository)
730
rev_id = graph.find_unique_lca(revision_a, revision_b)
732
other_branch.unlock()
733
if rev_id == revision.NULL_REVISION:
734
raise errors.NoCommonAncestor(revision_a, revision_b)
626
return RevisionInfo(branch, revno, rev_id)
740
628
SPEC_TYPES.append(RevisionSpec_ancestor)
766
653
except errors.NoSuchRevision:
768
655
return RevisionInfo(branch, revno, revision_b)
770
def _as_revision_id(self, context_branch):
771
from bzrlib.branch import Branch
772
other_branch = Branch.open(self.spec)
773
last_revision = other_branch.last_revision()
774
last_revision = revision.ensure_null(last_revision)
775
context_branch.fetch(other_branch, last_revision)
776
if last_revision == revision.NULL_REVISION:
777
raise errors.NoCommits(other_branch)
780
657
SPEC_TYPES.append(RevisionSpec_branch)
783
class RevisionSpec_submit(RevisionSpec_ancestor):
784
"""Selects a common ancestor with a submit branch."""
786
help_txt = """Selects a common ancestor with the submit branch.
788
Diffing against this shows all the changes that were made in this branch,
789
and is a good predictor of what merge will do. The submit branch is
790
used by the bundle and merge directive commands. If no submit branch
791
is specified, the parent branch is used instead.
793
The common ancestor is the last revision that existed in both
794
branches. Usually this is the branch point, but it could also be
795
a revision that was merged.
799
$ bzr diff -r submit:
804
def _get_submit_location(self, branch):
805
submit_location = branch.get_submit_branch()
806
location_type = 'submit branch'
807
if submit_location is None:
808
submit_location = branch.get_parent()
809
location_type = 'parent branch'
810
if submit_location is None:
811
raise errors.NoSubmitBranch(branch)
812
trace.note('Using %s %s', location_type, submit_location)
813
return submit_location
815
def _match_on(self, branch, revs):
816
trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
817
return self._find_revision_info(branch,
818
self._get_submit_location(branch))
820
def _as_revision_id(self, context_branch):
821
return self._find_revision_id(context_branch,
822
self._get_submit_location(context_branch))
825
SPEC_TYPES.append(RevisionSpec_submit)