~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: John Arbash Meinel
  • Date: 2008-05-28 23:20:33 UTC
  • mto: This revision was merged to the branch mainline in revision 3458.
  • Revision ID: john@arbash-meinel.com-20080528232033-cx3l3yg845udklps
Bring back always in the form of 'override'.
Change the functions so that they are compatible with the released
definition, and just allow callers to supply override=False
if they want the new behavior.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 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
27
27
# created, but it's not for now.
28
28
ROOT_ID = "TREE_ROOT"
29
29
 
30
 
 
31
 
import collections
32
 
import os.path
 
30
import os
33
31
import re
34
32
import sys
 
33
 
 
34
from bzrlib.lazy_import import lazy_import
 
35
lazy_import(globals(), """
 
36
import collections
35
37
import tarfile
36
 
import types
37
38
 
38
39
import bzrlib
39
 
from bzrlib.osutils import (pumpfile, quotefn, splitpath, joinpath,
40
 
                            pathjoin, sha_strings)
41
 
from bzrlib.errors import (NotVersionedError, InvalidEntryName,
42
 
                           BzrError, BzrCheckError, BinaryFile)
 
40
from bzrlib import (
 
41
    errors,
 
42
    generate_ids,
 
43
    osutils,
 
44
    symbol_versioning,
 
45
    workingtree,
 
46
    )
 
47
""")
 
48
 
 
49
from bzrlib.errors import (
 
50
    BzrCheckError,
 
51
    BzrError,
 
52
    )
 
53
from bzrlib.symbol_versioning import deprecated_method
43
54
from bzrlib.trace import mutter
44
55
 
45
56
 
77
88
    >>> i.path2id('')
78
89
    'TREE_ROOT'
79
90
    >>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
80
 
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
 
91
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
81
92
    >>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
82
 
    InventoryFile('2323', 'hello.c', parent_id='123')
83
 
    >>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
 
93
    InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
 
94
    >>> shouldbe = {0: '', 1: 'src', 2: 'src/hello.c'}
84
95
    >>> for ix, j in enumerate(i.iter_entries()):
85
96
    ...   print (j[0] == shouldbe[ix], j[1])
86
97
    ... 
87
 
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
88
 
    (True, InventoryFile('2323', 'hello.c', parent_id='123'))
89
 
    >>> i.add(InventoryFile('2323', 'bye.c', '123'))
90
 
    Traceback (most recent call last):
91
 
    ...
92
 
    BzrError: inventory already contains entry with id {2323}
 
