~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: 2008-03-16 14:01:20 UTC
  • mfrom: (3280.2.5 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080316140120-i3yq8yr1l66m11h7
Start 1.4 development

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
 
18
 
import re
19
 
 
20
 
from bzrlib.lazy_import import lazy_import
21
 
lazy_import(globals(), """
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
22
18
import bisect
23
19
import datetime
24
 
""")
 
20
import re
25
21
 
26
22
from bzrlib import (
27
23
    errors,
28
24
    osutils,
29
 
    registry,
30
25
    revision,
31
26
    symbol_versioning,
32
27
    trace,
 
28
    tsort,
33
29
    )
34
30
 
35
31
 
104
100
 
105
101
        Use this if you don't know or care what the revno is.
106
102
        """
107
 
        if revision_id == revision.NULL_REVISION:
108
 
            return RevisionInfo(branch, 0, revision_id)
109
103
        try:
110
104
            revno = revs.index(revision_id) + 1
111
105
        except ValueError:
113
107
        return RevisionInfo(branch, revno, revision_id)
114
108
 
115
109
 
 
110
# classes in this list should have a "prefix" attribute, against which
 
111
# string specs are matched
 
112
SPEC_TYPES = []
116
113
_revno_regex = None
117
114
 
118
115
 
121
118
 
122
119
    help_txt = """A parsed revision specification.
123
120
 
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.
 
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.
128
125
 
129
126
    Revision specs are an UI element, and they have been moved out
130
127
    of the branch class to leave "back-end" classes unaware of such
136
133
    """
137
134
 
138
135
    prefix = None
139
 
    wants_revision_history = True
140
 
    dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
141
 
    """Exceptions that RevisionSpec_dwim._match_on will catch.
142
 
 
143
 
    If the revspec is part of ``dwim_revspecs``, it may be tried with an
144
 
    invalid revspec and raises some exception. The exceptions mentioned here
145
 
    will not be reported to the user but simply ignored without stopping the
146
 
    dwim processing.
147
 
    """
 
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)
148
147
 
149
148
    @staticmethod
150
149
    def from_string(spec):
159
158
 
160
159
        if spec is None:
161
160
            return RevisionSpec(None, _internal=True)
162
 
        match = revspec_registry.get_prefix(spec)
163
 
        if match is not None:
164
 
            spectype, specsuffix = match
165
 
            trace.mutter('Returning RevisionSpec %s for %s',
166
 
                         spectype.__name__, spec)
167
 
            return spectype(spec, _internal=True)
 
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)
168
170
        else:
169
 
            for spectype in SPEC_TYPES:
170
 
                if spec.startswith(spectype.prefix):
171
 
                    trace.mutter('Returning RevisionSpec %s for %s',
172
 
                                 spectype.__name__, spec)
173
 
                    return spectype(spec, _internal=True)
174
 
            # Otherwise treat it as a DWIM, build the RevisionSpec object and
175
 
            # wait for _match_on to be called.
176
 
            return RevisionSpec_dwim(spec, _internal=True)
 
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)
177
182
 
178
183
    def __init__(self, spec, _internal=False):
179
184
        """Create a RevisionSpec referring to the Null revision.
183
188
            called directly. Only from RevisionSpec.from_string()
184
189
        """
185
190
        if not _internal:
 
191
            # XXX: Update this after 0.10 is released
186
192
            symbol_versioning.warn('Creating a RevisionSpec directly has'
187
193
                                   ' been deprecated in version 0.11. Use'
188
194
                                   ' RevisionSpec.from_string()'
195
201
 
196
202
    def _match_on(self, branch, revs):
197
203
        trace.mutter('Returning RevisionSpec._match_on: None')
198
 
        return RevisionInfo(branch, None, None)
 
204
        return RevisionInfo(branch, 0, None)
199
205
 
200
206
    def _match_on_and_check(self, branch, revs):
201
207
        info = self._match_on(branch, revs)
202
208
        if info:
203
209
            return info
204
 
        elif info == (None, None):
205
 
            # special case - nothing supplied
 
210
        elif info == (0, None):
 
211
            # special case - the empty tree
206
212
            return info
207
213
        elif self.prefix:
208
214
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
211
217
 
212
218
    def in_history(self, branch):
213
219
        if branch:
214
 
            if self.wants_revision_history:
215
 
                revs = branch.revision_history()
216
 
            else:
217
 
                revs = None
 
220
            revs = branch.revision_history()
218
221
        else:
219
222
            # this should never trigger.
220
223
            # TODO: make it a deprecated code path. RBC 20060928
230
233
    # will do what you expect.
231
234
    in_store = in_history
232
235
    in_branch = in_store
233
 
 
234
 
    def as_revision_id(self, context_branch):
235
 
        """Return just the revision_id for this revisions spec.
236
 
 
237
 
        Some revision specs require a context_branch to be able to determine
238
 
        their value. Not all specs will make use of it.
239
 
        """
240
 
        return self._as_revision_id(context_branch)
241
 
 
242
 
    def _as_revision_id(self, context_branch):
243
 
        """Implementation of as_revision_id()
244
 
 
245
 
        Classes should override this function to provide appropriate
246
 
        functionality. The default is to just call '.in_history().rev_id'
247
 
        """
248
 
        return self.in_history(context_branch).rev_id
249
 
 
250
 
    def as_tree(self, context_branch):
251
 
        """Return the tree object for this revisions spec.
252
 
 
253
 
        Some revision specs require a context_branch to be able to determine
254
 
        the revision id and access the repository. Not all specs will make
255
 
        use of it.
256
 
        """
257
 
        return self._as_tree(context_branch)
258
 
 
259
 
    def _as_tree(self, context_branch):
260
 
        """Implementation of as_tree().
261
 
 
262
 
        Classes should override this function to provide appropriate
263
 
        functionality. The default is to just call '.as_revision_id()'
264
 
        and get the revision tree from context_branch's repository.
265
 
        """
266
 
        revision_id = self.as_revision_id(context_branch)
267
 
        return context_branch.repository.revision_tree(revision_id)
268
 
 
 
236
        
269
237
    def __repr__(self):
270
238
        # this is mostly for helping with testing
271
239
        return '<%s %s>' % (self.__class__.__name__,
272
240
                              self.user_spec)
273
 
 
 
241
    
274
242
    def needs_branch(self):
275
243
        """Whether this revision spec needs a branch.
276
244
 
280
248
 
281
249
    def get_branch(self):
282
250
        """When the revision specifier contains a branch location, return it.
283
 
 
 
251
        
284
252
        Otherwise, return None.
285
253
        """
286
254
        return None
288
256
 
289
257
# private API
290
258
 
291
 
class RevisionSpec_dwim(RevisionSpec):
292
 
    """Provides a DWIMish revision specifier lookup.
293
 
 
294
 
    Note that this does not go in the revspec_registry because by definition
295
 
    there is no prefix to identify it.  It's solely called from
296
 
    RevisionSpec.from_string() because the DWIMification happen when _match_on
297
 
    is called so the string describing the revision is kept here until needed.
298
 
    """
299
 
 
300
 
    help_txt = None
301
 
    # We don't need to build the revision history ourself, that's delegated to
302
 
    # each revspec we try.
303
 
    wants_revision_history = False
304
 
 
305
 
    def _try_spectype(self, rstype, branch):
306
 
        rs = rstype(self.spec, _internal=True)
307
 
        # Hit in_history to find out if it exists, or we need to try the
308
 
        # next type.
309
 
        return rs.in_history(branch)
310
 
 
311
 
    def _match_on(self, branch, revs):
312
 
        """Run the lookup and see what we can get."""
313
 
 
314
 
        # First, see if it's a revno
315
 
        global _revno_regex
316
 
        if _revno_regex is None:
317
 
            _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
318
 
        if _revno_regex.match(self.spec) is not None:
319
 
            try:
320
 
                return self._try_spectype(RevisionSpec_revno, branch)
321
 
            except RevisionSpec_revno.dwim_catchable_exceptions:
322
 
                pass
323
 
 
324
 
        # Next see what has been registered
325
 
        for rs_class in dwim_revspecs:
326
 
            try:
327
 
                return self._try_spectype(rs_class, branch)
328
 
            except rs_class.dwim_catchable_exceptions:
329
 
                pass
330
 
 
331
 
        # Well, I dunno what it is. Note that we don't try to keep track of the
332
 
        # first of last exception raised during the DWIM tries as none seems
333
 
        # really relevant.
334
 
        raise errors.InvalidRevisionSpec(self.spec, branch)
335
 
 
336
 
 
337
259
class RevisionSpec_revno(RevisionSpec):
338
260
    """Selects a revision using a number."""
339
261
 
340
262
    help_txt = """Selects a revision using a number.
341
263
 
342
264
    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.
 
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.
347
269
    Examples::
348
270
 
349
 
      revno:1                   -> return the first revision of this branch
 
271
      revno:1                   -> return the first revision
350
272
      revno:3:/path/to/branch   -> return the 3rd revision of
351
273
                                   the branch '/path/to/branch'
352
274
      revno:-1                  -> The last revision in a branch.
356
278
                                   your history is very long.
357
279
    """
358
280
    prefix = 'revno:'
359
 
    wants_revision_history = False
360
281
 
361
282
    def _match_on(self, branch, revs):
362
283
        """Lookup a revision by revision number"""
363
 
        branch, revno, revision_id = self._lookup(branch, revs)
364
 
        return RevisionInfo(branch, revno, revision_id)
365
 
 
366
 
    def _lookup(self, branch, revs_or_none):
367
284
        loc = self.spec.find(':')
368
285
        if loc == -1:
369
286
            revno_spec = self.spec
383
300
                dotted = False
384
301
            except ValueError:
385
302
                # dotted decimal. This arguably should not be here
386
 
                # but the from_string method is a little primitive
 
303
                # but the from_string method is a little primitive 
387
304
                # right now - RBC 20060928
388
305
                try:
389
306
                    match_revno = tuple((int(number) for number in revno_spec.split('.')))
398
315
            # the branch object.
399
316
            from bzrlib.branch import Branch
400
317
            branch = Branch.open(branch_spec)
401
 
            revs_or_none = None
 
318
            # Need to use a new revision history
 
319
            # because we are using a specific branch
 
320
            revs = branch.revision_history()
402
321
 
403
322
        if dotted:
 
323
            branch.lock_read()
404
324
            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)
 
