~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Robert Collins
  • Date: 2010-05-06 07:48:22 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506074822-0bsgf2j4h8jx0xkk
Added ``bzrlib.tests.matchers`` as a place to put matchers, along with
our first in-tree matcher. See the module docstring for details.
(Robert Collins)

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
 
32
37
 
33
38
 
34
39
class RevisionInfo(object):
35
 
    """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.
36
43
 
37
44
    An instance has two useful attributes: revno, and rev_id.
38
45
 
91
98
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
92
99
            self.revno, self.rev_id, self.branch)
93
100
 
94
 
 
95
 
# classes in this list should have a "prefix" attribute, against which
96
 
# string specs are matched
97
 
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
 
98
116
_revno_regex = None
99
117
 
100
118
 
101
119
class RevisionSpec(object):
102
 
    """A parsed revision specification.
103
 
 
104
 
    A revision specification can be an integer, in which case it is
105
 
    assumed to be a revno (though this will translate negative values
106
 
    into positive ones); or it can be a string, in which case it is
107
 
    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.
108
128
 
109
129
    Revision specs are an UI element, and they have been moved out
110
130
    of the branch class to leave "back-end" classes unaware of such
116
136
    """
117
137
 
118
138
    prefix = None
119
 
 
120
 
    def __new__(cls, spec, _internal=False):
121
 
        if _internal:
122
 
            return object.__new__(cls, spec, _internal=_internal)
123
 
 
124
 
        symbol_versioning.warn('Creating a RevisionSpec directly has'
125
 
                               ' been deprecated in version 0.11. Use'
126
 
                               ' RevisionSpec.from_string()'
127
 
                               ' instead.',
128
 
                               DeprecationWarning, stacklevel=2)
129
 
        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
    """
130
148
 
131
149
    @staticmethod
132
150
    def from_string(spec):
141
159
 
142
160
        if spec is None:
143
161
            return RevisionSpec(None, _internal=True)
144
 
 
145
 
        assert isinstance(spec, basestring), \
146
 
            "You should only supply strings not %s" % (type(spec),)
147
 
 
148
 
        for spectype in SPEC_TYPES:
149
 
            if spec.startswith(spectype.prefix):
150
 
                trace.mutter('Returning RevisionSpec %s for %s',
151
 
                             spectype.__name__, spec)
152
 
                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)
153
168
        else:
154
 
            # RevisionSpec_revno is special cased, because it is the only
155
 
            # one that directly handles plain integers
156
 
            # TODO: This should not be special cased rather it should be
157
 
            # a method invocation on spectype.canparse()
158
 
            global _revno_regex
159
 
            if _revno_regex is None:
160
 
                _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
161
 
            if _revno_regex.match(spec) is not None:
162
 
                return RevisionSpec_revno(spec, _internal=True)
163
 
 
164
 
            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)
165
177
 
166
178
    def __init__(self, spec, _internal=False):
167
179
        """Create a RevisionSpec referring to the Null revision.
171
183
            called directly. Only from RevisionSpec.from_string()
172
184
        """
173
185
        if not _internal:
174
 
            # XXX: Update this after 0.10 is released
175
186
            symbol_versioning.warn('Creating a RevisionSpec directly has'
176
187
                                   ' been deprecated in version 0.11. Use'
177
188
                                   ' RevisionSpec.from_string()'
184
195
 
185
196
    def _match_on(self, branch, revs):
186
197
        trace.mutter('Returning RevisionSpec._match_on: None')
187
 
        return RevisionInfo(branch, 0, None)
 
198
        return RevisionInfo(branch, None, None)
188
199
 
189
200
    def _match_on_and_check(self, branch, revs):
190
201
        info = self._match_on(branch, revs)
191
202
        if info:
192
203
            return info
193
 
        elif info == (0, None):
194
 
            # special case - the empty tree
 
204
        elif info == (None, None):
 
205
            # special case - nothing supplied
195
206
            return info
196
207
        elif self.prefix:
197
208
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
200
211
 
201
212
    def in_history(self, branch):
202
213
        if branch:
203
 
            revs = branch.revision_history()
 
214
            if self.wants_revision_history:
 
215
                revs = branch.revision_history()
 
216
            else:
 
217
                revs = None
204
218
        else:
205
219
            # this should never trigger.
206
220
            # TODO: make it a deprecated code path. RBC 20060928
216
230
    # will do what you expect.
217
231
    in_store = in_history
218
232
    in_branch = in_store
219
 
        
 
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
 
220
269
    def __repr__(self):
221
270
        # this is mostly for helping with testing
222
271
        return '<%s %s>' % (self.__class__.__name__,
223
272
                              self.user_spec)
224
 
    
 
273
 
225
274
    def needs_branch(self):
226
275
        """Whether this revision spec needs a branch.
227
276
 
231
280
 
232
281
    def get_branch(self):
233
282
        """When the revision specifier contains a branch location, return it.
234
 
        
 
283
 
235
284
        Otherwise, return None.
236
285
        """
237
286
        return None
239
288
 
240
289
# private API
241
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
 
242
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
    """
243
358
    prefix = 'revno:'
 
359
    wants_revision_history = False
244
360
 
245
361
    def _match_on(self, branch, revs):
246
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):
247
367
        loc = self.spec.find(':')
