1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
3
 
# This program is free software; you can redistribute it and/or modify
 
4
 
# it under the terms of the GNU General Public License as published by
 
5
 
# the Free Software Foundation; either version 2 of the License, or
 
6
 
# (at your option) any later version.
 
8
 
# This program is distributed in the hope that it will be useful,
 
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
 
# GNU General Public License for more details.
 
13
 
# You should have received a copy of the GNU General Public License
 
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
 
20
 
from bzrlib.lazy_import import lazy_import
 
21
 
lazy_import(globals(), """
 
39
 
class RevisionInfo(object):
 
40
 
    """The results of applying a revision specification to a branch."""
 
42
 
    help_txt = """The results of applying a revision specification to a branch.
 
44
 
    An instance has two useful attributes: revno, and rev_id.
 
46
 
    They can also be accessed as spec[0] and spec[1] respectively,
 
47
 
    so that you can write code like:
 
48
 
    revno, rev_id = RevisionSpec(branch, spec)
 
49
 
    although this is probably going to be deprecated later.
 
51
 
    This class exists mostly to be the return value of a RevisionSpec,
 
52
 
    so that you can access the member you're interested in (number or id)
 
53
 
    or treat the result as a tuple.
 
56
 
    def __init__(self, branch, revno, rev_id=_marker):
 
60
 
            # allow caller to be lazy
 
61
 
            if self.revno is None:
 
64
 
                self.rev_id = branch.get_rev_id(self.revno)
 
68
 
    def __nonzero__(self):
 
69
 
        # first the easy ones...
 
70
 
        if self.rev_id is None:
 
72
 
        if self.revno is not None:
 
74
 
        # TODO: otherwise, it should depend on how I was built -
 
75
 
        # if it's in_history(branch), then check revision_history(),
 
76
 
        # if it's in_store(branch), do the check below
 
77
 
        return self.branch.repository.has_revision(self.rev_id)
 
82
 
    def __getitem__(self, index):
 
83
 
        if index == 0: return self.revno
 
84
 
        if index == 1: return self.rev_id
 
85
 
        raise IndexError(index)
 
88
 
        return self.branch.repository.get_revision(self.rev_id)
 
90
 
    def __eq__(self, other):
 
91
 
        if type(other) not in (tuple, list, type(self)):
 
93
 
        if type(other) is type(self) and self.branch is not other.branch:
 
95
 
        return tuple(self) == tuple(other)
 
98
 
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
 
99
 
            self.revno, self.rev_id, self.branch)
 
102
 
    def from_revision_id(branch, revision_id, revs):
 
103
 
        """Construct a RevisionInfo given just the id.
 
105
 
        Use this if you don't know or care what the revno is.
 
107
 
        if revision_id == revision.NULL_REVISION:
 
108
 
            return RevisionInfo(branch, 0, revision_id)
 
110
 
            revno = revs.index(revision_id) + 1
 
113
 
        return RevisionInfo(branch, revno, revision_id)
 
119
 
class RevisionSpec(object):
 
120
 
    """A parsed revision specification."""
 
122
 
    help_txt = """A parsed revision specification.
 
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.
 
129
 
    Revision specs are an UI element, and they have been moved out
 
130
 
    of the branch class to leave "back-end" classes unaware of such
 
131
 
    details.  Code that gets a revno or rev_id from other code should
 
132
 
    not be using revision specs - revnos and revision ids are the
 
133
 
    accepted ways to refer to revisions internally.
 
135
 
    (Equivalent to the old Branch method get_revision_info())
 
139
 
    wants_revision_history = True
 
140
 
    dwim_catchable_exceptions = (errors.InvalidRevisionSpec,)
 
141
 
    """Exceptions that RevisionSpec_dwim._match_on will catch.
 
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
 
150
 
    def from_string(spec):
 
151
 
        """Parse a revision spec string into a RevisionSpec object.
 
153
 
        :param spec: A string specified by the user
 
154
 
        :return: A RevisionSpec object that understands how to parse the
 
157
 
        if not isinstance(spec, (type(None), basestring)):
 
158
 
            raise TypeError('error')
 
161
 
            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)
 
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)
 
178
 
    def __init__(self, spec, _internal=False):
 
