~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Jelmer Vernooij
  • Date: 2011-08-19 22:34:02 UTC
  • mto: This revision was merged to the branch mainline in revision 6089.
  • Revision ID: jelmer@samba.org-20110819223402-wjywqb0fa1xxx522
Use get_transport_from_{url,path} in more places.

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
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,
 
25
    osutils,
24
26
    revision,
25
27
    symbol_versioning,
 
28
    workingtree,
 
29
    )
 
30
""")
 
31
 
 
32
from bzrlib import (
 
33
    errors,
 
34
    lazy_regex,
 
35
    registry,
26
36
    trace,
27
 
    tsort,
28
37
    )
29
38
 
30
39
 
93
102
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
94
103
            self.revno, self.rev_id, self.branch)
95
104
 
 
105
    @staticmethod
 
106
    def from_revision_id(branch, revision_id, revs):
 
107
        """Construct a RevisionInfo given just the id.
96
108
 
97
 
# classes in this list should have a "prefix" attribute, against which
98
 
# string specs are matched
99
 
SPEC_TYPES = []
100
 
_revno_regex = None
 
109
        Use this if you don't know or care what the revno is.
 
110
        """
 
111
        if revision_id == revision.NULL_REVISION:
 
112
            return RevisionInfo(branch, 0, revision_id)
 
113
        try:
 
114
            revno = revs.index(revision_id) + 1
 
115
        except ValueError:
 
116
            revno = None
 
117
        return RevisionInfo(branch, revno, revision_id)
101
118
 
102
119
 
103
120
class RevisionSpec(object):
105
122
 
106
123
    help_txt = """A parsed revision specification.
107
124
 
108
 
    A revision specification can be an integer, in which case it is
109
 
    assumed to be a revno (though this will translate negative values
110
 
    into positive ones); or it can be a string, in which case it is
111
 
    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.
112
129
 
113
130
    Revision specs are an UI element, and they have been moved out
114
131
    of the branch class to leave "back-end" classes unaware of such
120
137
    """
121
138
 
122
139
    prefix = None
123
 
 
124
 
    def __new__(cls, spec, _internal=False):
125
 
        if _internal:
126
 
            return object.__new__(cls, spec, _internal=_internal)
127
 
 
128
 
        symbol_versioning.warn('Creating a RevisionSpec directly has'
129
 
                               ' been deprecated in version 0.11. Use'
130
 
                               ' RevisionSpec.from_string()'
131
 
                               ' instead.',
132
 
                               DeprecationWarning, stacklevel=2)
133
 
        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
    """
134
149
 
135
150
    @staticmethod
136
151
    def from_string(spec):
145
160
 
146
161
        if spec is None:
147
162
            return RevisionSpec(None, _internal=True)
148
 
 
149
 
        assert isinstance(spec, basestring), \
150
 
            "You should only supply strings not %s" % (type(spec),)
151
 
 
152
 
        for spectype in SPEC_TYPES:
153
 
            if spec.startswith(spectype.prefix):
154
 
                trace.mutter('Returning RevisionSpec %s for %s',
155
 
                             spectype.__name__, spec)
156
 
                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)
157
169
        else:
158
 
            # RevisionSpec_revno is special cased, because it is the only
159
 
            # one that directly handles plain integers
160
 
            # TODO: This should not be special cased rather it should be
161
 
            # a method invocation on spectype.canparse()
162
 
            global _revno_regex
163
 
            if _revno_regex is None:
164
 
                _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
165
 
            if _revno_regex.match(spec) is not None:
166
 
                return RevisionSpec_revno(spec, _internal=True)
167
 
 
168
 
            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)
169
173
 
170
174
    def __init__(self, spec, _internal=False):
171
175
        """Create a RevisionSpec referring to the Null revision.
175
179
            called directly. Only from RevisionSpec.from_string()
176
180
        """
177
181
        if not _internal:
178
 
            # XXX: Update this after 0.10 is released
