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
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""Read in a bundle stream, and process it into a BundleReader object."""
24
24
from bzrlib import (
28
27
import bzrlib.errors
29
from bzrlib.bundle import apply_bundle
30
from bzrlib.errors import (TestamentMismatch, BzrError,
28
from bzrlib.errors import (TestamentMismatch, BzrError,
31
29
MalformedHeader, MalformedPatches, NotABundle)
32
30
from bzrlib.inventory import (Inventory, InventoryEntry,
33
31
InventoryDirectory, InventoryFile,
77
75
if self.properties:
78
76
for property in self.properties:
79
77
key_end = property.find(': ')
81
if not property.endswith(':'):
82
raise ValueError(property)
83
key = str(property[:-1])
86
key = str(property[:key_end])
87
value = property[key_end+2:]
78
assert key_end is not None
79
key = property[:key_end].encode('utf-8')
80
value = property[key_end+2:].encode('utf-8')
88
81
rev.properties[key] = value
93
def from_revision(revision):
94
revision_info = RevisionInfo(revision.revision_id)
95
date = timestamp.format_highres_date(revision.timestamp,
97
revision_info.date = date
98
revision_info.timezone = revision.timezone
99
revision_info.timestamp = revision.timestamp
100
revision_info.message = revision.message.split('\n')
101
revision_info.properties = [': '.join(p) for p in
102
revision.properties.iteritems()]
106
86
class BundleInfo(object):
107
87
"""This contains the meta information. Stuff that allows you to
108
88
recreate the revision or inventory XML.
110
def __init__(self, bundle_format=None):
111
self.bundle_format = None
112
91
self.committer = None
114
93
self.message = None
136
112
split up, based on the assumptions that can be made
137
113
when information is missing.
139
from bzrlib.timestamp import unpack_highres_date
115
from bzrlib.bundle.serializer import unpack_highres_date
140
116
# Put in all of the guessable information.
141
117
if not self.timestamp and self.date:
142
118
self.timestamp, self.timezone = unpack_highres_date(self.date)
159
135
def get_base(self, revision):
160
136
revision_info = self.get_revision_info(revision.revision_id)
161
137
if revision_info.base_id is not None:
162
return revision_info.base_id
138
if revision_info.base_id == NULL_REVISION:
141
return revision_info.base_id
163
142
if len(revision.parent_ids) == 0:
164
143
# There is no base listed, and
165
144
# the lowest revision doesn't have a parent
166
145
# so this is probably against the empty tree
167
# and thus base truly is NULL_REVISION
146
# and thus base truly is None
170
149
return revision.parent_ids[-1]
194
173
def revision_tree(self, repository, revision_id, base=None):
195
174
revision = self.get_revision(revision_id)
196
175
base = self.get_base(revision)
197
if base == revision_id:
198
raise AssertionError()
199
if not self._validated_revisions_against_repo:
200
self._validate_references_from_repository(repository)
176
assert base != revision_id
177
self._validate_references_from_repository(repository)
201
178
revision_info = self.get_revision_info(revision_id)
202
179
inventory_revision_id = revision_id
203
bundle_tree = BundleTree(repository.revision_tree(base),
180
bundle_tree = BundleTree(repository.revision_tree(base),
204
181
inventory_revision_id)
205
182
self._update_tree(bundle_tree, revision_id)
239
216
for rev_info in self.revisions:
240
217
checked[rev_info.revision_id] = True
241
218
add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
243
220
for (rev, rev_info) in zip(self.real_revisions, self.revisions):
244
221
add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
248
225
for revision_id, sha1 in rev_to_sha.iteritems():
249
226
if repository.has_revision(revision_id):
250
testament = StrictTestament.from_revision(repository,
227
testament = StrictTestament.from_revision(repository,
252
229
local_sha1 = self._testament_sha1_from_revision(repository,
254
231
if sha1 != local_sha1:
255
raise BzrError('sha1 mismatch. For revision id {%s}'
232
raise BzrError('sha1 mismatch. For revision id {%s}'
256
233
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
259
236
elif revision_id not in checked:
260
237
missing[revision_id] = sha1
239
for inv_id, sha1 in inv_to_sha.iteritems():
240
if repository.has_revision(inv_id):
241
# Note: branch.get_inventory_sha1() just returns the value that
242
# is stored in the revision text, and that value may be out
243
# of date. This is bogus, because that means we aren't
244
# validating the actual text, just that we wrote and read the
245
# string. But for now, what the hell.
246
local_sha1 = repository.get_inventory_sha1(inv_id)
247
if sha1 != local_sha1:
248
raise BzrError('sha1 mismatch. For inventory id {%s}'
249
'local: %s, bundle: %s' %
250
(inv_id, local_sha1, sha1))
262
254
if len(missing) > 0:
263
255
# I don't know if this is an error yet
264
256
warning('Not all revision hashes could be validated.'
265
257
' Unable validate %d hashes' % len(missing))
266
258
mutter('Verified %d sha hashes for the bundle.' % count)
267
self._validated_revisions_against_repo = True
269
260
def _validate_inventory(self, inv, revision_id):
270
261
"""At this point we should have generated the BundleTree,
271
262
so build up an inventory, and make sure the hashes match.
265
assert inv is not None
273
267
# Now we should have a complete inventory entry.
274
268
s = serializer_v5.write_inventory_to_string(inv)
275
269
sha1 = sha_string(s)
276
270
# Target revision is the last entry in the real_revisions list
277
271
rev = self.get_revision(revision_id)
278
if rev.revision_id != revision_id:
279
raise AssertionError()
272
assert rev.revision_id == revision_id
280
273
if sha1 != rev.inventory_sha1:
281
274
open(',,bogus-inv', 'wb').write(s)
282
275
warning('Inventory sha hash mismatch for revision %s. %s'
288
281
# This is a mapping from each revision id to it's sha hash
291
284
rev = self.get_revision(revision_id)
292
285
rev_info = self.get_revision_info(revision_id)
293
if not (rev.revision_id == rev_info.revision_id):
294
raise AssertionError()
295
if not (rev.revision_id == revision_id):
296
raise AssertionError()
286
assert rev.revision_id == rev_info.revision_id
287
assert rev.revision_id == revision_id
297
288
sha1 = self._testament_sha1(rev, inventory)
298
289
if sha1 != rev_info.sha1:
299
290
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
312
303
def get_rev_id(last_changed, path, kind):
313
304
if last_changed is not None:
314
# last_changed will be a Unicode string because of how it was
315
# read. Convert it back to utf8.
316
changed_revision_id = osutils.safe_revision_id(last_changed,
305
changed_revision_id = osutils.safe_revision_id(last_changed)
319
307
changed_revision_id = revision_id
320
308
bundle_tree.note_last_changed(path, changed_revision_id)
340
329
return last_changed, encoding
342
331
def do_patch(path, lines, encoding):
343
if encoding == 'base64':
332
if encoding is not None:
333
assert encoding == 'base64'
344
334
patch = base64.decodestring(''.join(lines))
345
elif encoding is None:
346
336
patch = ''.join(lines)
348
raise ValueError(encoding)
349
337
bundle_tree.note_patch(path, patch)
351
339
def renamed(kind, extra, lines):
390
378
# This will be Unicode because of how the stream is read. Turn it
391
379
# back into a utf8 file_id
392
file_id = osutils.safe_file_id(info[1][8:], warn=False)
380
file_id = osutils.safe_file_id(info[1][8:])
394
382
bundle_tree.note_id(file_id, path, kind)
395
383
# this will be overridden in extra_info if executable is specified.
440
428
' (unrecognized action): %r' % action_line)
441
429
valid_actions[action](kind, extra, lines)
443
def install_revisions(self, target_repo, stream_input=True):
444
"""Install revisions and return the target revision
446
:param target_repo: The repository to install into
447
:param stream_input: Ignored by this implementation.
449
apply_bundle.install_bundle(target_repo, self)
452
def get_merge_request(self, target_repo):
453
"""Provide data for performing a merge
455
Returns suggested base, suggested target, and patch verification status
457
return None, self.target, 'inapplicable'
460
432
class BundleTree(Tree):
461
433
def __init__(self, base_tree, revision_id):
480
452
def note_rename(self, old_path, new_path):
481
453
"""A file/directory has been renamed from old_path => new_path"""
482
if new_path in self._renamed:
483
raise AssertionError(new_path)
484
if old_path in self._renamed_r:
485
raise AssertionError(old_path)
454
assert new_path not in self._renamed
455
assert old_path not in self._renamed_r
486
456
self._renamed[new_path] = old_path
487
457
self._renamed_r[old_path] = new_path
519
489
def old_path(self, new_path):
520
490
"""Get the old_path (path in the base_tree) for the file at new_path"""
521
if new_path[:1] in ('\\', '/'):
522
raise ValueError(new_path)
491
assert new_path[:1] not in ('\\', '/')
523
492
old_path = self._renamed.get(new_path)
524
493
if old_path is not None:
540
509
if old_path in self._renamed_r:
544
513
def new_path(self, old_path):
545
514
"""Get the new_path (path in the target_tree) for the file at old_path
546
515
in the base tree.
548
if old_path[:1] in ('\\', '/'):
549
raise ValueError(old_path)
517
assert old_path[:1] not in ('\\', '/')
550
518
new_path = self._renamed_r.get(old_path)
551
519
if new_path is not None:
622
590
patch_original = None
623
591
file_patch = self.patches.get(self.id2path(file_id))
624
592
if file_patch is None:
625
if (patch_original is None and
593
if (patch_original is None and
626
594
self.get_kind(file_id) == 'directory'):
627
595
return StringIO()
628
if patch_original is None:
629
raise AssertionError("None: %s" % file_id)
596
assert patch_original is not None, "None: %s" % file_id
630
597
return patch_original
632
if file_patch.startswith('\\'):
634
'Malformed patch for %s, %r' % (file_id, file_patch))
599
assert not file_patch.startswith('\\'), \
600
'Malformed patch for %s, %r' % (file_id, file_patch)
635
601
return patched_file(file_patch, patch_original)
637
603
def get_symlink_target(self, file_id):