179
 
        """Create a RevisionSpec referring to the Null revision.
 
181
 
        :param spec: The original spec supplied by the user
 
182
 
        :param _internal: Used to ensure that RevisionSpec is not being
 
183
 
            called directly. Only from RevisionSpec.from_string()
 
186
 
            symbol_versioning.warn('Creating a RevisionSpec directly has'
 
187
 
                                   ' been deprecated in version 0.11. Use'
 
188
 
                                   ' RevisionSpec.from_string()'
 
190
 
                                   DeprecationWarning, stacklevel=2)
 
191
 
        self.user_spec = spec
 
192
 
        if self.prefix and spec.startswith(self.prefix):
 
193
 
            spec = spec[len(self.prefix):]
 
196
 
    def _match_on(self, branch, revs):
 
197
 
        trace.mutter('Returning RevisionSpec._match_on: None')
 
198
 
        return RevisionInfo(branch, None, None)
 
200
 
    def _match_on_and_check(self, branch, revs):
 
201
 
        info = self._match_on(branch, revs)
 
204
 
        elif info == (None, None):
 
205
 
            # special case - nothing supplied
 
208
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
210
 
            raise errors.InvalidRevisionSpec(self.spec, branch)
 
212
 
    def in_history(self, branch):
 
214
 
            if self.wants_revision_history:
 
215
 
                revs = branch.revision_history()
 
219
 
            # this should never trigger.
 
220
 
            # TODO: make it a deprecated code path. RBC 20060928
 
222
 
        return self._match_on_and_check(branch, revs)
 
224
 
        # FIXME: in_history is somewhat broken,
 
225
 
        # it will return non-history revisions in many
 
226
 
        # circumstances. The expected facility is that
 
227
 
        # in_history only returns revision-history revs,
 
228
 
        # in_store returns any rev. RBC 20051010
 
229
 
    # aliases for now, when we fix the core logic, then they
 
230
 
    # will do what you expect.
 
231
 
    in_store = in_history
 
234
 
    def as_revision_id(self, context_branch):
 
235
 
        """Return just the revision_id for this revisions spec.
 
237
 
        Some revision specs require a context_branch to be able to determine
 
238
 
        their value. Not all specs will make use of it.
 
240
 
        return self._as_revision_id(context_branch)
 
242
 
    def _as_revision_id(self, context_branch):
 
243
 
        """Implementation of as_revision_id()
 
245
 
        Classes should override this function to provide appropriate
 
246
 
        functionality. The default is to just call '.in_history().rev_id'
 
248
 
        return self.in_history(context_branch).rev_id
 
250
 
    def as_tree(self, context_branch):
 
251
 
        """Return the tree object for this revisions spec.
 
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
 
257
 
        return self._as_tree(context_branch)
 
259
 
    def _as_tree(self, context_branch):
 
260
 
        """Implementation of as_tree().
 
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.
 
266
 
        revision_id = self.as_revision_id(context_branch)
 
267
 
        return context_branch.repository.revision_tree(revision_id)
 
270
 
        # this is mostly for helping with testing
 
271
 
        return '<%s %s>' % (self.__class__.__name__,
 
274
 
    def needs_branch(self):
 
275
 
        """Whether this revision spec needs a branch.
 
277
 
        Set this to False the branch argument of _match_on is not used.
 
281
 
    def get_branch(self):
 
282
 
        """When the revision specifier contains a branch location, return it.
 
284
 
        Otherwise, return None.
 
291
 
class RevisionSpec_dwim(RevisionSpec):
 
292
 
    """Provides a DWIMish revision specifier lookup.
 
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.
 
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
 
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
 
309
 
        return rs.in_history(branch)
 
311
 
    def _match_on(self, branch, revs):
 
312
 
        """Run the lookup and see what we can get."""
 
314
 
        # First, see if it's a revno
 
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:
 
320
 
                return self._try_spectype(RevisionSpec_revno, branch)
 
321
 
            except RevisionSpec_revno.dwim_catchable_exceptions:
 
324
 
        # Next see what has been registered
 
325
 
        for rs_class in dwim_revspecs:
 
327
 
                return self._try_spectype(rs_class, branch)
 
328
 
            except rs_class.dwim_catchable_exceptions:
 
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
 
334
 
        raise errors.InvalidRevisionSpec(self.spec, branch)
 
337
 
class RevisionSpec_revno(RevisionSpec):
 
338
 
    """Selects a revision using a number."""
 
