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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
"""Read in a bundle stream, and process it into a BundleReader object."""
24
24
from bzrlib import (
27
28
import bzrlib.errors
28
29
from bzrlib.bundle import apply_bundle
29
from bzrlib.errors import (TestamentMismatch, BzrError,
30
from bzrlib.errors import (TestamentMismatch, BzrError,
30
31
MalformedHeader, MalformedPatches, NotABundle)
31
32
from bzrlib.inventory import (Inventory, InventoryEntry,
32
33
InventoryDirectory, InventoryFile,
76
77
if self.properties:
77
78
for property in self.properties:
78
79
key_end = property.find(': ')
79
assert key_end is not None
80
key = property[:key_end].encode('utf-8')
81
value = property[key_end+2:].encode('utf-8')
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:]
82
88
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()]
87
106
class BundleInfo(object):
88
107
"""This contains the meta information. Stuff that allows you to
89
108
recreate the revision or inventory XML.
110
def __init__(self, bundle_format=None):
111
self.bundle_format = None
92
112
self.committer = None
94
114
self.message = None
136
159
def get_base(self, revision):
137
160
revision_info = self.get_revision_info(revision.revision_id)
138
161
if revision_info.base_id is not None:
139
if revision_info.base_id == NULL_REVISION:
142
return revision_info.base_id
162
return revision_info.base_id
143
163
if len(revision.parent_ids) == 0:
144
164
# There is no base listed, and
145
165
# the lowest revision doesn't have a parent
146
166
# so this is probably against the empty tree
147
# and thus base truly is None
167
# and thus base truly is NULL_REVISION
150
170
return revision.parent_ids[-1]
172
192
raise KeyError(revision_id)
174
194
def revision_tree(self, repository, revision_id, base=None):
175
revision_id = osutils.safe_revision_id(revision_id)
176
195
revision = self.get_revision(revision_id)
177
196
base = self.get_base(revision)
178
assert base != revision_id
179
self._validate_references_from_repository(repository)
197
if base == revision_id:
198
raise AssertionError()
199
if not self._validated_revisions_against_repo:
200
self._validate_references_from_repository(repository)
180
201
revision_info = self.get_revision_info(revision_id)
181
202
inventory_revision_id = revision_id
182
bundle_tree = BundleTree(repository.revision_tree(base),
203
bundle_tree = BundleTree(repository.revision_tree(base),
183
204
inventory_revision_id)
184
205
self._update_tree(bundle_tree, revision_id)
218
239
for rev_info in self.revisions:
219
240
checked[rev_info.revision_id] = True
220
241
add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
222
243
for (rev, rev_info) in zip(self.real_revisions, self.revisions):
223
244
add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
227
248
for revision_id, sha1 in rev_to_sha.iteritems():
228
249
if repository.has_revision(revision_id):
229
testament = StrictTestament.from_revision(repository,
250
testament = StrictTestament.from_revision(repository,
231
252
local_sha1 = self._testament_sha1_from_revision(repository,
233
254
if sha1 != local_sha1:
234
raise BzrError('sha1 mismatch. For revision id {%s}'
255
raise BzrError('sha1 mismatch. For revision id {%s}'
235
256
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
238
259
elif revision_id not in checked:
239
260
missing[revision_id] = sha1
241
for inv_id, sha1 in inv_to_sha.iteritems():
242
if repository.has_revision(inv_id):
243
# Note: branch.get_inventory_sha1() just returns the value that
244
# is stored in the revision text, and that value may be out
245
# of date. This is bogus, because that means we aren't
246
# validating the actual text, just that we wrote and read the
247
# string. But for now, what the hell.
248
local_sha1 = repository.get_inventory_sha1(inv_id)
249
if sha1 != local_sha1:
250
raise BzrError('sha1 mismatch. For inventory id {%s}'
251
'local: %s, bundle: %s' %
252
(inv_id, local_sha1, sha1))
256
262
if len(missing) > 0:
257
263
# I don't know if this is an error yet
258
264
warning('Not all revision hashes could be validated.'
259
265
' Unable validate %d hashes' % len(missing))
260
266
mutter('Verified %d sha hashes for the bundle.' % count)
267
self._validated_revisions_against_repo = True
262
269
def _validate_inventory(self, inv, revision_id):
263
270
"""At this point we should have generated the BundleTree,
264
271
so build up an inventory, and make sure the hashes match.
267
assert inv is not None
269
273
# Now we should have a complete inventory entry.
270
274
s = serializer_v5.write_inventory_to_string(inv)
271
275
sha1 = sha_string(s)
272
276
# Target revision is the last entry in the real_revisions list
273
277
rev = self.get_revision(revision_id)
274
assert rev.revision_id == revision_id
278
if rev.revision_id != revision_id:
279
raise AssertionError()
275
280
if sha1 != rev.inventory_sha1:
276
281
open(',,bogus-inv', 'wb').write(s)
277
282
warning('Inventory sha hash mismatch for revision %s. %s'
283
288
# This is a mapping from each revision id to it's sha hash
286
291
rev = self.get_revision(revision_id)
287
292
rev_info = self.get_revision_info(revision_id)
288
assert rev.revision_id == rev_info.revision_id
289
assert rev.revision_id == 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()
290
297
sha1 = self._testament_sha1(rev, inventory)
291
298
if sha1 != rev_info.sha1:
292
299
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
334
340
return last_changed, encoding
336
342
def do_patch(path, lines, encoding):
337
if encoding is not None:
338
assert encoding == 'base64'
343
if encoding == 'base64':
339
344
patch = base64.decodestring(''.join(lines))
345
elif encoding is None:
341
346
patch = ''.join(lines)
348
raise ValueError(encoding)
342
349
bundle_tree.note_patch(path, patch)
344
351
def renamed(kind, extra, lines):
433
440
' (unrecognized action): %r' % action_line)
434
441
valid_actions[action](kind, extra, lines)
436
def install_revisions(self, target_repo):
437
"""Install revisions and return the target revision"""
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.
438
449
apply_bundle.install_bundle(target_repo, self)
439
450
return self.target
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'
442
460
class BundleTree(Tree):
443
461
def __init__(self, base_tree, revision_id):
462
480
def note_rename(self, old_path, new_path):
463
481
"""A file/directory has been renamed from old_path => new_path"""
464
assert new_path not in self._renamed
465
assert old_path not in self._renamed_r
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)
466
486
self._renamed[new_path] = old_path
467
487
self._renamed_r[old_path] = new_path
499
519
def old_path(self, new_path):
500
520
"""Get the old_path (path in the base_tree) for the file at new_path"""
501
assert new_path[:1] not in ('\\', '/')
521
if new_path[:1] in ('\\', '/'):
522
raise ValueError(new_path)
502
523
old_path = self._renamed.get(new_path)
503
524
if old_path is not None:
519
540
if old_path in self._renamed_r:
523
544
def new_path(self, old_path):
524
545
"""Get the new_path (path in the target_tree) for the file at old_path
525
546
in the base tree.
527
assert old_path[:1] not in ('\\', '/')
548
if old_path[:1] in ('\\', '/'):
549
raise ValueError(old_path)
528
550
new_path = self._renamed_r.get(old_path)
529
551
if new_path is not None:
600
622
patch_original = None
601
623
file_patch = self.patches.get(self.id2path(file_id))
602
624
if file_patch is None:
603
if (patch_original is None and
625
if (patch_original is None and
604
626
self.get_kind(file_id) == 'directory'):
605
627
return StringIO()
606
assert patch_original is not None, "None: %s" % file_id
628
if patch_original is None:
629
raise AssertionError("None: %s" % file_id)
607
630
return patch_original
609
assert not file_patch.startswith('\\'), \
610
'Malformed patch for %s, %r' % (file_id, file_patch)
632
if file_patch.startswith('\\'):
634
'Malformed patch for %s, %r' % (file_id, file_patch))
611
635
return patched_file(file_patch, patch_original)
613
637
def get_symlink_target(self, file_id):