98
    (True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
 
99
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
 
100
    (True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
93
101
    >>> i.add(InventoryFile('2324', 'bye.c', '123'))
94
 
    InventoryFile('2324', 'bye.c', parent_id='123')
 
102
    InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
95
103
    >>> i.add(InventoryDirectory('2325', 'wibble', '123'))
96
 
    InventoryDirectory('2325', 'wibble', parent_id='123')
 
104
    InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
97
105
    >>> i.path2id('src/wibble')
98
106
    '2325'
99
107
    >>> '2325' in i
100
108
    True
101
109
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
102
 
    InventoryFile('2326', 'wibble.c', parent_id='2325')
 
110
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
103
111
    >>> i['2326']
104
 
    InventoryFile('2326', 'wibble.c', parent_id='2325')
 
112
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
105
113
    >>> for path, entry in i.iter_entries():
106
114
    ...     print path
107
 
    ...     assert i.path2id(path)
108
115
    ... 
 
116
    <BLANKLINE>
109
117
    src
110
118
    src/bye.c
111
119
    src/hello.c
133
141
        """
134
142
        return False, False
135
143
 
136
 
    def diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
137
 
             output_to, reverse=False):
138
 
        """Perform a diff from this to to_entry.
139
 
 
140
 
        text_diff will be used for textual difference calculation.
141
 
        This is a template method, override _diff in child classes.
142
 
        """
143
 
        self._read_tree_state(tree.id2path(self.file_id), tree)
144
 
        if to_entry:
145
 
            # cannot diff from one kind to another - you must do a removal
146
 
            # and an addif they do not match.
147
 
            assert self.kind == to_entry.kind
148
 
            to_entry._read_tree_state(to_tree.id2path(to_entry.file_id),
149
 
                                      to_tree)
150
 
        self._diff(text_diff, from_label, tree, to_label, to_entry, to_tree,
151
 
                   output_to, reverse)
152
 
 
153
144
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
154
145
             output_to, reverse=False):
155
146
        """Perform a diff between two entries of the same kind."""
156
 
 
157
 
    def find_previous_heads(self, previous_inventories,
158
 
                            versioned_file_store,
159
 
                            transaction,
160
 
                            entry_vf=None):
161
 
        """Return the revisions and entries that directly precede this.
162
 
 
163
 
        Returned as a map from revision to inventory entry.
164
 
 
165
 
        This is a map containing the file revisions in all parents
166
 
        for which the file exists, and its revision is not a parent of
167
 
        any other. If the file is new, the set will be empty.
168
 
 
169
 
        :param versioned_file_store: A store where ancestry data on this
170
 
                                     file id can be queried.
171
 
        :param transaction: The transaction that queries to the versioned 
172
 
                            file store should be completed under.
173
 
        :param entry_vf: The entry versioned file, if its already available.
 
147
    
 
148
    def parent_candidates(self, previous_inventories):
 
149
        """Find possible per-file graph parents.
 
150
 
 
151
        This is currently defined by:
 
152
         - Select the last changed revision in the parent inventory.
 
153
         - Do deal with a short lived bug in bzr 0.8's development two entries
 
154
           that have the same last changed but different 'x' bit settings are
 
155
           changed in-place.
174
156
        """
175
 
        def get_ancestors(weave, entry):
176
 
            return set(weave.get_ancestry(entry.revision))
177
157
        # revision:ie mapping for each ie found in previous_inventories.
178
158
        candidates = {}
179
 
        # revision:ie mapping with one revision for each head.
180
 
        heads = {}
181
 
        # revision: ancestor list for each head
182
 
        head_ancestors = {}
183
159
        # identify candidate head revision ids.
184
160
        for inv in previous_inventories:
185
161
            if self.file_id in inv:
186
162
                ie = inv[self.file_id]
187
 
                assert ie.file_id == self.file_id
188
163
                if ie.revision in candidates:
189
164
                    # same revision value in two different inventories:
190
165
                    # correct possible inconsistencies:
196
171
                            ie.executable = False
197
172
                    except AttributeError:
198
173
                        pass
199
 
                    # must now be the same.
200
 
                    assert candidates[ie.revision] == ie
201
174
                else:
202
175
                    # add this revision as a candidate.
203
176
                    candidates[ie.revision] = ie
204
 
 
205
 
        # common case optimisation
206
 
        if len(candidates) == 1:
207
 
            # if there is only one candidate revision found
208
 
            # then we can opening the versioned file to access ancestry:
209
 
            # there cannot be any ancestors to eliminate when there is 
210
 
            # only one revision available.
211
 
            heads[ie.revision] = ie
212
 
            return heads
213
 
 
214
 
        # eliminate ancestors amongst the available candidates:
215
 
        # heads are those that are not an ancestor of any other candidate
216
 
        # - this provides convergence at a per-file level.
217
 
        for ie in candidates.values():
218
 
            # may be an ancestor of a known head:
219
 
            already_present = 0 != len(
220
 
                [head for head in heads 
221
 
                 if ie.revision in head_ancestors[head]])
222
 
            if already_present:
223
 
                # an ancestor of an analyzed candidate.
224
 
                continue
225
 
            # not an ancestor of a known head:
226
 
            # load the versioned file for this file id if needed
227
 
            if entry_vf is None:
228
 
                entry_vf = versioned_file_store.get_weave_or_empty(
229
 
                    self.file_id, transaction)
230
 
            ancestors = get_ancestors(entry_vf, ie)
231
 
            # may knock something else out:
232
 
            check_heads = list(heads.keys())
233
 
            for head in check_heads:
234
 
                if head in ancestors:
235
 
                    # this previously discovered 'head' is not
236
 
                    # really a head - its an ancestor of the newly 
237
 
                    # found head,
238
 
                    heads.pop(head)
239
 
            head_ancestors[ie.revision] = ancestors
240
 
            heads[ie.revision] = ie
241
 
        return heads
 
177
        return candidates
242
178
 
243
179
    def get_tar_item(self, root, dp, now, tree):
244
180
        """Get a tarfile item and a file stream for its content."""
245
 
        item = tarfile.TarInfo(pathjoin(root, dp))
 
181
        item = tarfile.TarInfo(osutils.pathjoin(root, dp).encode('utf8'))
246
182
        # TODO: would be cool to actually set it to the timestamp of the
247
183
        # revision it was last changed
248
184
        item.mtime = now
275
211
        Traceback (most recent call last):
276
212
        InvalidEntryName: Invalid entry name: src/hello.c
277
213
        """
278
 
        assert isinstance(name, basestring), name
279
214
        if '/' in name or '\\' in name:
280
 
            raise InvalidEntryName(name=name)
 
215
            raise errors.InvalidEntryName(name=name)
281
216
        self.executable = False
282
217
        self.revision = None
283
218
        self.text_sha1 = None
287
222
        self.text_id = text_id
288
223
        self.parent_id = parent_id
289
224
        self.symlink_target = None
 
225
        self.reference_revision = None
290
226
 
291
227
    def kind_character(self):
292
228
        """Return a short kind indicator useful for appending to names."""
293
229
        raise BzrError('unknown kind %r' % self.kind)
294
230
 
295
 
    known_kinds = ('file', 'directory', 'symlink', 'root_directory')
 
231
    known_kinds = ('file', 'directory', 'symlink')
296
232
 
297
233
    def _put_in_tar(self, item, tree):
298
234
        """populate item for stashing in a tar, and return the content stream.
307
243
        
308
244
        This is a template method - implement _put_on_disk in subclasses.
309
245
        """
310
 
        fullpath = pathjoin(dest, dp)
 
246
        fullpath = osutils.pathjoin(dest, dp)
311
247
        self._put_on_disk(fullpath, tree)
312
 
        mutter("  export {%s} kind %s to %s", self.file_id,
313
 
                self.kind, fullpath)
 
248
        # mutter("  export {%s} kind %s to %s", self.file_id,
 
249
        #         self.kind, fullpath)
314
250
 
315
251
    def _put_on_disk(self, fullpath, tree):
316
252
        """Put this entry onto disk at fullpath, from tree tree."""
321
257
 
322
258
    @staticmethod
323
259
    def versionable_kind(kind):
324
 
        return kind in ('file', 'directory', 'symlink')
 
260
        return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
325
261
 
326
262
    def check(self, checker, rev_id, inv, tree):
327
263
        """Check this inventory entry is intact.
372
308
            return 'added'
373
309
        elif new_entry is None:
374
310
            return 'removed'
 
311
        if old_entry.kind != new_entry.kind:
 
312
            return 'modified'
375
313
        text_modified, meta_modified = new_entry.detect_changes(old_entry)
376
314
        if text_modified or meta_modified:
377
315
            modified = True
393
331
        return 'unchanged'
394
332
 
395
333
    def __repr__(self):
396
 
        return ("%s(%r, %r, parent_id=%r)"
 
334
        return ("%s(%r, %r, parent_id=%r, revision=%r)"
397
335
                % (self.__class__.__name__,
398
336
                   self.file_id,
399
337
                   self.name,
400
 
                   self.parent_id))
401
 
 
402
 
    def snapshot(self, revision, path, previous_entries,
403
 
                 work_tree, commit_builder):
404
 
        """Make a snapshot of this entry which may or may not have changed.
405
 
        
406
 
        This means that all its fields are populated, that it has its
407
 
        text stored in the text store or weave.
408
 
        """
409
 
        mutter('new parents of %s are %r', path, previous_entries)
410
 
        self._read_tree_state(path, work_tree)
411
 
        # TODO: Where should we determine whether to reuse a
412
 
        # previous revision id or create a new revision? 20060606
413
 
        if len(previous_entries) == 1:
414
 
            # cannot be unchanged unless there is only one parent file rev.
415
 
            parent_ie = previous_entries.values()[0]
416
 
            if self._unchanged(parent_ie):
417
 
                mutter("found unchanged entry")
418
 
                self.revision = parent_ie.revision
419
 
                return "unchanged"
420
 
        return self._snapshot_into_revision(revision, previous_entries, 
421
 
                                            work_tree, commit_builder)
422
 
 
423
 
    def _snapshot_into_revision(self, revision, previous_entries, work_tree,
424
 
                                commit_builder):
425
 
        """Record this revision unconditionally into a store.
426
 
 
427
 
        The entry's last-changed revision property (`revision`) is updated to 
428
 
        that of the new revision.
429
 
        
430
 
        :param revision: id of the new revision that is being recorded.
431
 
 
432
 
        :returns: String description of the commit (e.g. "merged", "modified"), etc.
433
 
        """
434
 
        mutter('new revision {%s} for {%s}', revision, self.file_id)
435
 
        self.revision = revision
436
 
        self._snapshot_text(previous_entries, work_tree, commit_builder)
437
 
 
438
 
    def _snapshot_text(self, file_parents, work_tree, commit_builder): 
439
 
        """Record the 'text' of this entry, whatever form that takes.
440
 
        
441
 
        This default implementation simply adds an empty text.
442
 
        """
443
 
        raise NotImplementedError(self._snapshot_text)
 
338
                   self.parent_id,
 
339
                   self.revision))
444
340
 
445
341
    def __eq__(self, other):
446
342
        if not isinstance(other, InventoryEntry):
456
352
                and (self.kind == other.kind)
457
353
                and (self.revision == other.revision)
458
354
                and (self.executable == other.executable)
 
355
                and (self.reference_revision == other.reference_revision)
459
356
                )
