/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: v.ladeuil+lp at free
  • Date: 2006-10-12 14:29:32 UTC
  • mto: (2145.1.1 keepalive)
  • mto: This revision was merged to the branch mainline in revision 2146.
  • Revision ID: v.ladeuil+lp@free.fr-20061012142932-7221fe16d2b48fa3
Shuffle http related test code. Hopefully it ends up at the right place :)

* bzrlib/tests/HttpServer.py: 
New file. bzrlib.tests.ChrootedTestCase use HttpServer. So the
class can't be defined in bzrlib.tests.HTTPUtils because it
creates a circular dependency (bzrlib.tests.HTTPUtils needs to
import bzrlib.tests).

* bzrlib/transport/http/_urllib.py: 
Transfer test server definition to bzrlib.tests.HttpServer. Clean
up imports.

* bzrlib/transport/http/_pycurl.py: 
Transfer test server definition to bzrlib.tests.HttpServer. Clean
up imports.

* bzrlib/transport/http/__init__.py: 
Transfer all test related code to either bzrlib.tests.HttpServer
and bzrlib.tests.HTTPUtils.
Fix all use of TransportNotPossible and InvalidURL by prefixing it
by 'errors.' (this seems to be the preferred way in the rest of
bzr).
Get rid of unused imports.

* bzrlib/tests/test_transport.py:
(ReadonlyDecoratorTransportTest.test_local_parameters,
FakeNFSDecoratorTests.test_http_parameters): Use HttpServer from
bzrlib.tests.HttpServer instead of bzrlib.transport.http.

* bzrlib/tests/test_sftp_transport.py:
(set_test_transport_to_sftp): Use HttpServer from
bzrlib.tests.HttpServer instead of bzrlib.transport.http.

* bzrlib/tests/test_selftest.py:
(TestTestCaseWithTransport.test_get_readonly_url_http): Use
HttpServer from bzrlib.tests.HttpServer instead of
bzrlib.transport.http.

* bzrlib/tests/test_repository.py: 
Does *not* use HttpServer.

* bzrlib/tests/test_http.py: 
Build on top of bzrlib.tests.HttpServer and bzrlib.tests.HTTPUtils
instead of bzrlib.transport.http.

* bzrlib/tests/test_bzrdir.py:
(ChrootedTests.setUp): Use HttpServer from bzrlib.tests.HttpServer
instead of bzrlib.transport.http.

* bzrlib/tests/branch_implementations/test_http.py:
(HTTPBranchTests.setUp): Use HttpServer from bzrlib.tests.HttpServer
instead of bzrlib.transport.http.

* bzrlib/tests/branch_implementations/test_branch.py:
(ChrootedTests.setUp): Use HttpServer from bzrlib.tests.HttpServer
instead of bzrlib.transport.http.

* bzrlib/tests/__init__.py:
(ChrootedTestCase.setUp): Use HttpServer from
bzrlib.tests.HttpServer instead of bzrlib.transport.http.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
 
 
18
import bisect
 
19
import datetime
 
20
import re
 
21
 
 
22
from bzrlib import (
 
23
    errors,
 
24
    revision,
 
25
    symbol_versioning,
 
26
    trace,
 
27
    )
 
28
 
 
29
 
 
30
_marker = []
 
31
 
 
32
 
 
33
class RevisionInfo(object):
 
34
    """The results of applying a revision specification to a branch.
 
35
 
 
36
    An instance has two useful attributes: revno, and rev_id.
 
37
 
 
38
    They can also be accessed as spec[0] and spec[1] respectively,
 
39
    so that you can write code like:
 
40
    revno, rev_id = RevisionSpec(branch, spec)
 
41
    although this is probably going to be deprecated later.
 
42
 
 
43
    This class exists mostly to be the return value of a RevisionSpec,
 
44
    so that you can access the member you're interested in (number or id)
 
45
    or treat the result as a tuple.
 
46
    """
 
47
 
 
48
    def __init__(self, branch, revno, rev_id=_marker):
 
49
        self.branch = branch
 
50
        self.revno = revno
 
51
        if rev_id is _marker:
 
52
            # allow caller to be lazy
 
53
            if self.revno is None:
 
54
                self.rev_id = None
 
55
            else:
 
56
                self.rev_id = branch.get_rev_id(self.revno)
 
57
        else:
 
58
            self.rev_id = rev_id
 
59
 
 
60
    def __nonzero__(self):
 
61
        # first the easy ones...
 
62
        if self.rev_id is None:
 
63
            return False
 
64
        if self.revno is not None:
 
65
            return True
 
