~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-08-17 21:39:37 UTC
  • mfrom: (6072.2.5 match-tree-layout)
  • Revision ID: pqm@pqm.ubuntu.com-20110817213937-lzul6u2qz1svvgxm
(jelmer) Add a HasLayout matcher. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

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