/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Robert Collins
  • Date: 2010-05-11 08:36:16 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100511083616-b8fjb19zomwupid0
Make all lock methods return Result objects, rather than lock_read returning self, as per John's review.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
 
 
20
 
from .lazy_import import lazy_import
 
17
 
 
18
import re
 
19
 
 
20
from bzrlib.lazy_import import lazy_import
21
21
lazy_import(globals(), """
22
22
import bisect
23
23
import datetime
24
 
 
25
 
from breezy import (
26
 
    branch as _mod_branch,
27
 
    cache_utf8,
28
 
    revision,
29
 
    workingtree,
30
 
    )
31
 
from breezy.i18n import gettext
32
24
""")
33
25
 
34
 
from . import (
 
26
from bzrlib import (
35
27
    errors,
36
 
    lazy_regex,
 
28
    osutils,
37
29
    registry,
 
30
    revision,
 
31
    symbol_versioning,
38
32
    trace,
39
33
    )
40
 
from .sixish import (
41
 
    text_type,
42
 
    )
43
 
 
44
 
 
45
 
class InvalidRevisionSpec(errors.BzrError):
46
 
 
47
 
    _fmt = ("Requested revision: '%(spec)s' does not exist in branch:"
48
 
            " %(branch_url)s%(extra)s")
49
 
 
50
 
    def __init__(self, spec, branch, extra=None):
51
 
        errors.BzrError.__init__(self, branch=branch, spec=spec)
52
 
        self.branch_url = getattr(branch, 'user_url', str(branch))
53
 
        if extra:
54
 
            self.extra = '\n' + str(extra)
55
 
        else:
56
 
            self.extra = ''
 
34
 
 
35
 
 
36
_marker = []
57
37
 
58
38
 
59
39
class RevisionInfo(object):
73
53
    or treat the result as a tuple.
74
54
    """
75
55
 
76
 
    def __init__(self, branch, revno=None, rev_id=None):
 
56
    def __init__(self, branch, revno, rev_id=_marker):
77
57
        self.branch = branch
78
 
        self._has_revno = (revno is not None)
79
 
        self._revno = revno
80
 
        self.rev_id = rev_id
81
 
        if self.rev_id is None and self._revno is not None:
 
58
        self.revno = revno
 
59
        if rev_id is _marker:
82
60
            # allow caller to be lazy
83
 
            self.rev_id = branch.get_rev_id(self._revno)
84
 
 
85
 
    @property
86
 
    def revno(self):
87
 
        if not self._has_revno and self.rev_id is not None:
88
 
            try:
89
 
                self._revno = self.branch.revision_id_to_revno(self.rev_id)
90
 
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
91
 
                self._revno = None
92
 
            self._has_revno = True
93
 
        return self._revno
94
 
 
95
 
    def __bool__(self):
 
61
            if self.revno is None:
 
62
                self.rev_id = None
 
63
            else:
 
64
                self.rev_id = branch.get_rev_id(self.revno)
 
65
        else:
 
66
            self.rev_id = rev_id
 
67
 
 
68
    def __nonzero__(self):
 
69
        # first the easy ones...
96
70
        if self.rev_id is None:
97
71
            return False
 
72
        if self.revno is not None:
 
73
            return True
98
74
        # TODO: otherwise, it should depend on how I was built -
99
75
        # if it's in_history(branch), then check revision_history(),
100
76
        # if it's in_store(branch), do the check below
101
77
        return self.branch.repository.has_revision(self.rev_id)
102
78
 
103
 
    __nonzero__ = __bool__
104
 
 
105
79
    def __len__(self):
106
80
        return 2
107
81
 
108
82
    def __getitem__(self, index):
109
 
        if index == 0:
110
 
            return self.revno
111
 
        if index == 1:
112
 
            return self.rev_id
 
83
        if index == 0: return self.revno
 
84
        if index == 1: return self.rev_id
113
85
        raise IndexError(index)
114
86
 
115
87
    def get(self):
118
90
    def __eq__(self, other):
119
91
        if type(other) not in (tuple, list, type(self)):
120
92
            return False
121
 
        if isinstance(other, type(self)) and self.branch is not other.branch:
 
93
        if type(other) is type(self) and self.branch is not other.branch:
122
94
            return False
123
95
        return tuple(self) == tuple(other)
124
96
 
125
97
    def __repr__(self):
126
 
        return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
 
98
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
127
99
            self.revno, self.rev_id, self.branch)
128
100
 
129
101
    @staticmethod
130
 
    def from_revision_id(branch, revision_id):
 
102
    def from_revision_id(branch, revision_id, revs):
131
103
        """Construct a RevisionInfo given just the id.
132
104
 
133
105
        Use this if you don't know or care what the revno is.
134
106
        """
135
 
        return RevisionInfo(branch, revno=None, rev_id=revision_id)
 
107
        if revision_id == revision.NULL_REVISION:
 
108
            return RevisionInfo(branch, 0, revision_id)
 
109
        try:
 
110
            revno = revs.index(revision_id) + 1
 
111
        except ValueError:
 
112
            revno = None
 
113
        return RevisionInfo(branch, revno, revision_id)
 
114
 
 
115
 
 
116
_revno_regex = None
136
117
 
137
118
 
138
119
class RevisionSpec(object):
155
136
    """
156
137
 
157
138
    prefix = None
158
 
    dwim_catchable_exceptions = (InvalidRevisionSpec,)
 
139
    wants_revision_history = True
 
140
    dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
159
141
    """Exceptions that RevisionSpec_dwim._match_on will catch.
160
142
 
161
143
    If the revspec is part of ``dwim_revspecs``, it may be tried with an
172
154
        :return: A RevisionSpec object that understands how to parse the
173
155
            supplied notation.
174
156
        """
 
157
        if not isinstance(spec, (type(None), basestring)):
 
158
            raise TypeError('error')
 
159
 
175
160
        if spec is None:
176
161
            return RevisionSpec(None, _internal=True)
177
 
        if not isinstance(spec, (str, text_type)):
178
 
            raise TypeError("revision spec needs to be text")
179
162
        match = revspec_registry.get_prefix(spec)
180
163
        if match is not None:
181
164
            spectype, specsuffix = match
183
166
                         spectype.__name__, spec)
