1# Copyright (C) 2005, 2006, 2007, 2009, 2010 Canonical 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 17"""Serializer factory for reading and writing bundles. 18""" 19 20import base64 21from io import BytesIO 22import re 23 24from .... import ( 25 errors, 26 registry, 27 ) 28from ....diff import internal_diff 29from ....revision import NULL_REVISION 30# For backwards-compatibility 31from ....timestamp import unpack_highres_date, format_highres_date 32 33 34# New bundles should try to use this header format 35BUNDLE_HEADER = b'# Bazaar revision bundle v' 36BUNDLE_HEADER_RE = re.compile( 37 br'^# Bazaar revision bundle v(?P<version>\d+[\w.]*)(?P<lineending>\r?)\n$') 38CHANGESET_OLD_HEADER_RE = re.compile( 39 br'^# Bazaar-NG changeset v(?P<version>\d+[\w.]*)(?P<lineending>\r?)\n$') 40 41 42serializer_registry = registry.Registry() 43 44 45def _get_bundle_header(version): 46 return b''.join([BUNDLE_HEADER, version.encode('ascii'), b'\n']) 47 48 49def _get_filename(f): 50 return getattr(f, 'name', '<unknown>') 51 52 53def read_bundle(f): 54 """Read in a bundle from a filelike object. 55 56 :param f: A file-like object 57 :return: A list of Bundle objects 58 """ 59 version = None 60 for line in f: 61 m = BUNDLE_HEADER_RE.match(line) 62 if m: 63 if m.group('lineending') != b'': 64 raise errors.UnsupportedEOLMarker() 65 version = m.group('version') 66 break 67 elif line.startswith(BUNDLE_HEADER): 68 raise errors.MalformedHeader( 69 'Extra characters after version number') 70 m = CHANGESET_OLD_HEADER_RE.match(line) 71 if m: 72 version = m.group('version') 73 raise errors.BundleNotSupported(version, 74 'old format bundles not supported') 75 76 if version is None: 77 raise errors.NotABundle('Did not find an opening header') 78 79 return get_serializer(version.decode('ascii')).read(f) 80 81 82def get_serializer(version): 83 try: 84 serializer = serializer_registry.get(version) 85 except KeyError: 86 raise errors.BundleNotSupported(version, 87 'unknown bundle format') 88 89 return serializer(version) 90 91 92def write(source, revision_ids, f, version=None, forced_bases={}): 93 """Serialize a list of bundles to a filelike object. 94 95 :param source: A source for revision information 96 :param revision_ids: The list of revision ids to serialize 97 :param f: The file to output to 98 :param version: [optional] target serialization version 99 """ 100 101 with source.lock_read(): 102 return get_serializer(version).write(source, revision_ids, 103 forced_bases, f) 104 105 106def write_bundle(repository, revision_id, base_revision_id, out, format=None): 107 """Write a bundle of revisions. 108 109 :param repository: Repository containing revisions to serialize. 110 :param revision_id: Head revision_id of the bundle. 111 :param base_revision_id: Revision assumed to be present in repositories 112 applying the bundle. 113 :param out: Output file. 114 :return: List of revision ids written 115 """ 116 with repository.lock_read(): 117 return get_serializer(format).write_bundle(repository, revision_id, 118 base_revision_id, out) 119 120 121class BundleSerializer(object): 122 """The base class for Serializers. 123 124 Common functionality should be included here. 125 """ 126 127 def __init__(self, version): 128 self.version = version 129 130 def read(self, f): 131 """Read the rest of the bundles from the supplied file. 132 133 :param f: The file to read from 134 :return: A list of bundle trees 135 """ 136 raise NotImplementedError 137 138 def write_bundle(self, repository, target, base, fileobj): 139 """Write the bundle to the supplied file. 140 141 :param repository: The repository to retrieve revision data from 142 :param target: The revision to provide data for 143 :param base: The most recent of ancestor of the revision that does not 144 need to be included in the bundle 145 :param fileobj: The file to output to 146 :return: List of revision ids written 147 """ 148 raise NotImplementedError 149 150 151def binary_diff(old_filename, old_lines, new_filename, new_lines, to_file): 152 temp = BytesIO() 153 internal_diff(old_filename, old_lines, new_filename, new_lines, temp, 154 allow_binary=True) 155 temp.seek(0) 156 base64.encode(temp, to_file) 157 to_file.write(b'\n') 158 159 160serializer_registry.register_lazy( 161 '0.8', __name__ + '.v08', 'BundleSerializerV08') 162serializer_registry.register_lazy( 163 '0.9', __name__ + '.v09', 'BundleSerializerV09') 164serializer_registry.register_lazy('4', __name__ + '.v4', 165 'BundleSerializerV4') 166serializer_registry.default_key = '4' 167