340
 
    help_txt = """Selects a revision using a number.
 
342
 
    Use an integer to specify a revision in the history of the branch.
 
343
 
    Optionally a branch can be specified.  A negative number will count
 
344
 
    from the end of the branch (-1 is the last revision, -2 the previous
 
345
 
    one). If the negative number is larger than the branch's history, the
 
346
 
    first revision is returned.
 
349
 
      revno:1                   -> return the first revision of this branch
 
350
 
      revno:3:/path/to/branch   -> return the 3rd revision of
 
351
 
                                   the branch '/path/to/branch'
 
352
 
      revno:-1                  -> The last revision in a branch.
 
353
 
      -2:http://other/branch    -> The second to last revision in the
 
355
 
      -1000000                  -> Most likely the first revision, unless
 
356
 
                                   your history is very long.
 
359
 
    wants_revision_history = False
 
361
 
    def _match_on(self, branch, revs):
 
362
 
        """Lookup a revision by revision number"""
 
363
 
        branch, revno, revision_id = self._lookup(branch, revs)
 
364
 
        return RevisionInfo(branch, revno, revision_id)
 
366
 
    def _lookup(self, branch, revs_or_none):
 
367
 
        loc = self.spec.find(':')
 
369
 
            revno_spec = self.spec
 
372
 
            revno_spec = self.spec[:loc]
 
373
 
            branch_spec = self.spec[loc+1:]
 
377
 
                raise errors.InvalidRevisionSpec(self.user_spec,
 
378
 
                        branch, 'cannot have an empty revno and no branch')
 
382
 
                revno = int(revno_spec)
 
385
 
                # dotted decimal. This arguably should not be here
 
386
 
                # but the from_string method is a little primitive
 
387
 
                # right now - RBC 20060928
 
389
 
                    match_revno = tuple((int(number) for number in revno_spec.split('.')))
 
390
 
                except ValueError, e:
 
391
 
                    raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
 
396
 
            # the user has override the branch to look in.
 
397
 
            # we need to refresh the revision_history map and
 
399
 
            from bzrlib.branch import Branch
 
400
 
            branch = Branch.open(branch_spec)
 
405
 
                revision_id = branch.dotted_revno_to_revision_id(match_revno,
 
407
 
            except errors.NoSuchRevision:
 
408
 
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
410
 
                # there is no traditional 'revno' for dotted-decimal revnos.
 
411
 
                # so for  API compatability we return None.
 
412
 
                return branch, None, revision_id
 
414
 
            last_revno, last_revision_id = branch.last_revision_info()
 
416
 
                # if get_rev_id supported negative revnos, there would not be a
 
417
 
                # need for this special case.
 
418
 
                if (-revno) >= last_revno:
 
421
 
                    revno = last_revno + revno + 1
 
423
 
                revision_id = branch.get_rev_id(revno, revs_or_none)
 
424
 
            except errors.NoSuchRevision:
 
425
 
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
426
 
        return branch, revno, revision_id
 
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)
 
433
 
    def needs_branch(self):
 
434
 
        return self.spec.find(':') == -1
 
436
 
    def get_branch(self):
 
437
 
        if self.spec.find(':') == -1:
 
440
 
            return self.spec[self.spec.find(':')+1:]
 
443
 
RevisionSpec_int = RevisionSpec_revno
 
447
 
class RevisionSpec_revid(RevisionSpec):
 
448
 
    """Selects a revision using the revision id."""
 
450
 
    help_txt = """Selects a revision using the revision id.
 
452
 
    Supply a specific revision id, that can be used to specify any
 
453
 
    revision id in the ancestry of the branch.
 
454
 
    Including merges, and pending merges.
 
457
 
      revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
 
462
 
    def _match_on(self, branch, revs):
 
463
 
        # self.spec comes straight from parsing the command line arguments,
 
464
 
        # so we expect it to be a Unicode string. Switch it to the internal
 
466
 
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
 
467
 
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
 
469
 
    def _as_revision_id(self, context_branch):
 
470
 
        return osutils.safe_revision_id(self.spec, warn=False)
 
474
 
class RevisionSpec_last(RevisionSpec):
 
475
 
    """Selects the nth revision from the end."""
 
