~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

Merge bzr.dev and tree-file-ids-as-tuples.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
 
18
 
import re
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
from __future__ import absolute_import
 
18
 
19
19
 
20
20
from bzrlib.lazy_import import lazy_import
21
21
lazy_import(globals(), """
22
22
import bisect
23
23
import datetime
24
 
""")
25
24
 
26
25
from bzrlib import (
27
 
    errors,
 
26
    branch as _mod_branch,
28
27
    osutils,
29
28
    revision,
30
29
    symbol_versioning,
 
30
    workingtree,
 
31
    )
 
32
from bzrlib.i18n import gettext
 
33
""")
 
34
 
 
35
from bzrlib import (
 
36
    errors,
 
37
    lazy_regex,
 
38
    registry,
31
39
    trace,
32
40
    )
33
41
 
34
42
 
35
 
_marker = []
36
 
 
37
 
 
38
43
class RevisionInfo(object):
39
44
    """The results of applying a revision specification to a branch."""
40
45
 
52
57
    or treat the result as a tuple.
53
58
    """
54
59
 
55
 
    def __init__(self, branch, revno, rev_id=_marker):
 
60
    def __init__(self, branch, revno=None, rev_id=None):
56
61
        self.branch = branch
57
 
        self.revno = revno
58
 
        if rev_id is _marker:
 
62
        self._has_revno = (revno is not None)
 
63
        self._revno = revno
 
64
        self.rev_id = rev_id
 
65
        if self.rev_id is None and self._revno is not None:
59
66
            # allow caller to be lazy
60
 
            if self.revno is None:
61
 
                self.rev_id = None
62
 
            else:
63
 
                self.rev_id = branch.get_rev_id(self.revno)
64
 
        else:
65
 
            self.rev_id = rev_id
 
67
            self.rev_id = branch.get_rev_id(self._revno)
 
68
 
 
69
    @property
 
70
    def revno(self):
 
71
        if not self._has_revno and self.rev_id is not None:
 
72
            try:
 
73
                self._revno = self.branch.revision_id_to_revno(self.rev_id)
 
74
            except errors.NoSuchRevision:
 
75
                self._revno = None
 
76
            self._has_revno = True
 
77
        return self._revno
66
78
 
67
79
    def __nonzero__(self):
68
 
        # first the easy ones...
69
80
        if self.rev_id is None:
70
81
            return False
71
 
        if self.revno is not None:
72
 
            return True
73
82
        # TODO: otherwise, it should depend on how I was built -
74
83
        # if it's in_history(branch), then check revision_history(),
75
84
        # if it's in_store(branch), do the check below
98
107
            self.revno, self.rev_id, self.branch)
99
108
 
100
109
    @staticmethod
101
 
    def from_revision_id(branch, revision_id, revs):
 
110
    def from_revision_id(branch, revision_id, revs=symbol_versioning.DEPRECATED_PARAMETER):
102
111
        """Construct a RevisionInfo given just the id.
103
112
 
104
113
        Use this if you don't know or care what the revno is.
105
114
        """
106
 
        if revision_id == revision.NULL_REVISION:
107
 
            return RevisionInfo(branch, 0, revision_id)
108
 
        try:
109
 
            revno = revs.index(revision_id) + 1
110
 
        except ValueError:
111
 
            revno = None
112
 
        return RevisionInfo(branch, revno, revision_id)
113
 
 
114
 
 
115
 
# classes in this list should have a "prefix" attribute, against which
116
 
# string specs are matched
117
 
SPEC_TYPES = []
118
 
_revno_regex = None
 
115
        if symbol_versioning.deprecated_passed(revs):
 
116
            symbol_versioning.warn(
 
117
                'RevisionInfo.from_revision_id(revs) was deprecated in 2.5.',
 
118
                DeprecationWarning,
 
119
                stacklevel=2)
 
120
        return RevisionInfo(branch, revno=None, rev_id=revision_id)
