/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/bundle/bundle_data.py

  • Committer: Robert Collins
  • Date: 2010-05-05 00:05:29 UTC
  • mto: This revision was merged to the branch mainline in revision 5206.
  • Revision ID: robertc@robertcollins.net-20100505000529-ltmllyms5watqj5u
Make 'pydoc bzrlib.tests.build_tree_shape' useful.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
16
16
 
17
17
"""Read in a bundle stream, and process it into a BundleReader object."""
18
18
 
19
 
from __future__ import absolute_import
20
 
 
21
19
import base64
22
 
from io import BytesIO
 
20
from cStringIO import StringIO
23
21
import os
24
22
import pprint
25
23
 
26
 
from ... import (
27
 
    cache_utf8,
 
24
from bzrlib import (
28
25
    osutils,
29
26
    timestamp,
30
27
    )
31
 
from . import apply_bundle
32
 
from ...errors import (
33
 
    TestamentMismatch,
34
 
    BzrError,
35
 
    NoSuchId,
36
 
    )
37
 
from ..inventory import (
38
 
    Inventory,
39
 
    InventoryDirectory,
40
 
    InventoryFile,
41
 
    InventoryLink,
42
 
    )
43
 
from ...osutils import sha_string, sha_strings, pathjoin
44
 
from ...revision import Revision, NULL_REVISION
45
 
from ...sixish import (
46
 
    viewitems,
47
 
    )
48
 
from ..testament import StrictTestament
49
 
from ...trace import mutter, warning
50
 
from ...tree import (
51
 
    InterTree,
52
 
    Tree,
53
 
    )
54
 
from ..xml5 import serializer_v5
 
28
import bzrlib.errors
 
29
from bzrlib.bundle import apply_bundle
 
30
from bzrlib.errors import (TestamentMismatch, BzrError,
 
31
                           MalformedHeader, MalformedPatches, NotABundle)
 
32
from bzrlib.inventory import (Inventory, InventoryEntry,
 
33
                              InventoryDirectory, InventoryFile,
 
34
                              InventoryLink)
 
35
from bzrlib.osutils import sha_file, sha_string, pathjoin
 
36
from bzrlib.revision import Revision, NULL_REVISION
 
37
from bzrlib.testament import StrictTestament
 
38
from bzrlib.trace import mutter, warning
 
39
import bzrlib.transport
 
40
from bzrlib.tree import Tree
 
41
import bzrlib.urlutils
 
42
from bzrlib.xml5 import serializer_v5
55
43
 
56
44
 
57
45
class RevisionInfo(object):
58
46
    """Gets filled out for each revision object that is read.
59
47
    """
60
 
 
61
48
    def __init__(self, revision_id):
62
49
        self.revision_id = revision_id
63
50
        self.sha1 = None
78
65
 
79
66
    def as_revision(self):
80
67
        rev = Revision(revision_id=self.revision_id,
81
 
                       committer=self.committer,
82
 
                       timestamp=float(self.timestamp),
83
 
                       timezone=int(self.timezone),
84
 
                       inventory_sha1=self.inventory_sha1,
85
 
                       message='\n'.join(self.message))
 
68
            committer=self.committer,
 
69
            timestamp=float(self.timestamp),
 
70
            timezone=int(self.timezone),
 
71
            inventory_sha1=self.inventory_sha1,
 
72
            message='\n'.join(self.message))
86
73
 
87
74
        if self.parent_ids:
88
75
            rev.parent_ids.extend(self.parent_ids)
97
84
                    value = ''
98
85
                else:
99
86
                    key = str(property[:key_end])
100
 
                    value = property[key_end + 2:]
 
87
                    value = property[key_end+2:]
101
88
                rev.properties[key] = value
102
89
 
103
90
        return rev
112
99
        revision_info.timestamp = revision.timestamp
113
100
        revision_info.message = revision.message.split('\n')
114
101
        revision_info.properties = [': '.join(p) for p in
115
 
                                    viewitems(revision.properties)]
 
102
                                    revision.properties.iteritems()]
116
103
        return revision_info
117
104
 
118
105
 
120
107
    """This contains the meta information. Stuff that allows you to
121
108
    recreate the revision or inventory XML.
122
109
    """
123
 
 
124
110
    def __init__(self, bundle_format=None):
125
111
        self.bundle_format = None