460
357
 
461
358
    def __ne__(self, other):
476
373
        # renamed
477
374
        elif previous_ie.name != self.name:
478
375
            compatible = False
 
376
        elif previous_ie.kind != self.kind:
 
377
            compatible = False
479
378
        return compatible
480
379
 
481
380
    def _read_tree_state(self, path, work_tree):
496
395
class RootEntry(InventoryEntry):
497
396
 
498
397
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
499
 
                 'text_id', 'parent_id', 'children', 'executable', 
500
 
                 'revision', 'symlink_target']
 
398
                 'text_id', 'parent_id', 'children', 'executable',
 
399
                 'revision', 'symlink_target', 'reference_revision']
501
400
 
502
401
    def _check(self, checker, rev_id, tree):
503
402
        """See InventoryEntry._check"""
505
404
    def __init__(self, file_id):
506
405
        self.file_id = file_id
507
406
        self.children = {}
508
 
        self.kind = 'root_directory'
 
407
        self.kind = 'directory'
509
408
        self.parent_id = None
510
409
        self.name = u''
 
410
        self.revision = None
 
411
        symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
 
412
                               '  Please use InventoryDirectory instead.',
 
413
                               DeprecationWarning, stacklevel=2)
511
414
 
512
415
    def __eq__(self, other):
513
416
        if not isinstance(other, RootEntry):
521
424
    """A directory in an inventory."""
522
425
 
523
426
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
524
 
                 'text_id', 'parent_id', 'children', 'executable', 
525
 
                 'revision', 'symlink_target']
 
427
                 'text_id', 'parent_id', 'children', 'executable',
 
428
                 'revision', 'symlink_target', 'reference_revision']
526
429
 
527
430
    def _check(self, checker, rev_id, tree):
528
431
        """See InventoryEntry._check"""
559
462
        """See InventoryEntry._put_on_disk."""
560
463
        os.mkdir(fullpath)
561
464
 
562
 
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
563
 
        """See InventoryEntry._snapshot_text."""
564
 
        commit_builder.modified_directory(self.file_id, file_parents)
565
 
 
566
465
 
567
466
class InventoryFile(InventoryEntry):
568
467
    """A file in an inventory."""
569
468
 
570
469
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
571
 
                 'text_id', 'parent_id', 'children', 'executable', 
572
 
                 'revision', 'symlink_target']
 
470
                 'text_id', 'parent_id', 'children', 'executable',
 
471
                 'revision', 'symlink_target', 'reference_revision']
573
472
 
574
473
    def _check(self, checker, tree_revision_id, tree):
575
474
        """See InventoryEntry._check"""
577
476
        if t in checker.checked_texts:
578
477
            prev_sha = checker.checked_texts[t]
579
478
            if prev_sha != self.text_sha1:
580
 
                raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
581
 
                                    (self.file_id, tree_revision_id))
 
479
                raise BzrCheckError(
 
480
                    'mismatched sha1 on {%s} in {%s} (%s != %s) %r' %
 
481
                    (self.file_id, tree_revision_id, prev_sha, self.text_sha1,
 
482
                     t))
582
483
            else:
583
484
                checker.repeated_text_cnt += 1
584
485
                return
585
486
 
586
487
        if self.file_id not in checker.checked_weaves:
587
488
            mutter('check weave {%s}', self.file_id)
588
 
            w = tree.get_weave(self.file_id)
 
489
            w = tree._get_weave(self.file_id)
589
490
            # Not passing a progress bar, because it creates a new
590
491
            # progress, which overwrites the current progress,
591
492
            # and doesn't look nice
592
493
            w.check()
593
494
            checker.checked_weaves[self.file_id] = True
