~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Aaron Bentley
  • Date: 2007-02-06 14:52:16 UTC
  • mfrom: (2266 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2268.
  • Revision ID: abentley@panoramicfeedback.com-20070206145216-fcpi8o3ufvuzwbp9
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
21
21
 
22
22
from bzrlib import (
23
23
    errors,
24
 
    osutils,
25
24
    revision,
26
25
    symbol_versioning,
27
26
    trace,
94
93
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
95
94
            self.revno, self.rev_id, self.branch)
96
95
 
97
 
    @staticmethod
98
 
    def from_revision_id(branch, revision_id, revs):
99
 
        """Construct a RevisionInfo given just the id.
100
 
 
101
 
        Use this if you don't know or care what the revno is.
102
 
        """
103
 
        if revision_id == revision.NULL_REVISION:
104
 
            return RevisionInfo(branch, 0, revision_id)
105
 
        try:
106
 
            revno = revs.index(revision_id) + 1
107
 
        except ValueError:
108
 
            revno = None
109
 
        return RevisionInfo(branch, revno, revision_id)
110
 
 
111
96
 
112
97
# classes in this list should have a "prefix" attribute, against which
113
98
# string specs are matched
135
120
    """
136
121
 
137
122
    prefix = None
138
 
    wants_revision_history = True
139
123
 
140
124
    def __new__(cls, spec, _internal=False):
141
125
        if _internal:
161
145
 
162
146
        if spec is None:
163
147
            return RevisionSpec(None, _internal=True)
 
148
 
 
149
        assert isinstance(spec, basestring), \
 
150
            "You should only supply strings not %s" % (type(spec),)
 
151
 
164
152
        for spectype in SPEC_TYPES:
165
153
            if spec.startswith(spectype.prefix):
166
154
                trace.mutter('Returning RevisionSpec %s for %s',
200
188
 
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)
204
192
 
205
193
    def _match_on_and_check(self, branch, revs):
206
194
        info = self._match_on(branch, revs)
207
195
        if info:
208
196
            return info
209
 
        elif info == (None, None):
210
 
            # special case - nothing supplied
 
197
        elif info == (0, None):
 
198
            # special case - the empty tree
211
199
            return info
212
200
        elif self.prefix:
213
201
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
216
204
 
217
205
    def in_history(self, branch):
218
206
        if branch:
219
 
            if self.wants_revision_history:
220
 
                revs = branch.revision_history()
221
 
            else:
222
 
                revs = None
 
207
            revs = branch.revision_history()
223
208
        else:
224
209
            # this should never trigger.
225
210
            # TODO: make it a deprecated code path. RBC 20060928
235
220
    # will do what you expect.
236
221
    in_store = in_history
237
222
    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
 
 
 
223
        
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.
287
 
    Examples::
288
 
 
 
256
    examples:
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.
297
265
    """
298
266
    prefix = 'revno:'
299
 
    wants_revision_history = False
300
267
 
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)
305
 
 
306
 
    def _lookup(self, branch, revs_or_none):
307
270
        loc = self.spec.find(':')
308
271
        if loc == -1:
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)
341
 
            revs_or_none = None
 
304
            # Need to use a new revision history
 
305
            # because we are using a specific branch
 
306
            revs = branch.revision_history()
342
307
 
343
308
        if dotted:
344
309
            branch.lock_read()
345
310
            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]
 
311
                last_rev = branch.last_revision()
 
312
                merge_sorted_revisions = tsort.merge_sort(
 
313
                    branch.repository.get_revision_graph(last_rev),
 
314
                    last_rev,
 
315
                    generate_revno=True)
 
316
                def match(item):
 
317
                    return item[3] == match_revno
 
318
                revisions = filter(match, merge_sorted_revisions)
350
319
            finally:
351
320
                branch.unlock()
352
321
            if len(revisions) != 1:
353
 
                return branch, None, None
 
322
                return RevisionInfo(branch, None, None)
354
323
            else:
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])
358
327
        else:
359
 
            last_revno, last_revision_id = branch.last_revision_info()
360
328
            if revno < 0:
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):
364
332
                    revno = 1
365
333
                else:
366
 
                    revno = last_revno + revno + 1
 
334
                    revno = len(revs) + revno + 1
367
335
            try:
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
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
 
 
 
339
        return RevisionInfo(branch, revno, revision_id)
 
340
        
378
341
    def needs_branch(self):
379
342
        return self.spec.find(':') == -1
380
343
 
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.
401
 
    Examples::
402
 
 
 
364
    examples:
403
365
      revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
404
 
    """
405
 
 
 
366
    """    
406
367
    prefix = 'revid:'
407
368
 
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
411
 
        # representation.
412
 
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
413
 
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
414
 
 
415
 
    def _as_revision_id(self, context_branch):
416
 
        return osutils.safe_revision_id(self.spec, warn=False)
 
370
        try:
 
371
            revno = revs.index(self.spec) + 1
 
372
        except ValueError:
 
373
            revno = None
 
374
        return RevisionInfo(branch, revno, self.spec)
417
375
 
418
376
SPEC_TYPES.append(RevisionSpec_revid)
419
377
 
425
383
 
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.
428
 
    Examples::
429
 
 
 
386
    examples:
430
387
      last:1        -> return the last revision
431
388
      last:3        -> return the revision 2 before the end.
432
 
    """
 
389
    """    
433
390
 
