~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

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
 
    branch as _mod_branch,
28
23
    errors,
29
24
    osutils,
30
 
    registry,
31
25
    revision,
32
26
    symbol_versioning,
33
27
    trace,
34
 
    workingtree,
 
28
    tsort,
35
29
    )
36
30
 
37
31
 
106
100
 
107
101
        Use this if you don't know or care what the revno is.
108
102
        """
109
 
        if revision_id == revision.NULL_REVISION:
110
 
            return RevisionInfo(branch, 0, revision_id)
111
103
        try:
112
104
            revno = revs.index(revision_id) + 1
113
105
        except ValueError:
115
107
        return RevisionInfo(branch, revno, revision_id)
116
108
 
117
109
 
 
110
# classes in this list should have a "prefix" attribute, against which
 
111
# string specs are matched
 
112
SPEC_TYPES = []
118
113
_revno_regex = None
119
114
 
120
115
 
123
118
 
124
119
    help_txt = """A parsed revision specification.
125
120
 
126
 
    A revision specification is a string, which may be unambiguous about
127
 
    what it represents by giving a prefix like 'date:' or 'revid:' etc,
128
 
    or it may have no prefix, in which case it's tried against several
129
 
    specifier types in sequence to determine what the user meant.
 
121
    A revision specification can be an integer, in which case it is
 
122
    assumed to be a revno (though this will translate negative values
 
123
    into positive ones); or it can be a string, in which case it is
 
124
    parsed for something like 'date:' or 'revid:' etc.
130
125
 
131
126
    Revision specs are an UI element, and they have been moved out
132
127
    of the branch class to leave "back-end" classes unaware of such
138
133
    """
139
134
 
140
135
    prefix = None
141
 
    wants_revision_history = True
142
 
    dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
143
 
    """Exceptions that RevisionSpec_dwim._match_on will catch.
144
 
 
145
 
    If the revspec is part of ``dwim_revspecs``, it may be tried with an
146
 
    invalid revspec and raises some exception. The exceptions mentioned here
147
 
    will not be reported to the user but simply ignored without stopping the
148
 
    dwim processing.
149
 
    """
 
136
 
 
137
    def __new__(cls, spec, _internal=False):
 
138
        if _internal:
 
139
            return object.__new__(cls, spec, _internal=_internal)
 
140
 
 
141
        symbol_versioning.warn('Creating a RevisionSpec directly has'
 
142
                               ' been deprecated in version 0.11. Use'
 
143
                               ' RevisionSpec.from_string()'
 
144
                               ' instead.',
 
145
                               DeprecationWarning, stacklevel=2)
 
146
        return RevisionSpec.from_string(spec)
150
147
 
151
148
    @staticmethod
152
149
    def from_string(spec):
161
158
 
162
159
        if spec is None:
163
160
            return RevisionSpec(None, _internal=True)
164
 
        match = revspec_registry.get_prefix(spec)
165
 
        if match is not None:
166
 
            spectype, specsuffix = match
167
 
            trace.mutter('Returning RevisionSpec %s for %s',
168
 
                         spectype.__name__, spec)
169
 
            return spectype(spec, _internal=True)
 
161
 
 
162
        assert isinstance(spec, basestring), \
 
163
            "You should only supply strings not %s" % (type(spec),)
 
164
 
 
165
        for spectype in SPEC_TYPES:
 
166
            if spec.startswith(spectype.prefix):
 
167
                trace.mutter('Returning RevisionSpec %s for %s',
 
168
                             spectype.__name__, spec)
 
169
                return spectype(spec, _internal=True)
170
170
        else:
171
 
            for spectype in SPEC_TYPES:
172
 
                if spec.startswith(spectype.prefix):
173
 
                    trace.mutter('Returning RevisionSpec %s for %s',
174
 
                                 spectype.__name__, spec)
175
 
                    return spectype(spec, _internal=True)
176
 
            # Otherwise treat it as a DWIM, build the RevisionSpec object and
177
 
            # wait for _match_on to be called.
178
 
            return RevisionSpec_dwim(spec, _internal=True)
 
171
            # RevisionSpec_revno is special cased, because it is the only
 
172
            # one that directly handles plain integers
 
173
            # TODO: This should not be special cased rather it should be
 
174
            # a method invocation on spectype.canparse()
 
175
            global _revno_regex
 
176
            if _revno_regex is None:
 
177
                _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
 
178
            if _revno_regex.match(spec) is not None:
 
179
                return RevisionSpec_revno(spec, _internal=True)
 
180
 
 
181
            raise errors.NoSuchRevisionSpec(spec)
179
182
 
180
183
    def __init__(self, spec, _internal=False):
181
184
        """Create a RevisionSpec referring to the Null revision.