66
        # TODO: otherwise, it should depend on how I was built -
 
67
        # if it's in_history(branch), then check revision_history(),
 
68
        # if it's in_store(branch), do the check below
 
69
        return self.branch.repository.has_revision(self.rev_id)
 
70
 
 
71
    def __len__(self):
 
72
        return 2
 
73
 
 
74
    def __getitem__(self, index):
 
75
        if index == 0: return self.revno
 
76
        if index == 1: return self.rev_id
 
77
        raise IndexError(index)
 
78
 
 
79
    def get(self):
 
80
        return self.branch.repository.get_revision(self.rev_id)
 
81
 
 
82
    def __eq__(self, other):
 
83
        if type(other) not in (tuple, list, type(self)):
 
84
            return False
 
85
        if type(other) is type(self) and self.branch is not other.branch:
 
86
            return False
 
87
        return tuple(self) == tuple(other)
 
88
 
 
89
    def __repr__(self):
 
90
        return '<bzrlib.revisionspec.RevisionInfo object %s, %s for %r>' % (
 
91
            self.revno, self.rev_id, self.branch)
 
92
 
 
93
 
 
94
# classes in this list should have a "prefix" attribute, against which
 
95
# string specs are matched
 
96
SPEC_TYPES = []
 
97
_revno_regex = None
 
98
 
 
99
 
 
100
class RevisionSpec(object):
 
101
    """A parsed revision specification.
 
102
 
 
103
    A revision specification can be an integer, in which case it is
 
104
    assumed to be a revno (though this will translate negative values
 
105
    into positive ones); or it can be a string, in which case it is
 
106
    parsed for something like 'date:' or 'revid:' etc.
 
107
 
 
108
    Revision specs are an UI element, and they have been moved out
 
109
    of the branch class to leave "back-end" classes unaware of such
 
110
    details.  Code that gets a revno or rev_id from other code should
 
111
    not be using revision specs - revnos and revision ids are the
 
112
    accepted ways to refer to revisions internally.
 
113
 
 
114
    (Equivalent to the old Branch method get_revision_info())
 
115
    """
 
116
 
 
117
    prefix = None
 
118
 
 
119
    def __new__(cls, spec, _internal=False):
 
120
        if _internal:
 
121
            return object.__new__(cls, spec, _internal=_internal)
 
122
 
 
123
        symbol_versioning.warn('Creating a RevisionSpec directly has'
 
124
                               ' been deprecated in version 0.11. Use'
 
125
                               ' RevisionSpec.from_string()'
 
126
                               ' instead.',
 
127
                               DeprecationWarning, stacklevel=2)
 
128
        return RevisionSpec.from_string(spec)
 
129
 
 
130
    @staticmethod
 
131
    def from_string(spec):
 
132
        """Parse a revision spec string into a RevisionSpec object.
 
133
 
 
134
        :param spec: A string specified by the user
 
135
        :return: A RevisionSpec object that understands how to parse the
 
136
            supplied notation.
 
137
        """
 
138
        if not isinstance(spec, (type(None), basestring)):
 
139
            raise TypeError('error')
 
140
 
 
141
        if spec is None:
 
142
            return RevisionSpec(None, _internal=True)
 
143
 
 
144
        assert isinstance(spec, basestring), \
 
145
            "You should only supply strings not %s" % (type(spec),)
 
146
 
 
147
        for spectype in SPEC_TYPES:
 
148
            if spec.startswith(spectype.prefix):
 
149
                trace.mutter('Returning RevisionSpec %s for %s',
 
150
                             spectype.__name__, spec)
 
151
                return spectype(spec, _internal=True)
 
152
        else:
 
153
            # RevisionSpec_revno is special cased, because it is the only
 
154
            # one that directly handles plain integers
 
155
            global _revno_regex
 
156
            if _revno_regex is None:
 
157
                _revno_regex = re.compile(r'-?\d+(:.*)?$')
 
158
            if _revno_regex.match(spec) is not None:
 
159
                return RevisionSpec_revno(spec, _internal=True)
 
160
 
 
161
            raise errors.NoSuchRevisionSpec(spec)
 
162
 
 
163
    def __init__(self, spec, _internal=False):
 
164
        """Create a RevisionSpec referring to the Null revision.
 
165
 
 
166
        :param spec: The original spec supplied by the user
 
167
        :param _internal: Used to ensure that RevisionSpec is not being
 
168
            called directly. Only from RevisionSpec.from_string()
 
169
        """
 
170
        if not _internal:
 
171
            # XXX: Update this after 0.10 is released
 