179
182
            symbol_versioning.warn('Creating a RevisionSpec directly has'
180
183
                                   ' been deprecated in version 0.11. Use'
181
184
                                   ' RevisionSpec.from_string()'
188
191
 
189
192
    def _match_on(self, branch, revs):
190
193
        trace.mutter('Returning RevisionSpec._match_on: None')
191
 
        return RevisionInfo(branch, 0, None)
 
194
        return RevisionInfo(branch, None, None)
192
195
 
193
196
    def _match_on_and_check(self, branch, revs):
194
197
        info = self._match_on(branch, revs)
195
198
        if info:
196
199
            return info
197
 
        elif info == (0, None):
198
 
            # special case - the empty tree
 
200
        elif info == (None, None):
 
201
            # special case - nothing supplied
199
202
            return info
200
203
        elif self.prefix:
201
204
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
204
207
 
205
208
    def in_history(self, branch):
206
209
        if branch:
207
 
            revs = branch.revision_history()
 
210
            if self.wants_revision_history:
 
211
                revs = branch.revision_history()
 
212
            else:
 
213
                revs = None
208
214
        else:
209
215
            # this should never trigger.
210
216
            # TODO: make it a deprecated code path. RBC 20060928
220
226
    # will do what you expect.
221
227
    in_store = in_history
222
228
    in_branch = in_store
223
 
        
 
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
 
224
265
    def __repr__(self):
225
266
        # this is mostly for helping with testing
226
267
        return '<%s %s>' % (self.__class__.__name__,
227
268
                              self.user_spec)
228
 
    
 
269
 
229
270
    def needs_branch(self):
230
271
        """Whether this revision spec needs a branch.
231
272
 
235
276
 
236
277
    def get_branch(self):
237
278
        """When the revision specifier contains a branch location, return it.
238
 
        
 
279
 
239
280
        Otherwise, return None.
240
281
        """
241
282
        return None
243
284
 
244
285
# private API
245
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
 
246
361
class RevisionSpec_revno(RevisionSpec):
247
362
    """Selects a revision using a number."""
248
363
 
249
364
    help_txt = """Selects a revision using a number.
250
365
 
251
366
    Use an integer to specify a revision in the history of the branch.
252
 
    Optionally a branch can be specified. The 'revno:' prefix is optional.
253
 
    A negative number will count from the end of the branch (-1 is the
254
 
    last revision, -2 the previous one). If the negative number is larger
255
 
    than the branch's history, the first revision is returned.
256
 
    examples:
257
 
      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
258
374
      revno:3:/path/to/branch   -> return the 3rd revision of
259
375
                                   the branch '/path/to/branch'
260
376
      revno:-1                  -> The last revision in a branch.
264
380
                                   your history is very long.
265
381
    """
266
382
    prefix = 'revno:'
 
383
    wants_revision_history = False
267
384
 
268
385
    def _match_on(self, branch, revs):
269
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):
270
391
        loc = self.spec.find(':')
271
392
        if loc == -1:
272
393
            revno_spec = self.spec
286
407
                dotted = False
287
408
            except ValueError:
288
409
                # dotted decimal. This arguably should not be here
289
 
                # but the from_string method is a little primitive 
 
410
                # but the from_string method is a little primitive
290
411
                # right now - RBC 20060928
291
412
                try:
292
413
                    match_revno = tuple((int(number) for number in revno_spec.split('.')))
301
422
            # the branch object.
302
423
            from bzrlib.branch import Branch
303
424
            branch = Branch.open(branch_spec)
304
 
            # Need to use a new revision history
305
 
            # because we are using a specific branch
306
 
            revs = branch.revision_history()
 
425
            revs_or_none = None
307
426
 
308
427
        if dotted:
309
 
            branch.lock_read()
310
428
            try:
311
 
                last_rev = branch.last_revision()
312
 
                merge_sorted_revisions = tsort.merge_sort(
313
 
                    branch.repository.get_revision_graph(last_rev),
314
 
                    last_rev,
315
 
                    generate_revno=True)
316
 
                def match(item):
317
 
                    return item[3] == match_revno
318
 
                revisions = filter(match, merge_sorted_revisions)
319
 
            finally:
320
 
                branch.unlock()
321
 
            if len(revisions) != 1:
322
 
                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)
323
433
            else:
324
434
                # there is no traditional 'revno' for dotted-decimal revnos.
325
435
                # so for  API compatability we return None.
326
 
                return RevisionInfo(branch, None, revisions[0][1])
 
436
                return branch, None, revision_id
327
437
        else:
 
438
            last_revno, last_revision_id = branch.last_revision_info()
328
439
            if revno < 0:
329
440
                # if get_rev_id supported negative revnos, there would not be a
330
441
                # need for this special case.
331
 
                if (-revno) >= len(revs):
 
442
                if (-revno) >= last_revno:
332
443
                    revno = 1
333
444
                else:
334
 
                    revno = len(revs) + revno + 1
 
445
                    revno = last_revno + revno + 1
335
446
            try:
336
 
                revision_id = branch.get_rev_id(revno, revs)
 
447
                revision_id = branch.get_rev_id(revno, revs_or_none)
337
448
            except errors.NoSuchRevision:
338
449
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
339
 
        return RevisionInfo(branch, revno, revision_id)
340
 
        
 
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
 
341
457
    def needs_branch(self):
342
458
        return self.spec.find(':') == -1
343
459
 
347
463
        else:
348
464
            return self.spec[self.spec.find(':')+1:]
349
465
 
350
 
# Old compatibility 
 
466
# Old compatibility
351
467
RevisionSpec_int = RevisionSpec_revno
352
468
 
353
 
SPEC_TYPES.append(RevisionSpec_revno)
354
 
 
355
 
 
356
 
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):
357
479
    """Selects a revision using the revision id."""
358
480
 
359
481
    help_txt = """Selects a revision using the revision id.
360
482
 
361
483
    Supply a specific revision id, that can be used to specify any
362
 
    revision id in the ancestry of the branch. 
 
484
    revision id in the ancestry of the branch.
363
485
    Including merges, and pending merges.
364
 
    examples:
 
486
    Examples::
 
487
 
365
488
      revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
366
 
    """    
 
489
    """
 
490
 
367
491
    prefix = 'revid:'
368
492
 
369
 
    def _match_on(self, branch, revs):
370
 
        try:
371
 
            revno = revs.index(self.spec) + 1
372
 
        except ValueError:
373
 
            revno = None
374
 
        return RevisionInfo(branch, revno, self.spec)
 
493
    def _as_revision_id(self, context_branch):
 
494
        # self.spec comes straight from parsing the command line arguments,
 
495
        # so we expect it to be a Unicode string. Switch it to the internal
 
496
        # representation.
 
497
        return osutils.safe_revision_id(self.spec, warn=False)
375
498
 
376
 
SPEC_TYPES.append(RevisionSpec_revid)
377
499
 
378
500
 
379
501
class RevisionSpec_last(RevisionSpec):
383
505
 
384
506
    Supply a positive number to get the nth revision from the end.
385
507
    This is the same as supplying negative numbers to the 'revno:' spec.
386
 
    examples:
 
508
    Examples::
 
509
 
387
510
      last:1        -> return the last revision
388
511
      last:3        -> return the revision 2 before the end.
389
 
    """    
 
512
    """
390
513
 
391
514
    prefix = 'last:'
392
515
 
393
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
 
394
523
        if self.spec == '':
395
 
            if not revs:
396
 
                raise errors.NoCommits(branch)
397
 
            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
398
527
 
399
528
        try:
400
529
            offset = int(self.spec)
401
530
        except ValueError, e:
402
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
 
531
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
403
532
 
404
533
        if offset <= 0:
405
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
534
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
406
535
                                             'you must supply a positive value')