185
188
            called directly. Only from RevisionSpec.from_string()
186
189
        """
187
190
        if not _internal:
 
191
            # XXX: Update this after 0.10 is released
188
192
            symbol_versioning.warn('Creating a RevisionSpec directly has'
189
193
                                   ' been deprecated in version 0.11. Use'
190
194
                                   ' RevisionSpec.from_string()'
197
201
 
198
202
    def _match_on(self, branch, revs):
199
203
        trace.mutter('Returning RevisionSpec._match_on: None')
200
 
        return RevisionInfo(branch, None, None)
 
204
        return RevisionInfo(branch, 0, None)
201
205
 
202
206
    def _match_on_and_check(self, branch, revs):
203
207
        info = self._match_on(branch, revs)
204
208
        if info:
205
209
            return info
206
 
        elif info == (None, None):
207
 
            # special case - nothing supplied
 
210
        elif info == (0, None):
 
211
            # special case - the empty tree
208
212
            return info
209
213
        elif self.prefix:
210
214
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
213
217
 
214
218
    def in_history(self, branch):
215
219
        if branch:
216
 
            if self.wants_revision_history:
217
 
                revs = branch.revision_history()
218
 
            else:
219
 
                revs = None
 
220
            revs = branch.revision_history()
220
221
        else:
221
222
            # this should never trigger.
222
223
            # TODO: make it a deprecated code path. RBC 20060928
232
233
    # will do what you expect.
233
234
    in_store = in_history
234
235
    in_branch = in_store
235
 
 
236
 
    def as_revision_id(self, context_branch):
237
 
        """Return just the revision_id for this revisions spec.
238
 
 
239
 
        Some revision specs require a context_branch to be able to determine
240
 
        their value. Not all specs will make use of it.
241
 
        """
242
 
        return self._as_revision_id(context_branch)
243
 
 
244
 
    def _as_revision_id(self, context_branch):
245
 
        """Implementation of as_revision_id()
246
 
 
247
 
        Classes should override this function to provide appropriate
248
 
        functionality. The default is to just call '.in_history().rev_id'
249
 
        """
250
 
        return self.in_history(context_branch).rev_id
251
 
 
252
 
    def as_tree(self, context_branch):
253
 
        """Return the tree object for this revisions spec.
254
 
 
255
 
        Some revision specs require a context_branch to be able to determine
256
 
        the revision id and access the repository. Not all specs will make
257
 
        use of it.
258
 
        """
259
 
        return self._as_tree(context_branch)
260
 
 
261
 
    def _as_tree(self, context_branch):
262
 
        """Implementation of as_tree().
263
 
 
264
 
        Classes should override this function to provide appropriate
265
 
        functionality. The default is to just call '.as_revision_id()'
266
 
        and get the revision tree from context_branch's repository.
267
 
        """
268
 
        revision_id = self.as_revision_id(context_branch)
269
 
        return context_branch.repository.revision_tree(revision_id)
270
 
 
 
236
        
271
237
    def __repr__(self):
272
238
        # this is mostly for helping with testing
273
239
        return '<%s %s>' % (self.__class__.__name__,
274
240
                              self.user_spec)
275
 
 
 
241
    
276
242
    def needs_branch(self):
277
243
        """Whether this revision spec needs a branch.
278
244
 
282
248
 
283
249
    def get_branch(self):
284
250
        """When the revision specifier contains a branch location, return it.
285
 
 
 
251
        
286
252
        Otherwise, return None.
287
253
        """
288
254
        return None
290
256
 
291
257
# private API
292
258
 
293
 
class RevisionSpec_dwim(RevisionSpec):
294
 
    """Provides a DWIMish revision specifier lookup.
295
 
 
296
 
    Note that this does not go in the revspec_registry because by definition
297
 
    there is no prefix to identify it.  It's solely called from
298
 
    RevisionSpec.from_string() because the DWIMification happen when _match_on
299
 
    is called so the string describing the revision is kept here until needed.
300
 
    """
301
 
 
302
 
    help_txt = None
303
 
    # We don't need to build the revision history ourself, that's delegated to
304
 
    # each revspec we try.
