1
# (C) 2005 Canonical Development Ltd
1
# Copyright (C) 2005, 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
24
import bzrlib.errors as errors
25
25
from bzrlib.diff import internal_diff
26
26
from bzrlib.revision import NULL_REVISION
27
# For backwards-compatibility
28
from bzrlib.timestamp import unpack_highres_date, format_highres_date
28
31
# New bundles should try to use this header format
29
32
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$')
33
BUNDLE_HEADER_RE = re.compile(
34
r'^# Bazaar revision bundle v(?P<version>\d+[\w.]*)(?P<lineending>\r?)\n$')
35
CHANGESET_OLD_HEADER_RE = re.compile(
36
r'^# Bazaar-NG changeset v(?P<version>\d+[\w.]*)(?P<lineending>\r?)\n$')
43
def _get_bundle_header(version):
44
return '%s%s\n' % (BUNDLE_HEADER, version)
37
46
def _get_filename(f):
38
if hasattr(f, 'name'):
47
return getattr(f, 'name', '<unknown>')
44
51
"""Read in a bundle from a filelike object.
46
53
:param f: A file-like object
51
58
m = BUNDLE_HEADER_RE.match(line)
60
if m.group('lineending') != '':
61
raise errors.UnsupportedEOLMarker()
53
62
version = m.group('version')
64
elif line.startswith(BUNDLE_HEADER):
65
raise errors.MalformedHeader(
66
'Extra characters after version number')
55
67
m = CHANGESET_OLD_HEADER_RE.match(line)
57
69
version = m.group('version')
58
raise errors.BundleNotSupported(version, 'old format bundles not supported')
70
raise errors.BundleNotSupported(version,
71
'old format bundles not supported')
60
73
if version is None:
61
raise errors.NoBundleFound(_get_filename(f))
74
raise errors.NotABundle('Did not find an opening header')
63
76
# 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')
77
if version not in _serializers:
78
raise errors.BundleNotSupported(version,
79
'version not listed in known versions')
67
81
serializer = _serializers[version](version)
69
83
return serializer.read(f)
86
def get_serializer(version):
88
return _serializers[version](version)
90
raise errors.BundleNotSupported(version, 'unknown bundle format')
72
93
def write(source, revision_ids, f, version=None, forced_bases={}):
73
94
"""Serialize a list of bundles to a filelike object.
78
99
: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)
104
return get_serializer(version).write(source, revision_ids,
110
def write_bundle(repository, revision_id, base_revision_id, out, format=None):
111
"""Write a bundle of revisions.
113
:param repository: Repository containing revisions to serialize.
114
:param revision_id: Head revision_id of the bundle.
115
:param base_revision_id: Revision assumed to be present in repositories
117
:param out: Output file.
119
repository.lock_read()
121
return get_serializer(format).write_bundle(repository, revision_id,
122
base_revision_id, out)
197
127
class BundleSerializer(object):
211
141
raise NotImplementedError
143
def write_bundle(self, repository, target, base, fileobj):
144
"""Write the bundle to the supplied file.
146
:param repository: The repository to retrieve revision data from
147
:param target: The revision to provide data for
148
:param base: The most recent of ancestor of the revision that does not
149
need to be included in the bundle
150
:param fileobj: The file to output to
152
raise NotImplementedError
213
154
def write(self, source, revision_ids, forced_bases, f):
214
155
"""Write the bundle to the supplied file.
157
DEPRECATED: see write_bundle
216
158
:param source: A source for revision information
217
159
:param revision_ids: The list of revision ids to serialize
218
160
:param forced_bases: A dict of revision -> base that overrides default