325
                revision_id_to_revno = branch.get_revision_id_to_revno_map()
 
326
                revisions = [revision_id for revision_id, revno
 
327
                             in revision_id_to_revno.iteritems()
 
328
                             if revno == match_revno]
 
329
            finally:
 
330
                branch.unlock()
 
331
            if len(revisions) != 1:
 
332
                return RevisionInfo(branch, None, None)
409
333
            else:
410
334
                # there is no traditional 'revno' for dotted-decimal revnos.
411
335
                # so for  API compatability we return None.
412
 
                return branch, None, revision_id
 
336
                return RevisionInfo(branch, None, revisions[0])
413
337
        else:
414
 
            last_revno, last_revision_id = branch.last_revision_info()
415
338
            if revno < 0:
416
339
                # if get_rev_id supported negative revnos, there would not be a
417
340
                # need for this special case.
418
 
                if (-revno) >= last_revno:
 
341
                if (-revno) >= len(revs):
419
342
                    revno = 1
420
343
                else:
421
 
                    revno = last_revno + revno + 1
 
344
                    revno = len(revs) + revno + 1
422
345
            try:
423
 
                revision_id = branch.get_rev_id(revno, revs_or_none)
 
346
                revision_id = branch.get_rev_id(revno, revs)
424
347
            except errors.NoSuchRevision:
