~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Andrew Bennetts
  • Date: 2007-03-26 06:24:01 UTC
  • mto: This revision was merged to the branch mainline in revision 2376.
  • Revision ID: andrew.bennetts@canonical.com-20070326062401-k3nbefzje5332jaf
Deal with review comments from Robert:

  * Add my name to the NEWS file
  * Move the test case to a new module in branch_implementations
  * Remove revision_history cruft from identitymap and test_identitymap
  * Improve some docstrings

Also, this fixes a bug where revision_history was not returning a copy of the
cached data, allowing the cache to be corrupted.

Show diffs side-by-side

added added

removed removed

Lines of Context:
100
100
 
101
101
        Use this if you don't know or care what the revno is.
102
102
        """
103
 
        if revision_id == revision.NULL_REVISION:
104
 
            return RevisionInfo(branch, 0, revision_id)
105
103
        try:
106
104
            revno = revs.index(revision_id) + 1
107
105
        except ValueError:
135
133
    """
136
134
 
137
135
    prefix = None
138
 
    wants_revision_history = True
139
136
 
140
137
    def __new__(cls, spec, _internal=False):
141
138
        if _internal:
161
158
 
162
159
        if spec is None:
163
160
            return RevisionSpec(None, _internal=True)
 
161
 
 
162
        assert isinstance(spec, basestring), \
 
163
            "You should only supply strings not %s" % (type(spec),)
 
164
 
164
165
        for spectype in SPEC_TYPES:
165
166
            if spec.startswith(spectype.prefix):