594
495
        else:
595
 
            w = tree.get_weave(self.file_id)
 
496
            w = tree._get_weave(self.file_id)
596
497
 
597
498
        mutter('check version {%s} of {%s}', tree_revision_id, self.file_id)
598
499
        checker.checked_text_cnt += 1
599
500
        # We can't check the length, because Weave doesn't store that
600
501
        # information, and the whole point of looking at the weave's
601
502
        # sha1sum is that we don't have to extract the text.
602
 
        if self.text_sha1 != w.get_sha1(self.revision):
 
503
        if self.text_sha1 != w.get_sha1s([self.revision])[0]:
603
504
            raise BzrCheckError('text {%s} version {%s} wrong sha1' 
604
505
                                % (self.file_id, self.revision))
605
506
        checker.checked_texts[t] = self.text_sha1
615
516
 
616
517
    def detect_changes(self, old_entry):
617
518
        """See InventoryEntry.detect_changes."""
618
 
        assert self.text_sha1 is not None
619
 
        assert old_entry.text_sha1 is not None
620
519
        text_modified = (self.text_sha1 != old_entry.text_sha1)
621
520
        meta_modified = (self.executable != old_entry.executable)
622
521
        return text_modified, meta_modified
624
523
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
625
524
             output_to, reverse=False):
626
525
        """See InventoryEntry._diff."""
627
 
        try:
628
 
            from_text = tree.get_file(self.file_id).readlines()
629
 
            if to_entry:
630
 
                to_text = to_tree.get_file(to_entry.file_id).readlines()
631
 
            else:
632
 
                to_text = []
633
 
            if not reverse:
634
 
                text_diff(from_label, from_text,
635
 
                          to_label, to_text, output_to)
636
 
            else:
637
 
                text_diff(to_label, to_text,
638
 
                          from_label, from_text, output_to)
639
 
        except BinaryFile:
640
 
            if reverse:
641
 
                label_pair = (to_label, from_label)
642
 
            else:
643
 
                label_pair = (from_label, to_label)
644
 
            print >> output_to, "Binary files %s and %s differ" % label_pair
 
526
        from bzrlib.diff import DiffText
 
527
        from_file_id = self.file_id
 
528
        if to_entry:
 
529
            to_file_id = to_entry.file_id
 
530
        else:
 
531
            to_file_id = None
 
532
        if reverse:
 
533
            to_file_id, from_file_id = from_file_id, to_file_id
 
534
            tree, to_tree = to_tree, tree
 
535
            from_label, to_label = to_label, from_label
 
536
        differ = DiffText(tree, to_tree, output_to, 'utf-8', '', '',
 
537
                          text_diff)
 
538
        return differ.diff_text(from_file_id, to_file_id, from_label, to_label)
645
539
 
646
540
    def has_text(self):
647
541
        """See InventoryEntry.has_text."""
668
562
 
669
563
    def _put_on_disk(self, fullpath, tree):
670
564
        """See InventoryEntry._put_on_disk."""
671
 
        pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
 
565
        osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
672
566
        if tree.is_executable(self.file_id):
673
567
            os.chmod(fullpath, 0755)
674
568
 
679
573
        # in _read_tree_state
680
574
        self.executable = work_tree.is_executable(self.file_id, path=path)
681
575
 
 
576
    def __repr__(self):
 
577
        return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s)"
 
578
                % (self.__class__.__name__,
 
579
                   self.file_id,
 
580
                   self.name,
 
581
                   self.parent_id,
 
582
                   self.text_sha1,
 
583
                   self.text_size))
 
584
 
682
585
    def _forget_tree_state(self):
683
586
        self.text_sha1 = None
684
 
        self.executable = None
685
 
 
686
 
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
687
 
        """See InventoryEntry._snapshot_text."""
688
 
        def get_content_byte_lines():
689
 
            return work_tree.get_file(self.file_id).readlines()
690
 
        self.text_sha1, self.text_size = commit_builder.modified_file_text(
691
 
            self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
692
587
 
693
588
    def _unchanged(self, previous_ie):
694
589
        """See InventoryEntry._unchanged."""
708
603
    """A file in an inventory."""
709
604
 
710
605
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
711
 
                 'text_id', 'parent_id', 'children', 'executable', 
712
 
                 'revision', 'symlink_target']
 
606
                 'text_id', 'parent_id', 'children', 'executable',
 
607
                 'revision', 'symlink_target', 'reference_revision']
713
608
 
714
609
    def _check(self, checker, rev_id, tree):
715
610
        """See InventoryEntry._check"""
738
633
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
739
634
             output_to, reverse=False):
740
635
        """See InventoryEntry._diff."""
741
 
        from_text = self.symlink_target
 
636
        from bzrlib.diff import DiffSymlink
 
637
        old_target = self.symlink_target
742
638
        if to_entry is not None:
743
 
            to_text = to_entry.symlink_target
744
 
            if reverse:
745
 
                temp = from_text
746
 
                from_text = to_text
747
 
                to_text = temp
748
 
            print >>output_to, '=== target changed %r => %r' % (from_text, to_text)
749
 
        else:
750
 
            if not reverse:
751
 
                print >>output_to, '=== target was %r' % self.symlink_target
752
 
            else:
753
 
                print >>output_to, '=== target is %r' % self.symlink_target
 
639
            new_target = to_entry.symlink_target
 
640
        else:
 
641
            new_target = None
 
642
        if not reverse:
 
643
            old_tree = tree
 
644
            new_tree = to_tree
 
645
        else:
 
646
            old_tree = to_tree
 
647
            new_tree = tree
 
648
            new_target, old_target = old_target, new_target
 
649
        differ = DiffSymlink(old_tree, new_tree, output_to)
 
650
        return differ.diff_symlink(old_target, new_target)
754
651
 
755
652
    def __init__(self, file_id, name, parent_id):
