~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Robert Collins
  • Date: 2007-07-04 08:08:13 UTC
  • mfrom: (2572 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2587.
  • Revision ID: robertc@robertcollins.net-20070704080813-wzebx0r88fvwj5rq
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 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,
24
25
    revision,
25
26
    symbol_versioning,
26
27
    trace,
 
28
    tsort,
27
29
    )
28
30
 
29
31
 
31
33
 
32
34
 
33
35
class RevisionInfo(object):
34
 
    """The results of applying a revision specification to a branch.
 
36
    """The results of applying a revision specification to a branch."""
 
37
 
 
38
    help_txt = """The results of applying a revision specification to a branch.
35
39
 
36
40
    An instance has two useful attributes: revno, and rev_id.
37
41
 
90
94
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
91
95
            self.revno, self.rev_id, self.branch)
92
96
 
 
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
        try:
 
104
            revno = revs.index(revision_id) + 1
 
105
        except ValueError:
 
106
            revno = None
 
107
        return RevisionInfo(branch, revno, revision_id)
 
108
 
93
109
 
94
110
# classes in this list should have a "prefix" attribute, against which
95
111
# string specs are matched
98
114
 
99
115
 
100
116
class RevisionSpec(object):
101
 
    """A parsed revision specification.
 
117
    """A parsed revision specification."""
 
118
 
 
119
    help_txt = """A parsed revision specification.
102
120
 
103
121
    A revision specification can be an integer, in which case it is
104
122
    assumed to be a revno (though this will translate negative values
152
170
        else:
153
171
            # RevisionSpec_revno is special cased, because it is the only
154
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()
155
175
            global _revno_regex
156
176
            if _revno_regex is None:
157
 
                _revno_regex = re.compile(r'-?\d+(:.*)?$')
 
177
                _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
158
178
            if _revno_regex.match(spec) is not None:
159
179
                return RevisionSpec_revno(spec, _internal=True)
160
180
 
199
219
        if branch:
200
220
            revs = branch.revision_history()
201
221
        else:
 
222
            # this should never trigger.
 
223
            # TODO: make it a deprecated code path. RBC 20060928
202
224
            revs = None
203
225
        return self._match_on_and_check(branch, revs)
204
226
 
235
257
# private API
236
258
 
237
259
class RevisionSpec_revno(RevisionSpec):
 
260
    """Selects a revision using a number."""
 
261
 
 
262
    help_txt = """Selects a revision using a number.
 
263
 
 
264
    Use an integer to specify a revision in the history of the 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.
 
269
    examples:
 
270
      revno:1                   -> return the first revision
 
271
      revno:3:/path/to/branch   -> return the 3rd revision of
 
272
                                   the branch '/path/to/branch'
 
273
      revno:-1                  -> The last revision in a branch.
 
274
      -2:http://other/branch    -> The second to last revision in the
 
275
                                   remote branch.
 
276
      -1000000                  -> Most likely the first revision, unless
 
277
                                   your history is very long.
 
278
    """
238
279
    prefix = 'revno:'
239
280
 
240
281
    def _match_on(self, branch, revs):
255
296
        else:
256
297
            try:
257
298
                revno = int(revno_spec)
258
 
            except ValueError, e:
259
 
                raise errors.InvalidRevisionSpec(self.user_spec,
260
 
                                                 branch, e)
 
299
                dotted = False
 
300
            except ValueError:
 
301
                # dotted decimal. This arguably should not be here
 
302
                # but the from_string method is a little primitive 
 
303
                # right now - RBC 20060928
 
304
                try:
 
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)
 
308
 
 
309
                dotted = True
261
310
 
262
311
        if branch_spec:
 
312
            # the user has override the branch to look in.
 
313
            # we need to refresh the revision_history map and
 
314
            # the branch object.
263
315
            from bzrlib.branch import Branch
264
316
            branch = Branch.open(branch_spec)
