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