184
167
            return spectype(spec, _internal=True)
185
168
        else:
 
169
            for spectype in SPEC_TYPES:
 
170
                if spec.startswith(spectype.prefix):
 
171
                    trace.mutter('Returning RevisionSpec %s for %s',
 
172
                                 spectype.__name__, spec)
 
173
                    return spectype(spec, _internal=True)
186
174
            # Otherwise treat it as a DWIM, build the RevisionSpec object and
187
175
            # wait for _match_on to be called.
188
176
            return RevisionSpec_dwim(spec, _internal=True)
195
183
            called directly. Only from RevisionSpec.from_string()
196
184
        """
197
185
        if not _internal:
198
 
            raise AssertionError(
199
 
                'Creating a RevisionSpec directly is not supported. '
200
 
                'Use RevisionSpec.from_string() instead.')
 
186
            symbol_versioning.warn('Creating a RevisionSpec directly has'
 
187
                                   ' been deprecated in version 0.11. Use'
 
188
                                   ' RevisionSpec.from_string()'
 
189
                                   ' instead.',
 
190
                                   DeprecationWarning, stacklevel=2)
201
191
        self.user_spec = spec
202
192
        if self.prefix and spec.startswith(self.prefix):
203
193
            spec = spec[len(self.prefix):]
215
205
            # special case - nothing supplied
216
206
            return info
217
207
        elif self.prefix:
218
 
            raise InvalidRevisionSpec(self.user_spec, branch)
 
208
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
219
209
        else:
220
 
            raise InvalidRevisionSpec(self.spec, branch)
 
210
            raise errors.InvalidRevisionSpec(self.spec, branch)
221
211
 
222
212
    def in_history(self, branch):
223
 
        return self._match_on_and_check(branch, revs=None)
 
213
        if branch:
 
214
            if self.wants_revision_history:
 
215
                revs = branch.revision_history()
 
216
            else:
 
217
                revs = None
 
218
        else:
 
219
            # this should never trigger.
 
220
            # TODO: make it a deprecated code path. RBC 20060928
 
221
            revs = None
 
222
        return self._match_on_and_check(branch, revs)
224
223
 
225
224
        # FIXME: in_history is somewhat broken,
226
225
        # it will return non-history revisions in many
270
269
    def __repr__(self):
271
270
        # this is mostly for helping with testing
272
271
        return '<%s %s>' % (self.__class__.__name__,
273
 
                            self.user_spec)
 
272
                              self.user_spec)
274
273
 
275
274
    def needs_branch(self):
276
275
        """Whether this revision spec needs a branch.