425
348
                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
 
 
 
349
        return RevisionInfo(branch, revno, revision_id)
 
350
        
433
351
    def needs_branch(self):
434
352
        return self.spec.find(':') == -1
435
353
 
439
357
        else:
440
358
            return self.spec[self.spec.find(':')+1:]
441
359
 
442
 
# Old compatibility
 
360
# Old compatibility 
443
361
RevisionSpec_int = RevisionSpec_revno
444
362
 
 
363
SPEC_TYPES.append(RevisionSpec_revno)
445
364
 
446
365
 
447
366
class RevisionSpec_revid(RevisionSpec):
450
369
    help_txt = """Selects a revision using the revision id.
451
370
 
452
371
    Supply a specific revision id, that can be used to specify any
453
 
    revision id in the ancestry of the branch.
 
372
    revision id in the ancestry of the branch. 
454
373
    Including merges, and pending merges.
455
374
    Examples::
456
375
 
457
376
      revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
458
 
    """
459
 
 
 
377
    """    
460
378
    prefix = 'revid:'
461
379
 
462
380
    def _match_on(self, branch, revs):
466
384
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
467
385
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
468
386
 
469
 
    def _as_revision_id(self, context_branch):
470
 
        return osutils.safe_revision_id(self.spec, warn=False)
471
 
 
 
387
SPEC_TYPES.append(RevisionSpec_revid)
472
388
 
473
389
 
474
390
class RevisionSpec_last(RevisionSpec):
482
398
 
483
399
      last:1        -> return the last revision
484
400
      last:3        -> return the revision 2 before the end.
485
 
    """
 