166
167
                trace.mutter('Returning RevisionSpec %s for %s',
200
201
 
201
202
    def _match_on(self, branch, revs):
202
203
        trace.mutter('Returning RevisionSpec._match_on: None')
203
 
        return RevisionInfo(branch, None, None)
 
204
        return RevisionInfo(branch, 0, None)
204
205
 
205
206
    def _match_on_and_check(self, branch, revs):
206
207
        info = self._match_on(branch, revs)
207
208
        if info:
208
209
            return info
209
 
        elif info == (None, None):
210
 
            # special case - nothing supplied
 
210
        elif info == (0, None):
 
211
            # special case - the empty tree
211
212
            return info
212
213
        elif self.prefix:
213
214
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
216
217
 
217
218
    def in_history(self, branch):
218
219
        if branch:
219
 
            if self.wants_revision_history:
220
 
                revs = branch.revision_history()
221
 
            else:
222
 
                revs = None
 
220
            revs = branch.revision_history()
223
221
        else:
224
222
            # this should never trigger.
225
223
            # TODO: make it a deprecated code path. RBC 20060928
235
233
    # will do what you expect.
236
234
    in_store = in_history
237
235
    in_branch = in_store
238
 
 
239
 
    def as_revision_id(self, context_branch):
240
 
        """Return just the revision_id for this revisions spec.
241
 
 
242
 
        Some revision specs require a context_branch to be able to determine
243
 
        their value. Not all specs will make use of it.
244
 
        """
245
 
        return self._as_revision_id(context_branch)
246
 
 
247
 
    def _as_revision_id(self, context_branch):
248
 
        """Implementation of as_revision_id()
249
 
 
250
 
        Classes should override this function to provide appropriate
251
 
        functionality. The default is to just call '.in_history().rev_id'
252
 
        """
253
 
        return self.in_history(context_branch).rev_id
254
 
 
 
236
        
255
237
    def __repr__(self):
256
238
        # this is mostly for helping with testing
257
239
        return '<%s %s>' % (self.__class__.__name__,
284
266
    A negative number will count from the end of the branch (-1 is the
285
267
    last revision, -2 the previous one). If the negative number is larger
286
268
    than the branch's history, the first revision is returned.
287
 
    Examples::
288
 
 
 
269
    examples:
289
270
      revno:1                   -> return the first revision
290
271
      revno:3:/path/to/branch   -> return the 3rd revision of
291
272
                                   the branch '/path/to/branch'
296
277
                                   your history is very long.
297
278
    """
298
279
    prefix = 'revno:'
299
 
    wants_revision_history = False
300
280
 
301
281
    def _match_on(self, branch, revs):
302
282
        """Lookup a revision by revision number"""
303
 
        branch, revno, revision_id = self._lookup(branch, revs)
304
 
        return RevisionInfo(branch, revno, revision_id)
305
 
 
306
 
    def _lookup(self, branch, revs_or_none):
307
283
        loc = self.spec.find(':')
308
284
        if loc == -1:
309
285
            revno_spec = self.spec
338
314
            # the branch object.
339
315
            from bzrlib.branch import Branch
340
316
            branch = Branch.open(branch_spec)
341
 
            revs_or_none = None
 
317
            # Need to use a new revision history
 
318
            # because we are using a specific branch
 
319
            revs = branch.revision_history()
342
320
 
343
321
        if dotted:
344
322
            branch.lock_read()
345
323
            try:
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]
 
324
                last_rev = branch.last_revision()
 
325
                merge_sorted_revisions = tsort.merge_sort(
 
326
                    branch.repository.get_revision_graph(last_rev),
 
327
                    last_rev,
 
328
                    generate_revno=True)
 
329
                def match(item):
 
330
                    return item[3] == match_revno
 
331
                revisions = filter(match, merge_sorted_revisions)
350
332
            finally:
351
333
                branch.unlock()
352
334
            if len(revisions) != 1:
353
 
                return branch, None, None
 
335
                return RevisionInfo(branch, None, None)
354
336
            else:
355
337
                # there is no traditional 'revno' for dotted-decimal revnos.
356
338
                # so for  API compatability we return None.
357
 
                return branch, None, revisions[0]
 
339
                return RevisionInfo(branch, None, revisions[0][1])
358
340
        else:
359
 
            last_revno, last_revision_id = branch.last_revision_info()
360
341
            if revno < 0:
361
342
                # if get_rev_id supported negative revnos, there would not be a
362
343
                # need for this special case.
363
 
                if (-revno) >= last_revno:
 
344
                if (-revno) >= len(revs):
364
345
                    revno = 1
365
346
                else:
366
 
                    revno = last_revno + revno + 1
 
347
                    revno = len(revs) + revno + 1
367
348
            try:
368
 
                revision_id = branch.get_rev_id(revno, revs_or_none)
 
349
                revision_id = branch.get_rev_id(revno, revs)
369
350
            except errors.NoSuchRevision:
370
351
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
371
 
        return branch, revno, revision_id
372
 
 
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)
376
 
        return revision_id
377
 
 
 
352
        return RevisionInfo(branch, revno, revision_id)
 
353
        
378
354
    def needs_branch(self):
379
355
        return self.spec.find(':') == -1
380
356
 
398
374
    Supply a specific revision id, that can be used to specify any
399
375
    revision id in the ancestry of the branch. 
400
376
    Including merges, and pending merges.
401
 
    Examples::
402
 
 
 
377
    examples:
403
378
      revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
404
 
    """
405
 
 
 
379
    """    
406
380
    prefix = 'revid:'
407
381
 
408
382
    def _match_on(self, branch, revs):
412
386
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
413
387
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
414
388
 
415
 
    def _as_revision_id(self, context_branch):
416
 
        return osutils.safe_revision_id(self.spec, warn=False)
417
 
 
418
389
SPEC_TYPES.append(RevisionSpec_revid)
419
390
 
420
391
 
425
396
 
426
397
    Supply a positive number to get the nth revision from the end.
427
398
    This is the same as supplying negative numbers to the 'revno:' spec.
428
 
    Examples::
429
 
 
 
399
    examples:
430
400
      last:1        -> return the last revision
431
401
      last:3        -> return the revision 2 before the end.
432
 
    """
 
402
    """    
433
403
 
434
404
    prefix = 'last:'
435
405
 
436
406
    def _match_on(self, branch, revs):
437
 
        revno, revision_id = self._revno_and_revision_id(branch, revs)
438
 
        return RevisionInfo(branch, revno, revision_id)
439
 
 
440
 
    def _revno_and_revision_id(self, context_branch, revs_or_none):
441
 
        last_revno, last_revision_id = context_branch.last_revision_info()
442
 
 
443
407
        if self.spec == '':
444
 
            if not last_revno:
445
 
                raise errors.NoCommits(context_branch)
446
 
            return last_revno, last_revision_id
 
408
            if not revs:
 
409
                raise errors.NoCommits(branch)
 
410
            return RevisionInfo(branch, len(revs), revs[-1])
447
411
 
448
412
        try:
449
413
            offset = int(self.spec)
450
414
        except ValueError, e:
451
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
 
415
            raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
452
416
 
453
417
        if offset <= 0:
454
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
418
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
455
419
                                             'you must supply a positive value')
456
 
 
457
 
        revno = last_revno - offset + 1
 
420
        revno = len(revs) - offset + 1
458
421
        try:
459
 
            revision_id = context_branch.get_rev_id(revno, revs_or_none)
 
422
            revision_id = branch.get_rev_id(revno, revs)
460
423
        except errors.NoSuchRevision:
461
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
462
 
        return revno, revision_id
463
 
 
464
 
    def _as_revision_id(self, context_branch):
465
 
        # We compute the revno as part of the process, but we don't really care
466
 
        # about it.
467
 
        revno, revision_id = self._revno_and_revision_id(context_branch, None)
468
 
        return revision_id
 
424
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
425
        return RevisionInfo(branch, revno, revision_id)
469
426
 
470
427
SPEC_TYPES.append(RevisionSpec_last)
471
428
 
480
437
    This is mostly useful when inspecting revisions that are not in the
481
438
    revision history of a branch.
482
439
 
483
 
    Examples::
484
 
 
 
440
    examples:
485
441
      before:1913    -> Return the parent of revno 1913 (revno 1912)
486
442
      before:revid:aaaa@bbbb-1234567890  -> return the parent of revision
487
443
                                            aaaa@bbbb-1234567890
502
458
            rev = branch.repository.get_revision(r.rev_id)
503
459
            if not rev.parent_ids:
504
460
                revno = 0
505
 
                revision_id = revision.NULL_REVISION
 
461
                revision_id = None
506
462
            else:
507
463
                revision_id = rev.parent_ids[0]
508
464
                try:
518
474
                                                 branch)
519
475
        return RevisionInfo(branch, revno, revision_id)
520
476
 
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()
529
 
        try:
530
 
            parent_map = context_repo.get_parent_map([base_revision_id])
531
 
        finally:
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]
538
 
        if len(parents) < 1:
539
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
540
 
                'No parents for revision.')
541
 
        return parents[0]
542
 
 
543
477
SPEC_TYPES.append(RevisionSpec_before)
544
478
 
545
479
 
559
493
            branch.tags.lookup_tag(self.spec),
560
494
            revs)
561
495
 
562
 
    def _as_revision_id(self, context_branch):
563
 
        return context_branch.tags.lookup_tag(self.spec)
564
 
 
565
496
SPEC_TYPES.append(RevisionSpec_tag)
566
497
 
567
498
 
594
525
    Matches the first entry after a given date (either at midnight or
595
526
    at a specified time).
596
527
 
597
 
    One way to display all the changes since yesterday would be::
598
 
 
599
 
        bzr log -r date:yesterday..
600
 
 
601
 
    Examples::
602
 
 
 
528
    One way to display all the changes since yesterday would be:
 
529
        bzr log -r date:yesterday..-1
 
530
 
 
531
    examples:
603
532
      date:yesterday            -> select the first revision since yesterday
604
533
      date:2006-08-14,17:10:14  -> select the first revision after
605
534
                                   August 14th, 2006 at 5:10pm.
665
594
        finally:
666
595
            branch.unlock()
667
596
        if rev == len(revs):
668
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
597
            return RevisionInfo(branch, None)
669
598
        else:
670
599
            return RevisionInfo(branch, rev + 1)
671
600
 
687
616
    that your branch introduces, while excluding the changes that you
688
617
    have not merged from the remote branch.
689
618
 
690
 
    Examples::
691
 
 
 
619
    examples:
692
620
      ancestor:/path/to/branch
693
621
      $ bzr diff -r ancestor:../../mainline/branch
694
622
    """
698
626
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
699
627
        return self._find_revision_info(branch, self.spec)
700
628
 
701
 
    def _as_revision_id(self, context_branch):
702
 
        return self._find_revision_id(context_branch, self.spec)
703
 
 
704
629
    @staticmethod
705
630
    def _find_revision_info(branch, other_location):
706
 
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
707
 
                                                              other_location)
 
631
        from bzrlib.branch import Branch
 
632
 
 
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,
 
642
                                          revision_source)