299
298
    """
300
299
 
301
300
    help_txt = None
302
 
 
303
 
    _revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
304
 
 
305
 
    # The revspecs to try
306
 
    _possible_revspecs = []
 
301
    # We don't need to build the revision history ourself, that's delegated to
 
302
    # each revspec we try.
 
303
    wants_revision_history = False
307
304
 
308
305
    def _try_spectype(self, rstype, branch):
309
306
        rs = rstype(self.spec, _internal=True)
315
312
        """Run the lookup and see what we can get."""
316
313
 
317
314
        # First, see if it's a revno
318
 
        if self._revno_regex.match(self.spec) is not None:
 
315
        global _revno_regex
 
316
        if _revno_regex is None:
 
317
            _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
 
318
        if _revno_regex.match(self.spec) is not None:
319
319
            try:
320
320
                return self._try_spectype(RevisionSpec_revno, branch)
321
321
            except RevisionSpec_revno.dwim_catchable_exceptions:
322
322
                pass
323
323
 
324
324
        # Next see what has been registered
325
 
        for objgetter in self._possible_revspecs:
326
 
            rs_class = objgetter.get_obj()
 
325
        for rs_class in dwim_revspecs:
327
326
            try:
328
327
                return self._try_spectype(rs_class, branch)
329
328
            except rs_class.dwim_catchable_exceptions:
332
331
        # Well, I dunno what it is. Note that we don't try to keep track of the
333
332
        # first of last exception raised during the DWIM tries as none seems
334
333
        # really relevant.
335
 
        raise InvalidRevisionSpec(self.spec, branch)
336
 
 
337
 
    @classmethod
338
 
    def append_possible_revspec(cls, revspec):
339
 
        """Append a possible DWIM revspec.
340
 
 
341
 
        :param revspec: Revision spec to try.
342
 
        """
343
 
        cls._possible_revspecs.append(registry._ObjectGetter(revspec))
344
 
 
345
 
    @classmethod
346
 
    def append_possible_lazy_revspec(cls, module_name, member_name):
347
 
        """Append a possible lazily loaded DWIM revspec.
348
 
 
349
 
        :param module_name: Name of the module with the revspec
350
 
        :param member_name: Name of the revspec within the module
351
 
        """
352
 
        cls._possible_revspecs.append(
353
 
            registry._LazyObjectGetter(module_name, member_name))
 
334
        raise errors.InvalidRevisionSpec(self.spec, branch)
354
335
 
355
336
 
356
337
class RevisionSpec_revno(RevisionSpec):
375
356
                                   your history is very long.
376
357
    """
377
358
    prefix = 'revno:'
 
359
    wants_revision_history = False
378
360
 
379
361
    def _match_on(self, branch, revs):
380
362
        """Lookup a revision by revision number"""
381
 
        branch, revno, revision_id = self._lookup(branch)
 
363
        branch, revno, revision_id = self._lookup(branch, revs)
382
364
        return RevisionInfo(branch, revno, revision_id)
383
365
 
384
 
    def _lookup(self, branch):
 
366
    def _lookup(self, branch, revs_or_none):
385
367
        loc = self.spec.find(':')
386
368
        if loc == -1:
387
369
            revno_spec = self.spec
388
370
            branch_spec = None
389
371
        else:
390
372
            revno_spec = self.spec[:loc]
391
 
            branch_spec = self.spec[loc + 1:]
 
373
            branch_spec = self.spec[loc+1:]
392
374
 
393
375
        if revno_spec == '':
394
376
            if not branch_spec:
395
 
                raise InvalidRevisionSpec(
396
 
                    self.user_spec, branch, 'cannot have an empty revno and no branch')
 
377
                raise errors.InvalidRevisionSpec(self.user_spec,
 
378
                        branch, 'cannot have an empty revno and no branch')
397
379
            revno = None
398
380
        else:
399
381
            try:
404
386
                # but the from_string method is a little primitive
405
387
                # right now - RBC 20060928
406
388
                try:
407
 
                    match_revno = tuple((int(number)
408
 
                                         for number in revno_spec.split('.')))
409
 
                except ValueError as e:
410
 
                    raise InvalidRevisionSpec(self.user_spec, branch, e)
 
389
                    match_revno = tuple((int(number) for number in revno_spec.split('.')))
 
390
                except ValueError, e:
 
391
                    raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
411
392
 
412
393
                dotted = True
413
394
 