119
121
 
120
122
 
121
123
class RevisionSpec(object):
123
125
 
124
126
    help_txt = """A parsed revision specification.
125
127
 
126
 
    A revision specification can be an integer, in which case it is
127
 
    assumed to be a revno (though this will translate negative values
128
 
    into positive ones); or it can be a string, in which case it is
129
 
    parsed for something like 'date:' or 'revid:' etc.
 
128
    A revision specification is a string, which may be unambiguous about
 
129
    what it represents by giving a prefix like 'date:' or 'revid:' etc,
 
130
    or it may have no prefix, in which case it's tried against several
 
131
    specifier types in sequence to determine what the user meant.
130
132
 
131
133
    Revision specs are an UI element, and they have been moved out
132
134
    of the branch class to leave "back-end" classes unaware of such
138
140
    """
139
141
 
140
142
    prefix = None
141
 
    wants_revision_history = True
 
143
    # wants_revision_history has been deprecated in 2.5.
 
144
    wants_revision_history = False
 
145
    dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
 
146
    """Exceptions that RevisionSpec_dwim._match_on will catch.
 
147
 
 
148
    If the revspec is part of ``dwim_revspecs``, it may be tried with an
 
149
    invalid revspec and raises some exception. The exceptions mentioned here
 
150
    will not be reported to the user but simply ignored without stopping the
 
151
    dwim processing.
 
152
    """
142
153
 
143
154
    @staticmethod
144
155
    def from_string(spec):
153
164
 
154
165
        if spec is None:
155
166
            return RevisionSpec(None, _internal=True)
156
 
        for spectype in SPEC_TYPES:
157
 
            if spec.startswith(spectype.prefix):
158
 
                trace.mutter('Returning RevisionSpec %s for %s',
159
 
                             spectype.__name__, spec)
160
 
                return spectype(spec, _internal=True)
 
167
        match = revspec_registry.get_prefix(spec)
 
168
        if match is not None:
 
169
            spectype, specsuffix = match
 
170
            trace.mutter('Returning RevisionSpec %s for %s',
 
171
                         spectype.__name__, spec)
 
172
            return spectype(spec, _internal=True)
161
173
        else:
162
 
            # RevisionSpec_revno is special cased, because it is the only
163
 
            # one that directly handles plain integers
164
 
            # TODO: This should not be special cased rather it should be
165
 
            # a method invocation on spectype.canparse()
166
 
            global _revno_regex
167
 
            if _revno_regex is None:
168
 
                _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
169
 
            if _revno_regex.match(spec) is not None:
170
 
                return RevisionSpec_revno(spec, _internal=True)
171
 
 
172
 
            raise errors.NoSuchRevisionSpec(spec)
 
174
            # Otherwise treat it as a DWIM, build the RevisionSpec object and
 
175
            # wait for _match_on to be called.
 
176
            return RevisionSpec_dwim(spec, _internal=True)
173
177
 
174
178
    def __init__(self, spec, _internal=False):
175
179
        """Create a RevisionSpec referring to the Null revision.
179
183
            called directly. Only from RevisionSpec.from_string()
180
184
        """
181
185
        if not _internal:
182
 
            # XXX: Update this after 0.10 is released