434
391
    prefix = 'last:'
435
392
 
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)
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
394
        if self.spec == '':
444
 
            if not last_revno:
445
 
                raise errors.NoCommits(context_branch)
446
 
            return last_revno, last_revision_id
 
395
            if not revs:
 
396
                raise errors.NoCommits(branch)
 
397
            return RevisionInfo(branch, len(revs), revs[-1])
447
398
 
448
399
        try:
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)
452
403
 
453
404
        if offset <= 0:
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')
456
 
 
457
 
        revno = last_revno - offset + 1
 
407
        revno = len(revs) - offset + 1
458
408
        try:
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
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
 
411
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
412
        return RevisionInfo(branch, revno, revision_id)
469
413
 
470
414
SPEC_TYPES.append(RevisionSpec_last)
471
415
 
480
424
    This is mostly useful when inspecting revisions that are not in the
481
425
    revision history of a branch.
482
426
 
483
 
    Examples::
484
 
 
 
427
    examples:
485
428
      before:1913    -> Return the parent of revno 1913 (revno 1912)
486
429
      before:revid:aaaa@bbbb-1234567890  -> return the parent of revision
487
430
                                            aaaa@bbbb-1234567890
502
445
            rev = branch.repository.get_revision(r.rev_id)
503
446
            if not rev.parent_ids:
504
447
                revno = 0
505
 
                revision_id = revision.NULL_REVISION
 
448
                revision_id = None
506
449
            else:
507
450
                revision_id = rev.parent_ids[0]
508
451
                try:
518
461
                                                 branch)
519
462
        return RevisionInfo(branch, revno, revision_id)
520
463
 
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
464
SPEC_TYPES.append(RevisionSpec_before)
544
465
 
545
466
 
546
467
class RevisionSpec_tag(RevisionSpec):
547
 
    """Select a revision identified by tag name"""
548
 
 
549
 
    help_txt = """Selects a revision identified by a tag name.
550
 
 
551
 
    Tags are stored in the branch and created by the 'tag' command.
552
 
    """
 
468
    """To be implemented."""
 
469
 
 
470
    help_txt = """To be implemented."""
553
471
 
554
472
    prefix = 'tag:'
555
473
 
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),
560
 
            revs)
561
 
 
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')
564
478
 
565
479
SPEC_TYPES.append(RevisionSpec_tag)
566
480
 
594
508
    Matches the first entry after a given date (either at midnight or
595
509
    at a specified time).
596
510
 
597
 
    One way to display all the changes since yesterday would be::
598
 
 
599
 
        bzr log -r date:yesterday..
600
 
 
601
 
    Examples::
602
 
 
 
511
    One way to display all the changes since yesterday would be:
 
512
        bzr log -r date:yesterday..-1
 
513
 
 
514
    examples:
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.
665
577
        finally:
666
578
            branch.unlock()
667
579
        if rev == len(revs):
668
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
580
            return RevisionInfo(branch, None)
669
581
        else:
670
582
            return RevisionInfo(branch, rev + 1)
671
583
 
687
599
    that your branch introduces, while excluding the changes that you
688
600
    have not merged from the remote branch.
689
601
 
690
 
    Examples::
691
 
 
 
602
    examples:
692
603
      ancestor:/path/to/branch
693
604
      $ bzr diff -r ancestor:../../mainline/branch
694
605
    """
695
606
    prefix = 'ancestor:'
696
607
 
697
608
    def _match_on(self, branch, revs):
 
609
        from bzrlib.branch import Branch
 
610
 
698
611
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
699
 
        return self._find_revision_info(branch, self.spec)
700
 
 
701
 
    def _as_revision_id(self, context_branch):
702
 
        return self._find_revision_id(context_branch, self.spec)
703
 
 
704
 
    @staticmethod
705
 
    def _find_revision_info(branch, other_location):
706
 
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
707
 
                                                              other_location)
 
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,
 
621
                                          revision_source)
708
622
        try:
709
 
            revno = branch.revision_id_to_revno(revision_id)
 
623
            revno = branch.revision_id_to_revno(rev_id)
710
624
        except errors.NoSuchRevision:
711
625
            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()
738
 
 
739
 
 
 
626
        return RevisionInfo(branch, revno, rev_id)
 
627
        
740
628
SPEC_TYPES.append(RevisionSpec_ancestor)
741
629
 
742
630
 
747
635
 
748
636
    Supply the path to a branch to select its last revision.
749
637
 
750
 
    Examples::
751
 
 
 
638
    examples:
752
639
      branch:/path/to/branch
753
640
    """
754
641
    prefix = 'branch:'
766
653
        except errors.NoSuchRevision:
767
654
            revno = None
768
655
        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
 
 
 
656
        
780
657
SPEC_TYPES.append(RevisionSpec_branch)
781
 
 
782
 
 
783
 
class RevisionSpec_submit(RevisionSpec_ancestor):
784
 
    """Selects a common ancestor with a submit branch."""
785
 
 
786
 
    help_txt = """Selects a common ancestor with the submit branch.
787
 
 
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.
792
 
 
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.
796
 
 
797
 
    Examples::
798
 
 
799
 
      $ bzr diff -r submit:
800
 
    """
801
 
 
802
 
    prefix = 'submit:'
803
 
 
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
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))
823
 
 
824
 
 
825
 
SPEC_TYPES.append(RevisionSpec_submit)