414
395
        if branch_spec:
415
 
            # the user has overriden the branch to look in.
416
 
            branch = _mod_branch.Branch.open(branch_spec)
 
396
            # the user has override the branch to look in.
 
397
            # we need to refresh the revision_history map and
 
398
            # the branch object.
 
399
            from bzrlib.branch import Branch
 
400
            branch = Branch.open(branch_spec)
 
401
            revs_or_none = None
417
402
 
418
403
        if dotted:
419
404
            try:
420
405
                revision_id = branch.dotted_revno_to_revision_id(match_revno,
421
 
                                                                 _cache_reverse=True)
422
 
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
423
 
                raise InvalidRevisionSpec(self.user_spec, branch)
 
406
                    _cache_reverse=True)
 
407
            except errors.NoSuchRevision:
 
408
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
424
409
            else:
425
410
                # there is no traditional 'revno' for dotted-decimal revnos.
426
 
                # so for API compatibility we return None.
 
411
                # so for  API compatability we return None.
427
412
                return branch, None, revision_id
428
413
        else:
429
414
            last_revno, last_revision_id = branch.last_revision_info()
435
420
                else:
436
421
                    revno = last_revno + revno + 1
437
422
            try:
438
 
                revision_id = branch.get_rev_id(revno)
439
 
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
440
 
                raise InvalidRevisionSpec(self.user_spec, branch)
 
423
                revision_id = branch.get_rev_id(revno, revs_or_none)
 
424
            except errors.NoSuchRevision:
 
425
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
441
426
        return branch, revno, revision_id
442
427
 
443
428
    def _as_revision_id(self, context_branch):
444
429
        # We would have the revno here, but we don't really care
445
 
        branch, revno, revision_id = self._lookup(context_branch)
 
430
        branch, revno, revision_id = self._lookup(context_branch, None)
446
431
        return revision_id
447
432
 
448
433
    def needs_branch(self):
452
437
        if self.spec.find(':') == -1:
453
438
            return None
454
439
        else:
455
 
            return self.spec[self.spec.find(':') + 1:]
456
 
 
 
440
            return self.spec[self.spec.find(':')+1:]
457
441
 
458
442
# Old compatibility
459
443
RevisionSpec_int = RevisionSpec_revno
460
444
 
461
445
 
462
 
class RevisionIDSpec(RevisionSpec):
463
 
 
464
 
    def _match_on(self, branch, revs):
465
 
        revision_id = self.as_revision_id(branch)
466
 
        return RevisionInfo.from_revision_id(branch, revision_id)
467
 
 
468
 
 
469
 
class RevisionSpec_revid(RevisionIDSpec):
 
446
 
 
447
class RevisionSpec_revid(RevisionSpec):
470
448
    """Selects a revision using the revision id."""
471
449
 