183
186
            symbol_versioning.warn('Creating a RevisionSpec directly has'
184
187
                                   ' been deprecated in version 0.11. Use'
185
188
                                   ' RevisionSpec.from_string()'
209
212
    def in_history(self, branch):
210
213
        if branch:
211
214
            if self.wants_revision_history:
212
 
                revs = branch.revision_history()
 
215
                symbol_versioning.warn(
 
216
                    "RevisionSpec.wants_revision_history was "
 
217
                    "deprecated in 2.5 (%s)." % self.__class__.__name__,
 
218
                    DeprecationWarning)
 
219
                branch.lock_read()
 
220
                try:
 
221
                    graph = branch.repository.get_graph()
 
222
                    revs = list(graph.iter_lefthand_ancestry(
 
223
                        branch.last_revision(), [revision.NULL_REVISION]))
 
224
                finally:
 
225
                    branch.unlock()
 
226
                revs.reverse()
213
227
            else:
214
228
                revs = None
215
229
        else:
267
281
        # this is mostly for helping with testing
268
282
        return '<%s %s>' % (self.__class__.__name__,
269
283
                              self.user_spec)
270
 
    
 
284
 
271
285
    def needs_branch(self):
272
286
        """Whether this revision spec needs a branch.
273
287
 
277
291
 
278
292
    def get_branch(self):
279
293
        """When the revision specifier contains a branch location, return it.
280
 
        
 
294
 
281
295
        Otherwise, return None.
282
296
        """
283
297
        return None
285
299
 
286
300
# private API
287
301
 
 
302
class RevisionSpec_dwim(RevisionSpec):
 
303
    """Provides a DWIMish revision specifier lookup.
 
304
 
 
305
    Note that this does not go in the revspec_registry because by definition
 
306
    there is no prefix to identify it.  It's solely called from
 
307
    RevisionSpec.from_string() because the DWIMification happen when _match_on
 
308
    is called so the string describing the revision is kept here until needed.
 
309
    """
 
310
 
 
311
    help_txt = None
 
312
 
 
313
    _revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
 
314
 
 
315
    # The revspecs to try
 
316
    _possible_revspecs = []
 
317
 
 
318
    def _try_spectype(self, rstype, branch):
 
319
        rs = rstype(self.spec, _internal=True)
 
320
        # Hit in_history to find out if it exists, or we need to try the
 
321
        # next type.
 
322
        return rs.in_history(branch)
 
323
 
 
324
    def _match_on(self, branch, revs):
 
325
        """Run the lookup and see what we can get."""
 
326
 
 
327
        # First, see if it's a revno
 
328
        if self._revno_regex.match(self.spec) is not None:
 
329
            try:
 
330
                return self._try_spectype(RevisionSpec_revno, branch)
 
331
            except RevisionSpec_revno.dwim_catchable_exceptions:
 
332
                pass
 
333
 
 
334
        # Next see what has been registered
 
335
        for objgetter in self._possible_revspecs:
 
336
            rs_class = objgetter.get_obj()
 
337
            try:
 
338
                return self._try_spectype(rs_class, branch)
 
339
            except rs_class.dwim_catchable_exceptions:
 
340
                pass
 
341
 
 
342
        # Try the old (deprecated) dwim list:
 
343
        for rs_class in dwim_revspecs:
 
344
            try:
 
345
                return self._try_spectype(rs_class, branch)
 
346
            except rs_class.dwim_catchable_exceptions:
 
347
                pass
 
348
 
 
349
        # Well, I dunno what it is. Note that we don't try to keep track of the
 
350
        # first of last exception raised during the DWIM tries as none seems
 
351
        # really relevant.
 
352
        raise errors.InvalidRevisionSpec(self.spec, branch)
 
353
 
 
354
    @classmethod
 
355
    def append_possible_revspec(cls, revspec):
 
356
        """Append a possible DWIM revspec.
 
357
 
 
358
        :param revspec: Revision spec to try.
 
359
        """
 
360
        cls._possible_revspecs.append(registry._ObjectGetter(revspec))
 
361
 
 
362
    @classmethod
 
363
    def append_possible_lazy_revspec(cls, module_name, member_name):
 
364
        """Append a possible lazily loaded DWIM revspec.
 
365
 
 
366
        :param module_name: Name of the module with the revspec
 
367
        :param member_name: Name of the revspec within the module
 
368
        """
 
369
        cls._possible_revspecs.append(
 
370
            registry._LazyObjectGetter(module_name, member_name))
 
371
 
 
372
 
288
373
class RevisionSpec_revno(RevisionSpec):
289
374
    """Selects a revision using a number."""
290
375
 
291
376
    help_txt = """Selects a revision using a number.
292
377
 
293
378
    Use an integer to specify a revision in the history of the branch.
294
 
    Optionally a branch can be specified. The 'revno:' prefix is optional.
295
 
    A negative number will count from the end of the branch (-1 is the
296
 
    last revision, -2 the previous one). If the negative number is larger
297
 
    than the branch's history, the first revision is returned.
 
379
    Optionally a branch can be specified.  A negative number will count
 
380
    from the end of the branch (-1 is the last revision, -2 the previous
 
381
    one). If the negative number is larger than the branch's history, the
 
382
    first revision is returned.
298
383
    Examples::
299
384
 
300
385
      revno:1                   -> return the first revision of this branch
307
392
                                   your history is very long.
308
393
    """
309
394
    prefix = 'revno:'
310
 
    wants_revision_history = False
311
395
 
312
396
    def _match_on(self, branch, revs):
313
397
        """Lookup a revision by revision number"""
314
 
        branch, revno, revision_id = self._lookup(branch, revs)
 
398
        branch, revno, revision_id = self._lookup(branch)
315
399
        return RevisionInfo(branch, revno, revision_id)
316
400
 
317
 
    def _lookup(self, branch, revs_or_none):
 
401
    def _lookup(self, branch):
318
402
        loc = self.spec.find(':')
319
403
        if loc == -1:
320
404
            revno_spec = self.spec
334
418
                dotted = False
335
419
            except ValueError:
336
420
                # dotted decimal. This arguably should not be here
337
 
                # but the from_string method is a little primitive 
 
421
                # but the from_string method is a little primitive
338
422
                # right now - RBC 20060928
339
423
                try:
340
424
                    match_revno = tuple((int(number) for number in revno_spec.split('.')))
344
428
                dotted = True
345
429
 
346
430
        if branch_spec:
347
 
            # the user has override the branch to look in.
348
 
            # we need to refresh the revision_history map and
349
 
            # the branch object.
350
 
            from bzrlib.branch import Branch
351
 
            branch = Branch.open(branch_spec)
352
 
            revs_or_none = None
 
431
            # the user has overriden the branch to look in.
 
432
            branch = _mod_branch.Branch.open(branch_spec)
353
433
 
354
434
        if dotted:
355
 
            branch.lock_read()
356
435
            try:
357
 
                revision_id_to_revno = branch.get_revision_id_to_revno_map()
358
 
                revisions = [revision_id for revision_id, revno
359
 
                             in revision_id_to_revno.iteritems()
360
 
                             if revno == match_revno]
361
 
            finally:
362
 
                branch.unlock()
363
 
            if len(revisions) != 1:
364
 
                return branch, None, None
 
436
                revision_id = branch.dotted_revno_to_revision_id(match_revno,
 
437
                    _cache_reverse=True)
 
438
            except errors.NoSuchRevision:
 
439
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
365
440
            else:
366
441
                # there is no traditional 'revno' for dotted-decimal revnos.
367
 
                # so for  API compatability we return None.
368
 
                return branch, None, revisions[0]
 
442
                # so for API compatibility we return None.
 
443
                return branch, None, revision_id
369
444
        else:
370
445
            last_revno, last_revision_id = branch.last_revision_info()
371
446
            if revno < 0:
376
451
                else:
377
452
                    revno = last_revno + revno + 1
378
453
            try:
379
 
                revision_id = branch.get_rev_id(revno, revs_or_none)
 
454
                revision_id = branch.get_rev_id(revno)
380
455
            except errors.NoSuchRevision:
381
456
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
382
457
        return branch, revno, revision_id
383
458
 
384
459
    def _as_revision_id(self, context_branch):
385
460
        # We would have the revno here, but we don't really care
386
 
        branch, revno, revision_id = self._lookup(context_branch, None)
 
461
        branch, revno, revision_id = self._lookup(context_branch)
387
462
        return revision_id
388
463
 
389
464
    def needs_branch(self):
395
470
        else:
396
471
            return self.spec[self.spec.find(':')+1:]
397
472
 
398
 
# Old compatibility 
 
473
# Old compatibility
399
474
RevisionSpec_int = RevisionSpec_revno
400
475
 
401
 
SPEC_TYPES.append(RevisionSpec_revno)
402
 
 
403
 
 
404
 
class RevisionSpec_revid(RevisionSpec):
 
476
 
 
477
class RevisionIDSpec(RevisionSpec):
 
478
 
 
479
    def _match_on(self, branch, revs):
 
480
        revision_id = self.as_revision_id(branch)
 
481
        return RevisionInfo.from_revision_id(branch, revision_id)
 
482
 
 
483
 
 
484
class RevisionSpec_revid(RevisionIDSpec):
405
485
    """Selects a revision using the revision id."""
406
486
 
407
487
    help_txt = """Selects a revision using the revision id.
408
488
 
409
489
    Supply a specific revision id, that can be used to specify any
410
 
    revision id in the ancestry of the branch. 
 
490
    revision id in the ancestry of the branch.
411
491
    Including merges, and pending merges.
412
492
    Examples::
413
493
 
416
496
 
417
497
    prefix = 'revid:'
418
498
 
419
 
    def _match_on(self, branch, revs):
 
499
    def _as_revision_id(self, context_branch):
420
500
        # self.spec comes straight from parsing the command line arguments,
421
501
        # so we expect it to be a Unicode string. Switch it to the internal
422
502
        # representation.
423
 
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
424
 
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
425
 
 
426
 
    def _as_revision_id(self, context_branch):
427
503
        return osutils.safe_revision_id(self.spec, warn=False)
428
504
 
429
 
SPEC_TYPES.append(RevisionSpec_revid)
430
505
 
431
506
 
432
507
class RevisionSpec_last(RevisionSpec):
445
520
    prefix = 'last:'
446
521
 
447
522
    def _match_on(self, branch, revs):
448
 
        revno, revision_id = self._revno_and_revision_id(branch, revs)
 
523
        revno, revision_id = self._revno_and_revision_id(branch)
449
524
        return RevisionInfo(branch, revno, revision_id)
450
525
 
451
 
    def _revno_and_revision_id(self, context_branch, revs_or_none):
 
526
    def _revno_and_revision_id(self, context_branch):
452
527
        last_revno, last_revision_id = context_branch.last_revision_info()
453
528
 
454
529
        if self.spec == '':
467
542
 
468
543
        revno = last_revno - offset + 1
469
544
        try:
470
 
            revision_id = context_branch.get_rev_id(revno, revs_or_none)
 
545
            revision_id = context_branch.get_rev_id(revno)
471
546
        except errors.NoSuchRevision:
472
547
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
473
548
        return revno, revision_id
475
550
    def _as_revision_id(self, context_branch):
476
551
        # We compute the revno as part of the process, but we don't really care
477
552
        # about it.
478
 
        revno, revision_id = self._revno_and_revision_id(context_branch, None)
 
553
        revno, revision_id = self._revno_and_revision_id(context_branch)
479
554
        return revision_id
480
555
 
481
 
SPEC_TYPES.append(RevisionSpec_last)
482
556
 
483
557
 
484
558
class RevisionSpec_before(RevisionSpec):
504
578
    """
505
579
 
506
580
    prefix = 'before:'
507
 
    
 
581
 
508
582
    def _match_on(self, branch, revs):
509
583
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
510
584
        if r.revno == 0:
514
588
            # We need to use the repository history here
515
589
            rev = branch.repository.get_revision(r.rev_id)
516
590
            if not rev.parent_ids:
517
 
                revno = 0
518
591
                revision_id = revision.NULL_REVISION
519
592
            else:
520
593
                revision_id = rev.parent_ids[0]
521
 
                try:
522
 
                    revno = revs.index(revision_id) + 1
523
 
                except ValueError:
524
 
                    revno = None
 
594
            revno = None
525
595
        else:
526
596
            revno = r.revno - 1
527
597
            try:
532
602
        return RevisionInfo(branch, revno, revision_id)
533
603
 
534
604
    def _as_revision_id(self, context_branch):
535
 
        base_revspec = RevisionSpec.from_string(self.spec)
536
 
        base_revision_id = base_revspec.as_revision_id(context_branch)
 
605
        base_revision_id = RevisionSpec.from_string(self.spec)._as_revision_id(context_branch)
537
606
        if base_revision_id == revision.NULL_REVISION:
538
607
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
539
608
                                         'cannot go before the null: revision')
553
622
                'No parents for revision.')
554
623
        return parents[0]
555
624
 
556
 
SPEC_TYPES.append(RevisionSpec_before)
557
625
 
558
626
 
559
627
class RevisionSpec_tag(RevisionSpec):
565
633
    """
566
634
 
567
635
    prefix = 'tag:'
 
636
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
568
637
 
569
638
    def _match_on(self, branch, revs):
570
639
        # Can raise tags not supported, NoSuchTag, etc
571
640
        return RevisionInfo.from_revision_id(branch,
572
 
            branch.tags.lookup_tag(self.spec),
573
 
            revs)
 
641
            branch.tags.lookup_tag(self.spec))