407
 
        revno = len(revs) - offset + 1
 
536
 
 
537
        revno = last_revno - offset + 1
408
538
        try:
409
 
            revision_id = branch.get_rev_id(revno, revs)
 
539
            revision_id = context_branch.get_rev_id(revno, revs_or_none)
410
540
        except errors.NoSuchRevision:
411
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
412
 
        return RevisionInfo(branch, revno, revision_id)
413
 
 
414
 
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
 
415
550
 
416
551
 
417
552
class RevisionSpec_before(RevisionSpec):
419
554
 
420
555
    help_txt = """Selects the parent of the revision specified.
421
556
 
422
 
    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
 
423
561
    It is an error to request the parent of the null revision (before:0).
424
 
    This is mostly useful when inspecting revisions that are not in the
425
 
    revision history of a branch.
426
 
 
427
 
    examples:
 
562
 
 
563
    Examples::
 
564
 
428
565
      before:1913    -> Return the parent of revno 1913 (revno 1912)
429
566
      before:revid:aaaa@bbbb-1234567890  -> return the parent of revision
430
567
                                            aaaa@bbbb-1234567890
431
 
      bzr diff -r before:revid:aaaa..revid:aaaa
432
 
            -> Find the changes between revision 'aaaa' and its parent.
433
 
               (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
434
572
    """