472
450
    help_txt = """Selects a revision using the revision id.
481
459
 
482
460
    prefix = 'revid:'
483
461
 
484
 
    def _as_revision_id(self, context_branch):
 
462
    def _match_on(self, branch, revs):
485
463
        # self.spec comes straight from parsing the command line arguments,
486
464
        # so we expect it to be a Unicode string. Switch it to the internal
487
465
        # representation.
488
 
        if isinstance(self.spec, text_type):
489
 
            return cache_utf8.encode(self.spec)
490
 
        return self.spec
 
466
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
 
467
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
 
468
 
 
469
    def _as_revision_id(self, context_branch):
 
470
        return osutils.safe_revision_id(self.spec, warn=False)
 
471
 
491
472
 
492
473
 
493
474
class RevisionSpec_last(RevisionSpec):
506
487
    prefix = 'last:'
507
488
 
508
489
    def _match_on(self, branch, revs):
509
 
        revno, revision_id = self._revno_and_revision_id(branch)
 
490
        revno, revision_id = self._revno_and_revision_id(branch, revs)
510
491
        return RevisionInfo(branch, revno, revision_id)
511
492
 
512
 
    def _revno_and_revision_id(self, context_branch):
 
493
    def _revno_and_revision_id(self, context_branch, revs_or_none):
513
494
        last_revno, last_revision_id = context_branch.last_revision_info()
514
495
 
515
496
        if self.spec == '':
519
500
 
520
501
        try:
521
502
            offset = int(self.spec)
522
 
        except ValueError as e:
523
 
            raise InvalidRevisionSpec(self.user_spec, context_branch, e)
 
503
        except ValueError, e:
 
504
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
524
505
 
525
506
        if offset <= 0:
526
 
            raise InvalidRevisionSpec(self.user_spec, context_branch,
 
507
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
527
508
                                             'you must supply a positive value')
528
509
 
529
510
        revno = last_revno - offset + 1
530
511
        try:
531
 
            revision_id = context_branch.get_rev_id(revno)
532
 
        except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
533
 
            raise InvalidRevisionSpec(self.user_spec, context_branch)
 
512
            revision_id = context_branch.get_rev_id(revno, revs_or_none)
 
513
        except errors.NoSuchRevision:
 
514
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
534
515
        return revno, revision_id
535
516
 
536
517
    def _as_revision_id(self, context_branch):
537
518
        # We compute the revno as part of the process, but we don't really care
538
519
        # about it.
539
 
        revno, revision_id = self._revno_and_revision_id(context_branch)
 
520
        revno, revision_id = self._revno_and_revision_id(context_branch, None)
540
521
        return revision_id
541
522
 
542
523
 
 
524
 
543
525
class RevisionSpec_before(RevisionSpec):
544
526
    """Selects the parent of the revision specified."""
545
527
 
567
549
    def _match_on(self, branch, revs):
568
550
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
569
551
        if r.revno == 0:
570
 
            raise InvalidRevisionSpec(self.user_spec, branch, 'cannot go before the null: revision')
 
552
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
553
                                         'cannot go before the null: revision')
571
554
        if r.revno is None:
572
555
            # We need to use the repository history here
573
556
            rev = branch.repository.get_revision(r.rev_id)
574
557
            if not rev.parent_ids:
 
558
                revno = 0
575
559
                revision_id = revision.NULL_REVISION
576
560
            else:
577
561
                revision_id = rev.parent_ids[0]
578
 
            revno = None
 
562
                try:
 
563
                    revno = revs.index(revision_id) + 1
 
564
                except ValueError:
 
565
                    revno = None
579
566
        else:
580
567
            revno = r.revno - 1
581
568
            try:
582
569
                revision_id = branch.get_rev_id(revno, revs)
583
 
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
584
 
                raise InvalidRevisionSpec(self.user_spec, branch)
 
570
            except errors.NoSuchRevision:
 
571
                raise errors.InvalidRevisionSpec(self.user_spec,
 
572
                                                 branch)
585
573
        return RevisionInfo(branch, revno, revision_id)
586
574
 
587
575
    def _as_revision_id(self, context_branch):
588
 
        base_revision_id = RevisionSpec.from_string(
589
 
            self.spec)._as_revision_id(context_branch)
 
576
        base_revspec = RevisionSpec.from_string(self.spec)
 
577
        base_revision_id = base_revspec.as_revision_id(context_branch)
590
578
        if base_revision_id == revision.NULL_REVISION:
591
 
            raise InvalidRevisionSpec(
592
 
                self.user_spec, context_branch,
593
 
                'cannot go before the null: revision')
 
579
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
580
                                         'cannot go before the null: revision')
594
581
        context_repo = context_branch.repository
595
 
        with context_repo.lock_read():
 
582
        context_repo.lock_read()
 
583
        try:
596
584
            parent_map = context_repo.get_parent_map([base_revision_id])
 
585
        finally:
 
586
            context_repo.unlock()
597
587
        if base_revision_id not in parent_map:
598
588
            # Ghost, or unknown revision id
599
 
            raise InvalidRevisionSpec(
600
 
                self.user_spec, context_branch, 'cannot find the matching revision')
 
589
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
590
                'cannot find the matching revision')
601
591
        parents = parent_map[base_revision_id]
602
592
        if len(parents) < 1:
603
 
            raise InvalidRevisionSpec(
604
 
                self.user_spec, context_branch, 'No parents for revision.')
 
593
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
594
                'No parents for revision.')
605
595
        return parents[0]
606
596
 
607
597
 
 
598
 
608
599
class RevisionSpec_tag(RevisionSpec):
609
600
    """Select a revision identified by tag name"""
610
601
 
619
610
    def _match_on(self, branch, revs):
620
611
        # Can raise tags not supported, NoSuchTag, etc
621
612
        return RevisionInfo.from_revision_id(branch,
622
 
                                             branch.tags.lookup_tag(self.spec))
 
613
            branch.tags.lookup_tag(self.spec),
 
614
            revs)
623
615
 
624
616
    def _as_revision_id(self, context_branch):
625
617
        return context_branch.tags.lookup_tag(self.spec)
626
618
 
627
619
 
 
620
 
628
621
class _RevListToTimestamps(object):
629
622
    """This takes a list of revisions, and allows you to bisect by date"""
630
623
 
631
 
    __slots__ = ['branch']
 
624
    __slots__ = ['revs', 'branch']
632
625
 
633
 
    def __init__(self, branch):
 
626
    def __init__(self, revs, branch):
 
627
        self.revs = revs
634
628
        self.branch = branch
635
629
 
636
630
    def __getitem__(self, index):
637
631
        """Get the date of the index'd item"""
