~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Martin Pool
  • Date: 2010-02-23 07:43:11 UTC
  • mfrom: (4797.2.20 2.1)
  • mto: This revision was merged to the branch mainline in revision 5055.
  • Revision ID: mbp@sourcefrog.net-20100223074311-gnj55xdhrgz9l94e
Merge 2.1 back to trunk

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
import re
 
19
 
 
20
from bzrlib.lazy_import import lazy_import
 
21
lazy_import(globals(), """
 
22
import bisect
18
23
import datetime
19
 
import re
20
 
from bzrlib.errors import BzrError, NoSuchRevision, NoCommits
 
24
""")
 
25
 
 
26
from bzrlib import (
 
27
    errors,
 
28
    osutils,
 
29
    registry,
 
30
    revision,
 
31
    symbol_versioning,
 
32
    trace,
 
33
    )
 
34
 
21
35
 
22
36
_marker = []
23
37
 
 
38
 
24
39
class RevisionInfo(object):
25
 
    """The results of applying a revision specification to a branch.
 
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.
26
43
 
27
44
    An instance has two useful attributes: revno, and rev_id.
28
45
 
57
74
        # TODO: otherwise, it should depend on how I was built -
58
75
        # if it's in_history(branch), then check revision_history(),
59
76
        # if it's in_store(branch), do the check below
60
 
        return self.branch.revision_store.has_id(self.rev_id)
 
77
        return self.branch.repository.has_revision(self.rev_id)
61
78
 
62
79
    def __len__(self):
63
80
        return 2
68
85
        raise IndexError(index)
69
86
 
70
87
    def get(self):
71
 
        return self.branch.get_revision(self.rev_id)
 
88
        return self.branch.repository.get_revision(self.rev_id)
72
89
 
73
90
    def __eq__(self, other):
74
91
        if type(other) not in (tuple, list, type(self)):
81
98
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
82
99
            self.revno, self.rev_id, self.branch)
83
100
 
84
 
# classes in this list should have a "prefix" attribute, against which
85
 
# string specs are matched
86
 
SPEC_TYPES = []
 
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
 
87
118
 
88
119
class RevisionSpec(object):
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.
 
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.
95
128
 
96
129
    Revision specs are an UI element, and they have been moved out
97
130
    of the branch class to leave "back-end" classes unaware of such
103
136
    """
104
137
 
105
138
    prefix = None
106
 
 
107
 
    def __new__(cls, spec, foo=_marker):
108
 
        """Parse a revision specifier.
 
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.
109
156
        """
 
157
        if not isinstance(spec, (type(None), basestring)):
 
158
            raise TypeError('error')
 
159
 
110
160
        if spec is None:
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):
 
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:
121
169
            for spectype in SPEC_TYPES:
122
170
                if spec.startswith(spectype.prefix):
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):
 
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
131
192
        if self.prefix and spec.startswith(self.prefix):
132
193
            spec = spec[len(self.prefix):]
133
194
        self.spec = spec
134
195
 
135
196
    def _match_on(self, branch, revs):
136
 
        return RevisionInfo(branch, 0, None)
 
197
        trace.mutter('Returning RevisionSpec._match_on: None')
 
198
        return RevisionInfo(branch, None, None)
137
199
 
138
200
    def _match_on_and_check(self, branch, revs):
139
201
        info = self._match_on(branch, revs)
140
202
        if info:
141
203
            return info
142
 
        elif info == (0, None):
143
 
            # special case - the empty tree
 
204
        elif info == (None, None):
 
205
            # special case - nothing supplied
144
206
            return info
145
207
        elif self.prefix:
146
 
            raise NoSuchRevision(branch, self.prefix + str(self.spec))
 
208
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
147
209
        else:
148
 
            raise NoSuchRevision(branch, str(self.spec))
 
210
            raise errors.InvalidRevisionSpec(self.spec, branch)
149
211
 
150
212
    def in_history(self, branch):
151
 
        revs = branch.revision_history()
 
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
152
222
        return self._match_on_and_check(branch, revs)
153
223
 
154
224
        # FIXME: in_history is somewhat broken,
160
230
    # will do what you expect.
161
231
    in_store = in_history
162
232
    in_branch = in_store
163
 
        
 
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
 
164
269
    def __repr__(self):
165
270
        # this is mostly for helping with testing
166
 
        return '<%s %s%s>' % (self.__class__.__name__,
167
 
                              self.prefix or '',
168
 
                              self.spec)
 
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
169
287
 
170
288
 
171
289
# private API
172
290
 
173
 
class RevisionSpec_int(RevisionSpec):
174
 
    """Spec is a number.  Special case."""
175
 
    def __init__(self, spec):
