1
# (C) 2005 Canonical Development 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
17
"""Serializer factory for reading and writing bundles.
21
from StringIO import StringIO
24
import bzrlib.errors as errors
25
from bzrlib.diff import internal_diff
26
from bzrlib.revision import NULL_REVISION
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$')
38
if hasattr(f, 'name'):
44
"""Read in a bundle from a filelike object.
46
:param f: A file-like object
47
:return: A list of Bundle objects
51
m = BUNDLE_HEADER_RE.match(line)
53
version = m.group('version')
55
elif line.startswith(BUNDLE_HEADER):
56
raise errors.MalformedHeader()
57
m = CHANGESET_OLD_HEADER_RE.match(line)
59
version = m.group('version')
60
raise errors.BundleNotSupported(version, 'old format bundles not supported')
63
raise errors.NotABundle('Did not find an opening header')
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')
69
serializer = _serializers[version](version)
71
return serializer.read(f)
74
def write(source, revision_ids, f, version=None, forced_bases={}):
75
"""Serialize a list of bundles to a filelike object.
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
83
if not _serializers.has_key(version):
84
raise errors.BundleNotSupported(version, 'unknown bundle format')
86
serializer = _serializers[version](version)
87
return serializer.write(source, revision_ids, forced_bases, f)
90
def write_bundle(repository, revision_id, base_revision_id, out):
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
97
revision_ids = list(reversed(revision_ids))
98
write(repository, revision_ids, out,
99
forced_bases = {revision_id:base_revision_id})
103
def format_highres_date(t, offset=0):
104
"""Format a date, such that it includes higher precision in the
107
:param t: The local time in fractional seconds since the epoch
109
:param offset: The timezone offset in integer seconds
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.
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'
129
assert isinstance(t, float)
131
# This has to be formatted for "original" date, so that the
132
# revision XML entry will be reproduced faithfully.
135
tt = time.gmtime(t + offset)
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))
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.
148
:param date: A date formated by format_highres_date
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
160
>>> o = local_time_offset()
161
>>> t2, o2 = unpack_highres_date(format_highres_date(t, o))
166
>>> t -= 24*3600*365*2 # Start 2 years ago
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)
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
182
dot_loc = date.find('.')
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)
189
offset = int(offset / 100) * 3600 + offset % 100
191
# time.mktime returns localtime, but calendar.timegm returns UTC time
192
timestamp = calendar.timegm(base_time)
194
# Add back in the fractional seconds
195
timestamp += fract_seconds
196
return (timestamp, offset)
199
class BundleSerializer(object):
200
"""The base class for Serializers.
202
Common functionality should be included here.
204
def __init__(self, version):
205
self.version = version
208
"""Read the rest of the bundles from the supplied file.
210
:param f: The file to read from
211
:return: A list of bundle trees
213
raise NotImplementedError
215
def write(self, source, revision_ids, forced_bases, f):
216
"""Write the bundle to the supplied file.
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
223
raise NotImplementedError
226
def register(version, klass, overwrite=False):
227
"""Register a BundleSerializer version.
229
:param version: The version associated with this format
230
:param klass: The class to instantiate, which must take a version argument
234
_serializers[version] = klass
237
if not _serializers.has_key(version):
238
_serializers[version] = klass
241
def register_lazy(version, module, classname, overwrite=False):
242
"""Register lazy-loaded bundle serializer.
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
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)
256
def binary_diff(old_filename, old_lines, new_filename, new_lines, to_file):
258
internal_diff(old_filename, old_lines, new_filename, new_lines, temp,
261
base64.encode(temp, to_file)
264
register_lazy('0.8', 'bzrlib.bundle.serializer.v08', 'BundleSerializerV08')
265
register_lazy(None, 'bzrlib.bundle.serializer.v08', 'BundleSerializerV08')