~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/bundle_data.py

  • Committer: Patch Queue Manager
  • Date: 2013-05-23 10:35:23 UTC
  • mfrom: (6574.1.1 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20130523103523-2wt6jmauja1n1vdt
(jameinel) Merge bzr/2.5 into trunk. (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 by Canonical Ltd
 
1
# Copyright (C) 2005-2010 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
12
12
#
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
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
 
19
21
import base64
20
22
from cStringIO import StringIO
21
23
import os
22
24
import pprint
23
25
 
24
 
import bzrlib.errors
25
 
from bzrlib.errors import (TestamentMismatch, BzrError, 
26
 
                           MalformedHeader, MalformedPatches, NotABundle)
27
 
from bzrlib.inventory import (Inventory, InventoryEntry,
28
 
                              InventoryDirectory, InventoryFile,
29
 
                              InventoryLink)
30
 
from bzrlib.osutils import sha_file, sha_string, pathjoin
 
26
from bzrlib import (
 
27
    osutils,
 
28
    timestamp,
 
29
    )
 
30
from bzrlib.bundle import apply_bundle
 
31
from bzrlib.errors import (
 
32
    TestamentMismatch,
 
33
    BzrError,
 
34
    )
 
35
from bzrlib.inventory import (
 
36
    Inventory,
 
37
    InventoryDirectory,
 
38
    InventoryFile,
 
39
    InventoryLink,
 
40
    )
 
41
from bzrlib.osutils import sha_string, pathjoin
31
42
from bzrlib.revision import Revision, NULL_REVISION
32
43
from bzrlib.testament import StrictTestament
33
44
from bzrlib.trace import mutter, warning
34
 
import bzrlib.transport
35
45
from bzrlib.tree import Tree
36
 
import bzrlib.urlutils
37
46
from bzrlib.xml5 import serializer_v5
38
47
 
39
48
 
72
81
        if self.properties:
73
82
            for property in self.properties:
74
83
                key_end = property.find(': ')
75
 
                assert key_end is not None
76
 
                key = property[:key_end].encode('utf-8')
77
 
                value = property[key_end+2:].encode('utf-8')
 
84
                if key_end == -1:
 
85
                    if not property.endswith(':'):
 
86
                        raise ValueError(property)
 
87
                    key = str(property[:-1])
 
88
                    value = ''
 
89
                else:
 
90
                    key = str(property[:key_end])
 
91
                    value = property[key_end+2:]
78
92
                rev.properties[key] = value
79
93
 
80
94
        return rev
81
95
 
 
96
    @staticmethod
 
97
    def from_revision(revision):
 
98
        revision_info = RevisionInfo(revision.revision_id)
 
99
        date = timestamp.format_highres_date(revision.timestamp,
 
100
                                             revision.timezone)
 
101
        revision_info.date = date
 
102
        revision_info.timezone = revision.timezone
 
103
        revision_info.timestamp = revision.timestamp
 
104
        revision_info.message = revision.message.split('\n')
 
105
        revision_info.properties = [': '.join(p) for p in
 
106
                                    revision.properties.iteritems()]
 
107
        return revision_info
 
108
 
82
109
 
83
110
class BundleInfo(object):
84
111
    """This contains the meta information. Stuff that allows you to
85
112
    recreate the revision or inventory XML.
86
113
    """
87
 
    def __init__(self):
 
114
    def __init__(self, bundle_format=None):
 
115
        self.bundle_format = None
88
116
        self.committer = None
89
117
        self.date = None
90
118
        self.message = None
101
129
        self.timestamp = None
102
130
        self.timezone = None
103
131
 
 
132
        # Have we checked the repository yet?
 
133
        self._validated_revisions_against_repo = False
 
134
 
104
135
    def __str__(self):
105
136
        return pprint.pformat(self.__dict__)
106
137
 
109
140
        split up, based on the assumptions that can be made
110
141
        when information is missing.
111
142
        """
112
 
        from bzrlib.bundle.serializer import unpack_highres_date
 
143
        from bzrlib.timestamp import unpack_highres_date
113
144
        # Put in all of the guessable information.
114
145
        if not self.timestamp and self.date:
115
146
            self.timestamp, self.timezone = unpack_highres_date(self.date)
132
163
    def get_base(self, revision):
133
164
        revision_info = self.get_revision_info(revision.revision_id)
134
165
        if revision_info.base_id is not None:
135
 
            if revision_info.base_id == NULL_REVISION:
136
 
                return None
137
 
            else:
138
 
                return revision_info.base_id
 
166
            return revision_info.base_id
139
167
        if len(revision.parent_ids) == 0:
140
168
            # There is no base listed, and
141
169
            # the lowest revision doesn't have a parent
142
170
            # so this is probably against the empty tree
143
 
            # and thus base truly is None
144
 
            return None
 
171
            # and thus base truly is NULL_REVISION
 
172
            return NULL_REVISION
145
173
        else:
146
174
            return revision.parent_ids[-1]
147
175
 
170
198
    def revision_tree(self, repository, revision_id, base=None):
171
199
        revision = self.get_revision(revision_id)
172
200
        base = self.get_base(revision)
173
 
        assert base != revision_id
174
 
        self._validate_references_from_repository(repository)
 
201
        if base == revision_id:
 
202
            raise AssertionError()
 
203
        if not self._validated_revisions_against_repo:
 
204
            self._validate_references_from_repository(repository)
175
205
        revision_info = self.get_revision_info(revision_id)
176
206
        inventory_revision_id = revision_id
177
 
        bundle_tree = BundleTree(repository.revision_tree(base), 
 
207
        bundle_tree = BundleTree(repository.revision_tree(base),
178
208
                                  inventory_revision_id)
179
209
        self._update_tree(bundle_tree, revision_id)
180
210
 
181
211
        inv = bundle_tree.inventory
182
212
        self._validate_inventory(inv, revision_id)
183
 
        self._validate_revision(inv, revision_id)
 
213
        self._validate_revision(bundle_tree, revision_id)
184
214
 
185
215
        return bundle_tree
186
216
 
213
243
        for rev_info in self.revisions:
214
244
            checked[rev_info.revision_id] = True
215
245
            add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
216
 
                
 
246
 
217
247
        for (rev, rev_info) in zip(self.real_revisions, self.revisions):
218
248
            add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
219
249
 
221
251
        missing = {}
222
252
        for revision_id, sha1 in rev_to_sha.iteritems():
223
253
            if repository.has_revision(revision_id):
224
 
                testament = StrictTestament.from_revision(repository, 
 
254
                testament = StrictTestament.from_revision(repository,
225
255
                                                          revision_id)
226
 
                local_sha1 = testament.as_sha1()
 
256
                local_sha1 = self._testament_sha1_from_revision(repository,
 
257
                                                                revision_id)
227
258
                if sha1 != local_sha1:
228
 
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
 
259
                    raise BzrError('sha1 mismatch. For revision id {%s}'
229
260
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
230
261
                else:
231
262
                    count += 1
232
263
            elif revision_id not in checked:
233
264
                missing[revision_id] = sha1
234
265
 
235
 
        for inv_id, sha1 in inv_to_sha.iteritems():
236
 
            if repository.has_revision(inv_id):
237
 
                # Note: branch.get_inventory_sha1() just returns the value that
238
 
                # is stored in the revision text, and that value may be out
239
 
                # of date. This is bogus, because that means we aren't
240
 
                # validating the actual text, just that we wrote and read the
241
 
                # string. But for now, what the hell.
242
 
                local_sha1 = repository.get_inventory_sha1(inv_id)
243
 
                if sha1 != local_sha1:
244
 
                    raise BzrError('sha1 mismatch. For inventory id {%s}' 
245
 
                                   'local: %s, bundle: %s' % 
246
 
                                   (inv_id, local_sha1, sha1))
247
 
                else:
248
 
                    count += 1
249
 
 
250
266
        if len(missing) > 0:
251
267
            # I don't know if this is an error yet
252
268
            warning('Not all revision hashes could be validated.'
253
269
                    ' Unable validate %d hashes' % len(missing))
254
270
        mutter('Verified %d sha hashes for the bundle.' % count)
 
271
        self._validated_revisions_against_repo = True
255
272
 
256
273
    def _validate_inventory(self, inv, revision_id):
257
274
        """At this point we should have generated the BundleTree,
258
275
        so build up an inventory, and make sure the hashes match.
259
276
        """
260
 
 
261
 
        assert inv is not None
262
 
 
263
277
        # Now we should have a complete inventory entry.
264
278
        s = serializer_v5.write_inventory_to_string(inv)
265
279
        sha1 = sha_string(s)
266
280
        # Target revision is the last entry in the real_revisions list
267
281
        rev = self.get_revision(revision_id)
268
 
        assert rev.revision_id == revision_id
 
282
        if rev.revision_id != revision_id:
 
283
            raise AssertionError()
269
284
        if sha1 != rev.inventory_sha1:
270
 
            open(',,bogus-inv', 'wb').write(s)
 
285
            f = open(',,bogus-inv', 'wb')
 
286
            try:
 
287
                f.write(s)
 
288
            finally:
 
289
                f.close()
271
290
            warning('Inventory sha hash mismatch for revision %s. %s'
272
291
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
273
292
 
274
 
    def _validate_revision(self, inventory, revision_id):
 
293
    def _validate_revision(self, tree, revision_id):
275
294
        """Make sure all revision entries match their checksum."""
276
295
 
277
 
        # This is a mapping from each revision id to it's sha hash
 
296
        # This is a mapping from each revision id to its sha hash
278
297
        rev_to_sha1 = {}
279
 
        
 
298
 
280
299
        rev = self.get_revision(revision_id)
281
300
        rev_info = self.get_revision_info(revision_id)
282
 
        assert rev.revision_id == rev_info.revision_id
283
 
        assert rev.revision_id == revision_id
284
 
        sha1 = StrictTestament(rev, inventory).as_sha1()
 
301
        if not (rev.revision_id == rev_info.revision_id):
 
302
            raise AssertionError()
 
303
        if not (rev.revision_id == revision_id):
 
304
            raise AssertionError()
 
305
        sha1 = self._testament_sha1(rev, tree)
285
306
        if sha1 != rev_info.sha1:
286
307
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
287
 
        if rev_to_sha1.has_key(rev.revision_id):
 
308
        if rev.revision_id in rev_to_sha1:
288
309
            raise BzrError('Revision {%s} given twice in the list'
289
310
                    % (rev.revision_id))
290
311
        rev_to_sha1[rev.revision_id] = sha1
298
319
 
299
320
        def get_rev_id(last_changed, path, kind):
300
321
            if last_changed is not None:
301
 
                changed_revision_id = last_changed.decode('utf-8')
 
322
                # last_changed will be a Unicode string because of how it was
 
323
                # read. Convert it back to utf8.
 
324
                changed_revision_id = osutils.safe_revision_id(last_changed,
 
325
                                                               warn=False)
302
326
            else:
303
327
                changed_revision_id = revision_id
304
328
            bundle_tree.note_last_changed(path, changed_revision_id)
311
335
                try:
312
336
                    name, value = info_item.split(':', 1)
313
337
                except ValueError:
314
 
                    raise 'Value %r has no colon' % info_item
 
338
                    raise ValueError('Value %r has no colon' % info_item)
315
339
                if name == 'last-changed':
316
340
                    last_changed = value
317
341
                elif name == 'executable':
318
 
                    assert value in ('yes', 'no'), value
319
342
                    val = (value == 'yes')
320
343
                    bundle_tree.note_executable(new_path, val)
321
344
                elif name == 'target':
325
348
            return last_changed, encoding
326
349
 
327
350
        def do_patch(path, lines, encoding):
328
 
            if encoding is not None:
329
 
                assert encoding == 'base64'
 
351
            if encoding == 'base64':
330
352
                patch = base64.decodestring(''.join(lines))
331
 
            else:
 
353
            elif encoding is None:
332
354
                patch =  ''.join(lines)
 
355
            else:
 
356
                raise ValueError(encoding)
333
357
            bundle_tree.note_patch(path, patch)
334
358
 
335
359
        def renamed(kind, extra, lines):
371
395
            if not info[1].startswith('file-id:'):
372
396
                raise BzrError('The file-id should follow the path for an add'
373
397
                        ': %r' % extra)
374
 
            file_id = info[1][8:]
 
398
            # This will be Unicode because of how the stream is read. Turn it
 
399
            # back into a utf8 file_id
 
400
            file_id = osutils.safe_file_id(info[1][8:], warn=False)
375
401
 
376
402
            bundle_tree.note_id(file_id, path, kind)
377
403
            # this will be overridden in extra_info if executable is specified.
393
419
            revision = get_rev_id(last_modified, path, kind)
394
420
            if lines:
395
421
                do_patch(path, lines, encoding)
396
 
            
 
422
 
397
423
        valid_actions = {
398
424
            'renamed':renamed,
399
425
            'removed':removed,
422
448
                        ' (unrecognized action): %r' % action_line)
423
449
            valid_actions[action](kind, extra, lines)
424
450
 
 
451
    def install_revisions(self, target_repo, stream_input=True):
 
452
        """Install revisions and return the target revision
 
453
 
 
454
        :param target_repo: The repository to install into
 
455
        :param stream_input: Ignored by this implementation.
 
456
        """
 
457
        apply_bundle.install_bundle(target_repo, self)
 
458
        return self.target
 
459
 
 
460
    def get_merge_request(self, target_repo):
 
461
        """Provide data for performing a merge
 
462
 
 
463
        Returns suggested base, suggested target, and patch verification status
 
464
        """
 
465
        return None, self.target, 'inapplicable'
 
466
 
425
467
 
426
468
class BundleTree(Tree):
 
469
 
427
470
    def __init__(self, base_tree, revision_id):
428
471
        self.base_tree = base_tree
429
472
        self._renamed = {} # Mapping from old_path => new_path
445
488
 
446
489
    def note_rename(self, old_path, new_path):
447
490
        """A file/directory has been renamed from old_path => new_path"""
448
 
        assert not self._renamed.has_key(new_path)
449
 
        assert not self._renamed_r.has_key(old_path)
 
491
        if new_path in self._renamed:
 
492
            raise AssertionError(new_path)
 
493
        if old_path in self._renamed_r:
 
494
            raise AssertionError(old_path)
450
495
        self._renamed[new_path] = old_path
451
496
        self._renamed_r[old_path] = new_path
452
497
 
457
502
        self._kinds[new_id] = kind
458
503
 
459
504
    def note_last_changed(self, file_id, revision_id):
460
 
        if (self._last_changed.has_key(file_id)
 
505
        if (file_id in self._last_changed
461
506
                and self._last_changed[file_id] != revision_id):
462
507
            raise BzrError('Mismatched last-changed revision for file_id {%s}'
463
508
                    ': %s != %s' % (file_id,
482
527
 
483
528
    def old_path(self, new_path):
484
529
        """Get the old_path (path in the base_tree) for the file at new_path"""
485
 
        assert new_path[:1] not in ('\\', '/')
 
530
        if new_path[:1] in ('\\', '/'):
 
531
            raise ValueError(new_path)
486
532
        old_path = self._renamed.get(new_path)
487
533
        if old_path is not None:
488
534
            return old_path
500
546
            old_path = new_path
501
547
        #If the new path wasn't in renamed, the old one shouldn't be in
502
548
        #renamed_r
503
 
        if self._renamed_r.has_key(old_path):
 
549
        if old_path in self._renamed_r:
504
550
            return None
505
 
        return old_path 
 
551
        return old_path
506
552
 
507
553
    def new_path(self, old_path):
508
554
        """Get the new_path (path in the target_tree) for the file at old_path
509
555
        in the base tree.
510
556
        """
511
 
        assert old_path[:1] not in ('\\', '/')
 
557
        if old_path[:1] in ('\\', '/'):
 
558
            raise ValueError(old_path)
512
559
        new_path = self._renamed_r.get(old_path)
513
560
        if new_path is not None:
514
561
            return new_path
515
 
        if self._renamed.has_key(new_path):
 
562
        if new_path in self._renamed:
516
563
            return None
517
564
        dirname,basename = os.path.split(old_path)
518
565
        if dirname != '':
525
572
            new_path = old_path
526
573
        #If the old path wasn't in renamed, the new one shouldn't be in
527
574
        #renamed_r
528
 
        if self._renamed.has_key(new_path):
 
575
        if new_path in self._renamed:
529
576
            return None
530
 
        return new_path 
 
577
        return new_path
 
578
 
 
579
    def get_root_id(self):
 
580
        return self.path2id('')
531
581
 
532
582
    def path2id(self, path):
533
583
        """Return the id of the file present at path in the target tree."""
539
589
            return None
540
590
        if old_path in self.deleted:
541
591
            return None
542
 
        if hasattr(self.base_tree, 'path2id'):
543
 
            return self.base_tree.path2id(old_path)
544
 
        else:
545
 
            return self.base_tree.inventory.path2id(old_path)
 
592
        return self.base_tree.path2id(old_path)
546
593
 
547
594
    def id2path(self, file_id):
548
595
        """Return the new path in the target tree of the file with id file_id"""
567
614
                return None
568
615
        new_path = self.id2path(file_id)
569
616
        return self.base_tree.path2id(new_path)
570
 
        
 
617
 
571
618
    def get_file(self, file_id):
572
619
        """Return a file-like object containing the new contents of the
573
620
        file given by file_id.
577
624
                then be cached.
578
625
        """
579
626
        base_id = self.old_contents_id(file_id)
580
 
        if base_id is not None:
 
627
        if (base_id is not None and
 
628
            base_id != self.base_tree.get_root_id()):
581
629
            patch_original = self.base_tree.get_file(base_id)
582
630
        else:
583
631
            patch_original = None
584
632
        file_patch = self.patches.get(self.id2path(file_id))
585
633
        if file_patch is None:
586
 
            if (patch_original is None and 
587
 
                self.get_kind(file_id) == 'directory'):
 
634
            if (patch_original is None and
 
635
                self.kind(file_id) == 'directory'):
588
636
                return StringIO()
589
 
            assert patch_original is not None, "None: %s" % file_id
 
637
            if patch_original is None:
 
638
                raise AssertionError("None: %s" % file_id)
590
639
            return patch_original
591
640
 
592
 
        assert not file_patch.startswith('\\'), \
593
 
            'Malformed patch for %s, %r' % (file_id, file_patch)
 
641
        if file_patch.startswith('\\'):
 
642
            raise ValueError(
 
643
                'Malformed patch for %s, %r' % (file_id, file_patch))
594
644
        return patched_file(file_patch, patch_original)
595
645
 
596
 
    def get_symlink_target(self, file_id):
597
 
        new_path = self.id2path(file_id)
 
646
    def get_symlink_target(self, file_id, path=None):
 
647
        if path is None:
 
648
            path = self.id2path(file_id)
598
649
        try:
599
 
            return self._targets[new_path]
 
650
            return self._targets[path]
600
651
        except KeyError:
601
652
            return self.base_tree.get_symlink_target(file_id)
602
653
 
603
 
    def get_kind(self, file_id):
 
654
    def kind(self, file_id):
604
655
        if file_id in self._kinds:
605
656
            return self._kinds[file_id]
606
 
        return self.base_tree.inventory[file_id].kind
 
657
        return self.base_tree.kind(file_id)
 
658
 
 
659
    def get_file_revision(self, file_id):
 
660
        path = self.id2path(file_id)
 
661
        if path in self._last_changed:
 
662
            return self._last_changed[path]
 
663
        else:
 
664
            return self.base_tree.get_file_revision(file_id)
607
665
 
608
666
    def is_executable(self, file_id):
609
667
        path = self.id2path(file_id)
610
668
        if path in self._executable:
611
669
            return self._executable[path]
612
670
        else:
613
 
            return self.base_tree.inventory[file_id].executable
 
671
            return self.base_tree.is_executable(file_id)
614
672
 
615
673
    def get_last_changed(self, file_id):
616
674
        path = self.id2path(file_id)
617
675
        if path in self._last_changed:
618
676
            return self._last_changed[path]
619
 
        return self.base_tree.inventory[file_id].revision
 
677
        return self.base_tree.get_file_revision(file_id)
620
678
 
621
679
    def get_size_and_sha1(self, file_id):
622
680
        """Return the size and sha1 hash of the given file id.
629
687
        if new_path not in self.patches:
630
688
            # If the entry does not have a patch, then the
631
689
            # contents must be the same as in the base_tree
632
 
            ie = self.base_tree.inventory[file_id]
633
 
            if ie.text_size is None:
634
 
                return ie.text_size, ie.text_sha1
635
 
            return int(ie.text_size), ie.text_sha1
 
690
            text_size = self.base_tree.get_file_size(file_id)
 
691
            text_sha1 = self.base_tree.get_file_sha1(file_id)
 
692
            return text_size, text_sha1
636
693
        fileobj = self.get_file(file_id)
637
694
        content = fileobj.read()
638
695
        return len(content), sha_string(content)
643
700
        This need to be called before ever accessing self.inventory
644
701
        """
645
702
        from os.path import dirname, basename
646
 
 
647
 
        assert self.base_tree is not None
648
 
        base_inv = self.base_tree.inventory
649
 
        root_id = base_inv.root.file_id
650
 
        try:
651
 
            # New inventories have a unique root_id
652
 
            inv = Inventory(root_id, self.revision_id)
653
 
        except TypeError:
654
 
            inv = Inventory(revision_id=self.revision_id)
 
703
        inv = Inventory(None, self.revision_id)
655
704
 
656
705
        def add_entry(file_id):
657
706
            path = self.id2path(file_id)
658
707
            if path is None:
659
708
                return
660
 
            parent_path = dirname(path)
661
 
            if parent_path == u'':
662
 
                parent_id = root_id
 
709
            if path == '':
 
710
                parent_id = None
663
711
            else:
 
712
                parent_path = dirname(path)
664
713
                parent_id = self.path2id(parent_path)
665
714
 
666
 
            kind = self.get_kind(file_id)
 
715
            kind = self.kind(file_id)
667
716
            revision_id = self.get_last_changed(file_id)
668
717
 
669
718
            name = basename(path)
674
723
                ie.executable = self.is_executable(file_id)
675
724
            elif kind == 'symlink':
676
725
                ie = InventoryLink(file_id, name, parent_id)
677
 
                ie.symlink_target = self.get_symlink_target(file_id)
 
726
                ie.symlink_target = self.get_symlink_target(file_id, path)
678
727
            ie.revision = revision_id
679
728
 
680
 
            if kind in ('directory', 'symlink'):
681
 
                ie.text_size, ie.text_sha1 = None, None
682
 
            else:
 
729
            if kind == 'file':
683
730
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
684
 
            if (ie.text_size is None) and (kind == 'file'):
685
 
                raise BzrError('Got a text_size of None for file_id %r' % file_id)
 
731
                if ie.text_size is None:
 
732
                    raise BzrError(
 
733
                        'Got a text_size of None for file_id %r' % file_id)
686
734
            inv.add(ie)
687
735
 
688
736
        sorted_entries = self.sorted_path_id()
689
737
        for path, file_id in sorted_entries:
690
 
            if file_id == inv.root.file_id:
691
 
                continue
692
738
            add_entry(file_id)
693
739
 
694
740
        return inv
700
746
    # at that instant
701
747
    inventory = property(_get_inventory)
702
748
 
703
 
    def __iter__(self):
704
 
        for path, entry in self.inventory.iter_entries():
705
 
            yield entry.file_id
 
749
    root_inventory = property(_get_inventory)
 
750
 
 
751
    def all_file_ids(self):
 
752
        return set(
 
753
            [entry.file_id for path, entry in self.inventory.iter_entries()])
 
754
 
 
755
    def list_files(self, include_root=False, from_dir=None, recursive=True):
 
756
        # The only files returned by this are those from the version
 
757
        inv = self.inventory
 
758
        if from_dir is None:
 
759
            from_dir_id = None
 
760
        else:
 
761
            from_dir_id = inv.path2id(from_dir)
 
762
            if from_dir_id is None:
 
763
                # Directory not versioned
 
764
                return
 
765
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
 
766
        if inv.root is not None and not include_root and from_dir is None:
 
767
            # skip the root for compatability with the current apis.
 
768
            entries.next()
 
769
        for path, entry in entries:
 
770
            yield path, 'V', entry.kind, entry.file_id, entry
706
771
 
707
772
    def sorted_path_id(self):
708
773
        paths = []
709
774
        for result in self._new_id.iteritems():
710
775
            paths.append(result)
711
 
        for id in self.base_tree:
 
776
        for id in self.base_tree.all_file_ids():
712
777
            path = self.id2path(id)
713
778
            if path is None:
714
779
                continue