574
642
 
575
643
    def _as_revision_id(self, context_branch):
576
644
        return context_branch.tags.lookup_tag(self.spec)
577
645
 
578
 
SPEC_TYPES.append(RevisionSpec_tag)
579
646
 
580
647
 
581
648
class _RevListToTimestamps(object):
582
649
    """This takes a list of revisions, and allows you to bisect by date"""
583
650
 
584
 
    __slots__ = ['revs', 'branch']
 
651
    __slots__ = ['branch']
585
652
 
586
 
    def __init__(self, revs, branch):
587
 
        self.revs = revs
 
653
    def __init__(self, branch):
588
654
        self.branch = branch
589
655
 
590
656
    def __getitem__(self, index):
591
657
        """Get the date of the index'd item"""
592
 
        r = self.branch.repository.get_revision(self.revs[index])
 
658
        r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
593
659
        # TODO: Handle timezone.
594
660
        return datetime.datetime.fromtimestamp(r.timestamp)
595
661
 
596
662
    def __len__(self):
597
 
        return len(self.revs)
 
663
        return self.branch.revno()
598
664
 
599
665
 
600
666
class RevisionSpec_date(RevisionSpec):
616
682
      date:yesterday            -> select the first revision since yesterday
617
683
      date:2006-08-14,17:10:14  -> select the first revision after
