~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.

This is used to replace various ad hoc implementations of the same logic,
notably the version used in registry's _LazyObjectGetter which had a bug when
getting a module without also getting a member.  And of course, this new
function has unit tests, unlike the replaced code.

This also adds a KnownHooksRegistry subclass to provide a more natural home for
some other logic.

I'm not thrilled about the name of the new module or the new functions, but it's
hard to think of good names for such generic functionality.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
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
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(), """
18
22
import bisect
19
23
import datetime
20
 
import re
 
24
""")
21
25
 
22
26
from bzrlib import (
23
27
    errors,
 
28
    osutils,
 
29
    registry,
24
30
    revision,
25
31
    symbol_versioning,
26
32
    trace,
27
 
    tsort,
28
33
    )
29
34
 
30
35
 
93
98
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
94
99
            self.revno, self.rev_id, self.branch)
95
100
 
96
 
 
97
 
# classes in this list should have a "prefix" attribute, against which
98
 
# string specs are matched
99
 
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
 
100
116
_revno_regex = None
101
117
 
102
118
 
105
121
 
106
122
    help_txt = """A parsed revision specification.
107
123
 
108
 
    A revision specification can be an integer, in which case it is
109
 
    assumed to be a revno (though this will translate negative values
110
 
    into positive ones); or it can be a string, in which case it is
111
 
    parsed for something like 'date:' or 'revid:' etc.
 
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.
112
128
 
113
129
    Revision specs are an UI element, and they have been moved out
114
130
    of the branch class to leave "back-end" classes unaware of such
120
136
    """
121
137
 
122
138
    prefix = None
123
 
 
124
 
    def __new__(cls, spec, _internal=False):
125
 
        if _internal:
126
 
            return object.__new__(cls, spec, _internal=_internal)
127
 
 
128
 
        symbol_versioning.warn('Creating a RevisionSpec directly has'
129
 
                               ' been deprecated in version 0.11. Use'
130
 
                               ' RevisionSpec.from_string()'
131
 
                               ' instead.',
132
 
                               DeprecationWarning, stacklevel=2)
133
 
        return RevisionSpec.from_string(spec)
 
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
    """
134
148
 
135
149
    @staticmethod
136
150
    def from_string(spec):
145
159
 
146
160
        if spec is None:
147
161
            return RevisionSpec(None, _internal=True)
148
 
 
149
 
        assert isinstance(spec, basestring), \
150
 
            "You should only supply strings not %s" % (type(spec),)
151
 
 
152
 
        for spectype in SPEC_TYPES:
153
 
            if spec.startswith(spectype.prefix):
154
 
                trace.mutter('Returning RevisionSpec %s for %s',
155
 
                             spectype.__name__, spec)
156
 
                return spectype(spec, _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)
157
168
        else:
158
 
            # RevisionSpec_revno is special cased, because it is the only
159
 
            # one that directly handles plain integers
160
 
            # TODO: This should not be special cased rather it should be
161
 
            # a method invocation on spectype.canparse()
162
 
            global _revno_regex
163
 
            if _revno_regex is None:
164
 
                _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
165
 
            if _revno_regex.match(spec) is not None:
166
 
                return RevisionSpec_revno(spec, _internal=True)
167
 
 
168
 
            raise errors.NoSuchRevisionSpec(spec)
 
169
            for spectype in SPEC_TYPES:
 
170
                if spec.startswith(spectype.prefix):
 
171
                    trace.mutter('Returning RevisionSpec %s for %s',
 
172
                                 spectype.__name__, spec)
 
173
                    return spectype(spec, _internal=True)
 
174
            # 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)
169
177
 
170
178
    def __init__(self, spec, _internal=False):
171
179
        """Create a RevisionSpec referring to the Null revision.
175
183
            called directly. Only from RevisionSpec.from_string()