756
653
        super(InventoryLink, self).__init__(file_id, name, parent_id)
790
687
            compatible = False
791
688
        return compatible
792
689
 
793
 
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
794
 
        """See InventoryEntry._snapshot_text."""
795
 
        commit_builder.modified_link(
796
 
            self.file_id, file_parents, self.symlink_target)
 
690
 
 
691
class TreeReference(InventoryEntry):
 
692
    
 
693
    kind = 'tree-reference'
 
694
    
 
695
    def __init__(self, file_id, name, parent_id, revision=None,
 
696
                 reference_revision=None):
 
697
        InventoryEntry.__init__(self, file_id, name, parent_id)
 
698
        self.revision = revision
 
699
        self.reference_revision = reference_revision
 
700
 
 
701
    def copy(self):
 
702
        return TreeReference(self.file_id, self.name, self.parent_id,
 
703
                             self.revision, self.reference_revision)
 
704
 
 
705
    def _read_tree_state(self, path, work_tree):
 
706
        """Populate fields in the inventory entry from the given tree.
 
707
        """
 
708
        self.reference_revision = work_tree.get_reference_revision(
 
709
            self.file_id, path)
 
710
 
 
711
    def _forget_tree_state(self):
 
712
        self.reference_revision = None 
 
713
 
 
714
    def _unchanged(self, previous_ie):
 
715
        """See InventoryEntry._unchanged."""
 
716
        compatible = super(TreeReference, self)._unchanged(previous_ie)
 
717
        if self.reference_revision != previous_ie.reference_revision:
 
718
            compatible = False
 
719
        return compatible
797
720
 
798
721
 
799
722
class Inventory(object):
815
738
 
816
739
    >>> inv = Inventory()
817
740
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
818
 
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT')
 
741
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
819
742
    >>> inv['123-123'].name
820
743
    'hello.c'
821
744
 
829
752
    May also look up by name:
830
753
 
831
754
    >>> [x[0] for x in inv.iter_entries()]
832
 
    [u'hello.c']
 
755
    ['', u'hello.c']
833
756
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
834
757
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
835
 
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678')
 
758
    Traceback (most recent call last):
 
759
    BzrError: parent_id {TREE_ROOT} not in inventory
 
760
    >>> inv.add(InventoryFile('123-123', 'hello.c', 'TREE_ROOT-12345678-12345678'))
 
761
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
836
762
    """
837
763
    def __init__(self, root_id=ROOT_ID, revision_id=None):
838
764
        """Create or read an inventory.
844
770
        The inventory is created with a default root directory, with
845
771
        an id of None.
846
772
        """
847
 
        # We are letting Branch.create() create a unique inventory
848
 
        # root id. Rather than generating a random one here.
849
 
        #if root_id is None:
850
 
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
851
 
        self.root = RootEntry(root_id)
 
773
        if root_id is not None:
 
774
            self._set_root(InventoryDirectory(root_id, u'', None))
 
775
        else:
 
776
            self.root = None
 
777
            self._byid = {}
852
778
        self.revision_id = revision_id
 
779
 
 
780
    def __repr__(self):
 
781
        return "<Inventory object at %x, contents=%r>" % (id(self), self._byid)
 
782
 
 
783
    def apply_delta(self, delta):
 
784
        """Apply a delta to this inventory.
 
785
 
 
786
        :param delta: A list of changes to apply. After all the changes are
 
787
            applied the final inventory must be internally consistent, but it
 
788
            is ok to supply changes which, if only half-applied would have an
 
789
            invalid result - such as supplying two changes which rename two
 
790
            files, 'A' and 'B' with each other : [('A', 'B', 'A-id', a_entry),
 
791
            ('B', 'A', 'B-id', b_entry)].
 
792
 
 
793
            Each change is a tuple, of the form (old_path, new_path, file_id,
 
794
            new_entry).
 
795
            
 
796
            When new_path is None, the change indicates the removal of an entry
 
797
            from the inventory and new_entry will be ignored (using None is
 
798
            appropriate). If new_path is not None, then new_entry must be an
 
799
            InventoryEntry instance, which will be incorporated into the
 
800
            inventory (and replace any existing entry with the same file id).
 
801
            
 
802
            When old_path is None, the change indicates the addition of
 
803
            a new entry to the inventory.
 
804
            
 
805
            When neither new_path nor old_path are None, the change is a
 
806
            modification to an entry, such as a rename, reparent, kind change
 
807
            etc. 
 
808
 
 
809
            The children attribute of new_entry is ignored. This is because
 
810
            this method preserves children automatically across alterations to
 
811
            the parent of the children, and cases where the parent id of a
 
812
            child is changing require the child to be passed in as a separate
 
813
            change regardless. E.g. in the recursive deletion of a directory -
 
814
            the directory's children must be included in the delta, or the
 
815
            final inventory will be invalid.
 
