/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: Alexander Belchenko
  • Date: 2006-06-29 08:41:31 UTC
  • mto: (1860.1.1 win32.installer)
  • mto: This revision was merged to the branch mainline in revision 1906.
  • Revision ID: bialix@ukr.net-20060629084131-3ea4d44e3204e36f
win32 installer for bzr.dev.0.9

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
from __future__ import absolute_import
18
 
 
19
 
 
20
 
from .lazy_import import lazy_import
21
 
lazy_import(globals(), """
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
import datetime
 
19
import re
22
20
import bisect
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
 
""")
33
 
 
34
 
from . import (
35
 
    errors,
36
 
    lazy_regex,
37
 
    registry,
38
 
    trace,
39
 
    )
40
 
from .sixish import (
41
 
    text_type,
42
 
    )
43
 
 
 
21
from bzrlib.errors import BzrError, NoSuchRevision, NoCommits
 
22
 
 
23
_marker = []
44
24
 
45
25
class RevisionInfo(object):
46
 
    """The results of applying a revision specification to a branch."""
47
 
 
48
 
    help_txt = """The results of applying a revision specification to a branch.
 
26
    """The results of applying a revision specification to a branch.
49
27
 
50
28
    An instance has two useful attributes: revno, and rev_id.
51
29
 
59
37
    or treat the result as a tuple.
60
38
    """
61
39
 
62
 
    def __init__(self, branch, revno=None, rev_id=None):
 
40
    def __init__(self, branch, revno, rev_id=_marker):
63
41
        self.branch = branch
64
 
        self._has_revno = (revno is not None)
65
 
        self._revno = revno
66
 
        self.rev_id = rev_id
67
 
        if self.rev_id is None and self._revno is not None:
 
42
        self.revno = revno
 
43
        if rev_id is _marker:
68
44
            # allow caller to be lazy
69
 
            self.rev_id = branch.get_rev_id(self._revno)
70
 
 
71
 
    @property
72
 
    def revno(self):
73
 
        if not self._has_revno and self.rev_id is not None:
74
 
            try:
75
 
                self._revno = self.branch.revision_id_to_revno(self.rev_id)
76
 
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
77
 
                self._revno = None
78
 
            self._has_revno = True
79
 
        return self._revno
80
 
 
81
 
    def __bool__(self):
 
45
            if self.revno is None:
 
46
                self.rev_id = None
 
47
            else:
 
48
                self.rev_id = branch.get_rev_id(self.revno)
 
49
        else:
 
50
            self.rev_id = rev_id
 
51
 
 
52
    def __nonzero__(self):
 
53
        # first the easy ones...
82
54
        if self.rev_id is None:
83
55
            return False
 
56
        if self.revno is not None:
 
57
            return True
84
58
        # TODO: otherwise, it should depend on how I was built -
85
59
        # if it's in_history(branch), then check revision_history(),
86
60
        # if it's in_store(branch), do the check below
87
61
        return self.branch.repository.has_revision(self.rev_id)
88
62
 
89
 
    __nonzero__ = __bool__
90
 
 
91
63
    def __len__(self):
92
64
        return 2
93
65
 
94
66
    def __getitem__(self, index):
95
 
        if index == 0:
96
 
            return self.revno
97
 
        if index == 1:
98
 
            return self.rev_id
 
67
        if index == 0: return self.revno
 
68
        if index == 1: return self.rev_id
99
69
        raise IndexError(index)
100
70
 
101
71
    def get(self):
104
74
    def __eq__(self, other):
105
75
        if type(other) not in (tuple, list, type(self)):
106
76
            return False
107
 
        if isinstance(other, type(self)) and self.branch is not other.branch:
 
77
        if type(other) is type(self) and self.branch is not other.branch:
108
78
            return False
109
79
        return tuple(self) == tuple(other)
110
80
 
111
81
    def __repr__(self):
112
 
        return '<breezy.revisionspec.RevisionInfo object %s, %s for %r>' % (
 
82
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
113
83
            self.revno, self.rev_id, self.branch)
114
84
 
115
 
    @staticmethod
116
 
    def from_revision_id(branch, revision_id):
117
 
        """Construct a RevisionInfo given just the id.
118
 
 
119
 
        Use this if you don't know or care what the revno is.
120
 
        """
121
 
        return RevisionInfo(branch, revno=None, rev_id=revision_id)
122
 
 
 
85
# classes in this list should have a "prefix" attribute, against which
 
86
# string specs are matched
 
87
SPEC_TYPES = []
123
88
 
124
89
class RevisionSpec(object):
125
 
    """A parsed revision specification."""
126
 
 
127
 
    help_txt = """A parsed revision specification.
128
 
 
129
 
    A revision specification is a string, which may be unambiguous about
130
 
    what it represents by giving a prefix like 'date:' or 'revid:' etc,
131
 
    or it may have no prefix, in which case it's tried against several
132
 
    specifier types in sequence to determine what the user meant.
 
90
    """A parsed revision specification.
 
91
 
 
92
    A revision specification can be an integer, in which case it is
 
93
    assumed to be a revno (though this will translate negative values
 
94
    into positive ones); or it can be a string, in which case it is
 
95
    parsed for something like 'date:' or 'revid:' etc.
133
96
 
134
97
    Revision specs are an UI element, and they have been moved out
135
98
    of the branch class to leave "back-end" classes unaware of such
141
104
    """
142
105
 
143
106
    prefix = None
144
 
    dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
145
 
    """Exceptions that RevisionSpec_dwim._match_on will catch.
146
 
 
147
 
    If the revspec is part of ``dwim_revspecs``, it may be tried with an
148
 
    invalid revspec and raises some exception. The exceptions mentioned here
149
 
    will not be reported to the user but simply ignored without stopping the
150
 
    dwim processing.
151
 
    """