176
184
        """
177
185
        if not _internal:
178
 
            # XXX: Update this after 0.10 is released
179
186
            symbol_versioning.warn('Creating a RevisionSpec directly has'
180
187
                                   ' been deprecated in version 0.11. Use'
181
188
                                   ' RevisionSpec.from_string()'
188
195
 
189
196
    def _match_on(self, branch, revs):
190
197
        trace.mutter('Returning RevisionSpec._match_on: None')
191
 
        return RevisionInfo(branch, 0, None)
 
198
        return RevisionInfo(branch, None, None)
192
199
 
193
200
    def _match_on_and_check(self, branch, revs):
194
201
        info = self._match_on(branch, revs)
195
202
        if info:
196
203
            return info
197
 
        elif info == (0, None):
198
 
            # special case - the empty tree
 
204
        elif info == (None, None):
 
205
            # special case - nothing supplied
199
206
            return info
200
207
        elif self.prefix:
201
208
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
204
211
 
205
212
    def in_history(self, branch):
206
213
        if branch:
207
 
            revs = branch.revision_history()
 
214
            if self.wants_revision_history:
 
215
                revs = branch.revision_history()
 
216
            else:
 
217
                revs = None
208
218
        else:
209
219
            # this should never trigger.
210
220
            # TODO: make it a deprecated code path. RBC 20060928
220
230
    # will do what you expect.
221
231
    in_store = in_history
222
232
    in_branch = in_store
223
 
        
 
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
 
224
269
    def __repr__(self):
225
270
        # this is mostly for helping with testing
226
271
        return '<%s %s>' % (self.__class__.__name__,
227
272
                              self.user_spec)
228
 
    
 
273
 
229
274
    def needs_branch(self):
230
275
        """Whether this revision spec needs a branch.
231
276
 
235
280
 
236
281
    def get_branch(self):
237
282
        """When the revision specifier contains a branch location, return it.
238
 
        
 
283
 
239
284
        Otherwise, return None.
240
285
        """
241
286
        return None
243
288
 
244
289
# private API
245
290
 
 
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)
 
310
 
 
311
    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)
 
335
 
 
336
 
246
337
class RevisionSpec_revno(RevisionSpec):
247
338
    """Selects a revision using a number."""
248
339
 
249
340
    help_txt = """Selects a revision using a number.
250
341
 
251
342
    Use an integer to specify a revision in the history of the branch.
252
 
    Optionally a branch can be specified. The 'revno:' prefix is optional.
253
 
    A negative number will count from the end of the branch (-1 is the
254
 
    last revision, -2 the previous one). If the negative number is larger
255
 
    than the branch's history, the first revision is returned.
256
 
    examples:
257
 
      revno:1                   -> return the first revision
 
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
258
350
      revno:3:/path/to/branch   -> return the 3rd revision of
259
351
                                   the branch '/path/to/branch'
260
352
      revno:-1                  -> The last revision in a branch.
264
356
                                   your history is very long.
265
357
    """
266
358
    prefix = 'revno:'
 
359
    wants_revision_history = False
267
360
 
268
361
    def _match_on(self, branch, revs):
269
362
        """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):
270
367
        loc = self.spec.find(':')
271
368
        if loc == -1:
272
369
            revno_spec = self.spec
286
383
                dotted = False
287
384
            except ValueError:
288
385
                # dotted decimal. This arguably should not be here
289
 
                # but the from_string method is a little primitive 
 
386
                # but the from_string method is a little primitive
290
387
                # right now - RBC 20060928
291
388
                try:
292
389
                    match_revno = tuple((int(number) for number in revno_spec.split('.')))
301
398
            # the branch object.
302
399
            from bzrlib.branch import Branch
303
400
            branch = Branch.open(branch_spec)
304
 
            # Need to use a new revision history
305
 
            # because we are using a specific branch
306
 
            revs = branch.revision_history()
 
401
            revs_or_none = None
307
402
 
308
403
        if dotted:
309
 
            branch.lock_read()
310
404
            try:
311
 
                last_rev = branch.last_revision()
312
 
                merge_sorted_revisions = tsort.merge_sort(
313
 
                    branch.repository.get_revision_graph(last_rev),
314
 
                    last_rev,
315
 
                    generate_revno=True)
316
 
                def match(item):
317
 
                    return item[3] == match_revno
318
 
                revisions = filter(match, merge_sorted_revisions)
319
 
            finally:
320
 
                branch.unlock()
321
 
            if len(revisions) != 1:
322
 
                return RevisionInfo(branch, None, None)
 
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)
323
409
            else:
324
410
                # there is no traditional 'revno' for dotted-decimal revnos.
325
411
                # so for  API compatability we return None.
326
 
                return RevisionInfo(branch, None, revisions[0][1])
 
412
                return branch, None, revision_id
327
413
        else:
 
414
            last_revno, last_revision_id = branch.last_revision_info()
328
415
            if revno < 0:
329
416
                # if get_rev_id supported negative revnos, there would not be a
330
417
                # need for this special case.
331
 
                if (-revno) >= len(revs):
 
418
                if (-revno) >= last_revno:
332
419
                    revno = 1
333
420
                else:
334
 
                    revno = len(revs) + revno + 1
 
421
                    revno = last_revno + revno + 1
335
422
            try:
336
 
                revision_id = branch.get_rev_id(revno, revs)
 
423
                revision_id = branch.get_rev_id(revno, revs_or_none)
337
424
            except errors.NoSuchRevision:
338
425
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
339
 
        return RevisionInfo(branch, revno, revision_id)
340
 
        
 
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
 
341
433
    def needs_branch(self):
342
434
        return self.spec.find(':') == -1
343
435
 
347
439
        else:
348
440
            return self.spec[self.spec.find(':')+1:]
349
441
 
350
 
# Old compatibility 
 
442
# Old compatibility
351
443
RevisionSpec_int = RevisionSpec_revno
352
444
 
353
 
SPEC_TYPES.append(RevisionSpec_revno)
354
445
 
355
446
 
356
447
class RevisionSpec_revid(RevisionSpec):
359
450
    help_txt = """Selects a revision using the revision id.
