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