152
 
 
153
 
    @staticmethod
154
 
    def from_string(spec):
155
 
        """Parse a revision spec string into a RevisionSpec object.
156
 
 
157
 
        :param spec: A string specified by the user
158
 
        :return: A RevisionSpec object that understands how to parse the
159
 
            supplied notation.
 
107
 
 
108
    def __new__(cls, spec, foo=_marker):
 
109
        """Parse a revision specifier.
160
110
        """
161
111
        if spec is None:
162
 
            return RevisionSpec(None, _internal=True)
163
 
        if not isinstance(spec, (str, text_type)):
164
 
            raise TypeError("revision spec needs to be text")
165
 
        match = revspec_registry.get_prefix(spec)
166
 
        if match is not None:
167
 
            spectype, specsuffix = match
168
 
            trace.mutter('Returning RevisionSpec %s for %s',
169
 
                         spectype.__name__, spec)
170
 
            return spectype(spec, _internal=True)
 
112
            return object.__new__(RevisionSpec, spec)
 
113
 
 
114
        try:
 
115
            spec = int(spec)
 
116
        except ValueError:
 
117
            pass
 
118
 
 
119
        if isinstance(spec, int):
 
120
            return object.__new__(RevisionSpec_int, spec)
 
121
        elif isinstance(spec, basestring):
 
122
            for spectype in SPEC_TYPES:
 
123
                if spec.startswith(spectype.prefix):
 
124
                    return object.__new__(spectype, spec)
 
125
            else:
 
126
                raise BzrError('No namespace registered for string: %r' %
 
127
                               spec)
171
128
        else:
172
 
            # Otherwise treat it as a DWIM, build the RevisionSpec object and
173
 
            # wait for _match_on to be called.
174
 
            return RevisionSpec_dwim(spec, _internal=True)
175
 
 
176
 
    def __init__(self, spec, _internal=False):
177
 
        """Create a RevisionSpec referring to the Null revision.
178
 
 
179
 
        :param spec: The original spec supplied by the user
180
 
        :param _internal: Used to ensure that RevisionSpec is not being
181
 
            called directly. Only from RevisionSpec.from_string()
182
 
        """
183
 
        if not _internal:
184
 
            raise AssertionError(
185
 
                'Creating a RevisionSpec directly is not supported. '
186
 
                'Use RevisionSpec.from_string() instead.')
187
 
        self.user_spec = spec
 
129
            raise TypeError('Unhandled revision type %s' % spec)
 
130
 
 
131
    def __init__(self, spec):
188
132
        if self.prefix and spec.startswith(self.prefix):
189
133
            spec = spec[len(self.prefix):]
190
134
        self.spec = spec
191
135
 
192
136
    def _match_on(self, branch, revs):
193
 
        trace.mutter('Returning RevisionSpec._match_on: None')
194
 
        return RevisionInfo(branch, None, None)
 
137
        return RevisionInfo(branch, 0, None)
195
138
 
196
139
    def _match_on_and_check(self, branch, revs):
197
140
        info = self._match_on(branch, revs)
198
141
        if info:
199
142
            return info
200
 
        elif info == (None, None):
201
 
            # special case - nothing supplied
 
143
        elif info == (0, None):
 
144
            # special case - the empty tree
202
145
            return info
203
146
        elif self.prefix:
204
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
147
            raise NoSuchRevision(branch, self.prefix + str(self.spec))
205
148
        else:
206
 
            raise errors.InvalidRevisionSpec(self.spec, branch)
 
149
            raise NoSuchRevision(branch, str(self.spec))
207
150
 
208
151
    def in_history(self, branch):
209
 
        return self._match_on_and_check(branch, revs=None)
 
152
        revs = branch.revision_history()
 
153
        return self._match_on_and_check(branch, revs)
210
154
 
211
155
        # FIXME: in_history is somewhat broken,
212
156
        # it will return non-history revisions in many
217
161
    # will do what you expect.
218
162
    in_store = in_history
219
163
    in_branch = in_store
220
 
 
221
 
    def as_revision_id(self, context_branch):
222
 
        """Return just the revision_id for this revisions spec.
223
 
 
224
 
        Some revision specs require a context_branch to be able to determine
225
 
        their value. Not all specs will make use of it.
226
 
        """
227
 
        return self._as_revision_id(context_branch)
228
 
 
229
 
    def _as_revision_id(self, context_branch):
230
 
        """Implementation of as_revision_id()
231
 
 
232
 
        Classes should override this function to provide appropriate
233
 
        functionality. The default is to just call '.in_history().rev_id'
234
 
        """
235
 
        return self.in_history(context_branch).rev_id
236
 
 
237
 
    def as_tree(self, context_branch):
238
 
        """Return the tree object for this revisions spec.
239
 
 
240
 
        Some revision specs require a context_branch to be able to determine
241
 
        the revision id and access the repository. Not all specs will make
242
 
        use of it.
243
 
        """
244
 
        return self._as_tree(context_branch)
245
 
 
246
 
    def _as_tree(self, context_branch):
247
 
        """Implementation of as_tree().
248
 
 
249
 
        Classes should override this function to provide appropriate
250
 
        functionality. The default is to just call '.as_revision_id()'
251
 
        and get the revision tree from context_branch's repository.