305
 
    wants_revision_history = False
306
 
 
307
 
    def _try_spectype(self, rstype, branch):
308
 
        rs = rstype(self.spec, _internal=True)
309
 
        # Hit in_history to find out if it exists, or we need to try the
310
 
        # next type.
311
 
        return rs.in_history(branch)
312
 
 
313
 
    def _match_on(self, branch, revs):
314
 
        """Run the lookup and see what we can get."""
315
 
 
316
 
        # First, see if it's a revno
317
 
        global _revno_regex
318
 
        if _revno_regex is None:
319
 
            _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
320
 
        if _revno_regex.match(self.spec) is not None:
321
 
            try:
322
 
                return self._try_spectype(RevisionSpec_revno, branch)
323
 
            except RevisionSpec_revno.dwim_catchable_exceptions:
324
 
                pass
325
 
 
326
 
        # Next see what has been registered
327
 
        for rs_class in dwim_revspecs:
328
 
            try:
329
 
                return self._try_spectype(rs_class, branch)
330
 
            except rs_class.dwim_catchable_exceptions:
331
 
                pass
332
 
 
333
 
        # Well, I dunno what it is. Note that we don't try to keep track of the
334
 
        # first of last exception raised during the DWIM tries as none seems
335
 
        # really relevant.
336
 
        raise errors.InvalidRevisionSpec(self.spec, branch)
337
 
 
338
 
 
339
259
class RevisionSpec_revno(RevisionSpec):
340
260
    """Selects a revision using a number."""
341
261
 
342
262
    help_txt = """Selects a revision using a number.
343
263
 
344
264
    Use an integer to specify a revision in the history of the branch.
345
 
    Optionally a branch can be specified.  A negative number will count
346
 
    from the end of the branch (-1 is the last revision, -2 the previous
347
 
    one). If the negative number is larger than the branch's history, the
348
 
    first revision is returned.
349
 
    Examples::
350
 
 
351
 
      revno:1                   -> return the first revision of this branch
 
265
    Optionally a branch can be specified. The 'revno:' prefix is optional.
 
266
    A negative number will count from the end of the branch (-1 is the
 
267
    last revision, -2 the previous one). If the negative number is larger
 
268
    than the branch's history, the first revision is returned.
 
269
    examples:
 
270
      revno:1                   -> return the first revision
352
271
      revno:3:/path/to/branch   -> return the 3rd revision of
353
272
                                   the branch '/path/to/branch'
354
273
      revno:-1                  -> The last revision in a branch.
358
277
                                   your history is very long.
359
278
    """
360
279
    prefix = 'revno:'
361
 
    wants_revision_history = False
362
280
 
363
281
    def _match_on(self, branch, revs):
364
282
        """Lookup a revision by revision number"""
365
 
        branch, revno, revision_id = self._lookup(branch, revs)
366
 
        return RevisionInfo(branch, revno, revision_id)
367
 
 
368
 
    def _lookup(self, branch, revs_or_none):
369
283
        loc = self.spec.find(':')
370
284
        if loc == -1:
371
285
            revno_spec = self.spec
385
299
                dotted = False
386
300
            except ValueError:
387
301
                # dotted decimal. This arguably should not be here
388
 
                # but the from_string method is a little primitive
 
302
                # but the from_string method is a little primitive 
389
303
                # right now - RBC 20060928
390
304
                try:
391
305
                    match_revno = tuple((int(number) for number in revno_spec.split('.')))
400
314
            # the branch object.
401
315
            from bzrlib.branch import Branch
402
316
            branch = Branch.open(branch_spec)
403
 
            revs_or_none = None
 
317
            # Need to use a new revision history
 
318
            # because we are using a specific branch
 
319
            revs = branch.revision_history()
404
320
 
405
321
        if dotted:
 
322
            branch.lock_read()
406
323
            try:
407
 
                revision_id = branch.dotted_revno_to_revision_id(match_revno,
408
 
                    _cache_reverse=True)
409
 
            except errors.NoSuchRevision:
410
 
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
324
                last_rev = branch.last_revision()
 
325
                merge_sorted_revisions = tsort.merge_sort(
 
326
                    branch.repository.get_revision_graph(last_rev),
 
327
                    last_rev,
 
328
                    generate_revno=True)
 
329
                def match(item):
 
330
                    return item[3] == match_revno
 
331
                revisions = filter(match, merge_sorted_revisions)
 