176
 
        self.spec = int(spec)
 
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)
177
310
 
178
311
    def _match_on(self, branch, revs):
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)
 
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)
185
335
 
186
336
 
187
337
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
    """
188
358
    prefix = 'revno:'
 
359
    wants_revision_history = False
189
360
 
190
361
    def _match_on(self, branch, revs):
191
362
        """Lookup a revision by revision number"""
192
 
        try:
193
 
            return RevisionInfo(branch, int(self.spec))
194
 
        except ValueError:
195
 
            return RevisionInfo(branch, None)
196
 
 
197
 
SPEC_TYPES.append(RevisionSpec_revno)
 
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
 
198
445
 
199
446
 
200
447
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
 
201
460
    prefix = 'revid:'
202
461
 
203
462
    def _match_on(self, branch, revs):
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)
 
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
 
210
472
 
211
473
 
212
474
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
    """
213
486
 
214
487
    prefix = 'last:'
215
488
 
216
489
    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
 
217
501
        try:
218
502
            offset = int(self.spec)
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)
 
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
 
227
523
 
228
524
 
229
525
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
    """
230
546
 
231
547
    prefix = 'before:'
232
 
    
 
548
 
233
549
    def _match_on(self, branch, revs):
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)
 
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
 
240
597
 
241
598
 
242
599
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
 
243
607
    prefix = 'tag:'
 
608
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
244
609
 
245
610
    def _match_on(self, branch, revs):
246
 
        raise BzrError('tag: namespace registered, but not implemented.')
247
 
 
248
 
SPEC_TYPES.append(RevisionSpec_tag)
 
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)
249
638
 
250
639
 
251
640
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
    """
252
660
    prefix = 'date:'
253
661
    _date_re = re.compile(
254
662
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
257
665
        )
258
666
 
259
667
    def _match_on(self, branch, revs):
260
 
        """
261
 
        Spec for date revisions:
 
668
        """Spec for date revisions:
262
669
          date:value
263
670
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
264
671
          matches the first entry after a given date (either at midnight or
265
672
          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
269
673
        """
 
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
270
677
        today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
271
678
        if self.spec.lower() == 'yesterday':
272
679
            dt = today - datetime.timedelta(days=1)
277
684
        else:
278
685
            m = self._date_re.match(self.spec)
279
686
            if not m or (not m.group('date') and not m.group('time')):
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
 
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')
295
712
 
296
713
            dt = datetime.datetime(year=year, month=month, day=day,
297
714
                    hour=hour, minute=minute, second=second)
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)
 
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)
306
724
 
307
 
SPEC_TYPES.append(RevisionSpec_date)
308
725
 
309
726
 
310
727
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
    """
311
747
    prefix = 'ancestor:'
312
748
 
313
749
    def _match_on(self, branch, revs):
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)
 
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)
324
760
        try:
325
 
            revno = branch.revision_id_to_revno(rev_id)
326
 
        except NoSuchRevision:
 
761
            revno = branch.revision_id_to_revno(revision_id)
 
762
        except errors.NoSuchRevision:
327
763
            revno = None
328
 
        return RevisionInfo(branch, revno, rev_id)
329
 
        
330
 
SPEC_TYPES.append(RevisionSpec_ancestor)
 
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
 
331
795
 
332
796
class RevisionSpec_branch(RevisionSpec):
333
 
    """A branch: revision specifier.
334
 
 
335
 
    This takes the path to a branch and returns its tip revision id.
 
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
336
806
    """
337
807
    prefix = 'branch:'
 
808
    dwim_catchable_exceptions = (errors.NotBranchError,)
338
809
 
339
810
    def _match_on(self, branch, revs):
340
 
        from branch import Branch
341
 
        from fetch import greedy_fetch
342
 
        other_branch = Branch.open_containing(self.spec)[0]
 
811
        from bzrlib.branch import Branch
 
812
        other_branch = Branch.open(self.spec)
343
813
        revision_b = other_branch.last_revision()
344
 
        if revision_b is None:
345
 
            raise NoCommits(other_branch)
 
814
        if revision_b in (None, revision.NULL_REVISION):
 
815
            raise errors.NoCommits(other_branch)
346
816
        # pull in the remote revisions so we can diff
347
 
        greedy_fetch(branch, other_branch, revision=revision_b)
 
817
        branch.fetch(other_branch, revision_b)
348
818
        try:
349
819
            revno = branch.revision_id_to_revno(revision_b)
350
 
        except NoSuchRevision:
 
820
        except errors.NoSuchRevision:
351
821
            revno = None
352
822
        return RevisionInfo(branch, revno, revision_b)
353
 
        
354
 
SPEC_TYPES.append(RevisionSpec_branch)
 
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", [])