17
17
"""Serializer factory for reading and writing bundles.
20
from __future__ import absolute_import
26
from bzrlib.bundle.serializer import (BundleSerializer,
29
from bzrlib.bundle.serializer import binary_diff
30
from bzrlib.bundle.bundle_data import (RevisionInfo, BundleInfo)
31
from bzrlib.diff import internal_diff
32
from bzrlib.revision import NULL_REVISION
33
from bzrlib.testament import StrictTestament
34
from bzrlib.timestamp import (
29
from ..bundle_data import (
33
from ....diff import internal_diff
34
from ....revision import NULL_REVISION
35
from ...testament import StrictTestament
36
from ....timestamp import (
35
37
format_highres_date,
37
from bzrlib.textfile import text_file
38
from bzrlib.trace import mutter
39
from ....textfile import text_file
40
from ....trace import mutter
40
42
bool_text = {True: 'yes', False: 'no'}
115
115
self.forced_bases = forced_bases
117
117
self.check_compatible()
118
with source.lock_read():
120
119
self._write_main_header()
121
pb = ui.ui_factory.nested_progress_bar()
120
with ui.ui_factory.nested_progress_bar() as pb:
123
121
self._write_revisions(pb)
129
def write_bundle(self, repository, target, base, fileobj):
130
return self._write_bundle(repository, target, base, fileobj)
123
def write_bundle(self, repository, revision_id, base_revision_id, out):
124
"""Helper function for translating write_bundle to write"""
125
forced_bases = {revision_id: base_revision_id}
126
if base_revision_id is NULL_REVISION:
127
base_revision_id = None
128
graph = repository.get_graph()
129
revision_ids = graph.find_unique_ancestors(revision_id,
131
revision_ids = list(repository.get_graph().iter_topo_order(
133
revision_ids.reverse()
134
self.write(repository, revision_ids, forced_bases, out)
132
137
def _write_main_header(self):
133
138
"""Write the header for the changes"""
135
140
f.write(_get_bundle_header('0.8'))
138
143
def _write(self, key, value, indent=1, trailing_space_when_empty=False):
139
144
"""Write out meta information, with proper indenting, etc.
148
153
raise ValueError('indentation must be greater than 0')
150
f.write('#' + (' ' * indent))
155
f.write(b'#' + (b' ' * indent))
151
156
f.write(key.encode('utf-8'))
153
158
if trailing_space_when_empty and value == '':
162
elif isinstance(value, bytes):
157
166
elif isinstance(value, str):
161
elif isinstance(value, unicode):
163
168
f.write(value.encode('utf-8'))
167
172
for entry in value:
168
f.write('#' + (' ' * (indent+2)))
169
if isinstance(entry, str):
173
f.write(b'#' + (b' ' * (indent + 2)))
174
if isinstance(entry, bytes):
172
177
f.write(entry.encode('utf-8'))
175
180
def _write_revisions(self, pb):
176
181
"""Write the information for all of the revisions."""
241
246
trailing_space_when_empty=True)
243
248
# Add an extra blank space at the end
244
self.to_file.write('\n')
249
self.to_file.write(b'\n')
246
251
def _write_action(self, name, parameters, properties=None):
247
252
if properties is None:
249
254
p_texts = ['%s:%s' % v for v in properties]
250
self.to_file.write('=== ')
251
self.to_file.write(' '.join([name]+parameters).encode('utf-8'))
255
self.to_file.write(b'=== ')
256
self.to_file.write(' '.join([name] + parameters).encode('utf-8'))
252
257
self.to_file.write(' // '.join(p_texts).encode('utf-8'))
253
self.to_file.write('\n')
258
self.to_file.write(b'\n')
255
260
def _write_delta(self, new_tree, old_tree, default_revision_id,
262
267
def do_diff(file_id, old_path, new_path, action, force_binary):
263
def tree_lines(tree, require_text=False):
264
if tree.has_id(file_id):
265
tree_file = tree.get_file(file_id)
268
def tree_lines(tree, path, require_text=False):
270
tree_file = tree.get_file(path)
271
except errors.NoSuchFile:
266
274
if require_text is True:
267
275
tree_file = text_file(tree_file)
268
276
return tree_file.readlines()
274
280
raise errors.BinaryFile()
275
old_lines = tree_lines(old_tree, require_text=True)
276
new_lines = tree_lines(new_tree, require_text=True)
281
old_lines = tree_lines(old_tree, old_path, require_text=True)
282
new_lines = tree_lines(new_tree, new_path, require_text=True)
277
283
action.write(self.to_file)
278
284
internal_diff(old_path, old_lines, new_path, new_lines,
280
286
except errors.BinaryFile:
281
old_lines = tree_lines(old_tree, require_text=False)
282
new_lines = tree_lines(new_tree, require_text=False)
287
old_lines = tree_lines(old_tree, old_path, require_text=False)
288
new_lines = tree_lines(new_tree, new_path, require_text=False)
283
289
action.add_property('encoding', 'base64')
284
290
action.write(self.to_file)
285
291
binary_diff(old_path, old_lines, new_path, new_lines,
302
308
delta = new_tree.changes_from(old_tree, want_unchanged=True,
303
309
include_root=True)
304
for path, file_id, kind in delta.removed:
305
action = Action('removed', [kind, path]).write(self.to_file)
307
for path, file_id, kind in delta.added:
308
action = Action('added', [kind, path], [('file-id', file_id)])
309
meta_modified = (kind=='file' and
310
new_tree.is_executable(file_id))
311
finish_action(action, file_id, kind, meta_modified, True,
314
for (old_path, new_path, file_id, kind,
315
text_modified, meta_modified) in delta.renamed:
316
action = Action('renamed', [kind, old_path], [(new_path,)])
317
finish_action(action, file_id, kind, meta_modified, text_modified,
320
for (path, file_id, kind,
321
text_modified, meta_modified) in delta.modified:
322
action = Action('modified', [kind, path])
323
finish_action(action, file_id, kind, meta_modified, text_modified,
326
for path, file_id, kind in delta.unchanged:
327
new_rev = new_tree.get_file_revision(file_id)
310
for change in delta.removed:
311
action = Action('removed', [change.kind[0], change.path[0]]).write(self.to_file)
313
# TODO(jelmer): Treat copied specially here?
314
for change in delta.added + delta.copied:
316
'added', [change.kind[1], change.path[1]],
317
[('file-id', change.file_id.decode('utf-8'))])
318
meta_modified = (change.kind[1] == 'file' and
319
change.executable[1])
320
finish_action(action, change.file_id, change.kind[1], meta_modified, change.changed_content,
321
DEVNULL, change.path[1])
323
for change in delta.renamed:
324
action = Action('renamed', [change.kind[1], change.path[0]], [(change.path[1],)])
325
finish_action(action, change.file_id, change.kind[1], change.meta_modified(), change.changed_content,
326
change.path[0], change.path[1])
328
for change in delta.modified:
329
action = Action('modified', [change.kind[1], change.path[1]])
330
finish_action(action, change.file_id, change.kind[1], change.meta_modified(), change.changed_content,
331
change.path[0], change.path[1])
333
for change in delta.unchanged:
334
new_rev = new_tree.get_file_revision(change.path[1])
328
335
if new_rev is None:
330
old_rev = old_tree.get_file_revision(file_id)
337
old_rev = old_tree.get_file_revision(change.path[0])
331
338
if new_rev != old_rev:
332
action = Action('modified', [new_tree.kind(file_id),
333
new_tree.id2path(file_id)])
339
action = Action('modified', [change.kind[1], change.path[1]])
334
340
action.add_utf8_property('last-changed', new_rev)
335
341
action.write(self.to_file)
412
419
def _read_next_entry(self, line, indent=1):
413
420
"""Read in a key-value pair
415
if not line.startswith('#'):
422
if not line.startswith(b'#'):
416
423
raise errors.MalformedHeader('Bzr header did not start with #')
417
line = line[1:-1].decode('utf-8') # Remove the '#' and '\n'
418
if line[:indent] == ' '*indent:
424
line = line[1:-1].decode('utf-8') # Remove the '#' and '\n'
425
if line[:indent] == ' ' * indent:
419
426
line = line[indent:]
421
return None, None# Ignore blank lines
428
return None, None # Ignore blank lines
423
430
loc = line.find(': ')
433
value = line[loc + 2:]
428
value = self._read_many(indent=indent+2)
435
value = self._read_many(indent=indent + 2)
429
436
elif line[-1:] == ':':
431
value = self._read_many(indent=indent+2)
438
value = self._read_many(indent=indent + 2)
433
440
raise errors.MalformedHeader('While looking for key: value pairs,'
434
' did not find the colon %r' % (line))
441
' did not find the colon %r' % (line))
436
443
key = key.replace(' ', '_')
437
444
#mutter('found %s: %s' % (key, value))
487
496
#mutter('_read_one_patch: %r' % self._next_line)
488
497
# Peek and see if there are no patches
489
if self._next_line is None or self._next_line.startswith('#'):
498
if self._next_line is None or self._next_line.startswith(b'#'):
490
499
return None, [], False
494
503
for line in self._next():
496
if not line.startswith('==='):
505
if not line.startswith(b'==='):
497
506
raise errors.MalformedPatches('The first line of all patches'
498
' should be a bzr meta line "==="'
507
' should be a bzr meta line "==="'
500
509
action = line[4:-1].decode('utf-8')
501
elif line.startswith('... '):
502
action += line[len('... '):-1].decode('utf-8')
510
elif line.startswith(b'... '):
511
action += line[len(b'... '):-1].decode('utf-8')
504
513
if (self._next_line is not None and
505
self._next_line.startswith('===')):
514
self._next_line.startswith(b'===')):
506
515
return action, lines, True
507
elif self._next_line is None or self._next_line.startswith('#'):
516
elif self._next_line is None or self._next_line.startswith(b'#'):
508
517
return action, lines, False
512
elif not line.startswith('... '):
521
elif not line.startswith(b'... '):
513
522
lines.append(line)
515
524
return action, lines, False