1
# Copyright (C) 2005, 2006, 2007 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 
35
class RevisionInfo(object):
 
 
36
    """The results of applying a revision specification to a branch."""
 
 
38
    help_txt = """The results of applying a revision specification to a branch.
 
 
40
    An instance has two useful attributes: revno, and rev_id.
 
 
42
    They can also be accessed as spec[0] and spec[1] respectively,
 
 
43
    so that you can write code like:
 
 
44
    revno, rev_id = RevisionSpec(branch, spec)
 
 
45
    although this is probably going to be deprecated later.
 
 
47
    This class exists mostly to be the return value of a RevisionSpec,
 
 
48
    so that you can access the member you're interested in (number or id)
 
 
49
    or treat the result as a tuple.
 
 
52
    def __init__(self, branch, revno, rev_id=_marker):
 
 
56
            # allow caller to be lazy
 
 
57
            if self.revno is None:
 
 
60
                self.rev_id = branch.get_rev_id(self.revno)
 
 
64
    def __nonzero__(self):
 
 
65
        # first the easy ones...
 
 
66
        if self.rev_id is None:
 
 
68
        if self.revno is not None:
 
 
70
        # TODO: otherwise, it should depend on how I was built -
 
 
71
        # if it's in_history(branch), then check revision_history(),
 
 
72
        # if it's in_store(branch), do the check below
 
 
73
        return self.branch.repository.has_revision(self.rev_id)
 
 
78
    def __getitem__(self, index):
 
 
79
        if index == 0: return self.revno
 
 
80
        if index == 1: return self.rev_id
 
 
81
        raise IndexError(index)
 
 
84
        return self.branch.repository.get_revision(self.rev_id)
 
 
86
    def __eq__(self, other):
 
 
87
        if type(other) not in (tuple, list, type(self)):
 
 
89
        if type(other) is type(self) and self.branch is not other.branch:
 
 
91
        return tuple(self) == tuple(other)
 
 
94
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
 
 
95
            self.revno, self.rev_id, self.branch)
 
 
98
    def from_revision_id(branch, revision_id, revs):
 
 
99
        """Construct a RevisionInfo given just the id.
 
 
101
        Use this if you don't know or care what the revno is.
 
 
104
            revno = revs.index(revision_id) + 1
 
 
107
        return RevisionInfo(branch, revno, revision_id)
 
 
110
# classes in this list should have a "prefix" attribute, against which
 
 
111
# string specs are matched
 
 
116
class RevisionSpec(object):
 
 
117
    """A parsed revision specification."""
 
 
119
    help_txt = """A parsed revision specification.
 
 
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.
 
 
126
    Revision specs are an UI element, and they have been moved out
 
 
127
    of the branch class to leave "back-end" classes unaware of such
 
 
128
    details.  Code that gets a revno or rev_id from other code should
 
 
129
    not be using revision specs - revnos and revision ids are the
 
 
130
    accepted ways to refer to revisions internally.
 
 
132
    (Equivalent to the old Branch method get_revision_info())
 
 
137
    def __new__(cls, spec, _internal=False):
 
 
139
            return object.__new__(cls, spec, _internal=_internal)
 
 
141
        symbol_versioning.warn('Creating a RevisionSpec directly has'
 
 
142
                               ' been deprecated in version 0.11. Use'
 
 
143
                               ' RevisionSpec.from_string()'
 
 
145
                               DeprecationWarning, stacklevel=2)
 
 
146
        return RevisionSpec.from_string(spec)
 
 
149
    def from_string(spec):
 
 
150
        """Parse a revision spec string into a RevisionSpec object.
 
 
152
        :param spec: A string specified by the user
 
 
153
        :return: A RevisionSpec object that understands how to parse the
 
 
156
        if not isinstance(spec, (type(None), basestring)):
 
 
157
            raise TypeError('error')
 
 
160
            return RevisionSpec(None, _internal=True)
 
 
162
        assert isinstance(spec, basestring), \
 
 
163
            "You should only supply strings not %s" % (type(spec),)
 
 
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)
 
 
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()
 
 
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)
 
 
181
            raise errors.NoSuchRevisionSpec(spec)
 
 
183
    def __init__(self, spec, _internal=False):
 
 