638
 
        r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
 
632
        r = self.branch.repository.get_revision(self.revs[index])
639
633
        # TODO: Handle timezone.
640
634
        return datetime.datetime.fromtimestamp(r.timestamp)
641
635
 
642
636
    def __len__(self):
643
 
        return self.branch.revno()
 
637
        return len(self.revs)
644
638
 
645
639
 
646
640
class RevisionSpec_date(RevisionSpec):
655
649
 
656
650
    One way to display all the changes since yesterday would be::
657
651
 
658
 
        brz log -r date:yesterday..
 
652
        bzr log -r date:yesterday..
659
653
 
660
654
    Examples::
661
655
 
664
658
                                   August 14th, 2006 at 5:10pm.
665
659
    """
666
660
    prefix = 'date:'
667
 
    _date_regex = lazy_regex.lazy_compile(
668
 
        r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
669
 
        r'(,|T)?\s*'
670
 
        r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
661
    _date_re = re.compile(
 
662
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
663
            r'(,|T)?\s*'
 
664
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
671
665
        )
672
666
 
673
667
    def _match_on(self, branch, revs):
680
674
        #  XXX: This doesn't actually work
681
675
        #  So the proper way of saying 'give me all entries for today' is:
682
676
        #      -r date:yesterday..date:today
683
 
        today = datetime.datetime.fromordinal(
684
 
            datetime.date.today().toordinal())
 
677
        today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
685
678
        if self.spec.lower() == 'yesterday':
686
679
            dt = today - datetime.timedelta(days=1)
687
680
        elif self.spec.lower() == 'today':
689
682
        elif self.spec.lower() == 'tomorrow':
690
683
            dt = today + datetime.timedelta(days=1)
691
684
        else:
692
 
            m = self._date_regex.match(self.spec)
 
685
            m = self._date_re.match(self.spec)
693
686
            if not m or (not m.group('date') and not m.group('time')):
694
 
                raise InvalidRevisionSpec(self.user_spec, branch, 'invalid date')
 
687
                raise errors.InvalidRevisionSpec(self.user_spec,
 
688
                                                 branch, 'invalid date')
695
689
 
696
690
            try:
697
691
                if m.group('date'):
711
705
                    else:
712
706
                        second = 0
713
707
                else:
714
 
                    hour, minute, second = 0, 0, 0
 
708
                    hour, minute, second = 0,0,0
715
709
            except ValueError:
716
 
                raise InvalidRevisionSpec(self.user_spec, branch, 'invalid date')
 
710
                raise errors.InvalidRevisionSpec(self.user_spec,
 
711
                                                 branch, 'invalid date')
717
712
 
718
713
            dt = datetime.datetime(year=year, month=month, day=day,
719
 
                                   hour=hour, minute=minute, second=second)
720
 
        with branch.lock_read():
721
 
            rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
722
 
        if rev == branch.revno():
723
 
            raise InvalidRevisionSpec(self.user_spec, branch)
724
 
        return RevisionInfo(branch, rev)
 
714
                    hour=hour, minute=minute, second=second)
 
715
        branch.lock_read()
 
716
        try:
 
717
            rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
 
718
        finally:
 
719
            branch.unlock()
 
720
        if rev == len(revs):
 
721
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
722
        else:
 
723
            return RevisionInfo(branch, rev + 1)
 
724
 
725
725
 
726
726
 
727
727
class RevisionSpec_ancestor(RevisionSpec):
757
757
    def _find_revision_info(branch, other_location):
758
758
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
759
759
                                                              other_location)
760
 
        return RevisionInfo(branch, None, revision_id)
 
760
        try:
 
761
            revno = branch.revision_id_to_revno(revision_id)
 
762
        except errors.NoSuchRevision:
 
763
            revno = None
 
764
        return RevisionInfo(branch, revno, revision_id)
761
765
 
762
766
    @staticmethod
763
767
    def _find_revision_id(branch, other_location):
764
 
        from .branch import Branch
 
768
        from bzrlib.branch import Branch
765
769
 
766
 
        with branch.lock_read():
 
770
        branch.lock_read()
 
771
        try:
767
772
            revision_a = revision.ensure_null(branch.last_revision())
768
773
            if revision_a == revision.NULL_REVISION:
769
774
                raise errors.NoCommits(branch)
770
775
            if other_location == '':
771
776
                other_location = branch.get_parent()
772
777
            other_branch = Branch.open(other_location)
773
 
            with other_branch.lock_read():
 
778
            other_branch.lock_read()
 
779
            try:
774
780
                revision_b = revision.ensure_null(other_branch.last_revision())
775
781
                if revision_b == revision.NULL_REVISION:
776
782
                    raise errors.NoCommits(other_branch)
777
783
                graph = branch.repository.get_graph(other_branch.repository)
778
784
                rev_id = graph.find_unique_lca(revision_a, revision_b)
 
785
            finally:
 
786
                other_branch.unlock()
779
787
            if rev_id == revision.NULL_REVISION:
780
788
                raise errors.NoCommonAncestor(revision_a, revision_b)
781
789
            return rev_id
 
790
        finally:
 
791
            branch.unlock()
 
792
 
 
793
 
782
794
 
783
795
 
784
796
class RevisionSpec_branch(RevisionSpec):
796
808
    dwim_catchable_exceptions = (errors.NotBranchError,)
797
809
 
798
810
    def _match_on(self, branch, revs):
799
 
        from .branch import Branch
 
811
        from bzrlib.branch import Branch
800
812
        other_branch = Branch.open(self.spec)
801
813
        revision_b = other_branch.last_revision()
802
814
        if revision_b in (None, revision.NULL_REVISION):
803
815
            raise errors.NoCommits(other_branch)
804
 
        if branch is None:
805
 
            branch = other_branch
806
 
        else:
807
 
            try:
808
 
                # pull in the remote revisions so we can diff
809
 
                branch.fetch(other_branch, revision_b)
810
 
            except errors.ReadOnlyError:
811
 
                branch = other_branch
812
 
        return RevisionInfo(branch, None, revision_b)
 
816
        # pull in the remote revisions so we can diff
 
817
        branch.fetch(other_branch, revision_b)
 
818
        try:
 
819
            revno = branch.revision_id_to_revno(revision_b)
 
820
        except errors.NoSuchRevision:
 
821
            revno = None
 
822
        return RevisionInfo(branch, revno, revision_b)
813
823
 
814
824
    def _as_revision_id(self, context_branch):
815
 
        from .branch import Branch
 
825
        from bzrlib.branch import Branch
816
826
        other_branch = Branch.open(self.spec)
817
827
        last_revision = other_branch.last_revision()
818
828
        last_revision = revision.ensure_null(last_revision)
822
832
        return last_revision
823
833
 
824
834
    def _as_tree(self, context_branch):
825
 
        from .branch import Branch
 
835
        from bzrlib.branch import Branch
826
836
        other_branch = Branch.open(self.spec)
827
837
        last_revision = other_branch.last_revision()
828
838
        last_revision = revision.ensure_null(last_revision)
830
840
            raise errors.NoCommits(other_branch)
831
841
        return other_branch.repository.revision_tree(last_revision)
832
842
 
833
 
    def needs_branch(self):
834
 
        return False
835
 
 
836
 
    def get_branch(self):
837
 
        return self.spec
838
843
 
839
844
 
840
845
class RevisionSpec_submit(RevisionSpec_ancestor):
866
871
            location_type = 'parent branch'
867
872
        if submit_location is None:
868
873
            raise errors.NoSubmitBranch(branch)
869
 
        trace.note(gettext('Using {0} {1}').format(location_type,
870
 
                                                   submit_location))
 
874
        trace.note('Using %s %s', location_type, submit_location)
871
875
        return submit_location
872
876
 
873
877
    def _match_on(self, branch, revs):
874
878
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
875
879
        return self._find_revision_info(branch,
876
 
                                        self._get_submit_location(branch))
 
880
            self._get_submit_location(branch))
877
881
 
878
882
    def _as_revision_id(self, context_branch):
879
883
        return self._find_revision_id(context_branch,
880
 
                                      self._get_submit_location(context_branch))
881
 
 
882
 
 
883
 
class RevisionSpec_annotate(RevisionIDSpec):
884
 
 
885
 
    prefix = 'annotate:'
886
 
 
887
 
    help_txt = """Select the revision that last modified the specified line.