401
    """    
486
402
 
487
403
    prefix = 'last:'
488
404
 
489
405
    def _match_on(self, branch, revs):
490
 
        revno, revision_id = self._revno_and_revision_id(branch, revs)
491
 
        return RevisionInfo(branch, revno, revision_id)
492
 
 
493
 
    def _revno_and_revision_id(self, context_branch, revs_or_none):
494
 
        last_revno, last_revision_id = context_branch.last_revision_info()
495
 
 
496
406
        if self.spec == '':
497
 
            if not last_revno:
498
 
                raise errors.NoCommits(context_branch)
499
 
            return last_revno, last_revision_id
 
407
            if not revs:
 
408
                raise errors.NoCommits(branch)
 
409
            return RevisionInfo(branch, len(revs), revs[-1])
500
410
 
501
411
        try:
502
412
            offset = int(self.spec)
503
413
        except ValueError, e:
504
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
 
414
            raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
505
415
 
506
416
        if offset <= 0:
507
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
417
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
508
418
                                             'you must supply a positive value')
509
 
 
510
 
        revno = last_revno - offset + 1
 
419
        revno = len(revs) - offset + 1
511
420
        try:
512
 
            revision_id = context_branch.get_rev_id(revno, revs_or_none)
 
421
            revision_id = branch.get_rev_id(revno, revs)
513
422
        except errors.NoSuchRevision:
514
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
515
 
        return revno, revision_id
516
 
 
517
 
    def _as_revision_id(self, context_branch):
518
 
        # We compute the revno as part of the process, but we don't really care
519
 
        # about it.
520
 
        revno, revision_id = self._revno_and_revision_id(context_branch, None)
521
 
        return revision_id
522
 
 
 
423
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
424
        return RevisionInfo(branch, revno, revision_id)
 
425
 
 
426
SPEC_TYPES.append(RevisionSpec_last)
523
427
 
524
428
 
525
429
class RevisionSpec_before(RevisionSpec):
527
431
 
528
432
    help_txt = """Selects the parent of the revision specified.
529
433
 
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
 
 
 
434
    Supply any revision spec to return the parent of that revision.
534
435
    It is an error to request the parent of the null revision (before:0).
 
436
    This is mostly useful when inspecting revisions that are not in the
 
437
    revision history of a branch.
535
438
 
536
439
    Examples::
537
440
 
538
441
      before:1913    -> Return the parent of revno 1913 (revno 1912)
539
442
      before:revid:aaaa@bbbb-1234567890  -> return the parent of revision
540
443
                                            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
 
444
      bzr diff -r before:revid:aaaa..revid:aaaa
 
445
            -> Find the changes between revision 'aaaa' and its parent.
 
446
               (what changes did 'aaaa' introduce)
545
447
    """
546
448
 
547
449
    prefix = 'before:'
548
 
 
 
450
    
549
451
    def _match_on(self, branch, revs):
550
452
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
551
453
        if r.revno == 0:
572
474
                                                 branch)
573
475
        return RevisionInfo(branch, revno, revision_id)
574
476
 
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
 
 
 
477
SPEC_TYPES.append(RevisionSpec_before)
597
478
 
598
479
 
599
480
class RevisionSpec_tag(RevisionSpec):
605
486
    """
606
487
 
607
488
    prefix = 'tag:'
608
 
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
609
489
 
610
490
    def _match_on(self, branch, revs):
611
491
        # Can raise tags not supported, NoSuchTag, etc
613
493
            branch.tags.lookup_tag(self.spec),
614
494
            revs)
615
495
 
616
 
    def _as_revision_id(self, context_branch):
617
 
        return context_branch.tags.lookup_tag(self.spec)
618
 
 
 