252
 
        """
253
 
        revision_id = self.as_revision_id(context_branch)
254
 
        return context_branch.repository.revision_tree(revision_id)
255
 
 
 
164
        
256
165
    def __repr__(self):
257
166
        # this is mostly for helping with testing
258
 
        return '<%s %s>' % (self.__class__.__name__,
259
 
                            self.user_spec)
260
 
 
261
 
    def needs_branch(self):
262
 
        """Whether this revision spec needs a branch.
263
 
 
264
 
        Set this to False the branch argument of _match_on is not used.
265
 
        """
266
 
        return True
267
 
 
268
 
    def get_branch(self):
269
 
        """When the revision specifier contains a branch location, return it.
270
 
 
271
 
        Otherwise, return None.
272
 
        """
273
 
        return None
 
167
        return '<%s %s%s>' % (self.__class__.__name__,
 
168
                              self.prefix or '',
 
169
                              self.spec)
274
170
 
275
171
 
276
172
# private API
277
173
 
278
 
class RevisionSpec_dwim(RevisionSpec):
279
 
    """Provides a DWIMish revision specifier lookup.
280
 
 
281
 
    Note that this does not go in the revspec_registry because by definition
282
 
    there is no prefix to identify it.  It's solely called from
283
 
    RevisionSpec.from_string() because the DWIMification happen when _match_on
284
 
    is called so the string describing the revision is kept here until needed.
285
 
    """
286
 
 
287
 
    help_txt = None
288
 
 
289
 
    _revno_regex = lazy_regex.lazy_compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
290
 
 
291
 
    # The revspecs to try
292
 
    _possible_revspecs = []
293
 
 
294
 
    def _try_spectype(self, rstype, branch):
295
 
        rs = rstype(self.spec, _internal=True)
296
 
        # Hit in_history to find out if it exists, or we need to try the
297
 
        # next type.
298
 
        return rs.in_history(branch)
 
174
class RevisionSpec_int(RevisionSpec):
 
175
    """Spec is a number.  Special case."""
 
176
    def __init__(self, spec):
 
177
        self.spec = int(spec)
299
178
 
300
179
    def _match_on(self, branch, revs):
301
 
        """Run the lookup and see what we can get."""
302
 
 
303
 
        # First, see if it's a revno
304
 
        if self._revno_regex.match(self.spec) is not None:
305
 
            try:
306
 
                return self._try_spectype(RevisionSpec_revno, branch)
307
 
            except RevisionSpec_revno.dwim_catchable_exceptions:
308
 
                pass
309
 
 
310
 
        # Next see what has been registered
311
 
        for objgetter in self._possible_revspecs:
312
 
            rs_class = objgetter.get_obj()
313
 
            try:
314
 
                return self._try_spectype(rs_class, branch)
315
 
            except rs_class.dwim_catchable_exceptions:
316
 
                pass
317
 
 
318
 
        # Well, I dunno what it is. Note that we don't try to keep track of the
319
 
        # first of last exception raised during the DWIM tries as none seems
320
 
        # really relevant.
321
 
        raise errors.InvalidRevisionSpec(self.spec, branch)
322
 
 
323
 
    @classmethod
324
 
    def append_possible_revspec(cls, revspec):
325
 
        """Append a possible DWIM revspec.
326
 
 
327
 
        :param revspec: Revision spec to try.
328
 
        """
329
 
        cls._possible_revspecs.append(registry._ObjectGetter(revspec))
330
 
 
331
 
    @classmethod
332
 
    def append_possible_lazy_revspec(cls, module_name, member_name):
333
 
        """Append a possible lazily loaded DWIM revspec.
334
 
 
335
 
        :param module_name: Name of the module with the revspec
336
 
        :param member_name: Name of the revspec within the module
337
 
        """
338
 
        cls._possible_revspecs.append(
339
 
            registry._LazyObjectGetter(module_name, member_name))
 
180
        if self.spec < 0:
 
181
            revno = len(revs) + self.spec + 1
 
182
        else:
 
183
            revno = self.spec
 
184
        rev_id = branch.get_rev_id(revno, revs)
 
185
        return RevisionInfo(branch, revno, rev_id)
340
186
 
341
187
 
342
188
class RevisionSpec_revno(RevisionSpec):
343
 
    """Selects a revision using a number."""
344
 
 
345
 
    help_txt = """Selects a revision using a number.
346
 
 
347
 
    Use an integer to specify a revision in the history of the branch.
348
 
    Optionally a branch can be specified.  A negative number will count
349
 
    from the end of the branch (-1 is the last revision, -2 the previous
350
 
    one). If the negative number is larger than the branch's history, the
351
 
    first revision is returned.
352
 
    Examples::
353
 
 
354
 
      revno:1                   -> return the first revision of this branch
355
 
      revno:3:/path/to/branch   -> return the 3rd revision of
356
 
                                   the branch '/path/to/branch'
357
 
      revno:-1                  -> The last revision in a branch.
358
 
      -2:http://other/branch    -> The second to last revision in the
359
 
                                   remote branch.
360
 
      -1000000                  -> Most likely the first revision, unless
361
 
                                   your history is very long.
