/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

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