265
317
            # Need to use a new revision history
266
318
            # because we are using a specific branch
267
319
            revs = branch.revision_history()
268
320
 
269
 
        if revno < 0:
270
 
            if (-revno) >= len(revs):
271
 
                revno = 1
 
321
        if dotted:
 
322
            branch.lock_read()
 
323
            try:
 
324
                revision_id_to_revno = branch.get_revision_id_to_revno_map()
 
325
                revisions = [revision_id for revision_id, revno
 
326
                             in revision_id_to_revno.iteritems()
 
327
                             if revno == match_revno]
 
328
            finally:
 
329
                branch.unlock()
 
330
            if len(revisions) != 1:
 
331
                return RevisionInfo(branch, None, None)
272
332
            else:
273
 
                revno = len(revs) + revno + 1
274
 
        try:
275
 
            revision_id = branch.get_rev_id(revno, revs)
276
 
        except errors.NoSuchRevision:
277
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
333
                # there is no traditional 'revno' for dotted-decimal revnos.
 
334
                # so for  API compatability we return None.
 
335
                return RevisionInfo(branch, None, revisions[0])
 
336
        else:
 
337
            if revno < 0:
 
338
                # if get_rev_id supported negative revnos, there would not be a
 
339
                # need for this special case.
 
340
                if (-revno) >= len(revs):
 
341
                    revno = 1
 
342
                else:
 
343
                    revno = len(revs) + revno + 1
 
344
            try:
 
345
                revision_id = branch.get_rev_id(revno, revs)
 
346
            except errors.NoSuchRevision:
 
347
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
278
348
        return RevisionInfo(branch, revno, revision_id)
279
349
        
280
350
    def needs_branch(self):
293
363
 
294
364
 
295
365
class RevisionSpec_revid(RevisionSpec):
 
366
    """Selects a revision using the revision id."""
 
367
 
 
368
    help_txt = """Selects a revision using the revision id.
 
369
 
 
370
    Supply a specific revision id, that can be used to specify any
 
371
    revision id in the ancestry of the branch. 
 
372
    Including merges, and pending merges.
 
373
    examples:
 
374
      revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
 
375
    """    
296
376
    prefix = 'revid:'
297
377
 
298
378
    def _match_on(self, branch, revs):
299
 
        try:
300
 
            revno = revs.index(self.spec) + 1
301
 
        except ValueError:
302
 
            revno = None
303
 
        return RevisionInfo(branch, revno, self.spec)
 
379
        # self.spec comes straight from parsing the command line arguments,
 
380
        # so we expect it to be a Unicode string. Switch it to the internal
 
381
        # representation.
 
382
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
 
383
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
304
384
 
305
385
SPEC_TYPES.append(RevisionSpec_revid)
306
386
 
307
387
 
308
388
class RevisionSpec_last(RevisionSpec):
 
389
    """Selects the nth revision from the end."""
 
390
 
 
391
    help_txt = """Selects the nth revision from the end.
 
392
 
 
393
    Supply a positive number to get the nth revision from the end.
 
394
    This is the same as supplying negative numbers to the 'revno:' spec.
 
395
    examples:
 
396
      last:1        -> return the last revision
 
397
      last:3        -> return the revision 2 before the end.
 
398
    """    
309
399
 
310
400
    prefix = 'last:'
311
401
 
334
424
 
335
425
 
336
426
class RevisionSpec_before(RevisionSpec):
 
427
    """Selects the parent of the revision specified."""
 
428
 
 
429
    help_txt = """Selects the parent of the revision specified.
 
430
 
 
431
    Supply any revision spec to return the parent of that revision.
 
432
    It is an error to request the parent of the null revision (before:0).
 
433
    This is mostly useful when inspecting revisions that are not in the
 
434
    revision history of a branch.
 
435
 
 
436
    examples:
 
437
      before:1913    -> Return the parent of revno 1913 (revno 1912)
 
438
      before:revid:aaaa@bbbb-1234567890  -> return the parent of revision
 
439
                                            aaaa@bbbb-1234567890
 
440
      bzr diff -r before:revid:aaaa..revid:aaaa
 
441
            -> Find the changes between revision 'aaaa' and its parent.
 
442
               (what changes did 'aaaa' introduce)
 
443
    """