362
 
    """
363
189
    prefix = 'revno:'
364
190
 
365
191
    def _match_on(self, branch, revs):
366
192
        """Lookup a revision by revision number"""
367
 
        branch, revno, revision_id = self._lookup(branch)
368
 
        return RevisionInfo(branch, revno, revision_id)
369
 
 
370
 
    def _lookup(self, branch):
371
 
        loc = self.spec.find(':')
372
 
        if loc == -1:
373
 
            revno_spec = self.spec
374
 
            branch_spec = None
375
 
        else:
376
 
            revno_spec = self.spec[:loc]
377
 
            branch_spec = self.spec[loc + 1:]
378
 
 
379
 
        if revno_spec == '':
380
 
            if not branch_spec:
381
 
                raise errors.InvalidRevisionSpec(self.user_spec,
382
 
                                                 branch, 'cannot have an empty revno and no branch')
383
 
            revno = None
384
 
        else:
385
 
            try:
386
 
                revno = int(revno_spec)
387
 
                dotted = False
388
 
            except ValueError:
389
 
                # dotted decimal. This arguably should not be here
390
 
                # but the from_string method is a little primitive
391
 
                # right now - RBC 20060928
392
 
                try:
393
 
                    match_revno = tuple((int(number)
394
 
                                         for number in revno_spec.split('.')))
395
 
                except ValueError as e:
396
 
                    raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
397
 
 
398
 
                dotted = True
399
 
 
400
 
        if branch_spec:
401
 
            # the user has overriden the branch to look in.
402
 
            branch = _mod_branch.Branch.open(branch_spec)
403
 
 
404
 
        if dotted:
405
 
            try:
406
 
                revision_id = branch.dotted_revno_to_revision_id(match_revno,
407
 
                                                                 _cache_reverse=True)
408
 
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
409
 
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
410
 
            else:
411
 
                # there is no traditional 'revno' for dotted-decimal revnos.
412
 
                # so for API compatibility we return None.
413
 
                return branch, None, revision_id
414
 
        else:
415
 
            last_revno, last_revision_id = branch.last_revision_info()
416
 
            if revno < 0:
417
 
                # if get_rev_id supported negative revnos, there would not be a
418
 
                # need for this special case.
419
 
                if (-revno) >= last_revno:
420
 
                    revno = 1
421
 
                else:
422
 
                    revno = last_revno + revno + 1
423
 
            try:
424
 
                revision_id = branch.get_rev_id(revno)
425
 
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
426
 
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
427
 
        return branch, revno, revision_id
428
 
 
429
 
    def _as_revision_id(self, context_branch):
430
 
        # We would have the revno here, but we don't really care
431
 
        branch, revno, revision_id = self._lookup(context_branch)
432
 
        return revision_id
433
 
 
434
 
    def needs_branch(self):
435
 
        return self.spec.find(':') == -1
436
 
 
437
 
    def get_branch(self):
438
 
        if self.spec.find(':') == -1:
439
 
            return None
440
 
        else:
441
 
            return self.spec[self.spec.find(':') + 1:]
442
 
 
443
 
 
444
 
# Old compatibility
445
 
RevisionSpec_int = RevisionSpec_revno
446
 
 
447
 
 
448
 
class RevisionIDSpec(RevisionSpec):
449
 
 
450
 
    def _match_on(self, branch, revs):
451
 
        revision_id = self.as_revision_id(branch)
452
 
        return RevisionInfo.from_revision_id(branch, revision_id)
453
 
 
454
 
 
455
 
class RevisionSpec_revid(RevisionIDSpec):
456
 
    """Selects a revision using the revision id."""
457
 
 
458
 
    help_txt = """Selects a revision using the revision id.
459
 
 
460
 
    Supply a specific revision id, that can be used to specify any
461
 
    revision id in the ancestry of the branch.
462
 
    Including merges, and pending merges.
463
 
    Examples::
464
 
 
465
 
      revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
466
 
    """
467
 
 
 
193
        try:
 
194
            return RevisionInfo(branch, int(self.spec))
 
195
        except ValueError:
 
196
            return RevisionInfo(branch, None)
 
197
 
 
198
SPEC_TYPES.append(RevisionSpec_revno)
 
199
 
 
200
 
 
201
class RevisionSpec_revid(RevisionSpec):
468
202
    prefix = 'revid:'
469
203
 
470
 
    def _as_revision_id(self, context_branch):
471
 
        # self.spec comes straight from parsing the command line arguments,
472
 
        # so we expect it to be a Unicode string. Switch it to the internal
473
 
        # representation.
474
 
        if isinstance(self.spec, text_type):
475
 
            return cache_utf8.encode(self.spec)
476
 
        return self.spec
 
204
    def _match_on(self, branch, revs):
 
205
        try:
 
206
            return RevisionInfo(branch, revs.index(self.spec) + 1, self.spec)
 
207
        except ValueError:
 
208
            return RevisionInfo(branch, None, self.spec)
 
209
 
 
210
SPEC_TYPES.append(RevisionSpec_revid)
477
211
 
478
212
 
479
213
class RevisionSpec_last(RevisionSpec):
480
 
    """Selects the nth revision from the end."""
481
 
 
482
 
    help_txt = """Selects the nth revision from the end.
483
 
 
484
 
    Supply a positive number to get the nth revision from the end.
485
 
    This is the same as supplying negative numbers to the 'revno:' spec.
486
 
    Examples::
487
 
 
488
 
      last:1        -> return the last revision
489
 
      last:3        -> return the revision 2 before the end.