248
368
        if loc == -1:
249
369
            revno_spec = self.spec
263
383
                dotted = False
264
384
            except ValueError:
265
385
                # dotted decimal. This arguably should not be here
266
 
                # but the from_string method is a little primitive 
 
386
                # but the from_string method is a little primitive
267
387
                # right now - RBC 20060928
268
388
                try:
269
389
                    match_revno = tuple((int(number) for number in revno_spec.split('.')))
278
398
            # the branch object.
279
399
            from bzrlib.branch import Branch
280
400
            branch = Branch.open(branch_spec)
281
 
            # Need to use a new revision history
282
 
            # because we are using a specific branch
283
 
            revs = branch.revision_history()
 
401
            revs_or_none = None
284
402
 
285
403
        if dotted:
286
 
            branch.lock_read()
287
404
            try:
288
 
                last_rev = branch.last_revision()
289
 
                merge_sorted_revisions = tsort.merge_sort(
290
 
                    branch.repository.get_revision_graph(last_rev),
291
 
                    last_rev,
292
 
                    generate_revno=True)
293
 
                def match(item):
294
 
                    return item[3] == match_revno
295
 
                revisions = filter(match, merge_sorted_revisions)
296
 
            finally:
297
 
                branch.unlock()
298
 
            if len(revisions) != 1:
299
 
                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)
300
409
            else:
301
410
                # there is no traditional 'revno' for dotted-decimal revnos.
302
411
                # so for  API compatability we return None.
303
 
                return RevisionInfo(branch, None, revisions[0][1])
 
412
                return branch, None, revision_id
304
413
        else:
 
414
            last_revno, last_revision_id = branch.last_revision_info()
305
415
            if revno < 0:
306
 
                if (-revno) >= len(revs):
 
416
                # if get_rev_id supported negative revnos, there would not be a
 
417
                # need for this special case.
 
418
                if (-revno) >= last_revno:
307
419
                    revno = 1
308
420
                else:
309
 
                    revno = len(revs) + revno + 1
 
421
                    revno = last_revno + revno + 1
310
422
            try:
311
 
                revision_id = branch.get_rev_id(revno, revs)
 
423
                revision_id = branch.get_rev_id(revno, revs_or_none)
312
424
            except errors.NoSuchRevision:
313
425
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
314
 
        return RevisionInfo(branch, revno, revision_id)
315
 
        
 
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
 
316
433
    def needs_branch(self):
317
434
        return self.spec.find(':') == -1
318
435
 
322
439
        else:
323
440
            return self.spec[self.spec.find(':')+1:]
324
441
 
325
 
# Old compatibility 
 
442
# Old compatibility
326
443
RevisionSpec_int = RevisionSpec_revno
327
444
 
328
 
SPEC_TYPES.append(RevisionSpec_revno)
329
445
 
330
446
 
331
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
 
332
460
    prefix = 'revid:'
333
461
 
334
462
    def _match_on(self, branch, revs):
335
 
        try:
336
 
            revno = revs.index(self.spec) + 1
337
 
        except ValueError:
338
 
            revno = None
339
 
        return RevisionInfo(branch, revno, self.spec)
340
 
 
341
 
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
 
342
472
 
343
473
 