172
            symbol_versioning.warn('Creating a RevisionSpec directly has'
 
173
                                   ' been deprecated in version 0.11. Use'
 
174
                                   ' RevisionSpec.from_string()'
 
175
                                   ' instead.',
 
176
                                   DeprecationWarning, stacklevel=2)
 
177
        self.user_spec = spec
 
178
        if self.prefix and spec.startswith(self.prefix):
 
179
            spec = spec[len(self.prefix):]
 
180
        self.spec = spec
 
181
 
 
182
    def _match_on(self, branch, revs):
 
183
        trace.mutter('Returning RevisionSpec._match_on: None')
 
184
        return RevisionInfo(branch, 0, None)
 
185
 
 
186
    def _match_on_and_check(self, branch, revs):
 
187
        info = self._match_on(branch, revs)
 
188
        if info:
 
189
            return info
 
190
        elif info == (0, None):
 
191
            # special case - the empty tree
 
192
            return info
 
193
        elif self.prefix:
 
194
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
195
        else:
 
196
            raise errors.InvalidRevisionSpec(self.spec, branch)
 
197
 
 
198
    def in_history(self, branch):
 
199
        if branch:
 
200
            revs = branch.revision_history()
 
201
        else:
 
202
            revs = None
 
203
        return self._match_on_and_check(branch, revs)
 
204
 
 
205
        # FIXME: in_history is somewhat broken,
 
206
        # it will return non-history revisions in many
 
207
        # circumstances. The expected facility is that
 
208
        # in_history only returns revision-history revs,
 
209
        # in_store returns any rev. RBC 20051010
 
210
    # aliases for now, when we fix the core logic, then they
 
211
    # will do what you expect.
 
212
    in_store = in_history
 
213
    in_branch = in_store
 
214
        
 
215
    def __repr__(self):
 
216
        # this is mostly for helping with testing
 
217
        return '<%s %s>' % (self.__class__.__name__,
 
218
                              self.user_spec)
 
219
    
 
220
    def needs_branch(self):
 
221
        """Whether this revision spec needs a branch.
 
222
 
 
223
        Set this to False the branch argument of _match_on is not used.
 
224
        """
 
225
        return True
 
226
 
 
227
    def get_branch(self):
 
228
        """When the revision specifier contains a branch location, return it.
 
229
        
 
230
        Otherwise, return None.
 
231
        """
 
232
        return None
 
233
 
 
234
 
 
235
# private API
 
236
 
 
237
class RevisionSpec_revno(RevisionSpec):
 
238
    prefix = 'revno:'
 
239
 
 
240
    def _match_on(self, branch, revs):
 
241
        """Lookup a revision by revision number"""
 
242
        loc = self.spec.find(':')
 
243
        if loc == -1:
 
244
            revno_spec = self.spec
 
245
            branch_spec = None
 
246
        else:
 
247
            revno_spec = self.spec[:loc]
 
248
            branch_spec = self.spec[loc+1:]
 
249
 
 
250
        if revno_spec == '':
 
251
            if not branch_spec:
 
252
                raise errors.InvalidRevisionSpec(self.user_spec,
 
253
                        branch, 'cannot have an empty revno and no branch')
 
254
            revno = None
 
255
        else:
 
256
            try:
 
257
                revno = int(revno_spec)
 
258
            except ValueError, e:
 
259
                raise errors.InvalidRevisionSpec(self.user_spec,
 
260
                                                 branch, e)
 
261
 
 
262
        if branch_spec:
 
263
            from bzrlib.branch import Branch
 
264
            branch = Branch.open(branch_spec)
 
265
            # Need to use a new revision history
 
266
            # because we are using a specific branch
 
267
            revs = branch.revision_history()
 
268
 
 
269
        if revno < 0:
 
270
            if (-revno) >= len(revs):
 
271
                revno = 1
 
272
            else:
 
273
                revno = len(revs) + revno + 1
 
274
        try:
 
275
            revision_id = branch.get_rev_id(revno, revs)
 
276
        except errors.NoSuchRevision:
 
277
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
278
        return RevisionInfo(branch, revno, revision_id)
 
279
        
 
280
    def needs_branch(self):
 
281
        return self.spec.find(':') == -1
 
282
 
 
283
    def get_branch(self):
 
284
        if self.spec.find(':') == -1:
 
285
            return None
 
286
        else:
 
287
            return self.spec[self.spec.find(':')+1:]
 
288
 
 
289
# Old compatibility 
 
290
RevisionSpec_int = RevisionSpec_revno
 
291
 
 
292
SPEC_TYPES.append(RevisionSpec_revno)
 
