/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/bundle/serializer/__init__.py

  • Committer: Matthieu Moy
  • Date: 2006-07-08 19:32:30 UTC
  • mfrom: (1845 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1857.
  • Revision ID: Matthieu.Moy@imag.fr-20060708193230-3eb72d871471bd5b
merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# (C) 2005 Canonical Development 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
"""Serializer factory for reading and writing bundles.
 
18
"""
 
19
 
 
20
import base64
 
21
from StringIO import StringIO
 
22
import re
 
23
 
 
24
import bzrlib.errors as errors
 
25
from bzrlib.diff import internal_diff
 
26
from bzrlib.revision import NULL_REVISION
 
27
 
 
28
# New bundles should try to use this header format
 
29
BUNDLE_HEADER = '# Bazaar revision bundle v'
 
30
BUNDLE_HEADER_RE = re.compile(r'^# Bazaar revision bundle v(?P<version>\d+[\w.]*)\n$')
 
31
CHANGESET_OLD_HEADER_RE = re.compile(r'^# Bazaar-NG changeset v(?P<version>\d+[\w.]*)\n$')
 
32
 
 
33
 
 
34
_serializers = {} 
 
35
 
 
36
 
 
37
def _get_filename(f):
 
38
    if hasattr(f, 'name'):
 
39
        return f.name
 
40
    return '<unknown>'
 
41
 
 
42
 
 
43
def read_bundle(f):
 
44
    """Read in a bundle from a filelike object.
 
45
 
 
46
    :param f: A file-like object
 
47
    :return: A list of Bundle objects
 
48
    """
 
49
    version = None
 
50
    for line in f:
 
51
        m = BUNDLE_HEADER_RE.match(line)
 
52
        if m:
 
53
            version = m.group('version')
 
54
            break
 
55
        elif line.startswith(BUNDLE_HEADER):
 
56
            raise errors.MalformedHeader()
 
57
        m = CHANGESET_OLD_HEADER_RE.match(line)
 
58
        if m:
 
59
            version = m.group('version')
 
60
            raise errors.BundleNotSupported(version, 'old format bundles not supported')
 
61
 
 
62
    if version is None:
 
63
        raise errors.NotABundle('Did not find an opening header')
 
64
 
 
65
    # Now we have a version, to figure out how to read the bundle 
 
66
    if not _serializers.has_key(version):
 
67
        raise errors.BundleNotSupported(version, 'version not listed in known versions')
 
68
 
 
69
    serializer = _serializers[version](version)
 
70
 
 
71
    return serializer.read(f)
 
72
 
 
73
 
 
74
def write(source, revision_ids, f, version=None, forced_bases={}):
 
75
    """Serialize a list of bundles to a filelike object.
 
76
 
 
77
    :param source: A source for revision information
 
78
    :param revision_ids: The list of revision ids to serialize
 
79
    :param f: The file to output to
 
80
    :param version: [optional] target serialization version
 
81
    """
 
82
 
 
83
    if not _serializers.has_key(version):
 
84
        raise errors.BundleNotSupported(version, 'unknown bundle format')
 
85
 
 
86
    serializer = _serializers[version](version)
 
87
    return serializer.write(source, revision_ids, forced_bases, f) 
 
88
 
 
89
 
 
90
def write_bundle(repository, revision_id, base_revision_id, out):
 
91
    """"""
 
92
    if base_revision_id is NULL_REVISION:
 
93
        base_revision_id = None
 
94
    base_ancestry = set(repository.get_ancestry(base_revision_id))
 
95
    revision_ids = [r for r in repository.get_ancestry(revision_id) if r
 
96
                    not in base_ancestry]
 
97
    revision_ids = list(reversed(revision_ids))
 
98
    write(repository, revision_ids, out, 
 
99
          forced_bases = {revision_id:base_revision_id})
 
100
    return revision_ids
 
101
 
 
102
 
 
103
def format_highres_date(t, offset=0):
 
104
    """Format a date, such that it includes higher precision in the
 
105
    seconds field.
 
106
 
 
107
    :param t:   The local time in fractional seconds since the epoch
 
108
    :type t: float
 
109
    :param offset:  The timezone offset in integer seconds
 
110
    :type offset: int
 
111
 
 
112
    Example: format_highres_date(time.time(), -time.timezone)
 
113
    this will return a date stamp for right now,
 
114
    formatted for the local timezone.
 
115
 
 
116
    >>> from bzrlib.osutils import format_date
 
117
    >>> format_date(1120153132.350850105, 0)
 
118
    'Thu 2005-06-30 17:38:52 +0000'
 
119
    >>> format_highres_date(1120153132.350850105, 0)
 
120
    'Thu 2005-06-30 17:38:52.350850105 +0000'
 
121
    >>> format_date(1120153132.350850105, -5*3600)
 
122
    'Thu 2005-06-30 12:38:52 -0500'
 
123
    >>> format_highres_date(1120153132.350850105, -5*3600)
 
124
    'Thu 2005-06-30 12:38:52.350850105 -0500'
 
125
    >>> format_highres_date(1120153132.350850105, 7200)
 
126
    'Thu 2005-06-30 19:38:52.350850105 +0200'
 
127
    """
 
128
    import time
 
129
    assert isinstance(t, float)
 
130
    
 
131
    # This has to be formatted for "original" date, so that the
 
132
    # revision XML entry will be reproduced faithfully.
 
133
    if offset == None:
 
134
        offset = 0
 
135
    tt = time.gmtime(t + offset)
 
136
 
 
137
    return (time.strftime("%a %Y-%m-%d %H:%M:%S", tt)
 
138
            + ('%.9f' % (t - int(t)))[1:] # Get the high-res seconds, but ignore the 0
 
139
            + ' %+03d%02d' % (offset / 3600, (offset / 60) % 60))
 
140
 
 
141
 
 
142
def unpack_highres_date(date):
 
143
    """This takes the high-resolution date stamp, and
 
144
    converts it back into the tuple (timestamp, timezone)
 
145
    Where timestamp is in real UTC since epoch seconds, and timezone is an integer
 
146
    number of seconds offset.
 
147
 
 
148
    :param date: A date formated by format_highres_date
 
149
    :type date: string
 
150
 
 
151
    >>> import time, random
 
152
    >>> unpack_highres_date('Thu 2005-06-30 12:38:52.350850105 -0500')
 
153
    (1120153132.3508501, -18000)
 
154
    >>> unpack_highres_date('Thu 2005-06-30 17:38:52.350850105 +0000')
 
155
    (1120153132.3508501, 0)
 
156
    >>> unpack_highres_date('Thu 2005-06-30 19:38:52.350850105 +0200')
 
157
    (1120153132.3508501, 7200)
 
158
    >>> from bzrlib.osutils import local_time_offset
 
159
    >>> t = time.time()
 
160
    >>> o = local_time_offset()
 
161
    >>> t2, o2 = unpack_highres_date(format_highres_date(t, o))
 
162
    >>> t == t2
 
163
    True
 
164
    >>> o == o2
 
165
    True
 
166
    >>> t -= 24*3600*365*2 # Start 2 years ago
 
167
    >>> o = -12*3600
 
168
    >>> for count in xrange(500):
 
169
    ...   t += random.random()*24*3600*30
 
170
    ...   o = ((o/3600 + 13) % 25 - 12)*3600 # Add 1 wrap around from [-12, 12]
 
171
    ...   date = format_highres_date(t, o)
 
172
    ...   t2, o2 = unpack_highres_date(date)
 
173
    ...   if t != t2 or o != o2:
 
174
    ...      print 'Failed on date %r, %s,%s diff:%s' % (date, t, o, t2-t)
 
175
    ...      break
 
176
 
 
177
    """
 
178
    import time, calendar
 
179
    # Up until the first period is a datestamp that is generated
 
180
    # as normal from time.strftime, so use time.strptime to
 
181
    # parse it
 
182
    dot_loc = date.find('.')
 
183
    if dot_loc == -1:
 
184
        raise ValueError('Date string does not contain high-precision seconds: %r' % date)
 
185
    base_time = time.strptime(date[:dot_loc], "%a %Y-%m-%d %H:%M:%S")
 
186
    fract_seconds, offset = date[dot_loc:].split()
 
187
    fract_seconds = float(fract_seconds)
 
188
    offset = int(offset)
 
189
    offset = int(offset / 100) * 3600 + offset % 100
 
190
    
 
191
    # time.mktime returns localtime, but calendar.timegm returns UTC time
 
192
    timestamp = calendar.timegm(base_time)
 
193
    timestamp -= offset
 
194
    # Add back in the fractional seconds
 
195
    timestamp += fract_seconds
 
196
    return (timestamp, offset)
 
197
 
 
198
 
 
199
class BundleSerializer(object):
 
200
    """The base class for Serializers.
 
201
 
 
202
    Common functionality should be included here.
 
203
    """
 
204
    def __init__(self, version):
 
205
        self.version = version
 
206
 
 
207
    def read(self, f):
 
208
        """Read the rest of the bundles from the supplied file.
 
209
 
 
210
        :param f: The file to read from
 
211
        :return: A list of bundle trees
 
212
        """
 
213
        raise NotImplementedError
 
214
 
 
215
    def write(self, source, revision_ids, forced_bases, f):
 
216
        """Write the bundle to the supplied file.
 
217
 
 
218
        :param source: A source for revision information
 
219
        :param revision_ids: The list of revision ids to serialize
 
220
        :param forced_bases: A dict of revision -> base that overrides default
 
221
        :param f: The file to output to
 
222
        """
 
223
        raise NotImplementedError
 
224
 
 
225
 
 
226
def register(version, klass, overwrite=False):
 
227
    """Register a BundleSerializer version.
 
228
 
 
229
    :param version: The version associated with this format
 
230
    :param klass: The class to instantiate, which must take a version argument
 
231
    """
 
232
    global _serializers
 
233
    if overwrite:
 
234
        _serializers[version] = klass
 
235
        return
 
236
 
 
237
    if not _serializers.has_key(version):
 
238
        _serializers[version] = klass
 
239
 
 
240
 
 
241
def register_lazy(version, module, classname, overwrite=False):
 
242
    """Register lazy-loaded bundle serializer.
 
243
 
 
244
    :param version: The version associated with this reader
 
245
    :param module: String indicating what module should be loaded
 
246
    :param classname: Name of the class that will be instantiated
 
247
    :param overwrite: Should this version override a default
 
248
    """
 
249
    def _loader(version):
 
250
        mod = __import__(module, globals(), locals(), [classname])
 
251
        klass = getattr(mod, classname)
 
252
        return klass(version)
 
253
    register(version, _loader, overwrite=overwrite)
 
254
 
 
255
 
 
256
def binary_diff(old_filename, old_lines, new_filename, new_lines, to_file):
 
257
    temp = StringIO()
 
258
    internal_diff(old_filename, old_lines, new_filename, new_lines, temp,
 
259
                  allow_binary=True)
 
260
    temp.seek(0)
 
261
    base64.encode(temp, to_file)
 
262
    to_file.write('\n')
 
263
 
 
264
register_lazy('0.8', 'bzrlib.bundle.serializer.v08', 'BundleSerializerV08')
 
265
register_lazy(None, 'bzrlib.bundle.serializer.v08', 'BundleSerializerV08')
 
266