184
        """Create a RevisionSpec referring to the Null revision.
 
 
186
        :param spec: The original spec supplied by the user
 
 
187
        :param _internal: Used to ensure that RevisionSpec is not being
 
 
188
            called directly. Only from RevisionSpec.from_string()
 
 
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()'
 
 
196
                                   DeprecationWarning, stacklevel=2)
 
 
197
        self.user_spec = spec
 
 
198
        if self.prefix and spec.startswith(self.prefix):
 
 
199
            spec = spec[len(self.prefix):]
 
 
202
    def _match_on(self, branch, revs):
 
 
203
        trace.mutter('Returning RevisionSpec._match_on: None')
 
 
204
        return RevisionInfo(branch, 0, None)
 
 
206
    def _match_on_and_check(self, branch, revs):
 
 
207
        info = self._match_on(branch, revs)
 
 
210
        elif info == (0, None):
 
 
211
            # special case - the empty tree
 
 
214
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
 
216
            raise errors.InvalidRevisionSpec(self.spec, branch)
 
 
218
    def in_history(self, branch):
 
 
220
            revs = branch.revision_history()
 
 
222
            # this should never trigger.
 
 
223
            # TODO: make it a deprecated code path. RBC 20060928
 
 
225
        return self._match_on_and_check(branch, revs)
 
 
227
        # FIXME: in_history is somewhat broken,
 
 
228
        # it will return non-history revisions in many
 
 
229
        # circumstances. The expected facility is that
 
 
230
        # in_history only returns revision-history revs,
 
 
231
        # in_store returns any rev. RBC 20051010
 
 
232
    # aliases for now, when we fix the core logic, then they
 
 
233
    # will do what you expect.
 
 
234
    in_store = in_history
 
 
238
        # this is mostly for helping with testing
 
 
239
        return '<%s %s>' % (self.__class__.__name__,
 
 
242
    def needs_branch(self):
 
 
243
        """Whether this revision spec needs a branch.
 
 
245
        Set this to False the branch argument of _match_on is not used.
 
 
249
    def get_branch(self):
 
 
250
        """When the revision specifier contains a branch location, return it.
 
 
252
        Otherwise, return None.
 
 
259
class RevisionSpec_revno(RevisionSpec):
 
 
260
    """Selects a revision using a number."""
 
 
262
    help_txt = """Selects a revision using a number.
 
 
264
    Use an integer to specify a revision in the history of the 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.
 
 
270
      revno:1                   -> return the first revision
 
 
271
      revno:3:/path/to/branch   -> return the 3rd revision of
 
 
272
                                   the branch '/path/to/branch'
 
 
273
      revno:-1                  -> The last revision in a branch.
 
 
274
      -2:http://other/branch    -> The second to last revision in the
 
 
276
      -1000000                  -> Most likely the first revision, unless
 
 
277
                                   your history is very long.
 
 
281
    def _match_on(self, branch, revs):
 
 
282
        """Lookup a revision by revision number"""
 
 
283
        loc = self.spec.find(':')
 
 
285
            revno_spec = self.spec
 
 
288
            revno_spec = self.spec[:loc]
 
 
289
            branch_spec = self.spec[loc+1:]
 
 
293
                raise errors.InvalidRevisionSpec(self.user_spec,
 
 
294
                        branch, 'cannot have an empty revno and no branch')
 
 
298
                revno = int(revno_spec)
 
 
301
                # dotted decimal. This arguably should not be here
 
 
302
                # but the from_string method is a little primitive 
 
 
303
                # right now - RBC 20060928
 
 
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)
 
 
312
            # the user has override the branch to look in.
 
 
313
            # we need to refresh the revision_history map and
 
 
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()
 
 
324
                revision_id_to_revno = branch.get_revision_id_to_revno_map()
 
 
325
                revisions = [revision_id for revision_id, revno
 
 
326
                             in revision_id_to_revno.iteritems()
 
 
327
                             if revno == match_revno]
 
 
330
            if len(revisions) != 1:
 
 
331
                return RevisionInfo(branch, None, None)
 
 
333
                # there is no traditional 'revno' for dotted-decimal revnos.
 
 
334
                # so for  API compatability we return None.
 
 
335
                return RevisionInfo(branch, None, revisions[0])
 
 
338
                # if get_rev_id supported negative revnos, there would not be a
 
 
339
                # need for this special case.
 
 
340
                if (-revno) >= len(revs):
 
 