888
 
 
889
 
    Select the revision that last modified the specified line.  Line is
890
 
    specified as path:number.  Path is a relative path to the file.  Numbers
891
 
    start at 1, and are relative to the current version, not the last-
892
 
    committed version of the file.
893
 
    """
894
 
 
895
 
    def _raise_invalid(self, numstring, context_branch):
896
 
        raise InvalidRevisionSpec(self.user_spec, context_branch,
897
 
                                  'No such line: %s' % numstring)
898
 
 
899
 
    def _as_revision_id(self, context_branch):
900
 
        path, numstring = self.spec.rsplit(':', 1)
901
 
        try:
902
 
            index = int(numstring) - 1
903
 
        except ValueError:
904
 
            self._raise_invalid(numstring, context_branch)
905
 
        tree, file_path = workingtree.WorkingTree.open_containing(path)
906
 
        with tree.lock_read():
907
 
            if not tree.has_filename(file_path):
908
 
                raise InvalidRevisionSpec(
909
 
                    self.user_spec, context_branch,
910
 
                    "File '%s' is not versioned." % file_path)
911
 
            revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
912
 
        try:
913
 
            revision_id = revision_ids[index]
914
 
        except IndexError:
915
 
            self._raise_invalid(numstring, context_branch)
916
 
        if revision_id == revision.CURRENT_REVISION:
917
 
            raise InvalidRevisionSpec(
918
 
                self.user_spec, context_branch,
919
 
                'Line %s has not been committed.' % numstring)
920
 
        return revision_id
921
 
 
922
 
 
923
 
class RevisionSpec_mainline(RevisionIDSpec):
924
 
 
925
 
    help_txt = """Select mainline revision that merged the specified revision.