496
SPEC_TYPES.append(RevisionSpec_tag)
619
497
 
620
498
 
621
499
class _RevListToTimestamps(object):
649
527
 
650
528
    One way to display all the changes since yesterday would be::
651
529
 
652
 
        bzr log -r date:yesterday..
 
530
        bzr log -r date:yesterday..-1
653
531
 
654
532
    Examples::
655
533
 
656
534
      date:yesterday            -> select the first revision since yesterday
657
535
      date:2006-08-14,17:10:14  -> select the first revision after
658
536
                                   August 14th, 2006 at 5:10pm.
659
 
    """
 
537
    """    
660
538
    prefix = 'date:'
661
539
    _date_re = re.compile(
662
540
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
718
596
        finally:
719
597
            branch.unlock()
720
598
        if rev == len(revs):
721
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
599
            return RevisionInfo(branch, None)
722
600
        else:
723
601
            return RevisionInfo(branch, rev + 1)
724
602
 
 
603
SPEC_TYPES.append(RevisionSpec_date)
725
604
 
726
605
 
727
606
class RevisionSpec_ancestor(RevisionSpec):
750
629
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
751
630
        return self._find_revision_info(branch, self.spec)
752
631
 
753
 
    def _as_revision_id(self, context_branch):
754
 
        return self._find_revision_id(context_branch, self.spec)
755
 
 
756
632
    @staticmethod
757
633
    def _find_revision_info(branch, other_location):
758
 
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
759
 
                                                              other_location)
760
 
        try:
761
 
            revno = branch.revision_id_to_revno(revision_id)
762
 
        except errors.NoSuchRevision:
763
 
            revno = None
764
 
        return RevisionInfo(branch, revno, revision_id)
765
 
 
766
 
    @staticmethod
767
 
    def _find_revision_id(branch, other_location):
768
634
        from bzrlib.branch import Branch
769
635
 
 
636
        other_branch = Branch.open(other_location)
 
637
        revision_a = branch.last_revision()
 
638
        revision_b = other_branch.last_revision()
 
639
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
 
640
            if r in (None, revision.NULL_REVISION):
 
641
                raise errors.NoCommits(b)
770
642
        branch.lock_read()
 
643
        other_branch.lock_read()
771
644
        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)
 
645
            graph = branch.repository.get_graph(other_branch.repository)
 
646
            revision_a = revision.ensure_null(revision_a)
 
647
            revision_b = revision.ensure_null(revision_b)
 
648
            if revision.NULL_REVISION in (revision_a, revision_b):
 
649
                rev_id = revision.NULL_REVISION
 
650
            else:
784
651
                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
 
652
                if rev_id == revision.NULL_REVISION:
 
653
                    raise errors.NoCommonAncestor(revision_a, revision_b)
 
654
            try:
 
655
                revno = branch.revision_id_to_revno(rev_id)
 
656
            except errors.NoSuchRevision:
 
657
                revno = None
 
658
            return RevisionInfo(branch, revno, rev_id)
790
659
        finally:
791
660
            branch.unlock()
792
 
 
793
 
 
 
661
            other_branch.unlock()
 
662
 
 
663
 
 
664
SPEC_TYPES.append(RevisionSpec_ancestor)
794
665
 
795
666
 
796
667
class RevisionSpec_branch(RevisionSpec):
805
676
      branch:/path/to/branch