343
                    revno = len(revs) + revno + 1
 
 
345
                revision_id = branch.get_rev_id(revno, revs)
 
 
346
            except errors.NoSuchRevision:
 
 
347
                raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
 
348
        return RevisionInfo(branch, revno, revision_id)
 
 
350
    def needs_branch(self):
 
 
351
        return self.spec.find(':') == -1
 
 
353
    def get_branch(self):
 
 
354
        if self.spec.find(':') == -1:
 
 
357
            return self.spec[self.spec.find(':')+1:]
 
 
360
RevisionSpec_int = RevisionSpec_revno
 
 
362
SPEC_TYPES.append(RevisionSpec_revno)
 
 
365
class RevisionSpec_revid(RevisionSpec):
 
 
366
    """Selects a revision using the revision id."""
 
 
368
    help_txt = """Selects a revision using the revision id.
 
 
370
    Supply a specific revision id, that can be used to specify any
 
 
371
    revision id in the ancestry of the branch. 
 
 
372
    Including merges, and pending merges.
 
 
374
      revid:aaaa@bbbb-123456789 -> Select revision 'aaaa@bbbb-123456789'
 
 
378
    def _match_on(self, branch, revs):
 
 
379
        # self.spec comes straight from parsing the command line arguments,
 
 
380
        # so we expect it to be a Unicode string. Switch it to the internal
 
 
382
        revision_id = osutils.safe_revision_id(self.spec, warn=False)
 
 
383
        return RevisionInfo.from_revision_id(branch, revision_id, revs)
 
 
385
SPEC_TYPES.append(RevisionSpec_revid)
 
 
388
class RevisionSpec_last(RevisionSpec):
 
 
389
    """Selects the nth revision from the end."""
 
 
391
    help_txt = """Selects the nth revision from the end.
 
 
393
    Supply a positive number to get the nth revision from the end.
 
 
394
    This is the same as supplying negative numbers to the 'revno:' spec.
 
 
396
      last:1        -> return the last revision
 
 
397
      last:3        -> return the revision 2 before the end.
 
 
402
    def _match_on(self, branch, revs):
 
 
405
                raise errors.NoCommits(branch)
 
 
406
            return RevisionInfo(branch, len(revs), revs[-1])
 
 
409
            offset = int(self.spec)
 
 
410
        except ValueError, e:
 
 
411
            raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
 
 
414
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
 
415
                                             'you must supply a positive value')
 
 
416
        revno = len(revs) - offset + 1
 
 
418
            revision_id = branch.get_rev_id(revno, revs)
 
 
419
        except errors.NoSuchRevision:
 
 
420
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
 
421
        return RevisionInfo(branch, revno, revision_id)
 
 
423
SPEC_TYPES.append(RevisionSpec_last)
 
 
426
class RevisionSpec_before(RevisionSpec):
 
 
427
    """Selects the parent of the revision specified."""
 
 
429
    help_txt = """Selects the parent of the revision specified.
 
 
431
    Supply any revision spec to return the parent of that revision.
 
 
432
    It is an error to request the parent of the null revision (before:0).
 
 
433
    This is mostly useful when inspecting revisions that are not in the
 
 
434
    revision history of a branch.
 
 
437
      before:1913    -> Return the parent of revno 1913 (revno 1912)
 
 
438
      before:revid:aaaa@bbbb-1234567890  -> return the parent of revision
 
 
440
      bzr diff -r before:revid:aaaa..revid:aaaa
 
 
441
            -> Find the changes between revision 'aaaa' and its parent.
 
 
442
               (what changes did 'aaaa' introduce)
 
 
447
    def _match_on(self, branch, revs):
 
 
448
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
 
 
450
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
 
451
                                         'cannot go before the null: revision')
 
 
453
            # We need to use the repository history here
 
 
454
            rev = branch.repository.get_revision(r.rev_id)
 
 
455
            if not rev.parent_ids:
 
 
459
                revision_id = rev.parent_ids[0]
 
 
461
                    revno = revs.index(revision_id) + 1
 
 
467
                revision_id = branch.get_rev_id(revno, revs)
 
 
468
            except errors.NoSuchRevision:
 
 
469
                raise errors.InvalidRevisionSpec(self.user_spec,
 
 
471
        return RevisionInfo(branch, revno, revision_id)
 
 
473
SPEC_TYPES.append(RevisionSpec_before)
 
 
476
class RevisionSpec_tag(RevisionSpec):
 
 
477
    """Select a revision identified by tag name"""
 
 
479
    help_txt = """Selects a revision identified by a tag name.
 
 
