~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Patch Queue Manager
  • Date: 2011-09-15 15:37:20 UTC
  • mfrom: (6140.1.3 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20110915153720-n17t6m5oh5bblqad
(vila) Open 2.5b2 for bugfixes (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
 
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
 
 
18
from bzrlib.lazy_import import lazy_import
 
19
lazy_import(globals(), """
18
20
import bisect
19
21
import datetime
20
 
import re
21
22
 
22
23
from bzrlib import (
23
 
    errors,
 
24
    branch as _mod_branch,
24
25
    osutils,
25
26
    revision,
26
27
    symbol_versioning,
 
28
    workingtree,
 
29
    )
 
30
""")
 
31
 
 
32
from bzrlib import (
 
33
    errors,
 
34
    lazy_regex,
 
35
    registry,
27
36
    trace,
28
 
    tsort,
29
37
    )
30
38
 
31
39
 
109
117
        return RevisionInfo(branch, revno, revision_id)
110
118
 
111
119
 
112
 
# classes in this list should have a "prefix" attribute, against which
113
 
# string specs are matched
114
 
SPEC_TYPES = []
115
 
_revno_regex = None
116
 
 
117
 
 
118
120
class RevisionSpec(object):
119
121
    """A parsed revision specification."""
120
122
 
121
123
    help_txt = """A parsed revision specification.
122
124
 
123
 
    A revision specification can be an integer, in which case it is
124
 
    assumed to be a revno (though this will translate negative values
125
 
    into positive ones); or it can be a string, in which case it is
126
 
    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.
127
129
 
128
130
    Revision specs are an UI element, and they have been moved out
129
131
    of the branch class to leave "back-end" classes unaware of such
136
138
 
137
139
    prefix = None
138
140
    wants_revision_history = True
139
 
 
140
 
    def __new__(cls, spec, _internal=False):
141
 
        if _internal:
142
 
            return object.__new__(cls, spec, _internal=_internal)
143
 
 
144
 
        symbol_versioning.warn('Creating a RevisionSpec directly has'
145
 
                               ' been deprecated in version 0.11. Use'
146
 
                               ' RevisionSpec.from_string()'
147
 
                               ' instead.',
148
 
                               DeprecationWarning, stacklevel=2)
149
 
        return RevisionSpec.from_string(spec)
 
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
    """
150
149
 
151
150
    @staticmethod
152
151
    def from_string(spec):
161
160
 
162
161
        if spec is None:
163
162
            return RevisionSpec(None, _internal=True)
164
 
        for spectype in SPEC_TYPES:
165
 
            if spec.startswith(spectype.prefix):
166
 
                trace.mutter('Returning RevisionSpec %s for %s',
167
 
                             spectype.__name__, spec)
168
 
                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)
169
169
        else:
170
 
            # RevisionSpec_revno is special cased, because it is the only
171
 
            # one that directly handles plain integers
172
 
            # TODO: This should not be special cased rather it should be
173
 
            # a method invocation on spectype.canparse()
174
 
            global _revno_regex
175
 
            if _revno_regex is None:
176
 
                _revno_regex = re.compile(r'^(?:(\d+(\.\d+)*)|-\d+)(:.*)?$')
177
 
            if _revno_regex.match(spec) is not None:
178
 
                return RevisionSpec_revno(spec, _internal=True)
179
 
 
180
 
            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)
181
173
 
182
174
    def __init__(self, spec, _internal=False):
183
175
        """Create a RevisionSpec referring to the Null revision.
187
179
            called directly. Only from RevisionSpec.from_string()
188
180
        """
189
181
        if not _internal:
190
 
            # XXX: Update this after 0.10 is released