618
684
                                   August 14th, 2006 at 5:10pm.
619
 
    """    
 
685
    """
620
686
    prefix = 'date:'
621
 
    _date_re = re.compile(
 
687
    _date_regex = lazy_regex.lazy_compile(
622
688
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
623
689
            r'(,|T)?\s*'
624
690
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
642
708
        elif self.spec.lower() == 'tomorrow':
643
709
            dt = today + datetime.timedelta(days=1)
644
710
        else:
645
 
            m = self._date_re.match(self.spec)
 
711
            m = self._date_regex.match(self.spec)
646
712
            if not m or (not m.group('date') and not m.group('time')):
647
713
                raise errors.InvalidRevisionSpec(self.user_spec,
648
714
                                                 branch, 'invalid date')
674
740
                    hour=hour, minute=minute, second=second)
675
741
        branch.lock_read()
676
742
        try:
677
 
            rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
 
743
            rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
678
744
        finally:
679
745
            branch.unlock()
680
 
        if rev == len(revs):
 
746
        if rev == branch.revno():
681
747
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
682
 
        else:
683
 
            return RevisionInfo(branch, rev + 1)
 
748
        return RevisionInfo(branch, rev)
684
749
 
685
 
SPEC_TYPES.append(RevisionSpec_date)
686
750
 
687
751
 
688
752
class RevisionSpec_ancestor(RevisionSpec):
718
782
    def _find_revision_info(branch, other_location):
719
783
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
720
784
                                                              other_location)