481
    Tags are stored in the branch and created by the 'tag' command.
 
 
486
    def _match_on(self, branch, revs):
 
 
487
        # Can raise tags not supported, NoSuchTag, etc
 
 
488
        return RevisionInfo.from_revision_id(branch,
 
 
489
            branch.tags.lookup_tag(self.spec),
 
 
492
SPEC_TYPES.append(RevisionSpec_tag)
 
 
495
class _RevListToTimestamps(object):
 
 
496
    """This takes a list of revisions, and allows you to bisect by date"""
 
 
498
    __slots__ = ['revs', 'branch']
 
 
500
    def __init__(self, revs, branch):
 
 
504
    def __getitem__(self, index):
 
 
505
        """Get the date of the index'd item"""
 
 
506
        r = self.branch.repository.get_revision(self.revs[index])
 
 
507
        # TODO: Handle timezone.
 
 
508
        return datetime.datetime.fromtimestamp(r.timestamp)
 
 
511
        return len(self.revs)
 
 
514
class RevisionSpec_date(RevisionSpec):
 
 
515
    """Selects a revision on the basis of a datestamp."""
 
 
517
    help_txt = """Selects a revision on the basis of a datestamp.
 
 
519
    Supply a datestamp to select the first revision that matches the date.
 
 
520
    Date can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
 
521
    Matches the first entry after a given date (either at midnight or
 
 
522
    at a specified time).
 
 
524
    One way to display all the changes since yesterday would be:
 
 
525
        bzr log -r date:yesterday..-1
 
 
528
      date:yesterday            -> select the first revision since yesterday
 
 
529
      date:2006-08-14,17:10:14  -> select the first revision after
 
 
530
                                   August 14th, 2006 at 5:10pm.
 
 
533
    _date_re = re.compile(
 
 
534
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
 
536
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
 
539
    def _match_on(self, branch, revs):
 
 
540
        """Spec for date revisions:
 
 
542
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
 
543
          matches the first entry after a given date (either at midnight or
 
 
544
          at a specified time).
 
 
546
        #  XXX: This doesn't actually work
 
 
547
        #  So the proper way of saying 'give me all entries for today' is:
 
 
548
        #      -r date:yesterday..date:today
 
 
549
        today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
 
 
550
        if self.spec.lower() == 'yesterday':
 
 
551
            dt = today - datetime.timedelta(days=1)
 
 
552
        elif self.spec.lower() == 'today':
 
 
554
        elif self.spec.lower() == 'tomorrow':
 
 
555
            dt = today + datetime.timedelta(days=1)
 
 
557
            m = self._date_re.match(self.spec)
 
 
558
            if not m or (not m.group('date') and not m.group('time')):
 
 
559
                raise errors.InvalidRevisionSpec(self.user_spec,
 
 
560
                                                 branch, 'invalid date')
 
 
564
                    year = int(m.group('year'))
 
 
565
                    month = int(m.group('month'))
 
 
566
                    day = int(m.group('day'))
 
 
573
                    hour = int(m.group('hour'))
 
 
574
                    minute = int(m.group('minute'))
 
 
575
                    if m.group('second'):
 
 
576
                        second = int(m.group('second'))
 
 
580
                    hour, minute, second = 0,0,0
 
 
582
                raise errors.InvalidRevisionSpec(self.user_spec,
 
 
583
                                                 branch, 'invalid date')
 
 
585
            dt = datetime.datetime(year=year, month=month, day=day,
 
 
586
                    hour=hour, minute=minute, second=second)
 
 
589
            rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
 
 
593
            return RevisionInfo(branch, None)
 
 
595
            return RevisionInfo(branch, rev + 1)
 
 
597
SPEC_TYPES.append(RevisionSpec_date)
 
 
600
class RevisionSpec_ancestor(RevisionSpec):
 
 
601
    """Selects a common ancestor with a second branch."""
 
 
603
    help_txt = """Selects a common ancestor with a second branch.
 
 
605
    Supply the path to a branch to select the common ancestor.
 
 
607
    The common ancestor is the last revision that existed in both
 
 
608
    branches. Usually this is the branch point, but it could also be
 
 
609
    a revision that was merged.
 
 
611
    This is frequently used with 'diff' to return all of the changes
 
 
612
    that your branch introduces, while excluding the changes that you
 
 
613
    have not merged from the remote branch.
 
 
616
      ancestor:/path/to/branch
 
 
617
      $ bzr diff -r ancestor:../../mainline/branch
 
 
621
    def _match_on(self, branch, revs):
 
 
622
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
 
 
623
        return self._find_revision_info(branch, self.spec)
 
 
626
    def _find_revision_info(branch, other_location):
 
 
627
        from bzrlib.branch import Branch
 
 
629
        other_branch = Branch.open(other_location)
 
 
630
        revision_a = branch.last_revision()
 
 
631
        revision_b = other_branch.last_revision()
 
 
632
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
 
 
633
            if r in (None, revision.NULL_REVISION):
 
 
634
                raise errors.NoCommits(b)
 
 
635
        revision_source = revision.MultipleRevisionSources(
 
 
636
                branch.repository, other_branch.repository)
 
 
637
        graph = branch.repository.get_graph(other_branch.repository)
 
 
638
        revision_a = revision.ensure_null(revision_a)
 
 
639
        revision_b = revision.ensure_null(revision_b)
 
 
640
        if revision.NULL_REVISION in (revision_a, revision_b):
 
 
641
            rev_id = revision.NULL_REVISION
 
 
643
            rev_id = graph.find_unique_lca(revision_a, revision_b)
 
 
644
            if rev_id == revision.NULL_REVISION:
 
 
645
                raise errors.NoCommonAncestor(revision_a, revision_b)
 
 
647
            revno = branch.revision_id_to_revno(rev_id)
 
 
648
        except errors.NoSuchRevision:
 
 
650
        return RevisionInfo(branch, revno, rev_id)
 
 
653
SPEC_TYPES.append(RevisionSpec_ancestor)
 
 
656
class RevisionSpec_branch(RevisionSpec):
 
 
657
    """Selects the last revision of a specified branch."""
 
 
659
    help_txt = """Selects the last revision of a specified branch.
 
 
661
    Supply the path to a branch to select its last revision.
 
 
664
      branch:/path/to/branch
 
 
668
    def _match_on(self, branch, revs):
 
 
669
        from bzrlib.branch import Branch
 
 
670
        other_branch = Branch.open(self.spec)
 
 
671
        revision_b = other_branch.last_revision()
 
 
672
        if revision_b in (None, revision.NULL_REVISION):
 
 
673
            raise errors.NoCommits(other_branch)
 
 
674
        # pull in the remote revisions so we can diff
 
 
675
        branch.fetch(other_branch, revision_b)
 
 
677
            revno = branch.revision_id_to_revno(revision_b)
 
 
678
        except errors.NoSuchRevision:
 
 
680
        return RevisionInfo(branch, revno, revision_b)
 
 
682
SPEC_TYPES.append(RevisionSpec_branch)
 
 
685
class RevisionSpec_submit(RevisionSpec_ancestor):
 
 
686
    """Selects a common ancestor with a submit branch."""
 
 
688
    help_txt = """Selects a common ancestor with the submit branch.
 
 
690
    Diffing against this shows all the changes that were made in this branch,
 
 
691
    and is a good predictor of what merge will do.  The submit branch is
 
 
692
    used by the bundle and merge directive comands.  If no submit branch
 
 
693
    is specified, the parent branch is used instead.
 
 
695
    The common ancestor is the last revision that existed in both
 
 
696
    branches. Usually this is the branch point, but it could also be
 
 
697
    a revision that was merged.
 
 
700
      $ bzr diff -r submit:
 
 
705
    def _match_on(self, branch, revs):
 
 
706
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
 
 
707
        submit_location = branch.get_submit_branch()
 
 
708
        location_type = 'submit branch'
 
 
709
        if submit_location is None:
 
 
710
            submit_location = branch.get_parent()
 
 
711
            location_type = 'parent branch'
 
 
712
        if submit_location is None:
 
 
713
            raise errors.NoSubmitBranch(branch)
 
 
714
        trace.note('Using %s %s', location_type, submit_location)
 
 
715
        return self._find_revision_info(branch, submit_location)
 
 
718
SPEC_TYPES.append(RevisionSpec_submit)