477
 
    help_txt = """Selects the nth revision from the end.
 
479
 
    Supply a positive number to get the nth revision from the end.
 
480
 
    This is the same as supplying negative numbers to the 'revno:' spec.
 
483
 
      last:1        -> return the last revision
 
484
 
      last:3        -> return the revision 2 before the end.
 
489
 
    def _match_on(self, branch, revs):
 
490
 
        revno, revision_id = self._revno_and_revision_id(branch, revs)
 
491
 
        return RevisionInfo(branch, revno, revision_id)
 
493
 
    def _revno_and_revision_id(self, context_branch, revs_or_none):
 
494
 
        last_revno, last_revision_id = context_branch.last_revision_info()
 
498
 
                raise errors.NoCommits(context_branch)
 
499
 
            return last_revno, last_revision_id
 
502
 
            offset = int(self.spec)
 
503
 
        except ValueError, e:
 
504
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch, e)
 
507
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
508
 
                                             'you must supply a positive value')
 
510
 
        revno = last_revno - offset + 1
 
512
 
            revision_id = context_branch.get_rev_id(revno, revs_or_none)
 
513
 
        except errors.NoSuchRevision:
 
514
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch)
 
515
 
        return revno, revision_id
 
517
 
    def _as_revision_id(self, context_branch):
 
518
 
        # We compute the revno as part of the process, but we don't really care
 
520
 
        revno, revision_id = self._revno_and_revision_id(context_branch, None)
 
525
 
class RevisionSpec_before(RevisionSpec):
 
526
 
    """Selects the parent of the revision specified."""
 
528
 
    help_txt = """Selects the parent of the revision specified.
 
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
 
534
 
    It is an error to request the parent of the null revision (before:0).
 
538
 
      before:1913    -> Return the parent of revno 1913 (revno 1912)
 
539
 
      before:revid:aaaa@bbbb-1234567890  -> return the parent of revision
 
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
 
549
 
    def _match_on(self, branch, revs):
 
550
 
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
 
552
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
553
 
                                         'cannot go before the null: revision')
 
555
 
            # We need to use the repository history here
 
556
 
            rev = branch.repository.get_revision(r.rev_id)
 
557
 
            if not rev.parent_ids:
 
559
 
                revision_id = revision.NULL_REVISION
 
561
 
                revision_id = rev.parent_ids[0]
 
563
 
                    revno = revs.index(revision_id) + 1
 
569
 
                revision_id = branch.get_rev_id(revno, revs)
 
570
 
            except errors.NoSuchRevision:
 
571
 
                raise errors.InvalidRevisionSpec(self.user_spec,
 
573
 
        return RevisionInfo(branch, revno, revision_id)
 
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()
 
584
 
            parent_map = context_repo.get_parent_map([base_revision_id])
 
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]
 
593
 
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
 
594
 
                'No parents for revision.')
 
599
 
class RevisionSpec_tag(RevisionSpec):
 
600
 
    """Select a revision identified by tag name"""
 
602
 
    help_txt = """Selects a revision identified by a tag name.
 
604
 
    Tags are stored in the branch and created by the 'tag' command.
 
608
 
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
 
610
 
    def _match_on(self, branch, revs):
 
611
 
        # Can raise tags not supported, NoSuchTag, etc
 
612
 
        return RevisionInfo.from_revision_id(branch,
 
613
 
            branch.tags.lookup_tag(self.spec),
 
616
 
    def _as_revision_id(self, context_branch):
 
617
 
        return context_branch.tags.lookup_tag(self.spec)
 
621
 
class _RevListToTimestamps(object):
 
622
 
    """This takes a list of revisions, and allows you to bisect by date"""
 
624
 
    __slots__ = ['revs', 'branch']
 
626
 
    def __init__(self, revs, branch):
 
630
 
    def __getitem__(self, index):
 
631
 
        """Get the date of the index'd item"""
 
632
 
        r = self.branch.repository.get_revision(self.revs[index])
 
633
 
        # TODO: Handle timezone.
 
634
 
        return datetime.datetime.fromtimestamp(r.timestamp)
 
637
 
        return len(self.revs)
 
640
 
class RevisionSpec_date(RevisionSpec):
 
641
 
    """Selects a revision on the basis of a datestamp."""
 
643
 
    help_txt = """Selects a revision on the basis of a datestamp.
 
645
 
    Supply a datestamp to select the first revision that matches the date.
 
646
 
    Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
647
 
    Matches the first entry after a given date (either at midnight or
 
648
 
    at a specified time).
 
650
 
    One way to display all the changes since yesterday would be::
 
652
 
        bzr log -r date:yesterday..
 
656
 
      date:yesterday            -> select the first revision since yesterday
 
657
 
      date:2006-08-14,17:10:14  -> select the first revision after
 
658
 
                                   August 14th, 2006 at 5:10pm.
 
661
 
    _date_re = re.compile(
 
662
 
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
664
 
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
667
 
    def _match_on(self, branch, revs):
 
668
 
        """Spec for date revisions:
 