126
112
        self.committer = None
150
136
        split up, based on the assumptions that can be made
151
137
        when information is missing.
152
138
        """
153
 
        from breezy.timestamp import unpack_highres_date
 
139
        from bzrlib.timestamp import unpack_highres_date
154
140
        # Put in all of the guessable information.
155
141
        if not self.timestamp and self.date:
156
142
            self.timestamp, self.timezone = unpack_highres_date(self.date)
160
146
            if rev.timestamp is None:
161
147
                if rev.date is not None:
162
148
                    rev.timestamp, rev.timezone = \
163
 
                        unpack_highres_date(rev.date)
 
149
                            unpack_highres_date(rev.date)
164
150
                else:
165
151
                    rev.timestamp = self.timestamp
166
152
                    rev.timezone = self.timezone
215
201
        revision_info = self.get_revision_info(revision_id)
216
202
        inventory_revision_id = revision_id
217
203
        bundle_tree = BundleTree(repository.revision_tree(base),
218
 
                                 inventory_revision_id)
 
204
                                  inventory_revision_id)
219
205
        self._update_tree(bundle_tree, revision_id)
220
206
 
221
207
        inv = bundle_tree.inventory
222
208
        self._validate_inventory(inv, revision_id)
223
 
        self._validate_revision(bundle_tree, revision_id)
 
209
        self._validate_revision(inv, revision_id)
224
210
 
225
211
        return bundle_tree
226
212
 
231
217
        """
232
218
        rev_to_sha = {}
233
219
        inv_to_sha = {}
234
 
 
235
220
        def add_sha(d, revision_id, sha1):
236
221
            if revision_id is None:
237
222
                if sha1 is not None:
238
223
                    raise BzrError('A Null revision should always'
239
 
                                   'have a null sha1 hash')
 
224
                        'have a null sha1 hash')
240
225
                return
241
226
            if revision_id in d:
242
227
                # This really should have been validated as part
243
228
                # of _validate_revisions but lets do it again
244
229
                if sha1 != d[revision_id]:
245
230
                    raise BzrError('** Revision %r referenced with 2 different'
246
 
                                   ' sha hashes %s != %s' % (revision_id,
247
 
                                                             sha1, d[revision_id]))
 
231
                            ' sha hashes %s != %s' % (revision_id,
 
232
                                sha1, d[revision_id]))
248
233
            else:
249
234
                d[revision_id] = sha1
250
235
 
260
245
 
261
246
        count = 0
262
247
        missing = {}
263
 
        for revision_id, sha1 in viewitems(rev_to_sha):
 
248
        for revision_id, sha1 in rev_to_sha.iteritems():
264
249
            if repository.has_revision(revision_id):
265
250
                testament = StrictTestament.from_revision(repository,
266
251
                                                          revision_id)
268
253
                                                                revision_id)
269
254
                if sha1 != local_sha1:
270
255
                    raise BzrError('sha1 mismatch. For revision id {%s}'
271
 
                                   'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
 
256
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
272
257
                else:
273
258
                    count += 1
274
259
            elif revision_id not in checked:
286
271
        so build up an inventory, and make sure the hashes match.
287
272
        """
288
273
        # Now we should have a complete inventory entry.
289
 
        cs = serializer_v5.write_inventory_to_chunks(inv)
290
 
        sha1 = sha_strings(cs)
 
274
        s = serializer_v5.write_inventory_to_string(inv)
 
275
        sha1 = sha_string(s)
291
276
        # Target revision is the last entry in the real_revisions list
292
277
        rev = self.get_revision(revision_id)
293
278
        if rev.revision_id != revision_id:
294
279
            raise AssertionError()
295
280
        if sha1 != rev.inventory_sha1:
296
 
            with open(',,bogus-inv', 'wb') as f:
297
 
                f.writelines(cs)
 
281
            open(',,bogus-inv', 'wb').write(s)
298
282
            warning('Inventory sha hash mismatch for revision %s. %s'
299
283
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
300
284
 
301
 
    def _testament(self, revision, tree):
302
 
        raise NotImplementedError(self._testament)
303
 
 
304
 
    def _validate_revision(self, tree, revision_id):
 
285
    def _validate_revision(self, inventory, revision_id):
305
286
        """Make sure all revision entries match their checksum."""
306
287
 
307
 
        # This is a mapping from each revision id to its sha hash
 
288
        # This is a mapping from each revision id to it's sha hash
308
289
        rev_to_sha1 = {}
309
290
 
310
291
        rev = self.get_revision(revision_id)
313
294
            raise AssertionError()
314
295
        if not (rev.revision_id == revision_id):
315
296
            raise AssertionError()
316
 
        testament = self._testament(rev, tree)
317
 
        sha1 = testament.as_sha1()
 
297
        sha1 = self._testament_sha1(rev, inventory)
318
298
        if sha1 != rev_info.sha1:
319
299
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
320
300
        if rev.revision_id in rev_to_sha1:
321
301
            raise BzrError('Revision {%s} given twice in the list'
322
 
                           % (rev.revision_id))
 
302
                    % (rev.revision_id))
323
303
        rev_to_sha1[rev.revision_id] = sha1
324
304
 
325
305
    def _update_tree(self, bundle_tree, revision_id):
333
313
            if last_changed is not None:
334
314
                # last_changed will be a Unicode string because of how it was
335
315
                # read. Convert it back to utf8.
336
 
                changed_revision_id = cache_utf8.encode(last_changed)
 
316
                changed_revision_id = osutils.safe_revision_id(last_changed,
 
317
                                                               warn=False)
337
318
            else:
338
319
                changed_revision_id = revision_id
339
320
            bundle_tree.note_last_changed(path, changed_revision_id)
346
327
                try:
347
328
                    name, value = info_item.split(':', 1)
348
329
                except ValueError:
349
 
                    raise ValueError('Value %r has no colon' % info_item)
 
330
                    raise 'Value %r has no colon' % info_item
350
331
                if name == 'last-changed':
351
332
                    last_changed = value
352
333
                elif name == 'executable':
360
341
 
361
342
        def do_patch(path, lines, encoding):
362
343
            if encoding == 'base64':
363
 
                patch = base64.b64decode(b''.join(lines))
 
344
                patch = base64.decodestring(''.join(lines))
364
345
            elif encoding is None:
365
 
                patch = b''.join(lines)
 
346
                patch =  ''.join(lines)
366
347
            else:
367
348
                raise ValueError(encoding)
368
349
            bundle_tree.note_patch(path, patch)
371
352
            info = extra.split(' // ')
372
353
            if len(info) < 2:
373
354
                raise BzrError('renamed action lines need both a from and to'
374
 
                               ': %r' % extra)
 
355
                        ': %r' % extra)
375
356
            old_path = info[0]
376
357
            if info[1].startswith('=> '):
377
358
                new_path = info[1][3:]
390
371
                # TODO: in the future we might allow file ids to be
391
372
                # given for removed entries
392
373
                raise BzrError('removed action lines should only have the path'
393
 
                               ': %r' % extra)
 
374
                        ': %r' % extra)
394
375
            path = info[0]
395
376
            bundle_tree.note_deletion(path)
396
377
 
398
379
            info = extra.split(' // ')
399
380
            if len(info) <= 1:
400
381
                raise BzrError('add action lines require the path and file id'
401
 
                               ': %r' % extra)
 
382
                        ': %r' % extra)
402
383
            elif len(info) > 5:
403
384
                raise BzrError('add action lines have fewer than 5 entries.'
404
 
                               ': %r' % extra)
 
385
                        ': %r' % extra)
405
386
            path = info[0]
406
387
            if not info[1].startswith('file-id:'):
407
388
                raise BzrError('The file-id should follow the path for an add'
408
 
                               ': %r' % extra)
 
389
                        ': %r' % extra)
409
390
            # This will be Unicode because of how the stream is read. Turn it
410
391
            # back into a utf8 file_id
411
 
            file_id = cache_utf8.encode(info[1][8:])
 
392
            file_id = osutils.safe_file_id(info[1][8:], warn=False)
412
393
 
413
394
            bundle_tree.note_id(file_id, path, kind)
414
395
            # this will be overridden in extra_info if executable is specified.
423
404
            info = extra.split(' // ')
424
405
            if len(info) < 1:
425
406
                raise BzrError('modified action lines have at least'
426
 
                               'the path in them: %r' % extra)
 
407
                        'the path in them: %r' % extra)
427
408
            path = info[0]
428
409
 
429
410
            last_modified, encoding = extra_info(info[1:], path)
432
413
                do_patch(path, lines, encoding)
433
414
 
434
415
        valid_actions = {
435
 
            'renamed': renamed,
436
 
            'removed': removed,
437
 
            'added': added,
438
 
            'modified': modified
 
416
            'renamed':renamed,
 
417
            'removed':removed,
 
418
            'added':added,
 
419
            'modified':modified
439
420
        }
440
421
        for action_line, lines in \
441
 
                self.get_revision_info(revision_id).tree_actions:
 
422
            self.get_revision_info(revision_id).tree_actions:
442
423
            first = action_line.find(' ')
443
424
            if first == -1:
444
425
                raise BzrError('Bogus action line'
445
 
                               ' (no opening space): %r' % action_line)
446
 
            second = action_line.find(' ', first + 1)
 
426
                        ' (no opening space): %r' % action_line)
 
427
            second = action_line.find(' ', first+1)
447
428
            if second == -1:
448
429
                raise BzrError('Bogus action line'
449
 
                               ' (missing second space): %r' % action_line)
 
430
                        ' (missing second space): %r' % action_line)
450
431
            action = action_line[:first]
451
 
            kind = action_line[first + 1:second]
 
432
            kind = action_line[first+1:second]
452
433
            if kind not in ('file', 'directory', 'symlink'):
453
434
                raise BzrError('Bogus action line'
454
 
                               ' (invalid object kind %r): %r' % (kind, action_line))
455
 
            extra = action_line[second + 1:]
 
435
                        ' (invalid object kind %r): %r' % (kind, action_line))
 
436
            extra = action_line[second+1:]
456
437
 
457
438
            if action not in valid_actions:
458
439
                raise BzrError('Bogus action line'
459
 
                               ' (unrecognized action): %r' % action_line)
 
440
                        ' (unrecognized action): %r' % action_line)
460
441
            valid_actions[action](kind, extra, lines)
461
442
 
462
443
    def install_revisions(self, target_repo, stream_input=True):
477
458
 
478
459
 
479
460
class BundleTree(Tree):
480
 
 
481
461
    def __init__(self, base_tree, revision_id):
482
462
        self.base_tree = base_tree
483
 
        self._renamed = {}  # Mapping from old_path => new_path
484
 
        self._renamed_r = {}  # new_path => old_path
485
 
        self._new_id = {}  # new_path => new_id
486
 
        self._new_id_r = {}  # new_id => new_path
487
 
        self._kinds = {}  # new_path => kind
488
 
        self._last_changed = {}  # new_id => revision_id
489
 
        self._executable = {}  # new_id => executable value
 
463
        self._renamed = {} # Mapping from old_path => new_path
 
464
        self._renamed_r = {} # new_path => old_path
 
465
        self._new_id = {} # new_path => new_id
 
466
        self._new_id_r = {} # new_id => new_path
 
467
        self._kinds = {} # new_id => kind
 
468
        self._last_changed = {} # new_id => revision_id
 
469
        self._executable = {} # new_id => executable value
490
470
        self.patches = {}
491
 
        self._targets = {}  # new path => new symlink target
 
471
        self._targets = {} # new path => new symlink target
492
472
        self.deleted = []
 
473
        self.contents_by_id = True
493
474
        self.revision_id = revision_id
494
475
        self._inventory = None
495
 
        self._base_inter = InterTree.get(self.base_tree, self)
496
476
 
497
477
    def __str__(self):
498
478
        return pprint.pformat(self.__dict__)
510
490
        """Files that don't exist in base need a new id."""
511
491
        self._new_id[new_path] = new_id
512
492
        self._new_id_r[new_id] = new_path
513
 
        self._kinds[new_path] = kind
 
493
        self._kinds[new_id] = kind
514
494
 
515
495
    def note_last_changed(self, file_id, revision_id):
516
496
        if (file_id in self._last_changed
517
497
                and self._last_changed[file_id] != revision_id):
518
498
            raise BzrError('Mismatched last-changed revision for file_id {%s}'
519
 
                           ': %s != %s' % (file_id,
520
 
                                           self._last_changed[file_id],
521
 
                                           revision_id))
 
499
                    ': %s != %s' % (file_id,
 
500
                                    self._last_changed[file_id],
 
501
                                    revision_id))