337
444
 
338
445
    prefix = 'before:'
339
446
    
367
474
 
368
475
 
369
476
class RevisionSpec_tag(RevisionSpec):
 
477
    """Select a revision identified by tag name"""
 
478
 
 
479
    help_txt = """Selects a revision identified by a tag name.
 
480
 
 
481
    Tags are stored in the branch and created by the 'tag' command.
 
482
    """
 
483
 
370
484
    prefix = 'tag:'
371
485
 
372
486
    def _match_on(self, branch, revs):
373
 
        raise errors.InvalidRevisionSpec(self.user_spec, branch,
374
 
                                         'tag: namespace registered,'
375
 
                                         ' but not implemented')
 
487
        # Can raise tags not supported, NoSuchTag, etc
 
488
        return RevisionInfo.from_revision_id(branch,
 
489
            branch.tags.lookup_tag(self.spec),
 
490
            revs)
376
491
 
377
492
SPEC_TYPES.append(RevisionSpec_tag)
378
493
 
397
512
 
398
513
 
399
514
class RevisionSpec_date(RevisionSpec):
 
515
    """Selects a revision on the basis of a datestamp."""
 
516
 
 
517
    help_txt = """Selects a revision on the basis of a datestamp.
 
518
 
 
519
    Supply a datestamp to select the first revision that matches the date.
 
520
    Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
521
    Matches the first entry after a given date (either at midnight or
 
522
    at a specified time).
 
523
 
 
524
    One way to display all the changes since yesterday would be:
 
525
        bzr log -r date:yesterday..-1
 
526
 
 
527
    examples:
 
528
      date:yesterday            -> select the first revision since yesterday
 
529
      date:2006-08-14,17:10:14  -> select the first revision after
 
530
                                   August 14th, 2006 at 5:10pm.
 
531
    """    
400
532
    prefix = 'date:'
401
533
    _date_re = re.compile(
402
534
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
405
537
        )
406
538
 
407
539
    def _match_on(self, branch, revs):
408
 
        """
409
 
        Spec for date revisions:
 
540
        """Spec for date revisions:
410
541
          date:value
411
542
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
412
543
          matches the first entry after a given date (either at midnight or
413
544
          at a specified time).
414
 
 
415
 
          So the proper way of saying 'give me all entries for today' is:
416
 
              -r date:yesterday..date:today
417
545
        """
 
546
        #  XXX: This doesn't actually work
 
547
        #  So the proper way of saying 'give me all entries for today' is:
 
548
        #      -r date:yesterday..date:today
418
549
        today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
419
550
        if self.spec.lower() == 'yesterday':
420
551
            dt = today - datetime.timedelta(days=1)
467
598
 
468
599
 
469
600
class RevisionSpec_ancestor(RevisionSpec):
 
601
    """Selects a common ancestor with a second branch."""
 
602
 
 
603
    help_txt = """Selects a common ancestor with a second branch.
 
604
 
 
605
    Supply the path to a branch to select the common ancestor.
 
606
 
 
607
    The common ancestor is the last revision that existed in both
 
608
    branches. Usually this is the branch point, but it could also be
 
609
    a revision that was merged.
 
610
 
 
611
    This is frequently used with 'diff' to return all of the changes
 
612
    that your branch introduces, while excluding the changes that you
 
613
    have not merged from the remote branch.
 
614
 
 
615
    examples:
 
616
      ancestor:/path/to/branch
 
617
      $ bzr diff -r ancestor:../../mainline/branch
 