490
 
    """
491
214
 
492
215
    prefix = 'last:'
493
216
 
494
217
    def _match_on(self, branch, revs):
495
 
        revno, revision_id = self._revno_and_revision_id(branch)
496
 
        return RevisionInfo(branch, revno, revision_id)
497
 
 
498
 
    def _revno_and_revision_id(self, context_branch):
499
 
        last_revno, last_revision_id = context_branch.last_revision_info()
500
 
 
501
 
        if self.spec == '':
502
 
            if not last_revno:
503
 
                raise errors.NoCommits(context_branch)
504
 
            return last_revno, last_revision_id
505
 
 
506
218
        try:
507
219
            offset = int(self.spec)
508
 
        except ValueError as e:
509
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
510
 
 
511
 
        if offset <= 0:
512
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
513
 
                                             'you must supply a positive value')
514
 
 
515
 
        revno = last_revno - offset + 1
516
 
        try:
517
 
            revision_id = context_branch.get_rev_id(revno)
518
 
        except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
519
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
520
 
        return revno, revision_id
521
 
 
522
 
    def _as_revision_id(self, context_branch):
523
 
        # We compute the revno as part of the process, but we don't really care
524
 
        # about it.
525
 
        revno, revision_id = self._revno_and_revision_id(context_branch)
526
 
        return revision_id
 
220
        except ValueError:
 
221
            return RevisionInfo(branch, None)
 
222
        else:
 
223
            if offset <= 0:
 
224
                raise BzrError('You must supply a positive value for --revision last:XXX')
 
225
            return RevisionInfo(branch, len(revs) - offset + 1)
 
226
 
 
227
SPEC_TYPES.append(RevisionSpec_last)
527
228
 
528
229
 
529
230
class RevisionSpec_before(RevisionSpec):
530
 
    """Selects the parent of the revision specified."""
531
 
 
532
 
    help_txt = """Selects the parent of the revision specified.
533
 
 
534
 
    Supply any revision spec to return the parent of that revision.  This is
535
 
    mostly useful when inspecting revisions that are not in the revision history
536
 
    of a branch.
537
 
 
538
 
    It is an error to request the parent of the null revision (before:0).
539
 
 
540
 
    Examples::
541
 
 
542
 
      before:1913    -> Return the parent of revno 1913 (revno 1912)
543
 
      before:revid:aaaa@bbbb-1234567890  -> return the parent of revision
544
 
                                            aaaa@bbbb-1234567890
545
 
      bzr diff -r before:1913..1913
546
 
            -> Find the changes between revision 1913 and its parent (1912).
547
 
               (What changes did revision 1913 introduce).
548
 
               This is equivalent to:  bzr diff -c 1913
549
 
    """
550
231
 
551
232
    prefix = 'before:'
552
 
 
 
233
    
553
234
    def _match_on(self, branch, revs):
554
 
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
555
 
        if r.revno == 0:
556
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
557
 
                                             'cannot go before the null: revision')
558
 
        if r.revno is None:
559
 
            # We need to use the repository history here
560
 
            rev = branch.repository.get_revision(r.rev_id)
561
 
            if not rev.parent_ids:
562
 
                revision_id = revision.NULL_REVISION
563
 
            else:
564
 
                revision_id = rev.parent_ids[0]
565
 
            revno = None
566
 
        else:
567
 
            revno = r.revno - 1
568
 
            try:
569
 
                revision_id = branch.get_rev_id(revno, revs)
570
 
            except (errors.NoSuchRevision, errors.RevnoOutOfBounds):
571
 
                raise errors.InvalidRevisionSpec(self.user_spec,
572
 
                                                 branch)
573
 
        return RevisionInfo(branch, revno, revision_id)
 
235
        r = RevisionSpec(self.spec)._match_on(branch, revs)
 
236
        if (r.revno is None) or (r.revno == 0):
 
237
            return r
 
238
        return RevisionInfo(branch, r.revno - 1)
574
239
 
575
 
    def _as_revision_id(self, context_branch):
576
 
        base_revision_id = RevisionSpec.from_string(
577
 
            self.spec)._as_revision_id(context_branch)
578
 
        if base_revision_id == revision.NULL_REVISION:
579
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
580
 
                                             'cannot go before the null: revision')
581
 
        context_repo = context_branch.repository
582
 
        with context_repo.lock_read():
583
 
            parent_map = context_repo.get_parent_map([base_revision_id])
584
 
        if base_revision_id not in parent_map:
585
 
            # Ghost, or unknown revision id
586
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
587
 
                                             'cannot find the matching revision')
588
 
        parents = parent_map[base_revision_id]
589
 
        if len(parents) < 1:
590
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
591
 
                                             'No parents for revision.')
592
 
        return parents[0]
 
240
SPEC_TYPES.append(RevisionSpec_before)
593
241
 
594
242
 
595
243
class RevisionSpec_tag(RevisionSpec):
596
 
    """Select a revision identified by tag name"""
597
 
 
598
 
    help_txt = """Selects a revision identified by a tag name.
599
 
 
600
 
    Tags are stored in the branch and created by the 'tag' command.
601
 
    """
602
 
 
603
244
    prefix = 'tag:'
604
 
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
605
245
 
606
246
    def _match_on(self, branch, revs):
607
 
        # Can raise tags not supported, NoSuchTag, etc
608
 
        return RevisionInfo.from_revision_id(branch,
609
 
                                             branch.tags.lookup_tag(self.spec))
610
 
 
611
 
    def _as_revision_id(self, context_branch):
612
 
        return context_branch.tags.lookup_tag(self.spec)
613
 
 
614
 
 
615
 
class _RevListToTimestamps(object):
616
 
    """This takes a list of revisions, and allows you to bisect by date"""
617
 
 
618
 
    __slots__ = ['branch']
619
 
 
620
 
    def __init__(self, branch):
 
247
        raise BzrError('tag: namespace registered, but not implemented.')
 
248
 
 
249
SPEC_TYPES.append(RevisionSpec_tag)
 
250
 
 
251
 
 
252
class RevisionSpec_revs:
 
253
    def __init__(self, revs, branch):
 
254
        self.revs = revs
621
255
        self.branch = branch
622
 
 
623
256
    def __getitem__(self, index):
624
 
        """Get the date of the index'd item"""
625
 
        r = self.branch.repository.get_revision(self.branch.get_rev_id(index))
 
257
        r = self.branch.repository.get_revision(self.revs[index])