522
502
        self._last_changed[file_id] = revision_id
523
503
 
524
504
    def note_patch(self, new_path, patch):
543
523
        old_path = self._renamed.get(new_path)
544
524
        if old_path is not None:
545
525
            return old_path
546
 
        dirname, basename = os.path.split(new_path)
 
526
        dirname,basename = os.path.split(new_path)
547
527
        # dirname is not '' doesn't work, because
548
528
        # dirname may be a unicode entry, and is
549
529
        # requires the objects to be identical
555
535
                old_path = pathjoin(old_dir, basename)
556
536
        else:
557
537
            old_path = new_path
558
 
        # If the new path wasn't in renamed, the old one shouldn't be in
559
 
        # renamed_r
 
538
        #If the new path wasn't in renamed, the old one shouldn't be in
 
539
        #renamed_r
560
540
        if old_path in self._renamed_r:
561
541
            return None
562
542
        return old_path
572
552
            return new_path
573
553
        if new_path in self._renamed:
574
554
            return None
575
 
        dirname, basename = os.path.split(old_path)
 
555
        dirname,basename = os.path.split(old_path)
576
556
        if dirname != '':
577
557
            new_dir = self.new_path(dirname)
578
558
            if new_dir is None:
581
561
                new_path = pathjoin(new_dir, basename)