435
573
 
436
574
    prefix = 'before:'
437
 
    
 
575
 
438
576
    def _match_on(self, branch, revs):
439
577
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
440
578
        if r.revno == 0:
445
583
            rev = branch.repository.get_revision(r.rev_id)
446
584
            if not rev.parent_ids:
447
585
                revno = 0
448
 
                revision_id = None
 
586
                revision_id = revision.NULL_REVISION
449
587
            else:
450
588
                revision_id = rev.parent_ids[0]
451
589
                try:
461
599
                                                 branch)
462
600
        return RevisionInfo(branch, revno, revision_id)
463
601
 
464
 
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
 
465
624
 
466
625
 
467
626
class RevisionSpec_tag(RevisionSpec):
468
 
    """To be implemented."""
469
 
 
470
 
    help_txt = """To be implemented."""
 
627
    """Select a revision identified by tag name"""
 
628
 
 
629
    help_txt = """Selects a revision identified by a tag name.
 
630
 
 
631
    Tags are stored in the branch and created by the 'tag' command.
 
632
    """
471
633
 
472
634
    prefix = 'tag:'
 
635
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
473
636
 
474
637
    def _match_on(self, branch, revs):
475
 
        raise errors.InvalidRevisionSpec(self.user_spec, branch,
476
 
                                         'tag: namespace registered,'
477
 
                                         ' but not implemented')
478
 
 
479
 
SPEC_TYPES.append(RevisionSpec_tag)
 
638
        # Can raise tags not supported, NoSuchTag, etc
 
639
        return RevisionInfo.from_revision_id(branch,
 
640
            branch.tags.lookup_tag(self.spec),
 
641
            revs)
 
642
 
 
643
    def _as_revision_id(self, context_branch):
 
644
        return context_branch.tags.lookup_tag(self.spec)
 
645
 
480
646
 
481
647
 
482
648
class _RevListToTimestamps(object):
508
674
    Matches the first entry after a given date (either at midnight or
509
675
    at a specified time).
510
676
 
511
 
    One way to display all the changes since yesterday would be:
512
 
        bzr log -r date:yesterday..-1
513
 
 
514
 
    examples:
 
677
    One way to display all the changes since yesterday would be::
 
678
 
 
679
        bzr log -r date:yesterday..
 
680
 
 
681
    Examples::
 
682
 
515
683
      date:yesterday            -> select the first revision since yesterday
516
684
      date:2006-08-14,17:10:14  -> select the first revision after
517
685
                                   August 14th, 2006 at 5:10pm.
518
 
    """    
 
686
    """
519
687
    prefix = 'date:'