626
258
        # TODO: Handle timezone.
627
259
        return datetime.datetime.fromtimestamp(r.timestamp)
628
 
 
629
260
    def __len__(self):
630
 
        return self.branch.revno()
 
261
        return len(self.revs)
631
262
 
632
263
 
633
264
class RevisionSpec_date(RevisionSpec):
634
 
    """Selects a revision on the basis of a datestamp."""
635
 
 
636
 
    help_txt = """Selects a revision on the basis of a datestamp.
637
 
 
638
 
    Supply a datestamp to select the first revision that matches the date.
639
 
    Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
640
 
    Matches the first entry after a given date (either at midnight or
641
 
    at a specified time).
642
 
 
643
 
    One way to display all the changes since yesterday would be::
644
 
 
645
 
        brz log -r date:yesterday..
646
 
 
647
 
    Examples::
648
 
 
649
 
      date:yesterday            -> select the first revision since yesterday
650
 
      date:2006-08-14,17:10:14  -> select the first revision after
651
 
                                   August 14th, 2006 at 5:10pm.
652
 
    """
653
265
    prefix = 'date:'
654
 
    _date_regex = lazy_regex.lazy_compile(
655
 
        r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
656
 
        r'(,|T)?\s*'
657
 
        r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
266
    _date_re = re.compile(
 
267
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
268
            r'(,|T)?\s*'
 
269
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
658
270
        )
659
271
 
660
272
    def _match_on(self, branch, revs):
661
 
        """Spec for date revisions:
 
273
        """
 
274
        Spec for date revisions:
662
275
          date:value
663
276
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
664
277
          matches the first entry after a given date (either at midnight or
665
278
          at a specified time).
 
279
 
 
280
          So the proper way of saying 'give me all entries for today' is:
 
281
              -r date:today..date:tomorrow
666
282
        """
667
 
        #  XXX: This doesn't actually work
668
 
        #  So the proper way of saying 'give me all entries for today' is:
669
 
        #      -r date:yesterday..date:today
670
 
        today = datetime.datetime.fromordinal(
671
 
            datetime.date.today().toordinal())
 
283
        today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
672
284
        if self.spec.lower() == 'yesterday':
673
285
            dt = today - datetime.timedelta(days=1)
674
286
        elif self.spec.lower() == 'today':
676
288
        elif self.spec.lower() == 'tomorrow':
677
289
            dt = today + datetime.timedelta(days=1)
678
290
        else:
679
 
            m = self._date_regex.match(self.spec)
 
291
            m = self._date_re.match(self.spec)
680
292
            if not m or (not m.group('date') and not m.group('time')):
681
 
                raise errors.InvalidRevisionSpec(self.user_spec,
682
 
                                                 branch, 'invalid date')
683
 
 
684
 
            try:
685
 
                if m.group('date'):
686
 
                    year = int(m.group('year'))
687
 
                    month = int(m.group('month'))
688
 
                    day = int(m.group('day'))
689
 
                else:
690
 
                    year = today.year
691
 
                    month = today.month
692
 
                    day = today.day
693
 
 
694
 
                if m.group('time'):
695
 
                    hour = int(m.group('hour'))
696
 
                    minute = int(m.group('minute'))
697
 
                    if m.group('second'):
698
 
                        second = int(m.group('second'))
699
 
                    else:
700
 
                        second = 0
701
 
                else:
702
 
                    hour, minute, second = 0, 0, 0
703
 
            except ValueError:
704
 
                raise errors.InvalidRevisionSpec(self.user_spec,
705
 
                                                 branch, 'invalid date')
 
293
                raise BzrError('Invalid revision date %r' % self.spec)
 
294
 
 
295
            if m.group('date'):
 
296
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
 
297
            else:
 
298
                year, month, day = today.year, today.month, today.day
 
299
            if m.group('time'):
 
300
                hour = int(m.group('hour'))
 
301
                minute = int(m.group('minute'))
 
302
                if m.group('second'):
 
303
                    second = int(m.group('second'))
 
304
                else:
 
305
                    second = 0
 
306
            else:
 
307
                hour, minute, second = 0,0,0
706
308
 
707
309
            dt = datetime.datetime(year=year, month=month, day=day,
708
 
                                   hour=hour, minute=minute, second=second)
709
 
        with branch.lock_read():
710
 
            rev = bisect.bisect(_RevListToTimestamps(branch), dt, 1)
711
 
        if rev == branch.revno():
712
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
713
 
        return RevisionInfo(branch, rev)
 
310
                    hour=hour, minute=minute, second=second)
 
311
        branch.lock_read()
 
312
        try:
 
313
            rev = bisect.bisect(RevisionSpec_revs(revs, branch), dt)
 
314
        finally:
 
315
            branch.unlock()
 
316
        if rev == len(revs):
 
317
            return RevisionInfo(branch, None)
 
318
        else:
 
319
            return RevisionInfo(branch, rev + 1)
 
320
 
 
321
SPEC_TYPES.append(RevisionSpec_date)
714
322
 
715
323
 
716
324
class RevisionSpec_ancestor(RevisionSpec):
717
 
    """Selects a common ancestor with a second branch."""
718
 
 
719
 
    help_txt = """Selects a common ancestor with a second branch.
720
 
 
721
 
    Supply the path to a branch to select the common ancestor.
722
 
 
723
 
    The common ancestor is the last revision that existed in both
724
 
    branches. Usually this is the branch point, but it could also be
725
 
    a revision that was merged.
726
 
 
727
 
    This is frequently used with 'diff' to return all of the changes
728
 
    that your branch introduces, while excluding the changes that you
729
 
    have not merged from the remote branch.
730
 
 
731
 
    Examples::
732
 
 
733
 
      ancestor:/path/to/branch
734
 
      $ bzr diff -r ancestor:../../mainline/branch
735
 
    """
736
325
    prefix = 'ancestor:'
737
326
 
738
327
    def _match_on(self, branch, revs):
739
 
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
740
 
        return self._find_revision_info(branch, self.spec)
741
 
 
742
 
    def _as_revision_id(self, context_branch):
743
 
        return self._find_revision_id(context_branch, self.spec)
744
 
 
745
 
    @staticmethod
746
 
    def _find_revision_info(branch, other_location):
747
 
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
748
 
                                                              other_location)
749
 
        return RevisionInfo(branch, None, revision_id)
750
 
 
751
 
    @staticmethod
752
 
    def _find_revision_id(branch, other_location):
753
 
        from .branch import Branch
754
 
 
755
 
        with branch.lock_read():
756
 
            revision_a = revision.ensure_null(branch.last_revision())
757
 
            if revision_a == revision.NULL_REVISION:
758
 
                raise errors.NoCommits(branch)
759
 
            if other_location == '':
760
 
                other_location = branch.get_parent()
761
 
            other_branch = Branch.open(other_location)
762
 
            with other_branch.lock_read():
763
 
                revision_b = revision.ensure_null(other_branch.last_revision())
764
 
                if revision_b == revision.NULL_REVISION:
765
 
                    raise errors.NoCommits(other_branch)
766
 
                graph = branch.repository.get_graph(other_branch.repository)
767
 
                rev_id = graph.find_unique_lca(revision_a, revision_b)
768
 
            if rev_id == revision.NULL_REVISION:
769
 
                raise errors.NoCommonAncestor(revision_a, revision_b)
770
 
            return rev_id
771
 
 
 
328
        from branch import Branch
 
329
        from revision import common_ancestor, MultipleRevisionSources
 
330
        other_branch = Branch.open_containing(self.spec)[0]
 
331
        revision_a = branch.last_revision()
 
332
        revision_b = other_branch.last_revision()
 
333
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
 
334
            if r is None:
 
335
                raise NoCommits(b)
 
336
        revision_source = MultipleRevisionSources(branch.repository,
 
337
                                                  other_branch.repository)
 
338
        rev_id = common_ancestor(revision_a, revision_b, revision_source)
 
339
        try:
 
340
            revno = branch.revision_id_to_revno(rev_id)
 
341
        except NoSuchRevision:
 
342
            revno = None
 
343
        return RevisionInfo(branch, revno, rev_id)
 
344
        
 
345
SPEC_TYPES.append(RevisionSpec_ancestor)
772
346
 
773
347
class RevisionSpec_branch(RevisionSpec):
774
 
    """Selects the last revision of a specified branch."""
775
 
 
776
 
    help_txt = """Selects the last revision of a specified branch.
777
 
 
778
 
    Supply the path to a branch to select its last revision.
779
 
 
780
 
    Examples::
781
 
 
782
 
      branch:/path/to/branch
 
348
    """A branch: revision specifier.
 
349
 
 
350
    This takes the path to a branch and returns its tip revision id.
783
351
    """
784
352
    prefix = 'branch:'
785
 
    dwim_catchable_exceptions = (errors.NotBranchError,)
786
353
 
787
354
    def _match_on(self, branch, revs):
788
 
        from .branch import Branch
789
 
        other_branch = Branch.open(self.spec)
 
355
        from branch import Branch
 
356
        other_branch = Branch.open_containing(self.spec)[0]
790
357
        revision_b = other_branch.last_revision()
791
 
        if revision_b in (None, revision.NULL_REVISION):
792
 
            raise errors.NoCommits(other_branch)
793
 
        if branch is None:
794
 
            branch = other_branch
795
 
        else:
796
 
            try:
797
 
                # pull in the remote revisions so we can diff
798
 
                branch.fetch(other_branch, revision_b)
799
 
            except errors.ReadOnlyError:
800
 
                branch = other_branch
801
 
        return RevisionInfo(branch, None, revision_b)
802
 
 
803
 
    def _as_revision_id(self, context_branch):
804
 
        from .branch import Branch
805
 
        other_branch = Branch.open(self.spec)
806
 
        last_revision = other_branch.last_revision()
807
 
        last_revision = revision.ensure_null(last_revision)
808
 
        context_branch.fetch(other_branch, last_revision)
809
 
        if last_revision == revision.NULL_REVISION:
810
 
            raise errors.NoCommits(other_branch)
811
 
        return last_revision
812
 
 
813
 
    def _as_tree(self, context_branch):
814
 
        from .branch import Branch
815
 
        other_branch = Branch.open(self.spec)
816
 
        last_revision = other_branch.last_revision()
817
 
        last_revision = revision.ensure_null(last_revision)
818
 
        if last_revision == revision.NULL_REVISION:
819
 
            raise errors.NoCommits(other_branch)
820
 
        return other_branch.repository.revision_tree(last_revision)
821
 
 
822
 
    def needs_branch(self):
823
 
        return False
824
 
 
825
 
    def get_branch(self):
826
 
        return self.spec
827
 
 
828
 
 
829
 
class RevisionSpec_submit(RevisionSpec_ancestor):
830
 
    """Selects a common ancestor with a submit branch."""
831
 
 
832
 
    help_txt = """Selects a common ancestor with the submit branch.
833
 
 
834
 
    Diffing against this shows all the changes that were made in this branch,
835
 
    and is a good predictor of what merge will do.  The submit branch is
836
 
    used by the bundle and merge directive commands.  If no submit branch
837
 
    is specified, the parent branch is used instead.
838
 
 
839
 
    The common ancestor is the last revision that existed in both
840
 
    branches. Usually this is the branch point, but it could also be
841
 
    a revision that was merged.
842
 
 
843
 
    Examples::
844
 
 
845
 
      $ bzr diff -r submit:
846
 
    """
847
 
 
848
 
    prefix = 'submit:'
849
 
 
850
 
    def _get_submit_location(self, branch):
851
 
        submit_location = branch.get_submit_branch()
852
 
        location_type = 'submit branch'
853
 
        if submit_location is None:
854
 
            submit_location = branch.get_parent()
855
 
            location_type = 'parent branch'
856
 
        if submit_location is None:
857
 
            raise errors.NoSubmitBranch(branch)
858
 
        trace.note(gettext('Using {0} {1}').format(location_type,
859
 
                                                   submit_location))
860
 
        return submit_location
861
 
 
862
 
    def _match_on(self, branch, revs):
863
 
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
864
 
        return self._find_revision_info(branch,
865
 
                                        self._get_submit_location(branch))
866
 
 
867
 
    def _as_revision_id(self, context_branch):
868
 
        return self._find_revision_id(context_branch,
869
 
                                      self._get_submit_location(context_branch))
870
 
 
871
 
 
872
 
class RevisionSpec_annotate(RevisionIDSpec):
873
 
 
874
 
    prefix = 'annotate:'
875
 
 
876
 
    help_txt = """Select the revision that last modified the specified line.
877
 
 
878
 
    Select the revision that last modified the specified line.  Line is
879
 
    specified as path:number.  Path is a relative path to the file.  Numbers
880
 
    start at 1, and are relative to the current version, not the last-
881
 
    committed version of the file.
882
 
    """
883
 
 
884
 
    def _raise_invalid(self, numstring, context_branch):
885
 
        raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
886
 
                                         'No such line: %s' % numstring)