191
182
            symbol_versioning.warn('Creating a RevisionSpec directly has'
192
183
                                   ' been deprecated in version 0.11. Use'
193
184
                                   ' RevisionSpec.from_string()'
252
243
        """
253
244
        return self.in_history(context_branch).rev_id
254
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
 
255
265
    def __repr__(self):
256
266
        # this is mostly for helping with testing
257
267
        return '<%s %s>' % (self.__class__.__name__,
258
268
                              self.user_spec)
259
 
    
 
269
 
260
270
    def needs_branch(self):
261
271
        """Whether this revision spec needs a branch.
262
272
 
266
276
 
267
277
    def get_branch(self):
268
278
        """When the revision specifier contains a branch location, return it.
269
 
        
 
279
 
270
280
        Otherwise, return None.
271
281
        """
272
282
        return None
274
284
 
275
285
# private API
276
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
 
277
361
class RevisionSpec_revno(RevisionSpec):
278
362
    """Selects a revision using a number."""
279
363
 
280
364
    help_txt = """Selects a revision using a number.
281
365
 
282
366
    Use an integer to specify a revision in the history of the branch.
283
 
    Optionally a branch can be specified. The 'revno:' prefix is optional.
284
 
    A negative number will count from the end of the branch (-1 is the
285
 
    last revision, -2 the previous one). If the negative number is larger
286
 
    than the branch's history, the first revision is returned.
 
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.
287
371
    Examples::
288
372
 
289
 
      revno:1                   -> return the first revision
 
373
      revno:1                   -> return the first revision of this branch
290
374
      revno:3:/path/to/branch   -> return the 3rd revision of
291
375
                                   the branch '/path/to/branch'
292
376
      revno:-1                  -> The last revision in a branch.
323
407
                dotted = False
324
408
            except ValueError:
325
409
                # dotted decimal. This arguably should not be here
326
 
                # but the from_string method is a little primitive 
 
410
                # but the from_string method is a little primitive
327
411
                # right now - RBC 20060928
328
412
                try:
329
413
                    match_revno = tuple((int(number) for number in revno_spec.split('.')))
341
425
            revs_or_none = None
342
426
 
343
427
        if dotted:
344
 
            branch.lock_read()
345
428
            try:
346
 
                revision_id_to_revno = branch.get_revision_id_to_revno_map()
347
 
                revisions = [revision_id for revision_id, revno
348
 
                             in revision_id_to_revno.iteritems()
349
 
                             if revno == match_revno]
350
 
            finally:
351
 
                branch.unlock()
352
 
            if len(revisions) != 1:
353
 
                return 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)
354
433
            else:
355
434
                # there is no traditional 'revno' for dotted-decimal revnos.
356
435
                # so for  API compatability we return None.
357
 
                return branch, None, revisions[0]
 
436
                return branch, None, revision_id
358
437
        else:
359
438
            last_revno, last_revision_id = branch.last_revision_info()
360
439
            if revno < 0:
384
463
        else:
385
464
            return self.spec[self.spec.find(':')+1:]
386
465
 
387
 
# Old compatibility 
 
466
# Old compatibility
388
467
RevisionSpec_int = RevisionSpec_revno
389
468
 
390
 
SPEC_TYPES.append(RevisionSpec_revno)
391
 
 
392
 
 
393
 
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):
394
479
    """Selects a revision using the revision id."""
395
480
 
396
481
    help_txt = """Selects a revision using the revision id.
397
482
 
398
483
    Supply a specific revision id, that can be used to specify any
399
 
    revision id in the ancestry of the branch. 
 
484
    revision id in the ancestry of the branch.
400
485
    Including merges, and pending merges.
401
486
    Examples::
402
487
 
405
490
 
406
491
    prefix = 'revid:'
407
492
 
408
 
    def _match_on(self, branch, revs):
 
493
    def _as_revision_id(self, context_branch):
409
494
        # self.spec comes straight from parsing the command line arguments,
410
495
        # so we expect it to be a Unicode string. Switch it to the internal
411
496
        # representation.
412
 
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
413
 
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
414
 
 
415
 
    def _as_revision_id(self, context_branch):
416
497
        return osutils.safe_revision_id(self.spec, warn=False)
417
498
 
418
 
SPEC_TYPES.append(RevisionSpec_revid)
419
499
 
420
500
 
421
501
class RevisionSpec_last(RevisionSpec):
467
547
        revno, revision_id = self._revno_and_revision_id(context_branch, None)
468
548
        return revision_id
469
549
 
470
 
SPEC_TYPES.append(RevisionSpec_last)
471
550
 
472
551
 
473
552
class RevisionSpec_before(RevisionSpec):
475
554
 
476
555
    help_txt = """Selects the parent of the revision specified.
477
556
 
478
 
    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
 
479
561
    It is an error to request the parent of the null revision (before:0).
480
 
    This is mostly useful when inspecting revisions that are not in the
481
 
    revision history of a branch.
482
562
 
483
563
    Examples::
484
564
 
485
565
      before:1913    -> Return the parent of revno 1913 (revno 1912)
486
566
      before:revid:aaaa@bbbb-1234567890  -> return the parent of revision
487
567
                                            aaaa@bbbb-1234567890
488
 
      bzr diff -r before:revid:aaaa..revid:aaaa
489
 
            -> Find the changes between revision 'aaaa' and its parent.
490
 
               (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
491
572
    """
492
573
 
493
574
    prefix = 'before:'
494
 
    
 
575
 
495
576
    def _match_on(self, branch, revs):
496
577
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
497
578
        if r.revno == 0:
522
603
        base_revspec = RevisionSpec.from_string(self.spec)
523
604
        base_revision_id = base_revspec.as_revision_id(context_branch)
524
605
        if base_revision_id == revision.NULL_REVISION:
525
 
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
606
            raise errors.InvalidRevisionSpec(self.user_spec, context_branch,
526
607
                                         'cannot go before the null: revision')
527
608
        context_repo = context_branch.repository
528
609
        context_repo.lock_read()
540
621
                'No parents for revision.')