293
 
 
294
 
 
295
class RevisionSpec_revid(RevisionSpec):
 
296
    prefix = 'revid:'
 
297
 
 
298
    def _match_on(self, branch, revs):
 
299
        try:
 
300
            revno = revs.index(self.spec) + 1
 
301
        except ValueError:
 
302
            revno = None
 
303
        return RevisionInfo(branch, revno, self.spec)
 
304
 
 
305
SPEC_TYPES.append(RevisionSpec_revid)
 
306
 
 
307
 
 
308
class RevisionSpec_last(RevisionSpec):
 
309
 
 
310
    prefix = 'last:'
 
311
 
 
312
    def _match_on(self, branch, revs):
 
313
        if self.spec == '':
 
314
            if not revs:
 
315
                raise errors.NoCommits(branch)
 
316
            return RevisionInfo(branch, len(revs), revs[-1])
 
317
 
 
318
        try:
 
319
            offset = int(self.spec)
 
320
        except ValueError, e:
 
321
            raise errors.InvalidRevisionSpec(self.user_spec, branch, e)
 
322
 
 
323
        if offset <= 0:
 
324
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
325
                                             'you must supply a positive value')
 
326
        revno = len(revs) - offset + 1
 
327
        try:
 
328
            revision_id = branch.get_rev_id(revno, revs)
 
329
        except errors.NoSuchRevision:
 
330
            raise errors.InvalidRevisionSpec(self.user_spec, branch)
 
331
        return RevisionInfo(branch, revno, revision_id)
 
332
 
 
333
SPEC_TYPES.append(RevisionSpec_last)
 
334
 
 
335
 
 
336
class RevisionSpec_before(RevisionSpec):
 
337
 
 
338
    prefix = 'before:'
 
339
    
 
340
    def _match_on(self, branch, revs):
 
341
        r = RevisionSpec.from_string(self.spec)._match_on(branch, revs)
 
342
        if r.revno == 0:
 
343
            raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
344
                                         'cannot go before the null: revision')
 
345
        if r.revno is None:
 
346
            # We need to use the repository history here
 
347
            rev = branch.repository.get_revision(r.rev_id)
 
348
            if not rev.parent_ids:
 
349
                revno = 0
 
350
                revision_id = None
 
351
            else:
 
352
                revision_id = rev.parent_ids[0]
 
353
                try:
 
354
                    revno = revs.index(revision_id) + 1
 
355
                except ValueError:
 
356
                    revno = None
 
357
        else:
 
358
            revno = r.revno - 1
 
359
            try:
 
360
                revision_id = branch.get_rev_id(revno, revs)
 
361
            except errors.NoSuchRevision:
 
362
                raise errors.InvalidRevisionSpec(self.user_spec,
 
363
                                                 branch)
 
364
        return RevisionInfo(branch, revno, revision_id)
 
365
 
 
366
SPEC_TYPES.append(RevisionSpec_before)
 
367
 
 
368
 
 
369
class RevisionSpec_tag(RevisionSpec):
 
370
    prefix = 'tag:'
 
371
 
 
372
    def _match_on(self, branch, revs):
 
373
        raise errors.InvalidRevisionSpec(self.user_spec, branch,
 
374
                                         'tag: namespace registered,'
 
375
                                         ' but not implemented')
 
376
 
 
377
SPEC_TYPES.append(RevisionSpec_tag)
 
378
 
 
379
 
 
380
class _RevListToTimestamps(object):
 
381
    """This takes a list of revisions, and allows you to bisect by date"""
 
382
 
 
383
    __slots__ = ['revs', 'branch']
 
384
 
 
385
    def __init__(self, revs, branch):
 
386
        self.revs = revs
 
387
        self.branch = branch
 
388
 
 
389
    def __getitem__(self, index):
 
390
        """Get the date of the index'd item"""
 
391
        r = self.branch.repository.get_revision(self.revs[index])
 
392
        # TODO: Handle timezone.
 
393
        return datetime.datetime.fromtimestamp(r.timestamp)
 
394
 
 
395
    def __len__(self):
 
396
        return len(self.revs)
 
397
 
 
398
 
 
399
class RevisionSpec_date(RevisionSpec):
 
400
    prefix = 'date:'
 
401
    _date_re = re.compile(
 
402
            r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
403
            r'(,|T)?\s*'
 
404
            r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
405
        )
 
406
 
 
407
    def _match_on(self, branch, revs):
 