332
            finally:
 
333
                branch.unlock()
 
334
            if len(revisions) != 1:
 
335
                return RevisionInfo(branch, None, None)
411
336
            else:
412
337
                # there is no traditional 'revno' for dotted-decimal revnos.
413
338
                # so for  API compatability we return None.
414
 
                return branch, None, revision_id
 
339
                return RevisionInfo(branch, None, revisions[0][1])
415
340
        else:
416
 
            last_revno, last_revision_id = branch.last_revision_info()
417
341
            if revno < 0:
418
342
                # if get_rev_id supported negative revnos, there would not be a
419
343
                # need for this special case.
420
 
                if (-revno) >= last_revno:
 
344
                if (-revno) >= len(revs):
421
345
                    revno = 1
422
346
                else:
423
 
                    revno = last_revno + revno + 1
 
347
                    revno = len(revs) + revno + 1
424
348
            try:
425
 
                revision_id = branch.get_rev_id(revno, revs_or_none)
 
349
                revision_id = branch.get_rev_id(revno, revs)
426
350
            except errors.NoSuchRevision:
427
351
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
428
 
        return branch, revno, revision_id
429
 
 
430
 
    def _as_revision_id(self, context_branch):
431
 
        # We would have the revno here, but we don't really care
432
 
        branch, revno, revision_id = self._lookup(context_branch, None)
433
 
        return revision_id
434
 
 
 
352
        return RevisionInfo(branch, revno, revision_id)
 
353
        
435
354
    def needs_branch(self):
436
355
        return self.spec.find(':') == -1
437
356
 
441
360
        else:
442
361
            return self.spec[self.spec.find(':')+1:]
443
362
 
444
 
# Old compatibility
 
363
# Old compatibility 
445
364
RevisionSpec_int = RevisionSpec_revno
446
365
 
447
 
 
448
 
 
449
 
class RevisionIDSpec(RevisionSpec):
450
 
 
451
 
    def _match_on(self, branch, revs):
452
 
        revision_id = self.as_revision_id(branch)
453
 
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
454
 
 
455
 
 
456
 
class RevisionSpec_revid(RevisionIDSpec):
 
366
SPEC_TYPES.append(RevisionSpec_revno)
 
367
 
 
368
 
 
369
class RevisionSpec_revid(RevisionSpec):
457
370
    """Selects a revision using the revision id."""
458
371
 
459
372
    help_txt = """Selects a revision using the revision id.
460
373
 
461
374
    Supply a specific revision id, that can be used to specify any
462
 
    revision id in the ancestry of the branch.
 
375
    revision id in the ancestry of the branch. 
463
376
    Including merges, and pending merges.
464
 
    Examples::
465
 
 
 
377
    examples:
466
378
      revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
467
 
    """
468
 
 
 
379
    """    
469
380
    prefix = 'revid:'
470
381
 
471
 
    def _as_revision_id(self, context_branch):
 
382
    def _match_on(self, branch, revs):
472
383
        # self.spec comes straight from parsing the command line arguments,
473
384
        # so we expect it to be a Unicode string. Switch it to the internal
474
385
        # representation.
475
 
        return osutils.safe_revision_id(self.spec, warn=False)
 
386
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
 
387
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
476
388
 
 
389
SPEC_TYPES.append(RevisionSpec_revid)
477
390
 
478
391
 
479
392
class RevisionSpec_last(RevisionSpec):
483
396
 
484
397
    Supply a positive number to get the nth revision from the end.
485
398
    This is the same as supplying negative numbers to the 'revno:' spec.
486
 
    Examples::
487
 
 
 
399
    examples:
488
400
      last:1        -> return the last revision
489
401
      last:3        -> return the revision 2 before the end.
490
 
    """
 
