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
m = CHANGESET_OLD_HEADER_RE.match(line)
57
version = m.group('version')
58
raise errors.BundleNotSupported(version, 'old format bundles not supported')
61
raise errors.NoBundleFound(_get_filename(f))
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')
67
serializer = _serializers[version](version)
69
return serializer.read(f)
72
def write(source, revision_ids, f, version=None, forced_bases={}):
73
"""Serialize a list of bundles to a filelike object.
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
81
if not _serializers.has_key(version):
82
raise errors.BundleNotSupported(version, 'unknown bundle format')
84
serializer = _serializers[version](version)
85
return serializer.write(source, revision_ids, forced_bases, f)
88
def write_bundle(repository, revision_id, base_revision_id, out):
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
95
revision_ids = list(reversed(revision_ids))
96
write(repository, revision_ids, out,
97
forced_bases = {revision_id:base_revision_id})
101
def format_highres_date(t, offset=0):
102
"""Format a date, such that it includes higher precision in the
105
:param t: The local time in fractional seconds since the epoch
107
:param offset: The timezone offset in integer seconds
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.
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'
127
assert isinstance(t, float)
129
# This has to be formatted for "original" date, so that the
130
# revision XML entry will be reproduced faithfully.
133
tt = time.gmtime(t + offset)
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))
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.
146
:param date: A date formated by format_highres_date
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
158
>>> o = local_time_offset()
159
>>> t2, o2 = unpack_highres_date(format_highres_date(t, o))
164
>>> t -= 24*3600*365*2 # Start 2 years ago
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)
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
180
dot_loc = date.find('.')
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)
187
offset = int(offset / 100) * 3600 + offset % 100
189
# time.mktime returns localtime, but calendar.timegm returns UTC time
190
timestamp = calendar.timegm(base_time)
192
# Add back in the fractional seconds
193
timestamp += fract_seconds
194
return (timestamp, offset)
197
class BundleSerializer(object):
198
"""The base class for Serializers.
200
Common functionality should be included here.
202
def __init__(self, version):
203
self.version = version
206
"""Read the rest of the bundles from the supplied file.
208
:param f: The file to read from
209
:return: A list of bundle trees
211
raise NotImplementedError
213
def write(self, source, revision_ids, forced_bases, f):
214
"""Write the bundle to the supplied file.
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
221
raise NotImplementedError
224
def register(version, klass, overwrite=False):
225
"""Register a BundleSerializer version.
227
:param version: The version associated with this format
228
:param klass: The class to instantiate, which must take a version argument
232
_serializers[version] = klass
235
if not _serializers.has_key(version):
236
_serializers[version] = klass
239
def register_lazy(version, module, classname, overwrite=False):
240
"""Register lazy-loaded bundle serializer.
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
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)
254
def binary_diff(old_filename, old_lines, new_filename, new_lines, to_file):
256
internal_diff(old_filename, old_lines, new_filename, new_lines, temp,
259
base64.encode(temp, to_file)
262
register_lazy('0.7', 'bzrlib.bundle.serializer.v07', 'BundleSerializerV07')
263
register_lazy(None, 'bzrlib.bundle.serializer.v07', 'BundleSerializerV07')