344
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
    """
345
486
 
346
487
    prefix = 'last:'
347
488
 
348
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
 
349
496
        if self.spec == '':
350
 
            if not revs:
351
 
                raise errors.NoCommits(branch)
352
 
            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
353
500
 
354
501
        try:
355
502
            offset = int(self.spec)
356
503
        except ValueError, e:
357
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
 
504
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
358
505
 
359
506
        if offset <= 0:
360
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
507
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
361
508
                                             'you must supply a positive value')
362
 
        revno = len(revs) - offset + 1
 
509
 
 
510
        revno = last_revno - offset + 1
363
511
        try:
364
 
            revision_id = branch.get_rev_id(revno, revs)
 
512
            revision_id = context_branch.get_rev_id(revno, revs_or_none)
365
513
        except errors.NoSuchRevision:
366
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
367
 
        return RevisionInfo(branch, revno, revision_id)
368
 
 
369
 
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
 
370
523
 
371
524
 
372
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
    """
373
546
 
374
547
    prefix = 'before:'
375
 
    
 
548
 
376
549
    def _match_on(self, branch, revs):
377
550
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
378
551
        if r.revno == 0:
383
556
            rev = branch.repository.get_revision(r.rev_id)
384
557
            if not rev.parent_ids:
385
558
                revno = 0
386
 
                revision_id = None
 
559
                revision_id = revision.NULL_REVISION
387
560
            else:
388
561
                revision_id = rev.parent_ids[0]
389
562
                try:
399
572
                                                 branch)
400
573
        return RevisionInfo(branch, revno, revision_id)
401
574
 
402
 
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
 
403
597
 
404
598
 
405
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
 
406
607
    prefix = 'tag:'
 
608
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
407
609
 
408
610
    def _match_on(self, branch, revs):
409
 
        raise errors.InvalidRevisionSpec(self.user_spec, branch,
410
 
                                         'tag: namespace registered,'
411
 
                                         ' but not implemented')
412
 
 
413
 
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
 
414
619
 
415
620
 
416
621
class _RevListToTimestamps(object):
433
638
 
434
639
 
435
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
    """
436
660
    prefix = 'date:'
437
661
    _date_re = re.compile(
438
662
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
441
665
        )
442
666
 
443
667
    def _match_on(self, branch, revs):
444
 
        """
445
 
        Spec for date revisions:
 
668
        """Spec for date revisions:
446
669
          date:value
447
670
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
448
671
          matches the first entry after a given date (either at midnight or
449
672
          at a specified time).
450
 
 
451
 
          So the proper way of saying 'give me all entries for today' is:
452
 
              -r date:yesterday..date:today
453
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
454
677
        today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
455
678
        if self.spec.lower() == 'yesterday':
456
679
            dt = today - datetime.timedelta(days=1)
495
718
        finally:
496
719
            branch.unlock()
497
720
        if rev == len(revs):
498
 
            return RevisionInfo(branch, None)
 
721
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
499
722
        else:
500
723
            return RevisionInfo(branch, rev + 1)
501
724
 
502
 
SPEC_TYPES.append(RevisionSpec_date)
503
725
 
504
726
 
505
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
    """
506
747
    prefix = 'ancestor:'
507
748
 
508
749
    def _match_on(self, branch, revs):
509
 
        from bzrlib.branch import Branch
510
 
 
511
750
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
512
 
        other_branch = Branch.open(self.spec)
513
 
        revision_a = branch.last_revision()
514
 
        revision_b = other_branch.last_revision()
515
 
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
516
 
            if r in (None, revision.NULL_REVISION):
517
 
                raise errors.NoCommits(b)
518
 
        revision_source = revision.MultipleRevisionSources(
519
 
                branch.repository, other_branch.repository)
520
 
        rev_id = revision.common_ancestor(revision_a, revision_b,
521
 
                                          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)
522
760
        try:
523
 
            revno = branch.revision_id_to_revno(rev_id)
 
761
            revno = branch.revision_id_to_revno(revision_id)
524
762
        except errors.NoSuchRevision:
525
763
            revno = None
526
 
        return RevisionInfo(branch, revno, rev_id)
527
 
        
528
 
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
 
529
794
 
530
795
 
531
796
class RevisionSpec_branch(RevisionSpec):
532
 
    """A branch: revision specifier.
533
 
 
534
 
    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
535
806
    """
536
807
    prefix = 'branch:'
 
808
    dwim_catchable_exceptions = (errors.NotBranchError,)
537
809
 
538
810
    def _match_on(self, branch, revs):
539
811
        from bzrlib.branch import Branch
548
820
        except errors.NoSuchRevision:
549
821
            revno = None
550
822
        return RevisionInfo(branch, revno, revision_b)
551
 
        
552
 
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", [])