816
        """
 
817
        children = {}
 
818
        # Remove all affected items which were in the original inventory,
 
819
        # starting with the longest paths, thus ensuring parents are examined
 
820
        # after their children, which means that everything we examine has no
 
821
        # modified children remaining by the time we examine it.
 
822
        for old_path, file_id in sorted(((op, f) for op, np, f, e in delta
 
823
                                        if op is not None), reverse=True):
 
824
            if file_id not in self:
 
825
                # adds come later
 
826
                continue
 
827
            # Preserve unaltered children of file_id for later reinsertion.
 
828
            children[file_id] = getattr(self[file_id], 'children', {})
 
829
            # Remove file_id and the unaltered children. If file_id is not
 
830
            # being deleted it will be reinserted back later.
 
831
            self.remove_recursive_id(file_id)
 
832
        # Insert all affected which should be in the new inventory, reattaching
 
833
        # their children if they had any. This is done from shortest path to
 
834
        # longest, ensuring that items which were modified and whose parents in
 
835
        # the resulting inventory were also modified, are inserted after their
 
836
        # parents.
 
837
        for new_path, new_entry in sorted((np, e) for op, np, f, e in
 
838
                                          delta if np is not None):
 
839
            if new_entry.kind == 'directory':
 
840
                new_entry.children = children.get(new_entry.file_id, {})
 
841
            self.add(new_entry)
 
842
 
 
843
    def _set_root(self, ie):
 
844
        self.root = ie
853
845
        self._byid = {self.root.file_id: self.root}
854
846
 
855
847
    def copy(self):
856
848
        # TODO: jam 20051218 Should copy also copy the revision_id?
857
 
        other = Inventory(self.root.file_id)
 
849
        entries = self.iter_entries()
 
850
        if self.root is None:
 
851
            return Inventory(root_id=None)
 
852
        other = Inventory(entries.next()[1].file_id)
858
853
        # copy recursively so we know directories will be added before
859
854
        # their children.  There are more efficient ways than this...
860
 
        for path, entry in self.iter_entries():
861
 
            if entry == self.root:
862
 
                continue
 
855
        for path, entry in entries:
863
856
            other.add(entry.copy())
864
857
        return other
865
858
 
873
866
    def iter_entries(self, from_dir=None):
874
867
        """Return (path, entry) pairs, in order by name."""
875
868
        if from_dir is None:
876
 
            assert self.root
 
869
            if self.root is None:
 
870
                return
877
871
            from_dir = self.root
 
872
            yield '', self.root
878
873
        elif isinstance(from_dir, basestring):
879
874
            from_dir = self._byid[from_dir]
880
875
            
912
907
                # if we finished all children, pop it off the stack
913
908
                stack.pop()
914
909
 
915
 
    def iter_entries_by_dir(self, from_dir=None):
 
910
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
 
911
        yield_parents=False):
916
912
        """Iterate over the entries in a directory first order.
917
913
 
918
914
        This returns all entries for a directory before returning
920
916
        lexicographically sorted order, and is a hybrid between
921
917
        depth-first and breadth-first.
922
918
 
 
919
        :param yield_parents: If True, yield the parents from the root leading
 
920
            down to specific_file_ids that have been requested. This has no
 
921
            impact if specific_file_ids is None.
923
922
        :return: This yields (path, entry) pairs
924
923
        """
 
924
        if specific_file_ids and not isinstance(specific_file_ids, set):
 
925
            specific_file_ids = set(specific_file_ids)
925
926
        # TODO? Perhaps this should return the from_dir so that the root is
926
927
        # yielded? or maybe an option?
927
928
        if from_dir is None:
928
 
            assert self.root
 
929
            if self.root is None:
 
930
                return
 
931
            # Optimize a common case
 
932
            if (not yield_parents and specific_file_ids is not None and
 
933
                len(specific_file_ids) == 1):
 
934
                file_id = list(specific_file_ids)[0]
 
935
                if file_id in self:
 
936
                    yield self.id2path(file_id), self[file_id]
 
937
                return 
929
938
            from_dir = self.root
 
939
            if (specific_file_ids is None or yield_parents or
 
940
                self.root.file_id in specific_file_ids):
 
941
                yield u'', self.root
930
942
        elif isinstance(from_dir, basestring):
931
943
            from_dir = self._byid[from_dir]
 
944
 
 
945
        if specific_file_ids is not None:
 
946
            # TODO: jam 20070302 This could really be done as a loop rather
 
947
            #       than a bunch of recursive calls.
 
948
            parents = set()
 
949
            byid = self._byid
 
950
            def add_ancestors(file_id):
 
951
                if file_id not in byid:
 
952
                    return
 
953
                parent_id = byid[file_id].parent_id
 
954
                if parent_id is None:
 
955
                    return
 
956
                if parent_id not in parents:
 
957
                    parents.add(parent_id)
 
958
                    add_ancestors(parent_id)
 
959
            for file_id in specific_file_ids:
 
960
                add_ancestors(file_id)
 
961
        else:
 
962
            parents = None
932
963
            
933
964
        stack = [(u'', from_dir)]
934
965
        while stack:
939
970
 
940
971
                child_relpath = cur_relpath + child_name
941
972
 
942
 
                yield child_relpath, child_ie
 
973
                if (specific_file_ids is None or 
 
974
                    child_ie.file_id in specific_file_ids or
 
975
                    (yield_parents and child_ie.file_id in parents)):
 
976
                    yield child_relpath, child_ie
943
977
 
944
978
                if child_ie.kind == 'directory':
945
 
                    child_dirs.append((child_relpath+'/', child_ie))
 
979
                    if parents is None or child_ie.file_id in parents:
 
980
                        child_dirs.append((child_relpath+'/', child_ie))
946
981
            stack.extend(reversed(child_dirs))
947
982
 
 
983
    def make_entry(self, kind, name, parent_id, file_id=None):
 
984
        """Simple thunk to bzrlib.inventory.make_entry."""
 
985
        return make_entry(kind, name, parent_id, file_id)
 
986
 
948
987
    def entries(self):
949
988
        """Return list of (path, ie) for all entries except the root.
950
989
 
955
994
            kids = dir_ie.children.items()
956
995
            kids.sort()
957
996
            for name, ie in kids:
958
 
                child_path = pathjoin(dir_path, name)
 
997
                child_path = osutils.pathjoin(dir_path, name)
959
998
                accum.append((child_path, ie))
960
999
                if ie.kind == 'directory':
961
1000
                    descend(ie, child_path)
974
1013
            kids.sort()
975
1014
 
976
1015
            for name, child_ie in kids:
977
 
                child_path = pathjoin(parent_path, name)
 
1016
                child_path = osutils.pathjoin(parent_path, name)
978
1017
                descend(child_ie, child_path)
979
1018
        descend(self.root, u'')
980
1019
        return accum
984
1023
 
985
1024
        >>> inv = Inventory()
986
1025
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
987
 
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
 