618
    """
470
619
    prefix = 'ancestor:'
471
620
 
472
621
    def _match_on(self, branch, revs):
 
622
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
 
623
        return self._find_revision_info(branch, self.spec)
 
624
 
 
625
    @staticmethod
 
626
    def _find_revision_info(branch, other_location):
473
627
        from bzrlib.branch import Branch
474
628
 
475
 
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
476
 
        other_branch = Branch.open(self.spec)
 
629
        other_branch = Branch.open(other_location)
477
630
        revision_a = branch.last_revision()
478
631
        revision_b = other_branch.last_revision()
479
632
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
481
634
                raise errors.NoCommits(b)
482
635
        revision_source = revision.MultipleRevisionSources(
483
636
                branch.repository, other_branch.repository)
484
 
        rev_id = revision.common_ancestor(revision_a, revision_b,
485
 
                                          revision_source)
 
637
        graph = branch.repository.get_graph(other_branch.repository)
 
638
        revision_a = revision.ensure_null(revision_a)
 
639
        revision_b = revision.ensure_null(revision_b)
 
640
        if revision.NULL_REVISION in (revision_a, revision_b):
 
641
            rev_id = revision.NULL_REVISION
 
642
        else:
 
643
            rev_id = graph.find_unique_lca(revision_a, revision_b)
 
644
            if rev_id == revision.NULL_REVISION:
 
645
                raise errors.NoCommonAncestor(revision_a, revision_b)
486
646
        try:
487
647
            revno = branch.revision_id_to_revno(rev_id)
488
648
        except errors.NoSuchRevision:
489
649
            revno = None
490
650
        return RevisionInfo(branch, revno, rev_id)
491
 
        
 
651
 
 
652
 
492
653
SPEC_TYPES.append(RevisionSpec_ancestor)
493
654
 
494
655
 
495
656
class RevisionSpec_branch(RevisionSpec):
496
 
    """A branch: revision specifier.
497
 
 
498
 
    This takes the path to a branch and returns its tip revision id.
 
657
    """Selects the last revision of a specified branch."""
 
658
 
 
659
    help_txt = """Selects the last revision of a specified branch.
 
660
 
 
661
    Supply the path to a branch to select its last revision.
 
662
 
 
663
    examples:
 
664
      branch:/path/to/branch
499
665
    """
500
666
    prefix = 'branch:'
501
667
 
514
680
        return RevisionInfo(branch, revno, revision_b)
515
681
        
516
682
SPEC_TYPES.append(RevisionSpec_branch)
 
683
 
 
684
 
 
685
class RevisionSpec_submit(RevisionSpec_ancestor):
 
686
    """Selects a common ancestor with a submit branch."""
 
687
 
 
688
    help_txt = """Selects a common ancestor with the submit branch.
 
689
 
 
690
    Diffing against this shows all the changes that were made in this branch,
 
691
    and is a good predictor of what merge will do.  The submit branch is
 
692
    used by the bundle and merge directive comands.  If no submit branch
 
693
    is specified, the parent branch is used instead.
 
694
 
 
695
    The common ancestor is the last revision that existed in both
 
696
    branches. Usually this is the branch point, but it could also be
 
697
    a revision that was merged.
 
698
 
 
699
    examples:
 
700
      $ bzr diff -r submit:
 
701
    """
 
702
 
 
703
    prefix = 'submit:'
 
704
 
 
705
    def _match_on(self, branch, revs):
 
706
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
 
707
        submit_location = branch.get_submit_branch()
 
708
        location_type = 'submit branch'
 
709
        if submit_location is None:
 
710
            submit_location = branch.get_parent()
 
711
            location_type = 'parent branch'
 
712
        if submit_location is None:
 
713
            raise errors.NoSubmitBranch(branch)
 
714
        trace.note('Using %s %s', location_type, submit_location)
 
715
        return self._find_revision_info(branch, submit_location)
 
716
 
 
717
 
 
718
SPEC_TYPES.append(RevisionSpec_submit)