887
 
 
888
 
    def _as_revision_id(self, context_branch):
889
 
        path, numstring = self.spec.rsplit(':', 1)
890
 
        try:
891
 
            index = int(numstring) - 1
892
 
        except ValueError:
893
 
            self._raise_invalid(numstring, context_branch)
894
 
        tree, file_path = workingtree.WorkingTree.open_containing(path)
895
 
        with tree.lock_read():
896
 
            if not tree.has_filename(file_path):
897
 
                raise errors.InvalidRevisionSpec(self.user_spec,
898
 
                                                 context_branch, "File '%s' is not versioned." %
899
 
                                                 file_path)
900
 
            revision_ids = [r for (r, l) in tree.annotate_iter(file_path)]
901
 
        try:
902
 
            revision_id = revision_ids[index]
903
 
        except IndexError:
904
 
            self._raise_invalid(numstring, context_branch)
905
 
        if revision_id == revision.CURRENT_REVISION:
906
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
907
 
                                             'Line %s has not been committed.' % numstring)
908
 
        return revision_id
909
 
 
910
 
 
911
 
class RevisionSpec_mainline(RevisionIDSpec):
912
 
 
913
 
    help_txt = """Select mainline revision that merged the specified revision.
914
 
 
915
 
    Select the revision that merged the specified revision into mainline.
916
 
    """
