/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/revisionspec.py

  • Committer: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

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