408
        """
 
409
        Spec for date revisions:
 
410
          date:value
 
411
          value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
412
          matches the first entry after a given date (either at midnight or
 
413
          at a specified time).
 
414
 
 
415
          So the proper way of saying 'give me all entries for today' is:
 
416
              -r date:yesterday..date:today
 
417
        """
 
418
        today = datetime.datetime.fromordinal(datetime.date.today().toordinal())
 
419
        if self.spec.lower() == 'yesterday':
 
420
            dt = today - datetime.timedelta(days=1)
 
421
        elif self.spec.lower() == 'today':
 
422
            dt = today
 
423
        elif self.spec.lower() == 'tomorrow':
 
424
            dt = today + datetime.timedelta(days=1)
 
425
        else:
 
426
            m = self._date_re.match(self.spec)
 
427
            if not m or (not m.group('date') and not m.group('time')):
 
428
                raise errors.InvalidRevisionSpec(self.user_spec,
 
429
                                                 branch, 'invalid date')
 
430
 
 
431
            try:
 
432
                if m.group('date'):
 
433
                    year = int(m.group('year'))
 
434
                    month = int(m.group('month'))
 
435
                    day = int(m.group('day'))
 
436
                else:
 
437
                    year = today.year
 
438
                    month = today.month
 
439
                    day = today.day
 
440
 
 
441
                if m.group('time'):
 
442
                    hour = int(m.group('hour'))
 
443
                    minute = int(m.group('minute'))
 
444
                    if m.group('second'):
 
445
                        second = int(m.group('second'))
 
446
                    else:
 
447
                        second = 0
 
448
                else:
 
449
                    hour, minute, second = 0,0,0
 
450
            except ValueError:
 
451
                raise errors.InvalidRevisionSpec(self.user_spec,
 
452
                                                 branch, 'invalid date')
 
453
 
 
454
            dt = datetime.datetime(year=year, month=month, day=day,
 
455
                    hour=hour, minute=minute, second=second)
 
456
        branch.lock_read()
 
457
        try:
 
458
            rev = bisect.bisect(_RevListToTimestamps(revs, branch), dt)
 
459
        finally:
 
460
            branch.unlock()
 
461
        if rev == len(revs):
 
462
            return RevisionInfo(branch, None)
 
463
        else:
 
464
            return RevisionInfo(branch, rev + 1)
 
465
 
 
466
SPEC_TYPES.append(RevisionSpec_date)
 
467
 
 
468
 
 
469
class RevisionSpec_ancestor(RevisionSpec):
 
470
    prefix = 'ancestor:'
 
471
 
 
472
    def _match_on(self, branch, revs):
 
473
        from bzrlib.branch import Branch
 
474
 
 
475
        trace.mutter('matching ancestor: on: %s, %s', self.spec, branch)
 
476
        other_branch = Branch.open(self.spec)
 
477
        revision_a = branch.last_revision()
 
478
        revision_b = other_branch.last_revision()
 
479
        for r, b in ((revision_a, branch), (revision_b, other_branch)):
 
480
            if r in (None, revision.NULL_REVISION):
 
481
                raise errors.NoCommits(b)
 
482
        revision_source = revision.MultipleRevisionSources(
 
483
                branch.repository, other_branch.repository)
 
484
        rev_id = revision.common_ancestor(revision_a, revision_b,
 
485
                                          revision_source)
 
486
        try:
 
487
            revno = branch.revision_id_to_revno(rev_id)
 
488
        except errors.NoSuchRevision:
 
489
            revno = None
 
490
        return RevisionInfo(branch, revno, rev_id)
 
491
        
 
492
SPEC_TYPES.append(RevisionSpec_ancestor)
 
493
 
 
494
 
 
495
class RevisionSpec_branch(RevisionSpec):
 
496
    """A branch: revision specifier.
 
497
 
 
498
    This takes the path to a branch and returns its tip revision id.
 
499
    """
 
500
    prefix = 'branch:'
 
501
 
 
502
    def _match_on(self, branch, revs):
 
503
        from bzrlib.branch import Branch
 
504
        other_branch = Branch.open(self.spec)
 
505
        revision_b = other_branch.last_revision()
 
506
        if revision_b in (None, revision.NULL_REVISION):
 
507
            raise errors.NoCommits(other_branch)
 
508
        # pull in the remote revisions so we can diff
 
509
        branch.fetch(other_branch, revision_b)
 
510
        try:
 
511
            revno = branch.revision_id_to_revno(revision_b)
 
512
        except errors.NoSuchRevision:
 
513
            revno = None
 
514
        return RevisionInfo(branch, revno, revision_b)
 
515
        
 
516
SPEC_TYPES.append(RevisionSpec_branch)