582
562
        else:
583
563
            new_path = old_path
584
 
        # If the old path wasn't in renamed, the new one shouldn't be in
585
 
        # renamed_r
 
564
        #If the old path wasn't in renamed, the new one shouldn't be in
 
565
        #renamed_r
586
566
        if new_path in self._renamed:
587
567
            return None
588
568
        return new_path
597
577
            return None
598
578
        if old_path in self.deleted:
599
579
            return None
600
 
        return self.base_tree.path2id(old_path)
 
580
        if getattr(self.base_tree, 'path2id', None) is not None:
 
581
            return self.base_tree.path2id(old_path)
 
582
        else:
 
583
            return self.base_tree.inventory.path2id(old_path)
601
584
 
602
 
    def id2path(self, file_id, recurse='down'):
 
585
    def id2path(self, file_id):
603
586
        """Return the new path in the target tree of the file with id file_id"""
604
587
        path = self._new_id_r.get(file_id)
605
588
        if path is not None:
606
589
            return path
607
 
        old_path = self.base_tree.id2path(file_id, recurse)
 
590
        old_path = self.base_tree.id2path(file_id)
608
591
        if old_path is None:
609
 
            raise NoSuchId(file_id, self)
 
592
            return None
610
593
        if old_path in self.deleted:
611
 
            raise NoSuchId(file_id, self)
612
 
        new_path = self.new_path(old_path)
613
 
        if new_path is None:
614
 
            raise NoSuchId(file_id, self)
615
 
        return new_path
616
 
 
617
 
    def get_file(self, path):
 
594
            return None
 
595
        return self.new_path(old_path)
 
596
 
 
597
    def old_contents_id(self, file_id):
 
598
        """Return the id in the base_tree for the given file_id.
 
599
        Return None if the file did not exist in base.
 
600
        """
 
601
        if self.contents_by_id:
 
602
            if self.base_tree.has_id(file_id):
 
603
                return file_id
 
604
            else:
 
605
                return None
 
606
        new_path = self.id2path(file_id)
 
607
        return self.base_tree.path2id(new_path)
 
608
 
 
609
    def get_file(self, file_id):
618
610
        """Return a file-like object containing the new contents of the
619
611
        file given by file_id.
620
612
 
622
614
                in the text-store, so that the file contents would
623
615
                then be cached.
624
616
        """
625
 
        old_path = self._base_inter.find_source_path(path)
626
 
        if old_path is None:
 
617
        base_id = self.old_contents_id(file_id)
 
618
        if (base_id is not None and
 
619
            base_id != self.base_tree.inventory.root.file_id):
 
620
            patch_original = self.base_tree.get_file(base_id)
 
621
        else:
627
622
            patch_original = None
628
 
        else:
629
 
            patch_original = self.base_tree.get_file(old_path)
630
 
        file_patch = self.patches.get(path)
 
623
        file_patch = self.patches.get(self.id2path(file_id))
631
624
        if file_patch is None:
632
625
            if (patch_original is None and
633
 
                    self.kind(path) == 'directory'):
634
 
                return BytesIO()
 
