/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 breezy/revisionspec.py

  • Committer: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

Show diffs side-by-side

added added

removed removed

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