708
643
        try:
709
 
            revno = branch.revision_id_to_revno(revision_id)
 
644
            revno = branch.revision_id_to_revno(rev_id)
710
645
        except errors.NoSuchRevision:
711
646
            revno = None
712
 
        return RevisionInfo(branch, revno, revision_id)
713
 
 
714
 
    @staticmethod
715
 
    def _find_revision_id(branch, other_location):
716
 
        from bzrlib.branch import Branch
717
 
 
718
 
        branch.lock_read()
719
 
        try:
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()
725
 
            try:
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)
731
 
            finally:
732
 
                other_branch.unlock()
733
 
            if rev_id == revision.NULL_REVISION:
734
 
                raise errors.NoCommonAncestor(revision_a, revision_b)
735
 
            return rev_id
736
 
        finally:
737
 
            branch.unlock()
 
647
        return RevisionInfo(branch, revno, rev_id)
738
648
 
739
649
 
740
650
SPEC_TYPES.append(RevisionSpec_ancestor)
747
657
 
748
658
    Supply the path to a branch to select its last revision.
749
659
 
750
 
    Examples::
751
 
 
 
660
    examples:
752
661
      branch:/path/to/branch
753
662
    """
754
663
    prefix = 'branch:'
766
675
        except errors.NoSuchRevision:
767
676
            revno = None
768
677
        return RevisionInfo(branch, revno, revision_b)
769
 
 
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)
778
 
        return last_revision
779
 
 
 
678
        
780
679
SPEC_TYPES.append(RevisionSpec_branch)
781
680
 
782
681
 
787
686
 
788
687
    Diffing against this shows all the changes that were made in this branch,
789
688
    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
 
689
    used by the bundle and merge directive comands.  If no submit branch
791
690
    is specified, the parent branch is used instead.
792
691
 
793
692
    The common ancestor is the last revision that existed in both
794
693
    branches. Usually this is the branch point, but it could also be
795
694
    a revision that was merged.
796
695
 
797
 
    Examples::
798
 
 
 
696
    examples:
799
697
      $ bzr diff -r submit:
800
698
    """
801
699
 
802
700
    prefix = 'submit:'
803
701
 
804
 
    def _get_submit_location(self, branch):
 
702
    def _match_on(self, branch, revs):
 
703
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
805
704
        submit_location = branch.get_submit_branch()
806
705
        location_type = 'submit branch'
807
706
        if submit_location is None:
810
709
        if submit_location is None:
811
710
            raise errors.NoSubmitBranch(branch)
812
711
        trace.note('Using %s %s', location_type, submit_location)
813
 
        return submit_location
814
 
 
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))
819
 
 
820
 
    def _as_revision_id(self, context_branch):
821
 
        return self._find_revision_id(context_branch,
822
 
            self._get_submit_location(context_branch))
 
712
        return self._find_revision_info(branch, submit_location)
823
713
 
824
714
 
825
715
SPEC_TYPES.append(RevisionSpec_submit)