626
                self.get_kind(file_id) == 'directory'):
 
627
                return StringIO()
635
628
            if patch_original is None:
636
629
                raise AssertionError("None: %s" % file_id)
637
630
            return patch_original
638
631
 
639
 
        if file_patch.startswith(b'\\'):
 
632
        if file_patch.startswith('\\'):
640
633
            raise ValueError(
641
634
                'Malformed patch for %s, %r' % (file_id, file_patch))
642
635
        return patched_file(file_patch, patch_original)
643
636
 
644
 
    def get_symlink_target(self, path):
645
 
        try:
646
 
            return self._targets[path]
647
 
        except KeyError:
648
 
            old_path = self.old_path(path)
649
 
            return self.base_tree.get_symlink_target(old_path)
650
 
 
651
 
    def kind(self, path):
652
 
        try:
653
 
            return self._kinds[path]
654
 
        except KeyError:
655
 
            old_path = self.old_path(path)
656
 
            return self.base_tree.kind(old_path)
657
 
 
658
 
    def get_file_revision(self, path):
659
 
        if path in self._last_changed:
660
 
            return self._last_changed[path]
661
 
        else:
662
 
            old_path = self.old_path(path)
663
 
            return self.base_tree.get_file_revision(old_path)
664
 
 
665
 
    def is_executable(self, path):
 
637
    def get_symlink_target(self, file_id):
 
638
        new_path = self.id2path(file_id)
 
639
        try:
 
640
            return self._targets[new_path]
 
641
        except KeyError:
 
642
            return self.base_tree.get_symlink_target(file_id)
 
643
 
 
644
    def get_kind(self, file_id):
 
645
        if file_id in self._kinds:
 
646
            return self._kinds[file_id]
 
647
        return self.base_tree.inventory[file_id].kind
 
648
 
 
649
    def is_executable(self, file_id):
 
650
        path = self.id2path(file_id)
666
651
        if path in self._executable:
667
652
            return self._executable[path]
668
653
        else:
669
 
            old_path = self.old_path(path)
670
 
            return self.base_tree.is_executable(old_path)
 
654
            return self.base_tree.inventory[file_id].executable
671
655
 
672
 
    def get_last_changed(self, path):
 
656
    def get_last_changed(self, file_id):
 
657
        path = self.id2path(file_id)
673
658
        if path in self._last_changed:
674
659
            return self._last_changed[path]
675
 
        old_path = self.old_path(path)
676
 
        return self.base_tree.get_file_revision(old_path)
 
660
        return self.base_tree.inventory[file_id].revision
677
661
 
678
 
    def get_size_and_sha1(self, new_path):
 
662
    def get_size_and_sha1(self, file_id):
679
663
        """Return the size and sha1 hash of the given file id.
680
664
        If the file was not locally modified, this is extracted
681
665
        from the base_tree. Rather than re-reading the file.
682
666
        """
 
667
        new_path = self.id2path(file_id)
683
668
        if new_path is None:
684
669
            return None, None
685
670
        if new_path not in self.patches:
686
671
            # If the entry does not have a patch, then the
687
672
            # contents must be the same as in the base_tree
688
 
            base_path = self.old_path(new_path)
689
 
            text_size = self.base_tree.get_file_size(base_path)
690
 
            text_sha1 = self.base_tree.get_file_sha1(base_path)
691
 
            return text_size, text_sha1
692
 
        fileobj = self.get_file(new_path)
 
673
            ie = self.base_tree.inventory[file_id]
 
674
            if ie.text_size is None:
 
675
                return ie.text_size, ie.text_sha1
 
676
            return int(ie.text_size), ie.text_sha1
 
677
        fileobj = self.get_file(file_id)
693
678
        content = fileobj.read()
694
679
        return len(content), sha_string(content)
695
680
 
699
684
        This need to be called before ever accessing self.inventory