402
    """    
491
403
 
492
404
    prefix = 'last:'
493
405
 
494
406
    def _match_on(self, branch, revs):
495
 
        revno, revision_id = self._revno_and_revision_id(branch, revs)
496
 
        return RevisionInfo(branch, revno, revision_id)
497
 
 
498
 
    def _revno_and_revision_id(self, context_branch, revs_or_none):
499
 
        last_revno, last_revision_id = context_branch.last_revision_info()
500
 
 
501
407
        if self.spec == '':
502
 
            if not last_revno:
503
 
                raise errors.NoCommits(context_branch)
504
 
            return last_revno, last_revision_id
 
408
            if not revs:
 
409
                raise errors.NoCommits(branch)
 
410
            return RevisionInfo(branch, len(revs), revs[-1])
505
411
 
506
412
        try:
507
413
            offset = int(self.spec)
508
414
        except ValueError, e:
509
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
 
415
            raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
510
416
 
511
417
        if offset <= 0:
512
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
418
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
513
419
                                             'you must supply a positive value')
514
 
 
515
 
        revno = last_revno - offset + 1
 
420
        revno = len(revs) - offset + 1
516
421
        try:
517
 
            revision_id = context_branch.get_rev_id(revno, revs_or_none)
 
422
            revision_id = branch.get_rev_id(revno, revs)
518
423
        except errors.NoSuchRevision:
519
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
520
 
        return revno, revision_id
521
 
 
522
 
    def _as_revision_id(self, context_branch):
523
 
        # We compute the revno as part of the process, but we don't really care
524
 
        # about it.
525
 
        revno, revision_id = self._revno_and_revision_id(context_branch, None)
526
 
        return revision_id
527
 
 
 
424
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
425
        return RevisionInfo(branch, revno, revision_id)
 
426
 
 
427
SPEC_TYPES.append(RevisionSpec_last)
528
428
 
529
429
 
530
430
class RevisionSpec_before(RevisionSpec):
532
432
 
533
433
    help_txt = """Selects the parent of the revision specified.
534
434
 
535
 
    Supply any revision spec to return the parent of that revision.  This is
536
 
    mostly useful when inspecting revisions that are not in the revision history
537
 
    of a branch.
538
 
 
 
435
    Supply any revision spec to return the parent of that revision.
539
436
    It is an error to request the parent of the null revision (before:0).
540
 
 
541
 
    Examples::
542
 
 
 
437
    This is mostly useful when inspecting revisions that are not in the
 
438
    revision history of a branch.
 
439
 
 
440
    examples:
543
441
      before:1913    -> Return the parent of revno 1913 (revno 1912)
544
442
      before:revid:aaaa@bbbb-1234567890  -> return the parent of revision
545
443
                                            aaaa@bbbb-1234567890
546
 
      bzr diff -r before:1913..1913
547
 
            -> Find the changes between revision 1913 and its parent (1912).
548
 
               (What changes did revision 1913 introduce).
549
 
               This is equivalent to:  bzr diff -c 1913
 
444
      bzr diff -r before:revid:aaaa..revid:aaaa
 
445
            -> Find the changes between revision 'aaaa' and its parent.
 
446
               (what changes did 'aaaa' introduce)
550
447
    """
551
448
 
552
449
    prefix = 'before:'
553
 
 
 
450
    
554
451
    def _match_on(self, branch, revs):
555
452
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
556
453
        if r.revno == 0:
561
458
            rev = branch.repository.get_revision(r.rev_id)
562
459
            if not rev.parent_ids:
563
460
                revno = 0
564
 
                revision_id = revision.NULL_REVISION
 
461
                revision_id = None
565
462
            else:
566
463
                revision_id = rev.parent_ids[0]
567
464
                try:
577
474
                                                 branch)
578
475
        return RevisionInfo(branch, revno, revision_id)
579
476
 
580
 
    def _as_revision_id(self, context_branch):
581
 
        base_revspec = RevisionSpec.from_string(self.spec)
582
 
        base_revision_id = base_revspec.as_revision_id(context_branch)
583
 
        if base_revision_id == revision.NULL_REVISION:
584
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
585
 
                                         'cannot go before the null: revision')
586
 
        context_repo = context_branch.repository
587
 
        context_repo.lock_read()
588
 
        try:
589
 
            parent_map = context_repo.get_parent_map([base_revision_id])
590
 
        finally:
591
 
            context_repo.unlock()
592
 
        if base_revision_id not in parent_map:
593
 
            # Ghost, or unknown revision id
594
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
595
 
                'cannot find the matching revision')
596
 
        parents = parent_map[base_revision_id]
597
 
        if len(parents) < 1:
598
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
599
 
                'No parents for revision.')
600
 
        return parents[0]
601
 
 
 
477
SPEC_TYPES.append(RevisionSpec_before)
602
478
 
603
479
 
604
480
class RevisionSpec_tag(RevisionSpec):
610
486
    """
611
487
 
612
488
    prefix = 'tag:'
613
 
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
614
489
 
615
490
    def _match_on(self, branch, revs):
