~bzr-pqm/bzr/bzr.dev

1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
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
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
17
"""Serializer factory for reading and writing bundles.
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
18
"""
19
1185.82.96 by Aaron Bentley
Got first binary test passing
20
import base64
21
from StringIO import StringIO
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
22
import re
23
24
import bzrlib.errors as errors
1185.82.96 by Aaron Bentley
Got first binary test passing
25
from bzrlib.diff import internal_diff
1185.82.58 by Aaron Bentley
Handle empty branches properly
26
from bzrlib.revision import NULL_REVISION
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
27
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
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$')
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
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
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
43
def read_bundle(f):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
44
    """Read in a bundle from a filelike object.
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
45
46
    :param f: A file-like object
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
47
    :return: A list of Bundle objects
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
48
    """
49
    version = None
50
    for line in f:
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
51
        m = BUNDLE_HEADER_RE.match(line)
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
52
        if m:
53
            version = m.group('version')
54
            break
1793.2.7 by Aaron Bentley
Fix reporting of malformed, (especially, crlf) bundles
55
        elif line.startswith(BUNDLE_HEADER):
56
            raise errors.MalformedHeader()
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
57
        m = CHANGESET_OLD_HEADER_RE.match(line)
58
        if m:
59
            version = m.group('version')
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
60
            raise errors.BundleNotSupported(version, 'old format bundles not supported')
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
61
62
    if version is None:
1793.2.2 by Aaron Bentley
Move BundleReader into v07 serializer
63
        raise errors.NotABundle('Did not find an opening header')
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
64
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
65
    # Now we have a version, to figure out how to read the bundle 
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
66
    if not _serializers.has_key(version):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
67
        raise errors.BundleNotSupported(version, 'version not listed in known versions')
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
68
69
    serializer = _serializers[version](version)
70
71
    return serializer.read(f)
72
73
1185.82.74 by Aaron Bentley
Allow custom base for any revision
74
def write(source, revision_ids, f, version=None, forced_bases={}):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
75
    """Serialize a list of bundles to a filelike object.
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
76
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
77
    :param source: A source for revision information
78
    :param revision_ids: The list of revision ids to serialize
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
79
    :param f: The file to output to
80
    :param version: [optional] target serialization version
81
    """
82
83
    if not _serializers.has_key(version):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
84
        raise errors.BundleNotSupported(version, 'unknown bundle format')
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
85
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
86
    serializer = _serializers[version](version)
1185.82.74 by Aaron Bentley
Allow custom base for any revision
87
    return serializer.write(source, revision_ids, forced_bases, f) 
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
88
89
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
90
def write_bundle(repository, revision_id, base_revision_id, out):
1185.82.53 by Aaron Bentley
Factored out write_changeset to select revisions
91
    """"""
1185.82.58 by Aaron Bentley
Handle empty branches properly
92
    if base_revision_id is NULL_REVISION:
93
        base_revision_id = None
1185.82.53 by Aaron Bentley
Factored out write_changeset to select revisions
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))
1185.82.74 by Aaron Bentley
Allow custom base for any revision
98
    write(repository, revision_ids, out, 
99
          forced_bases = {revision_id:base_revision_id})
1185.82.53 by Aaron Bentley
Factored out write_changeset to select revisions
100
    return revision_ids
101
102
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
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
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
199
class BundleSerializer(object):
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
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):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
208
        """Read the rest of the bundles from the supplied file.
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
209
210
        :param f: The file to read from
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
211
        :return: A list of bundle trees
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
212
        """
213
        raise NotImplementedError
214
1185.82.74 by Aaron Bentley
Allow custom base for any revision
215
    def write(self, source, revision_ids, forced_bases, f):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
216
        """Write the bundle to the supplied file.
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
217
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
218
        :param source: A source for revision information
219
        :param revision_ids: The list of revision ids to serialize
1185.82.74 by Aaron Bentley
Allow custom base for any revision
220
        :param forced_bases: A dict of revision -> base that overrides default
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
221
        :param f: The file to output to
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
222
        """
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
223
        raise NotImplementedError
224
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
225
226
def register(version, klass, overwrite=False):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
227
    """Register a BundleSerializer version.
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
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):
1185.82.130 by Aaron Bentley
Rename changesets to revision bundles
242
    """Register lazy-loaded bundle serializer.
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
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)
1185.82.4 by John Arbash Meinel
Created output format, slightly simplified code
253
    register(version, _loader, overwrite=overwrite)
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
254
255
1185.82.96 by Aaron Bentley
Got first binary test passing
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
1551.7.3 by Aaron Bentley
Fix strict testaments, as_sha1
264
register_lazy('0.8', 'bzrlib.bundle.serializer.v08', 'BundleSerializerV08')
265
register_lazy(None, 'bzrlib.bundle.serializer.v08', 'BundleSerializerV08')
1185.82.3 by John Arbash Meinel
Working on creating a factor for serializing changesets.
266