926
 
 
927
 
    Select the revision that merged the specified revision into mainline.
928
 
    """
929
 
 
930
 
    prefix = 'mainline:'
931
 
 
932
 
    def _as_revision_id(self, context_branch):
933
 
        revspec = RevisionSpec.from_string(self.spec)
934
 
        if revspec.get_branch() is None:
935
 
            spec_branch = context_branch
936
 
        else:
937
 
            spec_branch = _mod_branch.Branch.open(revspec.get_branch())
938
 
        revision_id = revspec.as_revision_id(spec_branch)
939
 
        graph = context_branch.repository.get_graph()
940
 
        result = graph.find_lefthand_merger(revision_id,
941
 
                                            context_branch.last_revision())
942
 
        if result is None:
943
 
            raise InvalidRevisionSpec(self.user_spec, context_branch)
944
 
        return result
 
884
            self._get_submit_location(context_branch))
945
885
 
946
886
 
947
887
# The order in which we want to DWIM a revision spec without any prefix.
948
888
# revno is always tried first and isn't listed here, this is used by
949
889
# RevisionSpec_dwim._match_on
950
 
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
951
 
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
952
 
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
953
 
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
 
890
dwim_revspecs = [
 
891
    RevisionSpec_tag, # Let's try for a tag
 
892
    RevisionSpec_revid, # Maybe it's a revid?
 
893
    RevisionSpec_date, # Perhaps a date?
 
894
    RevisionSpec_branch, # OK, last try, maybe it's a branch
 
895
    ]
 
896
 
954
897
 
955
898
revspec_registry = registry.Registry()
956
 
 
957
 
 
958
899
def _register_revspec(revspec):
959
900
    revspec_registry.register(revspec.prefix, revspec)
960
901
 
961
 
 
962
902
_register_revspec(RevisionSpec_revno)
963
903
_register_revspec(RevisionSpec_revid)
964
904
_register_revspec(RevisionSpec_last)
968
908
_register_revspec(RevisionSpec_ancestor)
969
909
_register_revspec(RevisionSpec_branch)
970
910
_register_revspec(RevisionSpec_submit)
971
 
_register_revspec(RevisionSpec_annotate)
972
 
_register_revspec(RevisionSpec_mainline)
 
911
 
 
912
# classes in this list should have a "prefix" attribute, against which
 
913
# string specs are matched
 
914
SPEC_TYPES = symbol_versioning.deprecated_list(
 
915
    symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])