670
 
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
671
 
          matches the first entry after a given date (either at midnight or
 
672
 
          at a specified time).
 
674
 
        #  XXX: This doesn't actually work
 
675
 
        #  So the proper way of saying 'give me all entries for today' is:
 
676
 
        #      -r date:yesterday..date:today
 
677
 
        today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
 
678
 
        if self.spec.lower() == 'yesterday':
 
679
 
            dt = today - datetime.timedelta(days=1)
 
680
 
        elif self.spec.lower() == 'today':
 
682
 
        elif self.spec.lower() == 'tomorrow':
 
683
 
            dt = today + datetime.timedelta(days=1)
 
685
 
            m = self._date_re.match(self.spec)
 
686
 
            if not m or (not m.group('date') and not m.group('time')):
 
687
 
                raise errors.InvalidRevisionSpec(self.user_spec,
 
688
 
                                                 branch, 'invalid date')
 
692
 
                    year = int(m.group('year'))
 
693
 
                    month = int(m.group('month'))
 
694
 
                    day = int(m.group('day'))
 
701
 
                    hour = int(m.group('hour'))
 
702
 
                    minute = int(m.group('minute'))
 
703
 
                    if m.group('second'):
 
704
 
                        second = int(m.group('second'))
 
708
 
                    hour, minute, second = 0,0,0
 
710
 
                raise errors.InvalidRevisionSpec(self.user_spec,
 
711
 
                                                 branch, 'invalid date')
 
713
 
            dt = datetime.datetime(year=year, month=month, day=day,
 
714
 
                    hour=hour, minute=minute, second=second)
 
717
 
            rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
 
721
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
723
 
            return RevisionInfo(branch, rev + 1)
 
727
 
class RevisionSpec_ancestor(RevisionSpec):
 
728
 
    """Selects a common ancestor with a second branch."""
 
730
 
    help_txt = """Selects a common ancestor with a second branch.
 
732
 
    Supply the path to a branch to select the common ancestor.
 
734
 
    The common ancestor is the last revision that existed in both
 
735
 
    branches. Usually this is the branch point, but it could also be
 
736
 
    a revision that was merged.
 
738
 
    This is frequently used with 'diff' to return all of the changes
 
739
 
    that your branch introduces, while excluding the changes that you
 
740
 
    have not merged from the remote branch.
 
744
 
      ancestor:/path/to/branch
 
745
 
      $ bzr diff -r ancestor:../../mainline/branch
 
749
 
    def _match_on(self, branch, revs):
 
750
 
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
 
751
 
        return self._find_revision_info(branch, self.spec)
 
753
 
    def _as_revision_id(self, context_branch):
 
754
 
        return self._find_revision_id(context_branch, self.spec)
 
757
 
    def _find_revision_info(branch, other_location):
 
758
 
        revision_id = RevisionSpec_ancestor._find_revision_id(branch,
 
761
 
            revno = branch.revision_id_to_revno(revision_id)
 
762
 
        except errors.NoSuchRevision:
 
764
 
        return RevisionInfo(branch, revno, revision_id)
 
767
 
    def _find_revision_id(branch, other_location):
 
768
 
        from bzrlib.branch import Branch
 
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()
 
780
 
                revision_b = revision.ensure_null(other_branch.last_revision())
 
781
 
                if revision_b == revision.NULL_REVISION:
 
782
 
                    raise errors.NoCommits(other_branch)
 
783
 
                graph = branch.repository.get_graph(other_branch.repository)
 
784
 
                rev_id = graph.find_unique_lca(revision_a, revision_b)
 
786
 
                other_branch.unlock()
 
787
 
            if rev_id == revision.NULL_REVISION:
 
788
 
                raise errors.NoCommonAncestor(revision_a, revision_b)
 
796
 
class RevisionSpec_branch(RevisionSpec):
 
797
 
    """Selects the last revision of a specified branch."""
 
799
 
    help_txt = """Selects the last revision of a specified branch.
 
801
 
    Supply the path to a branch to select its last revision.
 
805
 
      branch:/path/to/branch
 
808
 
    dwim_catchable_exceptions = (errors.NotBranchError,)
 
810
 
    def _match_on(self, branch, revs):
 
811
 
        from bzrlib.branch import Branch
 
812
 
        other_branch = Branch.open(self.spec)
 
813
 
        revision_b = other_branch.last_revision()
 
814
 
        if revision_b in (None, revision.NULL_REVISION):
 