721
 
        try:
722
 
            revno = branch.revision_id_to_revno(revision_id)
723
 
        except errors.NoSuchRevision:
724
 
            revno = None
725
 
        return RevisionInfo(branch, revno, revision_id)
 
785
        return RevisionInfo(branch, None, revision_id)
726
786
 
727
787
    @staticmethod
728
788
    def _find_revision_id(branch, other_location):
733
793
            revision_a = revision.ensure_null(branch.last_revision())
734
794
            if revision_a == revision.NULL_REVISION:
735
795
                raise errors.NoCommits(branch)
 
796
            if other_location == '':
 
797
                other_location = branch.get_parent()
736
798
            other_branch = Branch.open(other_location)
737
799
            other_branch.lock_read()
738
800
            try:
750
812
            branch.unlock()
751
813
 
752
814
 
753
 
SPEC_TYPES.append(RevisionSpec_ancestor)
754
815
 
755
816
 
756
817
class RevisionSpec_branch(RevisionSpec):
765
826
      branch:/path/to/branch
766
827
    """
767
828
    prefix = 'branch:'
 
829
    dwim_catchable_exceptions = (errors.NotBranchError,)
768
830
 
769
831
    def _match_on(self, branch, revs):
770
832
        from bzrlib.branch import Branch
772
834
        revision_b = other_branch.last_revision()
773
835
        if revision_b in (None, revision.NULL_REVISION):
774
836
            raise errors.NoCommits(other_branch)
775
 
        # pull in the remote revisions so we can diff
776
 
        branch.fetch(other_branch, revision_b)
777
 
        try:
778
 
            revno = branch.revision_id_to_revno(revision_b)
779
 
        except errors.NoSuchRevision:
780
 
            revno = None
781
 
        return RevisionInfo(branch, revno, revision_b)
 
837
        if branch is None:
 
838
            branch = other_branch
 
839
        else:
 
840
            try:
 
841
                # pull in the remote revisions so we can diff
 
842
                branch.fetch(other_branch, revision_b)
 
843
            except errors.ReadOnlyError:
 
844
                branch = other_branch
 
845
        return RevisionInfo(branch, None, revision_b)
782
846
 
783
847
    def _as_revision_id(self, context_branch):
784
848
        from bzrlib.branch import Branch
799
863
            raise errors.NoCommits(other_branch)
800
864
        return other_branch.repository.revision_tree(last_revision)
801
865
 
802
 
SPEC_TYPES.append(RevisionSpec_branch)
 
866
    def needs_branch(self):
 
867
        return False
 
868
 
 
869
    def get_branch(self):
 
870
        return self.spec
 
871
 
803
872
 
804
873
 
805
874
class RevisionSpec_submit(RevisionSpec_ancestor):
831
900
            location_type = 'parent branch'
832
901
        if submit_location is None:
833
902
            raise errors.NoSubmitBranch(branch)
834
 
        trace.note('Using %s %s', location_type, submit_location)
 
903
        trace.note(gettext('Using {0} {1}').format(location_type,
 
904
                                                        submit_location))
835
905
        return submit_location
836
906
 
837
907
    def _match_on(self, branch, revs):
844
914
            self._get_submit_location(context_branch))
845
915
 
846
916
 
847
 
SPEC_TYPES.append(RevisionSpec_submit)
 
917
class RevisionSpec_annotate(RevisionIDSpec):
 
918
 
 
919
    prefix = 'annotate:'
 
920
 
 
921
    help_txt = """Select the revision that last modified the specified line.
 