700
685
        """
701
686
        from os.path import dirname, basename
 
687
        base_inv = self.base_tree.inventory
702
688
        inv = Inventory(None, self.revision_id)
703
689
 
704
 
        def add_entry(path, file_id):
 
690
        def add_entry(file_id):
 
691
            path = self.id2path(file_id)
 
692
            if path is None:
 
693
                return
705
694
            if path == '':
706
695
                parent_id = None
707
696
            else:
708
697
                parent_path = dirname(path)
709
698
                parent_id = self.path2id(parent_path)
710
699
 
711
 
            kind = self.kind(path)
712
 
            revision_id = self.get_last_changed(path)
 
700
            kind = self.get_kind(file_id)
 
701
            revision_id = self.get_last_changed(file_id)
713
702
 
714
703
            name = basename(path)
715
704
            if kind == 'directory':
716
705
                ie = InventoryDirectory(file_id, name, parent_id)
717
706
            elif kind == 'file':
718
707
                ie = InventoryFile(file_id, name, parent_id)
719
 
                ie.executable = self.is_executable(path)
 
708
                ie.executable = self.is_executable(file_id)
720
709
            elif kind == 'symlink':
721
710
                ie = InventoryLink(file_id, name, parent_id)
722
 
                ie.symlink_target = self.get_symlink_target(path)
 
711
                ie.symlink_target = self.get_symlink_target(file_id)
723
712
            ie.revision = revision_id
724
713
 
725
 
            if kind == 'file':
726
 
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(path)
727
 
                if ie.text_size is None:
728
 
                    raise BzrError(
729
 
                        'Got a text_size of None for file_id %r' % file_id)
 
714
            if kind in ('directory', 'symlink'):
 
715
                ie.text_size, ie.text_sha1 = None, None
 
716
            else:
 
717
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
 
718
            if (ie.text_size is None) and (kind == 'file'):
 
719
                raise BzrError('Got a text_size of None for file_id %r' % file_id)
730
720
            inv.add(ie)
731
721
 
732
722
        sorted_entries = self.sorted_path_id()
733
723
        for path, file_id in sorted_entries:
734
 
            add_entry(path, file_id)
 
724
            add_entry(file_id)
735
725
 
736
726
        return inv
737
727
 
742
732
    # at that instant
743
733
    inventory = property(_get_inventory)
744
734
 
745
 
    root_inventory = property(_get_inventory)
746
 
 
747
 
    def all_file_ids(self):
748
 
        return {entry.file_id for path, entry in self.inventory.iter_entries()}
749
 
 
750
 
    def all_versioned_paths(self):
751
 
        return {path for path, entry in self.inventory.iter_entries()}
752
 
 
753
 
    def list_files(self, include_root=False, from_dir=None, recursive=True):
754
 
        # The only files returned by this are those from the version
755
 
        inv = self.inventory
756
 
        if from_dir is None:
757
 
            from_dir_id = None
758
 
        else:
759
 
            from_dir_id = inv.path2id(from_dir)
760
 
            if from_dir_id is None:
761
 
                # Directory not versioned
762
 
                return
763
 
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
764
 
        if inv.root is not None and not include_root and from_dir is None:
765
 
            # skip the root for compatibility with the current apis.
766
 
            next(entries)
767
 
        for path, entry in entries:
768
 
            yield path, 'V', entry.kind, entry
 
735
    def __iter__(self):
 
736
        for path, entry in self.inventory.iter_entries():
 
737
            yield entry.file_id
769
738
 
770
739
    def sorted_path_id(self):
771
740
        paths = []
772
 
        for result in viewitems(self._new_id):
 
741
        for result in self._new_id.iteritems():
773
742
            paths.append(result)
774
 
        for id in self.base_tree.all_file_ids():
775
 
            try:
776
 
                path = self.id2path(id, recurse='none')
777
 
            except NoSuchId:
 
743
        for id in self.base_tree:
 
744
            path = self.id2path(id)
 
745
            if path is None:
778
746
                continue
779
747
            paths.append((path, id))
780
748
        paths.sort()
783
751
 
784
752
def patched_file(file_patch, original):
785
753
    """Produce a file-like object with the patched version of a text"""
786
 
    from breezy.patches import iter_patched
787
 
    from breezy.iterablefile import IterableFile
788
 
    if file_patch == b"":
 
754
    from bzrlib.patches import iter_patched
 
755
    from bzrlib.iterablefile import IterableFile
 
756
    if file_patch == "":
789
757
        return IterableFile(())
790
758
    # string.splitlines(True) also splits on '\r', but the iter_patched code
791
759
    # only expects to iterate over '\n' style lines
792
760
    return IterableFile(iter_patched(original,
793
 
                                     BytesIO(file_patch).readlines()))
 
761
                StringIO(file_patch).readlines()))