541
622
        return parents[0]
542
623
 
543
 
SPEC_TYPES.append(RevisionSpec_before)
544
624
 
545
625
 
546
626
class RevisionSpec_tag(RevisionSpec):
552
632
    """
553
633
 
554
634
    prefix = 'tag:'
 
635
    dwim_catchable_exceptions = (errors.NoSuchTag, errors.TagsNotSupported)
555
636
 
556
637
    def _match_on(self, branch, revs):
557
638
        # Can raise tags not supported, NoSuchTag, etc
562
643
    def _as_revision_id(self, context_branch):
563
644
        return context_branch.tags.lookup_tag(self.spec)
564
645
 
565
 
SPEC_TYPES.append(RevisionSpec_tag)
566
646
 
567
647
 
568
648
class _RevListToTimestamps(object):
596
676
 
597
677
    One way to display all the changes since yesterday would be::
598
678
 
599
 
        bzr log -r date:yesterday..-1
 
679
        bzr log -r date:yesterday..
600
680
 
601
681
    Examples::
602
682
 
603
683
      date:yesterday            -> select the first revision since yesterday
604
684
      date:2006-08-14,17:10:14  -> select the first revision after
605
685
                                   August 14th, 2006 at 5:10pm.
606
 
    """    
 
686
    """
607
687
    prefix = 'date:'
608
 
    _date_re = re.compile(
 
688
    _date_regex = lazy_regex.lazy_compile(
609
689
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
610
690
            r'(,|T)?\s*'
611
691
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
629
709
        elif self.spec.lower() == 'tomorrow':
630
710
            dt = today + datetime.timedelta(days=1)
631
711
        else:
632
 
            m = self._date_re.match(self.spec)
 
712
            m = self._date_regex.match(self.spec)
633
713
            if not m or (not m.group('date') and not m.group('time')):
634
714
                raise errors.InvalidRevisionSpec(self.user_spec,
635
715
                                                 branch, 'invalid date')
669
749
        else:
670
750
            return RevisionInfo(branch, rev + 1)
671
751
 
672
 
SPEC_TYPES.append(RevisionSpec_date)
673
752
 
674
753
 
675
754
class RevisionSpec_ancestor(RevisionSpec):
720
799
            revision_a = revision.ensure_null(branch.last_revision())
721
800
            if revision_a == revision.NULL_REVISION:
722
801
                raise errors.NoCommits(branch)
 
802
            if other_location == '':
 
803
                other_location = branch.get_parent()
723
804
            other_branch = Branch.open(other_location)
724
805
            other_branch.lock_read()
725
806
            try:
737
818
            branch.unlock()
738
819
 
739
820
 
740
 
SPEC_TYPES.append(RevisionSpec_ancestor)
741
821
 
742
822
 
743
823
class RevisionSpec_branch(RevisionSpec):
752
832
      branch:/path/to/branch
753
833
    """
754
834
    prefix = 'branch:'
 
835
    dwim_catchable_exceptions = (errors.NotBranchError,)
755
836
 
756
837
    def _match_on(self, branch, revs):
757
838
        from bzrlib.branch import Branch
759
840
        revision_b = other_branch.last_revision()
760
841
        if revision_b in (None, revision.NULL_REVISION):
761
842
            raise errors.NoCommits(other_branch)
762
 
        # pull in the remote revisions so we can diff
763
 
        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
764
851
        try:
765
852
            revno = branch.revision_id_to_revno(revision_b)
766
853
        except errors.NoSuchRevision:
777
864
            raise errors.NoCommits(other_branch)
778
865
        return last_revision
779
866
 
780
 
SPEC_TYPES.append(RevisionSpec_branch)
 
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
 
781
882
 
782
883
 
783
884
class RevisionSpec_submit(RevisionSpec_ancestor):
787
888
 
788
889
    Diffing against this shows all the changes that were made in this branch,
789
890
    and is a good predictor of what merge will do.  The submit branch is
790
 
    used by the bundle and merge directive comands.  If no submit branch
 
891
    used by the bundle and merge directive commands.  If no submit branch
791
892
    is specified, the parent branch is used instead.
792
893
 
793
894
    The common ancestor is the last revision that existed in both
822
923
            self._get_submit_location(context_branch))
823
924
 
824
925
 
825
 
SPEC_TYPES.append(RevisionSpec_submit)
 
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)