922
 
 
923
    Select the revision that last modified the specified line.  Line is
 
924
    specified as path:number.  Path is a relative path to the file.  Numbers
 
925
    start at 1, and are relative to the current version, not the last-
 
926
    committed version of the file.
 
927
    """
 
928
 
 
929
    def _raise_invalid(self, numstring, context_branch):
 
930
        raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
931
            'No such line: %s' % numstring)
 
932
 
 
933
    def _as_revision_id(self, context_branch):
 
934
        path, numstring = self.spec.rsplit(':', 1)
 
935
        try:
 
936
            index = int(numstring) - 1
 
937
        except ValueError:
 
938
            self._raise_invalid(numstring, context_branch)
 
939
        tree, file_path = workingtree.WorkingTree.open_containing(path)
 
940
        tree.lock_read()
 
941
        try:
 
942
            file_id = tree.path2id(file_path)
 
943
            if file_id is None:
 
944
                raise errors.InvalidRevisionSpec(self.user_spec,
 
945
                    context_branch, "File '%s' is not versioned." %
 
946
                    file_path)
 
947
            revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
 
948
        finally:
 
949
            tree.unlock()
 
950
        try:
 
951
            revision_id = revision_ids[index]
 
952
        except IndexError:
 
953
            self._raise_invalid(numstring, context_branch)
 
954
        if revision_id == revision.CURRENT_REVISION:
 
955
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
956
                'Line %s has not been committed.' % numstring)
 
957
        return revision_id
 
958
 
 
959
 
 
960
class RevisionSpec_mainline(RevisionIDSpec):
 
961
 
 
962
    help_txt = """Select mainline revision that merged the specified revision.
 
