~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Robert Collins
  • Date: 2007-03-07 01:14:11 UTC
  • mfrom: (2321 +trunk)
  • mto: (2321.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 2322.
  • Revision ID: robertc@robertcollins.net-20070307011411-0cmmc8atx67v3nv7
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

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