616
491
        # Can raise tags not supported, NoSuchTag, etc
618
493
            branch.tags.lookup_tag(self.spec),
619
494
            revs)
620
495
 
621
 
    def _as_revision_id(self, context_branch):
622
 
        return context_branch.tags.lookup_tag(self.spec)
623
 
 
 
496
SPEC_TYPES.append(RevisionSpec_tag)
624
497
 
625
498
 
626
499
class _RevListToTimestamps(object):
652
525
    Matches the first entry after a given date (either at midnight or
653
526
    at a specified time).
654
527
 
655
 
    One way to display all the changes since yesterday would be::
656
 
 
657
 
        bzr log -r date:yesterday..
658
 
 
659
 
    Examples::
660
 
 
 
528
    One way to display all the changes since yesterday would be:
 
529
        bzr log -r date:yesterday..-1
 
530
 
 
531
    examples:
661
532
      date:yesterday            -> select the first revision since yesterday
662
533
      date:2006-08-14,17:10:14  -> select the first revision after
663
534
                                   August 14th, 2006 at 5:10pm.
664
 
    """
 
535
    """    
665
536
    prefix = 'date:'
666
537
    _date_re = re.compile(
667
538
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
723
594
        finally:
724
595
            branch.unlock()
725
596
        if rev == len(revs):
726
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
597
            return RevisionInfo(branch, None)
727
598
        else:
728
599
            return RevisionInfo(branch, rev + 1)
729
600
 
 
601
SPEC_TYPES.append(RevisionSpec_date)
730
602
 
731
603
 
732
604
class RevisionSpec_ancestor(RevisionSpec):
744
616
    that your branch introduces, while excluding the changes that you
745
617
    have not merged from the remote branch.
746
618
 
747
 
    Examples::
748
 
 
 
619
    examples:
749
620
      ancestor:/path/to/branch
750
621
      $ bzr diff -r ancestor:../../mainline/branch
751
622
    """
755
626
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
756
627
        return self._find_revision_info(branch, self.spec)
757
628
 
758
 
    def _as_revision_id(self, context_branch):
759
 
        return self._find_revision_id(context_branch, self.spec)
760
 
 
761
629
    @staticmethod
762
630
    def _find_revision_info(branch, other_location):
763
 
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
764
 
                                                              other_location)
 
631
        from bzrlib.branch import Branch
 
632
 
 
633
        other_branch = Branch.open(other_location)
 
634
        revision_a = branch.last_revision()
 
635
        revision_b = other_branch.last_revision()
 
636
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
 
637
            if r in (None, revision.NULL_REVISION):
 
638
                raise errors.NoCommits(b)
 
639
        revision_source = revision.MultipleRevisionSources(
 
640
                branch.repository, other_branch.repository)
 
641
        rev_id = revision.common_ancestor(revision_a, revision_b,
 
642
                                          revision_source)
765
643
        try:
766
 
            revno = branch.revision_id_to_revno(revision_id)
 
644
            revno = branch.revision_id_to_revno(rev_id)
767
645
        except errors.NoSuchRevision:
768
646
            revno = None
769
 
        return RevisionInfo(branch, revno, revision_id)
770
 
 
771
 
    @staticmethod
772
 
    def _find_revision_id(branch, other_location):
773
 
        from bzrlib.branch import Branch
774
 
 
775
 
        branch.lock_read()
776
 
        try:
777
 
            revision_a = revision.ensure_null(branch.last_revision())
778
 
            if revision_a == revision.NULL_REVISION:
779
 
                raise errors.NoCommits(branch)
780
 
            if other_location == '':
781
 
                other_location = branch.get_parent()
782
 
            other_branch = Branch.open(other_location)
783
 
            other_branch.lock_read()
784
 
            try:
785
 
                revision_b = revision.ensure_null(other_branch.last_revision())
786
 
                if revision_b == revision.NULL_REVISION:
787
 
                    raise errors.NoCommits(other_branch)
788
 
                graph = branch.repository.get_graph(other_branch.repository)
789
 
                rev_id = graph.find_unique_lca(revision_a, revision_b)
790
 
            finally:
791
 
                other_branch.unlock()
792
 
            if rev_id == revision.NULL_REVISION:
793
 
                raise errors.NoCommonAncestor(revision_a, revision_b)
794
 
            return rev_id
795
 
        finally:
796
 
            branch.unlock()
797
 
 
798
 
 
 
647
        return RevisionInfo(branch, revno, rev_id)
 
648
 
 
649
 
 
650
SPEC_TYPES.append(RevisionSpec_ancestor)
799
651
 
800
652
 
801
653
class RevisionSpec_branch(RevisionSpec):
805
657
 
806
658
    Supply the path to a branch to select its last revision.
807
659
 
808
 
    Examples::
809
 
 
 
660
    examples:
810
661
      branch:/path/to/branch
811
662
    """
812
663
    prefix = 'branch:'
813
 
    dwim_catchable_exceptions = (errors.NotBranchError,)
814
664
 
815
665
    def _match_on(self, branch, revs):
816
666
        from bzrlib.branch import Branch
818
668
        revision_b = other_branch.last_revision()
819
669
        if revision_b in (None, revision.NULL_REVISION):
820
670
            raise errors.NoCommits(other_branch)
821
 
        if branch is None:
822
 
            branch = other_branch
823
 
        else:
824
 
            try:
825
 
                # pull in the remote revisions so we can diff
826
 
                branch.fetch(other_branch, revision_b)
827
 
            except errors.ReadOnlyError:
828
 
                branch = other_branch
 
671
        # pull in the remote revisions so we can diff
 
672
        branch.fetch(other_branch, revision_b)
829
673
        try:
830
674
            revno = branch.revision_id_to_revno(revision_b)
831
675
        except errors.NoSuchRevision:
832
676
            revno = None
833
677
        return RevisionInfo(branch, revno, revision_b)
834
 
 
835
 
    def _as_revision_id(self, context_branch):
836
 
        from bzrlib.branch import Branch
837
 
        other_branch = Branch.open(self.spec)
838
 
        last_revision = other_branch.last_revision()
839
 
        last_revision = revision.ensure_null(last_revision)
840
 
        context_branch.fetch(other_branch, last_revision)
841
 
        if last_revision == revision.NULL_REVISION:
842
 
            raise errors.NoCommits(other_branch)
843
 
        return last_revision
844
 
 
845
 
    def _as_tree(self, context_branch):
846
 
        from bzrlib.branch import Branch
847
 
        other_branch = Branch.open(self.spec)
848
 
        last_revision = other_branch.last_revision()
849
 
        last_revision = revision.ensure_null(last_revision)
850
 
        if last_revision == revision.NULL_REVISION:
851
 
            raise errors.NoCommits(other_branch)
852
 
        return other_branch.repository.revision_tree(last_revision)
853
 
 
854
 
    def needs_branch(self):
855
 
        return False
856
 
 
857
 
    def get_branch(self):
858
 
        return self.spec
859
 
 
 
678
        
 
679
SPEC_TYPES.append(RevisionSpec_branch)
860
680
 
861
681
 
862
682
class RevisionSpec_submit(RevisionSpec_ancestor):
866
686
 
867
687
    Diffing against this shows all the changes that were made in this branch,
868
688
    and is a good predictor of what merge will do.  The submit branch is
869
 
    used by the bundle and merge directive commands.  If no submit branch
 
689
    used by the bundle and merge directive comands.  If no submit branch
870
690
    is specified, the parent branch is used instead.
871
691
 
872
692
    The common ancestor is the last revision that existed in both
873
693
    branches. Usually this is the branch point, but it could also be
874
694
    a revision that was merged.
875
695
 
876
 
    Examples::
877
 
 
 
696
    examples:
878
697
      $ bzr diff -r submit:
879
698
    """
880
699
 
881
700
    prefix = 'submit:'
882
701
 
883
 
    def _get_submit_location(self, branch):
 
702
    def _match_on(self, branch, revs):
 
703
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
884
704
        submit_location = branch.get_submit_branch()
885
705
        location_type = 'submit branch'
886
706
        if submit_location is None:
889
709
        if submit_location is None:
890
710
            raise errors.NoSubmitBranch(branch)
891
711
        trace.note('Using %s %s', location_type, submit_location)
892
 
        return submit_location
893
 
 
894
 
    def _match_on(self, branch, revs):
895
 
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
896
 
        return self._find_revision_info(branch,
897
 
            self._get_submit_location(branch))
898
 
 
899
 
    def _as_revision_id(self, context_branch):
900
 
        return self._find_revision_id(context_branch,
901
 
            self._get_submit_location(context_branch))
902
 
 
903
 
 
904
 
class RevisionSpec_annotate(RevisionIDSpec):
905
 
 
906
 
    prefix = 'annotate:'
907
 
 
908
 
    help_txt = """Select the revision that last modified the specified line.
909
 
 
910
 
    Select the revision that last modified the specified line.  Line is
911
 
    specified as path:number.  Path is a relative path to the file.  Numbers
912
 
    start at 1, and are relative to the current version, not the last-
913
 
    committed version of the file.
914
 
    """
915
 
 
916
 
    def _raise_invalid(self, numstring, context_branch):
917
 
        raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
918
 
            'No such line: %s' % numstring)
919
 
 
920
 
    def _as_revision_id(self, context_branch):
921
 
        path, numstring = self.spec.rsplit(':', 1)
922
 
        try:
923
 
            index = int(numstring) - 1
924
 
        except ValueError:
925
 
            self._raise_invalid(numstring, context_branch)
926
 
        tree, file_path = workingtree.WorkingTree.open_containing(path)
927
 
        tree.lock_read()
928
 
        try:
929
 
            file_id = tree.path2id(file_path)
930
 
            if file_id is None:
931
 
                raise errors.InvalidRevisionSpec(self.user_spec,
932
 
                    context_branch, "File '%s' is not versioned." %
933
 
                    file_path)
934
 
            revision_ids = [r for (r, l) in tree.annotate_iter(file_id)]
935
 
        finally:
936
 
            tree.unlock()
937
 
        try:
938
 
            revision_id = revision_ids[index]
939
 
        except IndexError:
940
 
            self._raise_invalid(numstring, context_branch)
941
 
        if revision_id == revision.CURRENT_REVISION:
942
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
943
 
                'Line %s has not been committed.' % numstring)
944
 
        return revision_id
945
 
 
946
 
 
947
 
class RevisionSpec_mainline(RevisionIDSpec):
948
 
 
949
 
    help_txt = """Select mainline revision that merged the specified revision.
950
 
 
951
 
    Select the revision that merged the specified revision into mainline.
952
 
    """
953
 
 
954
 
    prefix = 'mainline:'
955
 
 
956
 
    def _as_revision_id(self, context_branch):
957
 
        revspec = RevisionSpec.from_string(self.spec)
958
 
        if revspec.get_branch() is None:
959
 
            spec_branch = context_branch
960
 
        else:
961
 
            spec_branch = _mod_branch.Branch.open(revspec.get_branch())
962
 
        revision_id = revspec.as_revision_id(spec_branch)
963
 
        graph = context_branch.repository.get_graph()
964
 
        result = graph.find_lefthand_merger(revision_id,
965
 
                                            context_branch.last_revision())
966
 
        if result is None:
967
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
968
 
        return result
969
 
 
970
 
 
971
 
# The order in which we want to DWIM a revision spec without any prefix.
972
 
# revno is always tried first and isn't listed here, this is used by
973
 
# RevisionSpec_dwim._match_on
974
 
dwim_revspecs = [
975
 
    RevisionSpec_tag, # Let's try for a tag
976
 
    RevisionSpec_revid, # Maybe it's a revid?
977
 
    RevisionSpec_date, # Perhaps a date?
978
 
    RevisionSpec_branch, # OK, last try, maybe it's a branch
979
 
    ]
980
 
 
981
 
 
982
 
revspec_registry = registry.Registry()
983
 
def _register_revspec(revspec):
984
 
    revspec_registry.register(revspec.prefix, revspec)
985
 
 
986
 
_register_revspec(RevisionSpec_revno)
987
 
_register_revspec(RevisionSpec_revid)
988
 
_register_revspec(RevisionSpec_last)
989
 
_register_revspec(RevisionSpec_before)
990
 
_register_revspec(RevisionSpec_tag)
991
 
_register_revspec(RevisionSpec_date)
992
 
_register_revspec(RevisionSpec_ancestor)
993
 
_register_revspec(RevisionSpec_branch)
994
 
_register_revspec(RevisionSpec_submit)
995
 
_register_revspec(RevisionSpec_annotate)
996
 
_register_revspec(RevisionSpec_mainline)
997
 
 
998
 
# classes in this list should have a "prefix" attribute, against which
999
 
# string specs are matched
1000
 
SPEC_TYPES = symbol_versioning.deprecated_list(
1001
 
    symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])
 
712
        return self._find_revision_info(branch, submit_location)
 
713
 
 
714
 
 
715
SPEC_TYPES.append(RevisionSpec_submit)