~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Patch Queue Manager
  • Date: 2011-09-22 14:12:18 UTC
  • mfrom: (6155.3.1 jam)
  • Revision ID: pqm@pqm.ubuntu.com-20110922141218-86s4uu6nqvourw4f
(jameinel) Cleanup comments bzrlib/smart/__init__.py (John A Meinel)

Show diffs side-by-side

added added

removed removed

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