520
 
    _date_re = re.compile(
 
688
    _date_regex = lazy_regex.lazy_compile(
521
689
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
522
690
            r'(,|T)?\s*'
523
691
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
541
709
        elif self.spec.lower() == 'tomorrow':
542
710
            dt = today + datetime.timedelta(days=1)
543
711
        else:
544
 
            m = self._date_re.match(self.spec)
 
712
            m = self._date_regex.match(self.spec)
545
713
            if not m or (not m.group('date') and not m.group('time')):
546
714
                raise errors.InvalidRevisionSpec(self.user_spec,
547
715
                                                 branch, 'invalid date')
577
745
        finally:
578
746
            branch.unlock()
579
747
        if rev == len(revs):
580
 
            return RevisionInfo(branch, None)
 
748
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
581
749
        else:
582
750
            return RevisionInfo(branch, rev + 1)
583
751
 
584
 
SPEC_TYPES.append(RevisionSpec_date)
585
752
 
586
753
 
587
754
class RevisionSpec_ancestor(RevisionSpec):
599
766
    that your branch introduces, while excluding the changes that you
600
767
    have not merged from the remote branch.
601
768
 
602
 
    examples:
 
769
    Examples::
 
770
 
603
771
      ancestor:/path/to/branch
604
772
      $ bzr diff -r ancestor:../../mainline/branch
605
773
    """
606
774
    prefix = 'ancestor:'
607
775
 
608
776
    def _match_on(self, branch, revs):
609
 
        from bzrlib.branch import Branch
610
 
 
611
777
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
612
 
        other_branch = Branch.open(self.spec)
613
 
        revision_a = branch.last_revision()
614
 
        revision_b = other_branch.last_revision()
615
 
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
616
 
            if r in (None, revision.NULL_REVISION):
617
 
                raise errors.NoCommits(b)
618
 
        revision_source = revision.MultipleRevisionSources(
619
 
                branch.repository, other_branch.repository)
620
 
        rev_id = revision.common_ancestor(revision_a, revision_b,
621
 
                                          revision_source)
 
778
        return self._find_revision_info(branch, self.spec)
 
779
 
 
780
    def _as_revision_id(self, context_branch):
 
781
        return self._find_revision_id(context_branch, self.spec)
 
782
 
 
783
    @staticmethod
 
784
    def _find_revision_info(branch, other_location):
 
785
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
 
786
                                                              other_location)
622
787
        try:
623
 
            revno = branch.revision_id_to_revno(rev_id)
 
788
            revno = branch.revision_id_to_revno(revision_id)
624
789
        except errors.NoSuchRevision:
625
790
            revno = None
626
 
        return RevisionInfo(branch, revno, rev_id)
627
 
        
628
 
SPEC_TYPES.append(RevisionSpec_ancestor)
 
791
        return RevisionInfo(branch, revno, revision_id)
 
792
 
 
793
    @staticmethod
 
794
    def _find_revision_id(branch, other_location):
 
795
        from bzrlib.branch import Branch
 
796
 
 
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()
 
814
            if rev_id == revision.NULL_REVISION:
 
815
                raise errors.NoCommonAncestor(revision_a, revision_b)
 
816
            return rev_id
 
817
        finally:
 
818
            branch.unlock()
 
819
 
 
820
 
629
821
 
630
822
 
631
823
class RevisionSpec_branch(RevisionSpec):
635
827
 
636
828
    Supply the path to a branch to select its last revision.
637
829
 
638
 
    examples:
 
830
    Examples::
 
831
 
639
832
      branch:/path/to/branch
640
833
    """
641
834
    prefix = 'branch:'
 
835
    dwim_catchable_exceptions = (errors.NotBranchError,)
642
836
 
643
837
    def _match_on(self, branch, revs):
644
838
        from bzrlib.branch import Branch
646
840
        revision_b = other_branch.last_revision()
647
841
        if revision_b in (None, revision.NULL_REVISION):
648
842
            raise errors.NoCommits(other_branch)
649
 
        # pull in the remote revisions so we can diff
650
 
        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
651
851
        try:
652
852
            revno = branch.revision_id_to_revno(revision_b)
653
853
        except errors.NoSuchRevision:
654
854
            revno = None
655
855
        return RevisionInfo(branch, revno, revision_b)
656
 
        
657
 
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
 
 
882
 
 
883
 
 
884
class RevisionSpec_submit(RevisionSpec_ancestor):
 
885
    """Selects a common ancestor with a submit branch."""
 
886
 
 
887
    help_txt = """Selects a common ancestor with the submit branch.
 
888
 
 
889
    Diffing against this shows all the changes that were made in this branch,
 
890
    and is a good predictor of what merge will do.  The submit branch is
 
891
    used by the bundle and merge directive commands.  If no submit branch
 
892
    is specified, the parent branch is used instead.
 
893
 
 
894
    The common ancestor is the last revision that existed in both
 
895
    branches. Usually this is the branch point, but it could also be
 
896
    a revision that was merged.
 
897
 
 
898
    Examples::
 
899
 
 
900
      $ bzr diff -r submit:
 
901
    """
 
902
 
 
903
    prefix = 'submit:'
 
904
 
 
905
    def _get_submit_location(self, branch):
 
906
        submit_location = branch.get_submit_branch()
 
907
        location_type = 'submit branch'
 
908
        if submit_location is None:
 
909
            submit_location = branch.get_parent()
 
910
            location_type = 'parent branch'
 
911
        if submit_location is None:
 
912
            raise errors.NoSubmitBranch(branch)
 
913
        trace.note('Using %s %s', location_type, submit_location)
 
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)