917
 
 
918
 
    prefix = 'mainline:'
919
 
 
920
 
    def _as_revision_id(self, context_branch):
921
 
        revspec = RevisionSpec.from_string(self.spec)
922
 
        if revspec.get_branch() is None:
923
 
            spec_branch = context_branch
924
 
        else:
925
 
            spec_branch = _mod_branch.Branch.open(revspec.get_branch())
926
 
        revision_id = revspec.as_revision_id(spec_branch)
927
 
        graph = context_branch.repository.get_graph()
928
 
        result = graph.find_lefthand_merger(revision_id,
929
 
                                            context_branch.last_revision())
930
 
        if result is None:
931
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
932
 
        return result
933
 
 
934
 
 
935
 
# The order in which we want to DWIM a revision spec without any prefix.
936
 
# revno is always tried first and isn't listed here, this is used by
937
 
# RevisionSpec_dwim._match_on
938
 
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_tag)
939
 
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_revid)
940
 
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_date)
941
 
RevisionSpec_dwim.append_possible_revspec(RevisionSpec_branch)
942
 
 
943
 
revspec_registry = registry.Registry()
944
 
 
945
 
 
946
 
def _register_revspec(revspec):
947
 
    revspec_registry.register(revspec.prefix, revspec)
948
 
 
949
 
 
950
 
_register_revspec(RevisionSpec_revno)
951
 
_register_revspec(RevisionSpec_revid)
952
 
_register_revspec(RevisionSpec_last)
953
 
_register_revspec(RevisionSpec_before)
954
 
_register_revspec(RevisionSpec_tag)
955
 
_register_revspec(RevisionSpec_date)
956
 
_register_revspec(RevisionSpec_ancestor)
957
 
_register_revspec(RevisionSpec_branch)
958
 
_register_revspec(RevisionSpec_submit)
959
 
_register_revspec(RevisionSpec_annotate)
960
 
_register_revspec(RevisionSpec_mainline)
 
358
        if revision_b is None:
 
359
            raise NoCommits(other_branch)
 
360
        # pull in the remote revisions so we can diff
 
361
        branch.fetch(other_branch, revision_b)
 
362
        try:
 
363
            revno = branch.revision_id_to_revno(revision_b)
 
364
        except NoSuchRevision:
 
365
            revno = None
 
366
        return RevisionInfo(branch, revno, revision_b)
 
367
        
 
368
SPEC_TYPES.append(RevisionSpec_branch)