~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/serializer/__init__.py

  • Committer: Martin Pool
  • Date: 2006-06-10 23:16:19 UTC
  • mfrom: (1759 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1761.
  • Revision ID: mbp@sourcefrog.net-20060610231619-05b997deeb005d02
[merge] bzr.dev

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