963
 
 
964
    Select the revision that merged the specified revision into mainline.
 
965
    """
 
966
 
 
967
    prefix = 'mainline:'
 
968
 
 
969
    def _as_revision_id(self, context_branch):
 
970
        revspec = RevisionSpec.from_string(self.spec)
 
971
        if revspec.get_branch() is None:
 
972
            spec_branch = context_branch
 
973
        else:
 
974
            spec_branch = _mod_branch.Branch.open(revspec.get_branch())
 
975
        revision_id = revspec.as_revision_id(spec_branch)
 
976
        graph = context_branch.repository.get_graph()
 
977
        result = graph.find_lefthand_merger(revision_id,
 
978
                                            context_branch.last_revision())
 
979
        if result is None:
 
980
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
 
981
        return result
 
982
 
 
983
 
 
984
# The order in which we want to DWIM a revision spec without any prefix.
 
985
# revno is always tried first and isn't listed here, this is used by
 
986
# RevisionSpec_dwim._match_on
 
987
dwim_revspecs = symbol_versioning.deprecated_list(
 
988
    symbol_versioning.deprecated_in((2, 4, 0)), "dwim_revspecs", [])
 
989
 
 
990
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
 
991
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
 
992
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
 
993
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
 
994
 
 
995
revspec_registry = registry.Registry()
 
996
def _register_revspec(revspec):
 
997
    revspec_registry.register(revspec.prefix, revspec)
 
998
 
 
999
_register_revspec(RevisionSpec_revno)
 
1000
_register_revspec(RevisionSpec_revid)
 
1001
_register_revspec(RevisionSpec_last)
 
1002
_register_revspec(RevisionSpec_before)
 
1003
_register_revspec(RevisionSpec_tag)
 
1004
_register_revspec(RevisionSpec_date)
 
1005
_register_revspec(RevisionSpec_ancestor)
 
1006
_register_revspec(RevisionSpec_branch)
 
1007
_register_revspec(RevisionSpec_submit)
 
1008
_register_revspec(RevisionSpec_annotate)
 
1009
_register_revspec(RevisionSpec_mainline)