360
451
 
361
452
    Supply a specific revision id, that can be used to specify any
362
 
    revision id in the ancestry of the branch. 
 
453
    revision id in the ancestry of the branch.
363
454
    Including merges, and pending merges.
364
 
    examples:
 
455
    Examples::
 
456
 
365
457
      revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
366
 
    """    
 
458
    """
 
459
 
367
460
    prefix = 'revid:'
368
461
 
369
462
    def _match_on(self, branch, revs):
370
 
        try:
371
 
            revno = revs.index(self.spec) + 1
372
 
        except ValueError:
373
 
            revno = None
374
 
        return RevisionInfo(branch, revno, self.spec)
375
 
 
376
 
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
 
377
472
 
378
473
 
379
474
class RevisionSpec_last(RevisionSpec):
383
478
 
384
479
    Supply a positive number to get the nth revision from the end.
385
480
    This is the same as supplying negative numbers to the 'revno:' spec.
386
 
    examples:
 
481
    Examples::
 
482
 
387
483
      last:1        -> return the last revision
388
484
      last:3        -> return the revision 2 before the end.
389
 
    """    
 
485
    """
390
486
 
391
487
    prefix = 'last:'
392
488
 
393
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
 
394
496
        if self.spec == '':
395
 
            if not revs:
396
 
                raise errors.NoCommits(branch)
397
 
            return RevisionInfo(branch, len(revs), revs[-1])
 
497
            if not last_revno:
 
498
                raise errors.NoCommits(context_branch)
 
499
            return last_revno, last_revision_id
398
500
 
399
501
        try:
400
502
            offset = int(self.spec)
401
503
        except ValueError, e:
402
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
 
504
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
403
505
 
404
506
        if offset <= 0:
405
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
507
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
406
508
                                             'you must supply a positive value')
407
 
        revno = len(revs) - offset + 1
 
509
 
 
510
        revno = last_revno - offset + 1
408
511
        try:
409
 
            revision_id = branch.get_rev_id(revno, revs)
 
512
            revision_id = context_branch.get_rev_id(revno, revs_or_none)
410
513
        except errors.NoSuchRevision:
411
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
412
 
        return RevisionInfo(branch, revno, revision_id)
413
 
 
414
 
SPEC_TYPES.append(RevisionSpec_last)
 
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
 
415
523
 
416
524
 
417
525
class RevisionSpec_before(RevisionSpec):
419
527
 
420
528
    help_txt = """Selects the parent of the revision specified.
421
529
 
422
 
    Supply any revision spec to return the parent of that revision.
 
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
 
423
534
    It is an error to request the parent of the null revision (before:0).
424
 
    This is mostly useful when inspecting revisions that are not in the
425
 
    revision history of a branch.
426
 
 
427
 
    examples:
 
535
 
 
536
    Examples::
 
537
 
428
538
      before:1913    -> Return the parent of revno 1913 (revno 1912)
429
539
      before:revid:aaaa@bbbb-1234567890  -> return the parent of revision
430
540
                                            aaaa@bbbb-1234567890
431
 
      bzr diff -r before:revid:aaaa..revid:aaaa
432
 
            -> Find the changes between revision 'aaaa' and its parent.
433
 
               (what changes did 'aaaa' introduce)
 
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
434
545
    """
435
546
 
436
547
    prefix = 'before:'
437
 
    
 
548
 
438
549
    def _match_on(self, branch, revs):
439
550
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
440
551
        if r.revno == 0:
445
556
            rev = branch.repository.get_revision(r.rev_id)
446
557
            if not rev.parent_ids:
447
558
                revno = 0
448
 
                revision_id = None
 
559
                revision_id = revision.NULL_REVISION
449
560
            else:
450
561
                revision_id = rev.parent_ids[0]
451
562
                try:
461
572
                                                 branch)
462
573
        return RevisionInfo(branch, revno, revision_id)
463
574
 
464
 
SPEC_TYPES.append(RevisionSpec_before)
 
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
 
465
597
 
466
598
 
467
599
class RevisionSpec_tag(RevisionSpec):
468
 
    """To be implemented."""
469
 
 
470
 
    help_txt = """To be implemented."""
 
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
    """
471
606
 
472
607
    prefix = 'tag:'
 
608
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
473
609
 
474
610
    def _match_on(self, branch, revs):
475
 
        raise errors.InvalidRevisionSpec(self.user_spec, branch,
476
 
                                         'tag: namespace registered,'
477
 
                                         ' but not implemented')
478
 
 
479
 
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
 
480
619
 
481
620
 
482
621
class _RevListToTimestamps(object):
508
647
    Matches the first entry after a given date (either at midnight or
509
648
    at a specified time).
510
649
 
511
 
    One way to display all the changes since yesterday would be:
512
 
        bzr log -r date:yesterday..-1
513
 
 
514
 
    examples:
 
650
    One way to display all the changes since yesterday would be::
 
651
 
 
652
        bzr log -r date:yesterday..
 
653
 
 
654
    Examples::
 
655
 
515
656
      date:yesterday            -> select the first revision since yesterday
516
657
      date:2006-08-14,17:10:14  -> select the first revision after
517
658
                                   August 14th, 2006 at 5:10pm.
518
 
    """    
 
659
    """
519
660
    prefix = 'date:'
520
661
    _date_re = re.compile(
521
662
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
577
718
        finally:
578
719
            branch.unlock()
579
720
        if rev == len(revs):
580
 
            return RevisionInfo(branch, None)
 
721
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
581
722
        else:
582
723
            return RevisionInfo(branch, rev + 1)
583
724
 
584
 
SPEC_TYPES.append(RevisionSpec_date)
585
725
 
586
726
 
587
727
class RevisionSpec_ancestor(RevisionSpec):
599
739
    that your branch introduces, while excluding the changes that you
600
740
    have not merged from the remote branch.
601
741
 
602
 
    examples:
 
742
    Examples::
 
743
 
603
744
      ancestor:/path/to/branch
604
745
      $ bzr diff -r ancestor:../../mainline/branch
605
746
    """
606
747
    prefix = 'ancestor:'
607
748
 
608
749
    def _match_on(self, branch, revs):
609
 
        from bzrlib.branch import Branch
610
 
 
611
750
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
612
 
        other_branch = Branch.open(self.spec)
613
 
        revision_a = branch.last_revision()
614
 
        revision_b = other_branch.last_revision()
615
 
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
616
 
            if r in (None, revision.NULL_REVISION):
617
 
                raise errors.NoCommits(b)
618
 
        revision_source = revision.MultipleRevisionSources(
619
 
                branch.repository, other_branch.repository)
620
 
        rev_id = revision.common_ancestor(revision_a, revision_b,
621
 
                                          revision_source)
 
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)
622
760
        try:
623
 
            revno = branch.revision_id_to_revno(rev_id)
 
761
            revno = branch.revision_id_to_revno(revision_id)
624
762
        except errors.NoSuchRevision:
625
763
            revno = None
626
 
        return RevisionInfo(branch, revno, rev_id)
627
 
        
628
 
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
 
629
794
 
630
795
 
631
796
class RevisionSpec_branch(RevisionSpec):
635
800
 
636
801
    Supply the path to a branch to select its last revision.
637
802
 
638
 
    examples:
 
803
    Examples::
 
804
 
639
805
      branch:/path/to/branch
