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,
77
78
for property in self.properties:
78
79
key_end = property.find(': ')
80
assert property.endswith(':')
81
if not property.endswith(':'):
82
raise ValueError(property)
81
83
key = str(property[:-1])
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()]
91
106
class BundleInfo(object):
92
107
"""This contains the meta information. Stuff that allows you to
93
108
recreate the revision or inventory XML.
110
def __init__(self, bundle_format=None):
111
self.bundle_format = None
96
112
self.committer = None
98
114
self.message = None
143
159
def get_base(self, revision):
144
160
revision_info = self.get_revision_info(revision.revision_id)
145
161
if revision_info.base_id is not None:
146
if revision_info.base_id == NULL_REVISION:
149
return revision_info.base_id
162
return revision_info.base_id
150
163
if len(revision.parent_ids) == 0:
151
164
# There is no base listed, and
152
165
# the lowest revision doesn't have a parent
153
166
# so this is probably against the empty tree
154
# and thus base truly is None
167
# and thus base truly is NULL_REVISION
157
170
return revision.parent_ids[-1]
179
192
raise KeyError(revision_id)
181
194
def revision_tree(self, repository, revision_id, base=None):
182
revision_id = osutils.safe_revision_id(revision_id)
183
195
revision = self.get_revision(revision_id)
184
196
base = self.get_base(revision)
185
assert base != revision_id
197
if base == revision_id:
198
raise AssertionError()
186
199
if not self._validated_revisions_against_repo:
187
200
self._validate_references_from_repository(repository)
188
201
revision_info = self.get_revision_info(revision_id)
189
202
inventory_revision_id = revision_id
190
bundle_tree = BundleTree(repository.revision_tree(base),
203
bundle_tree = BundleTree(repository.revision_tree(base),
191
204
inventory_revision_id)
192
205
self._update_tree(bundle_tree, revision_id)
226
239
for rev_info in self.revisions:
227
240
checked[rev_info.revision_id] = True
228
241
add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
230
243
for (rev, rev_info) in zip(self.real_revisions, self.revisions):
231
244
add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
235
248
for revision_id, sha1 in rev_to_sha.iteritems():
236
249
if repository.has_revision(revision_id):
237
testament = StrictTestament.from_revision(repository,
250
testament = StrictTestament.from_revision(repository,
239
252
local_sha1 = self._testament_sha1_from_revision(repository,
241
254
if sha1 != local_sha1:
242
raise BzrError('sha1 mismatch. For revision id {%s}'
255
raise BzrError('sha1 mismatch. For revision id {%s}'
243
256
'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
246
259
elif revision_id not in checked:
247
260
missing[revision_id] = sha1
249
for inv_id, sha1 in inv_to_sha.iteritems():
250
if repository.has_revision(inv_id):
251
# Note: branch.get_inventory_sha1() just returns the value that
252
# is stored in the revision text, and that value may be out
253
# of date. This is bogus, because that means we aren't
254
# validating the actual text, just that we wrote and read the
255
# string. But for now, what the hell.
256
local_sha1 = repository.get_inventory_sha1(inv_id)
257
if sha1 != local_sha1:
258
raise BzrError('sha1 mismatch. For inventory id {%s}'
259
'local: %s, bundle: %s' %
260
(inv_id, local_sha1, sha1))
264
262
if len(missing) > 0:
265
263
# I don't know if this is an error yet
266
264
warning('Not all revision hashes could be validated.'
272
270
"""At this point we should have generated the BundleTree,
273
271
so build up an inventory, and make sure the hashes match.
276
assert inv is not None
278
273
# Now we should have a complete inventory entry.
279
274
s = serializer_v5.write_inventory_to_string(inv)
280
275
sha1 = sha_string(s)
281
276
# Target revision is the last entry in the real_revisions list
282
277
rev = self.get_revision(revision_id)
283
assert rev.revision_id == revision_id
278
if rev.revision_id != revision_id:
279
raise AssertionError()
284
280
if sha1 != rev.inventory_sha1:
285
281
open(',,bogus-inv', 'wb').write(s)
286
282
warning('Inventory sha hash mismatch for revision %s. %s'
292
288
# This is a mapping from each revision id to it's sha hash
295
291
rev = self.get_revision(revision_id)
296
292
rev_info = self.get_revision_info(revision_id)
297
assert rev.revision_id == rev_info.revision_id
298
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()
299
297
sha1 = self._testament_sha1(rev, inventory)
300
298
if sha1 != rev_info.sha1:
301
299
raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
333
331
if name == 'last-changed':
334
332
last_changed = value
335
333
elif name == 'executable':
336
assert value in ('yes', 'no'), value
337
334
val = (value == 'yes')
338
335
bundle_tree.note_executable(new_path, val)
339
336
elif name == 'target':
343
340
return last_changed, encoding
345
342
def do_patch(path, lines, encoding):
346
if encoding is not None:
347
assert encoding == 'base64'
343
if encoding == 'base64':
348
344
patch = base64.decodestring(''.join(lines))
345
elif encoding is None:
350
346
patch = ''.join(lines)
348
raise ValueError(encoding)
351
349
bundle_tree.note_patch(path, patch)
353
351
def renamed(kind, extra, lines):
442
440
' (unrecognized action): %r' % action_line)
443
441
valid_actions[action](kind, extra, lines)
445
def install_revisions(self, target_repo):
446
"""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.
447
449
apply_bundle.install_bundle(target_repo, self)
448
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'
451
460
class BundleTree(Tree):
452
461
def __init__(self, base_tree, revision_id):
471
480
def note_rename(self, old_path, new_path):
472
481
"""A file/directory has been renamed from old_path => new_path"""
473
assert new_path not in self._renamed
474
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)
475
486
self._renamed[new_path] = old_path
476
487
self._renamed_r[old_path] = new_path
508
519
def old_path(self, new_path):
509
520
"""Get the old_path (path in the base_tree) for the file at new_path"""
510
assert new_path[:1] not in ('\\', '/')
521
if new_path[:1] in ('\\', '/'):
522
raise ValueError(new_path)
511
523
old_path = self._renamed.get(new_path)
512
524
if old_path is not None:
528
540
if old_path in self._renamed_r:
532
544
def new_path(self, old_path):
533
545
"""Get the new_path (path in the target_tree) for the file at old_path
534
546
in the base tree.
536
assert old_path[:1] not in ('\\', '/')
548
if old_path[:1] in ('\\', '/'):
549
raise ValueError(old_path)
537
550
new_path = self._renamed_r.get(old_path)
538
551
if new_path is not None:
593
606
new_path = self.id2path(file_id)
594
607
return self.base_tree.path2id(new_path)
596
609
def get_file(self, file_id):
597
610
"""Return a file-like object containing the new contents of the
598
611
file given by file_id.
609
622
patch_original = None
610
623
file_patch = self.patches.get(self.id2path(file_id))
611
624
if file_patch is None:
612
if (patch_original is None and
625
if (patch_original is None and
613
626
self.get_kind(file_id) == 'directory'):
614
627
return StringIO()
615
assert patch_original is not None, "None: %s" % file_id
628
if patch_original is None:
629
raise AssertionError("None: %s" % file_id)
616
630
return patch_original
618
assert not file_patch.startswith('\\'), \
619
'Malformed patch for %s, %r' % (file_id, file_patch)
632
if file_patch.startswith('\\'):
634
'Malformed patch for %s, %r' % (file_id, file_patch))
620
635
return patched_file(file_patch, patch_original)
622
637
def get_symlink_target(self, file_id):
669
684
This need to be called before ever accessing self.inventory
671
686
from os.path import dirname, basename
673
assert self.base_tree is not None
674
687
base_inv = self.base_tree.inventory
675
688
inv = Inventory(None, self.revision_id)