1026
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
988
1027
        >>> '123' in inv
989
1028
        True
990
1029
        >>> '456' in inv
991
1030
        False
992
1031
        """
993
 
        return file_id in self._byid
 
1032
        return (file_id in self._byid)
994
1033
 
995
1034
    def __getitem__(self, file_id):
996
1035
        """Return the entry for given file_id.
997
1036
 
998
1037
        >>> inv = Inventory()
999
1038
        >>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
1000
 
        InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT')
 
1039
        InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
1001
1040
        >>> inv['123123'].name
1002
1041
        'hello.c'
1003
1042
        """
1004
1043
        try:
1005
1044
            return self._byid[file_id]
1006
1045
        except KeyError:
1007
 
            if file_id is None:
1008
 
                raise BzrError("can't look up file_id None")
1009
 
            else:
1010
 
                raise BzrError("file_id {%s} not in inventory" % file_id)
 
1046
            # really we're passing an inventory, not a tree...
 
1047
            raise errors.NoSuchId(self, file_id)
1011
1048
 
1012
1049
    def get_file_kind(self, file_id):
1013
1050
        return self._byid[file_id].kind
1015
1052
    def get_child(self, parent_id, filename):
1016
1053
        return self[parent_id].children.get(filename)
1017
1054
 
 
1055
    def _add_child(self, entry):
 
1056
        """Add an entry to the inventory, without adding it to its parent"""
 
1057
        if entry.file_id in self._byid:
 
1058
            raise BzrError("inventory already contains entry with id {%s}" %
 
1059
                           entry.file_id)
 
1060
        self._byid[entry.file_id] = entry
 
1061
        for child in getattr(entry, 'children', {}).itervalues():
 
1062
            self._add_child(child)
 
1063
        return entry
 
1064
 
1018
1065
    def add(self, entry):
1019
1066
        """Add entry to inventory.
1020
1067
 
1024
1071
        Returns the new entry object.
1025
1072
        """
1026
1073
        if entry.file_id in self._byid:
1027
 
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1028
 
 
1029
 
        if entry.parent_id == ROOT_ID or entry.parent_id is None:
1030
 
            entry.parent_id = self.root.file_id
1031
 
 
1032
 
        try:
1033
 
            parent = self._byid[entry.parent_id]
1034
 
        except KeyError:
1035
 
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
1036
 
 
1037
 
        if entry.name in parent.children:
1038
 
            raise BzrError("%s is already versioned" %
1039
 
                    pathjoin(self.id2path(parent.file_id), entry.name))
1040
 
 
1041
 
        self._byid[entry.file_id] = entry
1042
 
        parent.children[entry.name] = entry
1043
 
        return entry
 
1074
            raise errors.DuplicateFileId(entry.file_id,
 
1075
                                         self._byid[entry.file_id])
 
1076
 
 
1077
        if entry.parent_id is None:
 
1078
            self.root = entry
 
1079
        else:
 
1080
            try:
 
1081
                parent = self._byid[entry.parent_id]
 
1082
            except KeyError:
 
1083
                raise BzrError("parent_id {%s} not in inventory" %
 
1084
                               entry.parent_id)
 
1085
 
 
1086
            if entry.name in parent.children:
 
1087
                raise BzrError("%s is already versioned" %
 
1088
                        osutils.pathjoin(self.id2path(parent.file_id),
 
1089
                        entry.name).encode('utf-8'))
 
1090
            parent.children[entry.name] = entry
 
1091
        return self._add_child(entry)
1044
1092
 
1045
1093
    def add_path(self, relpath, kind, file_id=None, parent_id=None):
1046
1094
        """Add entry from a path.
1049
1097
 
1050
1098
        Returns the new entry object."""
1051
1099
        
1052
 
        parts = bzrlib.osutils.splitpath(relpath)
 
1100
        parts = osutils.splitpath(relpath)
1053
1101
 
1054
1102
        if len(parts) == 0:
1055
1103
            if file_id is None:
1056
 
                file_id = bzrlib.workingtree.gen_root_id()
1057
 
            self.root = RootEntry(file_id)
 
1104
                file_id = generate_ids.gen_root_id()
 
1105
            self.root = InventoryDirectory(file_id, '', None)
1058
1106
            self._byid = {self.root.file_id: self.root}
1059
 
            return
 
1107
            return self.root
1060
1108
        else:
1061
1109
            parent_path = parts[:-1]
1062
1110
            parent_id = self.path2id(parent_path)
1063
1111
            if parent_id is None:
1064
 
                raise NotVersionedError(path=parent_path)
 
1112
                raise errors.NotVersionedError(path=parent_path)
1065
1113
        ie = make_entry(kind, parts[-1], parent_id, file_id)
1066
1114
        return self.add(ie)
1067
1115
 
1070
1118
 
1071
1119
        >>> inv = Inventory()
1072
1120
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1073
 
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
 
1121
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
1074
1122
        >>> '123' in inv
1075
1123
        True
1076
1124
        >>> del inv['123']
1078
1126
        False
1079
1127
        """
1080
1128
        ie = self[file_id]
1081
 
 
1082
 
        assert ie.parent_id is None or \
1083
 
            self[ie.parent_id].children[ie.name] == ie
1084
 
        
1085
1129
        del self._byid[file_id]
1086
1130
        if ie.parent_id is not None:
1087
1131
            del self[ie.parent_id].children[ie.name]
1094
1138
        >>> i1 == i2
1095
1139
        True
1096
1140
        >>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
1097
 
        InventoryFile('123', 'foo', parent_id='TREE_ROOT')
 
1141
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1098
1142
        >>> i1 == i2
1099
1143
        False
1100
1144
        >>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
1101
 
        InventoryFile('123', 'foo', parent_id='TREE_ROOT')
 
1145
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1102
1146
        >>> i1 == i2
1103
1147
        True