815
 
            raise errors.NoCommits(other_branch)
 
816
 
        # pull in the remote revisions so we can diff
 
817
 
        branch.fetch(other_branch, revision_b)
 
819
 
            revno = branch.revision_id_to_revno(revision_b)
 
820
 
        except errors.NoSuchRevision:
 
822
 
        return RevisionInfo(branch, revno, revision_b)
 
824
 
    def _as_revision_id(self, context_branch):
 
825
 
        from bzrlib.branch import Branch
 
826
 
        other_branch = Branch.open(self.spec)
 
827
 
        last_revision = other_branch.last_revision()
 
828
 
        last_revision = revision.ensure_null(last_revision)
 
829
 
        context_branch.fetch(other_branch, last_revision)
 
830
 
        if last_revision == revision.NULL_REVISION:
 
831
 
            raise errors.NoCommits(other_branch)
 
834
 
    def _as_tree(self, context_branch):
 
835
 
        from bzrlib.branch import Branch
 
836
 
        other_branch = Branch.open(self.spec)
 
837
 
        last_revision = other_branch.last_revision()
 
838
 
        last_revision = revision.ensure_null(last_revision)
 
839
 
        if last_revision == revision.NULL_REVISION:
 
840
 
            raise errors.NoCommits(other_branch)
 
841
 
        return other_branch.repository.revision_tree(last_revision)
 
845
 
class RevisionSpec_submit(RevisionSpec_ancestor):
 
846
 
    """Selects a common ancestor with a submit branch."""
 
848
 
    help_txt = """Selects a common ancestor with the submit branch.
 
850
 
    Diffing against this shows all the changes that were made in this branch,
 
851
 
    and is a good predictor of what merge will do.  The submit branch is
 
852
 
    used by the bundle and merge directive commands.  If no submit branch
 
853
 
    is specified, the parent branch is used instead.
 
855
 
    The common ancestor is the last revision that existed in both
 
856
 
    branches. Usually this is the branch point, but it could also be
 
857
 
    a revision that was merged.
 
861
 
      $ bzr diff -r submit:
 
866
 
    def _get_submit_location(self, branch):
 
867
 
        submit_location = branch.get_submit_branch()
 
868
 
        location_type = 'submit branch'
 
869
 
        if submit_location is None:
 
870
 
            submit_location = branch.get_parent()
 
871
 
            location_type = 'parent branch'
 
872
 
        if submit_location is None:
 
873
 
            raise errors.NoSubmitBranch(branch)
 
874
 
        trace.note('Using %s %s', location_type, submit_location)
 
875
 
        return submit_location
 
877
 
    def _match_on(self, branch, revs):
 
878
 
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
 
879
 
        return self._find_revision_info(branch,
 
880
 
            self._get_submit_location(branch))
 
882
 
    def _as_revision_id(self, context_branch):
 
883
 
        return self._find_revision_id(context_branch,
 
884
 
            self._get_submit_location(context_branch))
 
887
 
# The order in which we want to DWIM a revision spec without any prefix.
 
888
 
# revno is always tried first and isn't listed here, this is used by
 
889
 
# RevisionSpec_dwim._match_on
 
891
 
    RevisionSpec_tag, # Let's try for a tag
 
892
 
    RevisionSpec_revid, # Maybe it's a revid?
 
893
 
    RevisionSpec_date, # Perhaps a date?
 
894
 
    RevisionSpec_branch, # OK, last try, maybe it's a branch
 
898
 
revspec_registry = registry.Registry()
 
899
 
def _register_revspec(revspec):
 
900
 
    revspec_registry.register(revspec.prefix, revspec)
 
902
 
_register_revspec(RevisionSpec_revno)
 
903
 
_register_revspec(RevisionSpec_revid)
 
904
 
_register_revspec(RevisionSpec_last)
 
905
 
_register_revspec(RevisionSpec_before)
 
906
 
_register_revspec(RevisionSpec_tag)
 
907
 
_register_revspec(RevisionSpec_date)
 
908
 
_register_revspec(RevisionSpec_ancestor)
 
909
 
_register_revspec(RevisionSpec_branch)
 
910
 
_register_revspec(RevisionSpec_submit)
 
912
 
# classes in this list should have a "prefix" attribute, against which
 
913
 
# string specs are matched
 
914
 
SPEC_TYPES = symbol_versioning.deprecated_list(
 
915
 
    symbol_versioning.deprecated_in((1, 12, 0)), "SPEC_TYPES", [])