806
677
    """
807
678
    prefix = 'branch:'
808
 
    dwim_catchable_exceptions = (errors.NotBranchError,)
809
679
 
810
680
    def _match_on(self, branch, revs):
811
681
        from bzrlib.branch import Branch
813
683
        revision_b = other_branch.last_revision()
814
684
        if revision_b in (None, revision.NULL_REVISION):
815
685
            raise errors.NoCommits(other_branch)
816
 
        if branch is None:
817
 
            branch = other_branch
818
 
        else:
819
 
            try:
820
 
                # pull in the remote revisions so we can diff
821
 
                branch.fetch(other_branch, revision_b)
822
 
            except errors.ReadOnlyError:
823
 
                branch = other_branch
 
686
        # pull in the remote revisions so we can diff
 
687
        branch.fetch(other_branch, revision_b)
824
688
        try:
825
689
            revno = branch.revision_id_to_revno(revision_b)
826
690
        except errors.NoSuchRevision:
827
691
            revno = None
828
692
        return RevisionInfo(branch, revno, revision_b)
829
 
 
830
 
    def _as_revision_id(self, context_branch):
831
 
        from bzrlib.branch import Branch
832
 
        other_branch = Branch.open(self.spec)
833
 
        last_revision = other_branch.last_revision()
834
 
        last_revision = revision.ensure_null(last_revision)
835
 
        context_branch.fetch(other_branch, last_revision)
836
 
        if last_revision == revision.NULL_REVISION:
837
 
            raise errors.NoCommits(other_branch)
838
 
        return last_revision
839
 
 
840
 
    def _as_tree(self, context_branch):
841
 
        from bzrlib.branch import Branch
842
 
        other_branch = Branch.open(self.spec)
843
 
        last_revision = other_branch.last_revision()
844
 
        last_revision = revision.ensure_null(last_revision)
845
 
        if last_revision == revision.NULL_REVISION:
846
 
            raise errors.NoCommits(other_branch)
847
 
        return other_branch.repository.revision_tree(last_revision)
848
 
 
849
 
    def needs_branch(self):
850
 
        return False
851
 
 
852
 
    def get_branch(self):
853
 
        return self.spec
854
 
 
 
693
        
 
694
SPEC_TYPES.append(RevisionSpec_branch)
855
695
 
856
696
 
857
697
class RevisionSpec_submit(RevisionSpec_ancestor):
861
701
 
862
702
    Diffing against this shows all the changes that were made in this branch,
863
703
    and is a good predictor of what merge will do.  The submit branch is
864
 
    used by the bundle and merge directive commands.  If no submit branch
 
704
    used by the bundle and merge directive comands.  If no submit branch
865
705
    is specified, the parent branch is used instead.
866
706
 
867
707
    The common ancestor is the last revision that existed in both
875
715
 
876
716
    prefix = 'submit:'
877
717
 
878
 
    def _get_submit_location(self, branch):
 
718
    def _match_on(self, branch, revs):
 
719
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
879
720
        submit_location = branch.get_submit_branch()
880
721
        location_type = 'submit branch'
881
722
        if submit_location is None:
884
725
        if submit_location is None:
885
726
            raise errors.NoSubmitBranch(branch)
886
727
        trace.note('Using %s %s', location_type, submit_location)
887
 
        return submit_location
888
 
 
889
 
    def _match_on(self, branch, revs):
890
 
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
891
 
        return self._find_revision_info(branch,
892
 
            self._get_submit_location(branch))
893
 
 
894
 
    def _as_revision_id(self, context_branch):
895
 
        return self._find_revision_id(context_branch,
896
 
            self._get_submit_location(context_branch))
897
 
 
898
 
 
899
 
# The order in which we want to DWIM a revision spec without any prefix.
900
 
# revno is always tried first and isn't listed here, this is used by
901
 
# RevisionSpec_dwim._match_on
902
 
dwim_revspecs = [
903
 
    RevisionSpec_tag, # Let's try for a tag
904
 
    RevisionSpec_revid, # Maybe it's a revid?
905
 
    RevisionSpec_date, # Perhaps a date?
906
 
    RevisionSpec_branch, # OK, last try, maybe it's a branch
907
 
    ]
908
 
 
909
 
 
910
 
revspec_registry = registry.Registry()
911
 
def _register_revspec(revspec):
912
 
    revspec_registry.register(revspec.prefix, revspec)
913
 
 
914
 
_register_revspec(RevisionSpec_revno)
915
 
_register_revspec(RevisionSpec_revid)
916
 
_register_revspec(RevisionSpec_last)
917
 
_register_revspec(RevisionSpec_before)
918
 
_register_revspec(RevisionSpec_tag)
919
 
_register_revspec(RevisionSpec_date)
920
 
_register_revspec(RevisionSpec_ancestor)
921
 
_register_revspec(RevisionSpec_branch)
922
 
_register_revspec(RevisionSpec_submit)
923
 
 
924
 
# classes in this list should have a "prefix" attribute, against which
925
 
# string specs are matched
926
 
SPEC_TYPES = symbol_versioning.deprecated_list(
927
 
    symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])
 
728
        return self._find_revision_info(branch, submit_location)
 
729
 
 
730
 
 
731
SPEC_TYPES.append(RevisionSpec_submit)