640
806
    """
641
807
    prefix = 'branch:'
 
808
    dwim_catchable_exceptions = (errors.NotBranchError,)
642
809
 
643
810
    def _match_on(self, branch, revs):
644
811
        from bzrlib.branch import Branch
646
813
        revision_b = other_branch.last_revision()
647
814
        if revision_b in (None, revision.NULL_REVISION):
648
815
            raise errors.NoCommits(other_branch)
649
 
        # pull in the remote revisions so we can diff
650
 
        branch.fetch(other_branch, revision_b)
 
816
        if branch is None:
 
817
            branch = other_branch
 
818
        else:
 
819
            try:
 
820
                # pull in the remote revisions so we can diff
 
821
                branch.fetch(other_branch, revision_b)
 
822
            except errors.ReadOnlyError:
 
823
                branch = other_branch
651
824
        try:
652
825
            revno = branch.revision_id_to_revno(revision_b)
653
826
        except errors.NoSuchRevision:
654
827
            revno = None
655
828
        return RevisionInfo(branch, revno, revision_b)
656
 
        
657
 
SPEC_TYPES.append(RevisionSpec_branch)
 
829
 
 
830
    def _as_revision_id(self, context_branch):
 
831
        from bzrlib.branch import Branch
 
832
        other_branch = Branch.open(self.spec)
 
833
        last_revision = other_branch.last_revision()
 
834
        last_revision = revision.ensure_null(last_revision)
 
835
        context_branch.fetch(other_branch, last_revision)
 
836
        if last_revision == revision.NULL_REVISION:
 
837
            raise errors.NoCommits(other_branch)
 
838
        return last_revision
 
839
 
 
840
    def _as_tree(self, context_branch):
 
841
        from bzrlib.branch import Branch
 
842
        other_branch = Branch.open(self.spec)
 
843
        last_revision = other_branch.last_revision()
 
844
        last_revision = revision.ensure_null(last_revision)
 
845
        if last_revision == revision.NULL_REVISION:
 
846
            raise errors.NoCommits(other_branch)
 
847
        return other_branch.repository.revision_tree(last_revision)
 
848
 
 
849
    def needs_branch(self):
 
850
        return False
 
851
 
 
852
    def get_branch(self):
 
853
        return self.spec
 
854
 
 
855
 
 
856
 
 
857
class RevisionSpec_submit(RevisionSpec_ancestor):
 
858
    """Selects a common ancestor with a submit branch."""
 
859
 
 
860
    help_txt = """Selects a common ancestor with the submit branch.
 
861
 
 
862
    Diffing against this shows all the changes that were made in this branch,
 
863
    and is a good predictor of what merge will do.  The submit branch is
 
864
    used by the bundle and merge directive commands.  If no submit branch
 
865
    is specified, the parent branch is used instead.
 
866
 
 
867
    The common ancestor is the last revision that existed in both
 
868
    branches. Usually this is the branch point, but it could also be
 
869
    a revision that was merged.
 
870
 
 
871
    Examples::
 
872
 
 
873
      $ bzr diff -r submit:
 
874
    """
 
875
 
 
876
    prefix = 'submit:'
 
877
 
 
878
    def _get_submit_location(self, branch):
 
879
        submit_location = branch.get_submit_branch()
 
880
        location_type = 'submit branch'
 
881
        if submit_location is None:
 
882
            submit_location = branch.get_parent()
 
883
            location_type = 'parent branch'
 
884
        if submit_location is None:
 
885
            raise errors.NoSubmitBranch(branch)
 
886
        trace.note('Using %s %s', location_type, submit_location)
 
887
        return submit_location
 
888
 
 
889
    def _match_on(self, branch, revs):
 
890
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
 
891
        return self._find_revision_info(branch,
 
892
            self._get_submit_location(branch))
 
893
 
 
894
    def _as_revision_id(self, context_branch):
 
895
        return self._find_revision_id(context_branch,
 
896
            self._get_submit_location(context_branch))
 
897
 
 
898
 
 
899
# The order in which we want to DWIM a revision spec without any prefix.
 
900
# revno is always tried first and isn't listed here, this is used by
 
901
# RevisionSpec_dwim._match_on
 
902
dwim_revspecs = [
 
903
    RevisionSpec_tag, # Let's try for a tag
 
904
    RevisionSpec_revid, # Maybe it's a revid?
 
905
    RevisionSpec_date, # Perhaps a date?
 
906
    RevisionSpec_branch, # OK, last try, maybe it's a branch
 
907
    ]
 
908
 
 
909
 
 
910
revspec_registry = registry.Registry()
 
911
def _register_revspec(revspec):
 
912
    revspec_registry.register(revspec.prefix, revspec)
 
913
 
 
914
_register_revspec(RevisionSpec_revno)
 
915
_register_revspec(RevisionSpec_revid)
 
916
_register_revspec(RevisionSpec_last)
 
917
_register_revspec(RevisionSpec_before)
 
918
_register_revspec(RevisionSpec_tag)
 
919
_register_revspec(RevisionSpec_date)
 
920
_register_revspec(RevisionSpec_ancestor)
 
921
_register_revspec(RevisionSpec_branch)
 
922
_register_revspec(RevisionSpec_submit)
 
923
 
 
924
# classes in this list should have a "prefix" attribute, against which
 
925
# string specs are matched
 
926
SPEC_TYPES = symbol_versioning.deprecated_list(
 
927
    symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])