1104
1148
        """
1105
1149
        if not isinstance(other, Inventory):
1106
1150
            return NotImplemented
1107
1151
 
1108
 
        if len(self._byid) != len(other._byid):
1109
 
            # shortcut: obviously not the same
1110
 
            return False
1111
 
 
1112
1152
        return self._byid == other._byid
1113
1153
 
1114
1154
    def __ne__(self, other):
1123
1163
            try:
1124
1164
                ie = self._byid[file_id]
1125
1165
            except KeyError:
1126
 
                raise BzrError("file_id {%s} not found in inventory" % file_id)
 
1166
                raise errors.NoSuchId(tree=None, file_id=file_id)
1127
1167
            yield ie
1128
1168
            file_id = ie.parent_id
1129
1169
 
1165
1205
 
1166
1206
        Returns None IFF the path is not found.
1167
1207
        """
1168
 
        if isinstance(name, types.StringTypes):
1169
 
            name = splitpath(name)
 
1208
        if isinstance(name, basestring):
 
1209
            name = osutils.splitpath(name)
1170
1210
 
1171
1211
        # mutter("lookup path %r" % name)
1172
1212
 
1173
1213
        parent = self.root
 
1214
        if parent is None:
 
1215
            return None
1174
1216
        for f in name:
1175
1217
            try:
1176
 
                cie = parent.children[f]
1177
 
                assert cie.name == f
1178
 
                assert cie.parent_id == parent.file_id
 
1218
                children = getattr(parent, 'children', None)
 
1219
                if children is None:
 
1220
                    return None
 
1221
                cie = children[f]
1179
1222
                parent = cie
1180
1223
            except KeyError:
1181
1224
                # or raise an error?
1187
1230
        return bool(self.path2id(names))
1188
1231
 
1189
1232
    def has_id(self, file_id):
1190
 
        return self._byid.has_key(file_id)
 
1233
        return (file_id in self._byid)
 
1234
 
 
1235
    def remove_recursive_id(self, file_id):
 
1236
        """Remove file_id, and children, from the inventory.
 
1237
        
 
1238
        :param file_id: A file_id to remove.
 
1239
        """
 
1240
        to_find_delete = [self._byid[file_id]]
 
1241
        to_delete = []
 
1242
        while to_find_delete:
 
1243
            ie = to_find_delete.pop()
 
1244
            to_delete.append(ie.file_id)
 
1245
            if ie.kind == 'directory':
 
1246
                to_find_delete.extend(ie.children.values())
 
1247
        for file_id in reversed(to_delete):
 
1248
            ie = self[file_id]
 
1249
            del self._byid[file_id]
 
1250
        if ie.parent_id is not None:
 
1251
            del self[ie.parent_id].children[ie.name]
 
1252
        else:
 
1253
            self.root = None
1191
1254
 
1192
1255
    def rename(self, file_id, new_parent_id, new_name):
1193
1256
        """Move a file within the inventory.
1194
1257
 
1195
1258
        This can change either the name, or the parent, or both.
1196
1259
 
1197
 
        This does not move the working file."""
 
1260
        This does not move the working file.
 
1261
        """
 
1262
        new_name = ensure_normalized_name(new_name)
1198
1263
        if not is_valid_name(new_name):
1199
1264
            raise BzrError("not an acceptable filename: %r" % new_name)
1200
1265
 
1218
1283
        file_ie.name = new_name
1219
1284
        file_ie.parent_id = new_parent_id
1220
1285
 
 
1286
    def is_root(self, file_id):
 
1287
        return self.root is not None and file_id == self.root.file_id
 
1288
 
 
1289
 
 
1290
entry_factory = {
 
1291
    'directory': InventoryDirectory,
 
1292
    'file': InventoryFile,
 
1293
    'symlink': InventoryLink,
 
1294
    'tree-reference': TreeReference
 
1295
}
1221
1296
 
1222
1297
def make_entry(kind, name, parent_id, file_id=None):
1223
1298
    """Create an inventory entry.
1228
1303
    :param file_id: the file_id to use. if None, one will be created.
1229
1304
    """
1230
1305
    if file_id is None:
1231
 
        file_id = bzrlib.workingtree.gen_file_id(name)
1232
 
    if kind == 'directory':
1233
 
        return InventoryDirectory(file_id, name, parent_id)
1234
 
    elif kind == 'file':
1235
 
        return InventoryFile(file_id, name, parent_id)
1236
 
    elif kind == 'symlink':
1237
 
        return InventoryLink(file_id, name, parent_id)
1238
 
    else:
 
1306
        file_id = generate_ids.gen_file_id(name)
 
1307
    name = ensure_normalized_name(name)
 
1308
    try:
 
1309
        factory = entry_factory[kind]
 
1310
    except KeyError:
1239
1311
        raise BzrError("unknown kind %r" % kind)
1240
 
 
 
1312
    return factory(file_id, name, parent_id)
 
1313
 
 
1314
 
 
1315
def ensure_normalized_name(name):
 
1316
    """Normalize name.
 
1317
 
 
1318
    :raises InvalidNormalization: When name is not normalized, and cannot be
 
1319
        accessed on this platform by the normalized path.
 
1320
    :return: The NFC normalised version of name.
 
1321
    """
 
1322
    #------- This has been copied to bzrlib.dirstate.DirState.add, please
 
1323
    # keep them synchronised.
 
1324
    # we dont import normalized_filename directly because we want to be
 
1325
    # able to change the implementation at runtime for tests.
 
1326
    norm_name, can_access = osutils.normalized_filename(name)
 
1327
    if norm_name != name:
 
1328
        if can_access:
 
1329
            return norm_name
 
1330
        else:
 
1331
            # TODO: jam 20060701 This would probably be more useful
 
1332
            #       if the error was raised with the full path
 
1333
            raise errors.InvalidNormalization(name)
 
1334
    return name
1241
1335
 
1242
1336
 
1243
1337
_NAME_RE = None