~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

doc

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