~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-07-12 15:13:59 UTC
  • mfrom: (1850.1.2 diff-6391)
  • Revision ID: pqm@pqm.ubuntu.com-20060712151359-b22a8c043fa52eb7
(jam) update help text for diff (bug #6391)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# (C) 2005 Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
 
17
# FIXME: This refactoring of the workingtree code doesn't seem to keep 
 
18
# the WorkingTree's copy of the inventory in sync with the branch.  The
 
19
# branch modifies its working inventory when it does a commit to make
 
20
# missing files permanently removed.
17
21
 
18
22
# TODO: Maybe also keep the full path of the entry, and the children?
19
23
# But those depend on its position within a particular inventory, and
24
28
ROOT_ID = "TREE_ROOT"
25
29
 
26
30
 
 
31
import collections
27
32
import os.path
28
33
import re
29
34
import sys
31
36
import types
32
37
 
33
38
import bzrlib
34
 
from bzrlib.errors import BzrError, BzrCheckError
35
 
 
 
39
from bzrlib import errors, osutils
36
40
from bzrlib.osutils import (pumpfile, quotefn, splitpath, joinpath,
37
 
                            appendpath, sha_strings)
 
41
                            pathjoin, sha_strings)
 
42
from bzrlib.errors import (NotVersionedError, InvalidEntryName,
 
43
                           BzrError, BzrCheckError, BinaryFile)
38
44
from bzrlib.trace import mutter
39
 
from bzrlib.errors import NotVersionedError
40
45
 
41
46
 
42
47
class InventoryEntry(object):
73
78
    >>> i.path2id('')
74
79
    'TREE_ROOT'
75
80
    >>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
76
 
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
 
81
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
77
82
    >>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
78
 
    InventoryFile('2323', 'hello.c', parent_id='123')
79
 
    >>> for j in i.iter_entries():
80
 
    ...   print j
 
83
    InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
 
84
    >>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
 
85
    >>> for ix, j in enumerate(i.iter_entries()):
 
86
    ...   print (j[0] == shouldbe[ix], j[1])
81
87
    ... 
82
 
    ('src', InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
83
 
    ('src/hello.c', InventoryFile('2323', 'hello.c', parent_id='123'))
 
88
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
 
89
    (True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
84
90
    >>> i.add(InventoryFile('2323', 'bye.c', '123'))
85
91
    Traceback (most recent call last):
86
92
    ...
87
93
    BzrError: inventory already contains entry with id {2323}
88
94
    >>> i.add(InventoryFile('2324', 'bye.c', '123'))
89
 
    InventoryFile('2324', 'bye.c', parent_id='123')
 
95
    InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
90
96
    >>> i.add(InventoryDirectory('2325', 'wibble', '123'))
91
 
    InventoryDirectory('2325', 'wibble', parent_id='123')
 
97
    InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
92
98
    >>> i.path2id('src/wibble')
93
99
    '2325'
94
100
    >>> '2325' in i
95
101
    True
96
102
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
97
 
    InventoryFile('2326', 'wibble.c', parent_id='2325')
 
103
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
98
104
    >>> i['2326']
99
 
    InventoryFile('2326', 'wibble.c', parent_id='2325')
 
105
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
100
106
    >>> for path, entry in i.iter_entries():
101
 
    ...     print path.replace('\\\\', '/')     # for win32 os.sep
 
107
    ...     print path
102
108
    ...     assert i.path2id(path)
103
109
    ... 
104
110
    src
106
112
    src/hello.c
107
113
    src/wibble
108
114
    src/wibble/wibble.c
109
 
    >>> i.id2path('2326').replace('\\\\', '/')
 
115
    >>> i.id2path('2326')
110
116
    'src/wibble/wibble.c'
111
117
    """
 
118
 
 
119
    # Constants returned by describe_change()
 
120
    #
 
121
    # TODO: These should probably move to some kind of FileChangeDescription 
 
122
    # class; that's like what's inside a TreeDelta but we want to be able to 
 
123
    # generate them just for one file at a time.
 
124
    RENAMED = 'renamed'
 
125
    MODIFIED_AND_RENAMED = 'modified and renamed'
112
126
    
113
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
114
 
                 'text_id', 'parent_id', 'children', 'executable', 
115
 
                 'revision']
116
 
 
117
 
    def _add_text_to_weave(self, new_lines, parents, weave_store):
118
 
        weave_store.add_text(self.file_id, self.revision, new_lines, parents)
 
127
    __slots__ = []
119
128
 
120
129
    def detect_changes(self, old_entry):
121
130
        """Return a (text_modified, meta_modified) from this to old_entry.
146
155
             output_to, reverse=False):
147
156
        """Perform a diff between two entries of the same kind."""
148
157
 
149
 
    def find_previous_heads(self, previous_inventories, entry_weave):
150
 
        """Return the revisions and entries that directly preceed this.
 
158
    def find_previous_heads(self, previous_inventories,
 
159
                            versioned_file_store,
 
160
                            transaction,
 
161
                            entry_vf=None):
 
162
        """Return the revisions and entries that directly precede this.
151
163
 
152
164
        Returned as a map from revision to inventory entry.
153
165
 
154
166
        This is a map containing the file revisions in all parents
155
167
        for which the file exists, and its revision is not a parent of
156
168
        any other. If the file is new, the set will be empty.
 
169
 
 
170
        :param versioned_file_store: A store where ancestry data on this
 
171
                                     file id can be queried.
 
172
        :param transaction: The transaction that queries to the versioned 
 
173
                            file store should be completed under.
 
174
        :param entry_vf: The entry versioned file, if its already available.
157
175
        """
158
176
        def get_ancestors(weave, entry):
159
 
            return set(map(weave.idx_to_name,
160
 
                           weave.inclusions([weave.lookup(entry.revision)])))
 
177
            return set(weave.get_ancestry(entry.revision))
 
178
        # revision:ie mapping for each ie found in previous_inventories.
 
179
        candidates = {}
 
180
        # revision:ie mapping with one revision for each head.
161
181
        heads = {}
 
182
        # revision: ancestor list for each head
162
183
        head_ancestors = {}
 
184
        # identify candidate head revision ids.
163
185
        for inv in previous_inventories:
164
186
            if self.file_id in inv:
165
187
                ie = inv[self.file_id]
166
188
                assert ie.file_id == self.file_id
167
 
                if ie.revision in heads:
168
 
                    assert heads[ie.revision] == ie
 
189
                if ie.revision in candidates:
 
190
                    # same revision value in two different inventories:
 
191
                    # correct possible inconsistencies:
 
192
                    #     * there was a bug in revision updates with 'x' bit 
 
193
                    #       support.
 
194
                    try:
 
195
                        if candidates[ie.revision].executable != ie.executable:
 
196
                            candidates[ie.revision].executable = False
 
197
                            ie.executable = False
 
198
                    except AttributeError:
 
199
                        pass
 
200
                    # must now be the same.
 
201
                    assert candidates[ie.revision] == ie
169
202
                else:
170
 
                    # may want to add it.
171
 
                    # may already be covered:
172
 
                    already_present = 0 != len(
173
 
                        [head for head in heads 
174
 
                         if ie.revision in head_ancestors[head]])
175
 
                    if already_present:
176
 
                        # an ancestor of a known head.
177
 
                        continue
178
 
                    # definately a head:
179
 
                    ancestors = get_ancestors(entry_weave, ie)
180
 
                    # may knock something else out:
181
 
                    check_heads = list(heads.keys())
182
 
                    for head in check_heads:
183
 
                        if head in ancestors:
184
 
                            # this head is not really a head
185
 
                            heads.pop(head)
186
 
                    head_ancestors[ie.revision] = ancestors
187
 
                    heads[ie.revision] = ie
 
203
                    # add this revision as a candidate.
 
204
                    candidates[ie.revision] = ie
 
205
 
 
206
        # common case optimisation
 
207
        if len(candidates) == 1:
 
208
            # if there is only one candidate revision found
 
209
            # then we can opening the versioned file to access ancestry:
 
210
            # there cannot be any ancestors to eliminate when there is 
 
211
            # only one revision available.
 
212
            heads[ie.revision] = ie
 
213
            return heads
 
214
 
 
215
        # eliminate ancestors amongst the available candidates:
 
216
        # heads are those that are not an ancestor of any other candidate
 
217
        # - this provides convergence at a per-file level.
 
218
        for ie in candidates.values():
 
219
            # may be an ancestor of a known head:
 
220
            already_present = 0 != len(
 
221
                [head for head in heads 
 
222
                 if ie.revision in head_ancestors[head]])
 
223
            if already_present:
 
224
                # an ancestor of an analyzed candidate.
 
225
                continue
 
226
            # not an ancestor of a known head:
 
227
            # load the versioned file for this file id if needed
 
228
            if entry_vf is None:
 
229
                entry_vf = versioned_file_store.get_weave_or_empty(
 
230
                    self.file_id, transaction)
 
231
            ancestors = get_ancestors(entry_vf, ie)
 
232
            # may knock something else out:
 
233
            check_heads = list(heads.keys())
 
234
            for head in check_heads:
 
235
                if head in ancestors:
 
236
                    # this previously discovered 'head' is not
 
237
                    # really a head - its an ancestor of the newly 
 
238
                    # found head,
 
239
                    heads.pop(head)
 
240
            head_ancestors[ie.revision] = ancestors
 
241
            heads[ie.revision] = ie
188
242
        return heads
189
243
 
190
244
    def get_tar_item(self, root, dp, now, tree):
191
245
        """Get a tarfile item and a file stream for its content."""
192
 
        item = tarfile.TarInfo(os.path.join(root, dp))
 
246
        item = tarfile.TarInfo(pathjoin(root, dp))
193
247
        # TODO: would be cool to actually set it to the timestamp of the
194
248
        # revision it was last changed
195
249
        item.mtime = now
220
274
        '123'
221
275
        >>> e = InventoryFile('123', 'src/hello.c', ROOT_ID)
222
276
        Traceback (most recent call last):
223
 
        BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
 
277
        InvalidEntryName: Invalid entry name: src/hello.c
224
278
        """
225
279
        assert isinstance(name, basestring), name
226
280
        if '/' in name or '\\' in name:
227
 
            raise BzrCheckError('InventoryEntry name %r is invalid' % name)
228
 
        
 
281
            raise InvalidEntryName(name=name)
229
282
        self.executable = False
230
283
        self.revision = None
231
284
        self.text_sha1 = None
255
308
        
256
309
        This is a template method - implement _put_on_disk in subclasses.
257
310
        """
258
 
        fullpath = appendpath(dest, dp)
 
311
        fullpath = pathjoin(dest, dp)
259
312
        self._put_on_disk(fullpath, tree)
260
 
        mutter("  export {%s} kind %s to %s" % (self.file_id, self.kind, fullpath))
 
313
        mutter("  export {%s} kind %s to %s", self.file_id,
 
314
                self.kind, fullpath)
261
315
 
262
316
    def _put_on_disk(self, fullpath, tree):
263
317
        """Put this entry onto disk at fullpath, from tree tree."""
264
318
        raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
265
319
 
266
320
    def sorted_children(self):
267
 
        l = self.children.items()
268
 
        l.sort()
269
 
        return l
 
321
        return sorted(self.children.items())
270
322
 
271
323
    @staticmethod
272
324
    def versionable_kind(kind):
277
329
 
278
330
        This is a template method, override _check for kind specific
279
331
        tests.
 
332
 
 
333
        :param checker: Check object providing context for the checks; 
 
334
             can be used to find out what parts of the repository have already
 
335
             been checked.
 
336
        :param rev_id: Revision id from which this InventoryEntry was loaded.
 
337
             Not necessarily the last-changed revision for this file.
 
338
        :param inv: Inventory from which the entry was loaded.
 
339
        :param tree: RevisionTree for this entry.
280
340
        """
281
 
        if self.parent_id != None:
 
341
        if self.parent_id is not None:
282
342
            if not inv.has_id(self.parent_id):
283
343
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
284
344
                        % (self.parent_id, rev_id))
289
349
        raise BzrCheckError('unknown entry kind %r in revision {%s}' % 
290
350
                            (self.kind, rev_id))
291
351
 
292
 
 
293
352
    def copy(self):
294
353
        """Clone this inventory entry."""
295
354
        raise NotImplementedError
296
355
 
297
 
    def _get_snapshot_change(self, previous_entries):
298
 
        if len(previous_entries) > 1:
299
 
            return 'merged'
300
 
        elif len(previous_entries) == 0:
 
356
    @staticmethod
 
357
    def describe_change(old_entry, new_entry):
 
358
        """Describe the change between old_entry and this.
 
359
        
 
360
        This smells of being an InterInventoryEntry situation, but as its
 
361
        the first one, we're making it a static method for now.
 
362
 
 
363
        An entry with a different parent, or different name is considered 
 
364
        to be renamed. Reparenting is an internal detail.
 
365
        Note that renaming the parent does not trigger a rename for the
 
366
        child entry itself.
 
367
        """
 
368
        # TODO: Perhaps return an object rather than just a string
 
369
        if old_entry is new_entry:
 
370
            # also the case of both being None
 
371
            return 'unchanged'
 
372
        elif old_entry is None:
301
373
            return 'added'
302
 
        else:
303
 
            return 'modified/renamed/reparented'
 
374
        elif new_entry is None:
 
375
            return 'removed'
 
376
        text_modified, meta_modified = new_entry.detect_changes(old_entry)
 
377
        if text_modified or meta_modified:
 
378
            modified = True
 
379
        else:
 
380
            modified = False
 
381
        # TODO 20060511 (mbp, rbc) factor out 'detect_rename' here.
 
382
        if old_entry.parent_id != new_entry.parent_id:
 
383
            renamed = True
 
384
        elif old_entry.name != new_entry.name:
 
385
            renamed = True
 
386
        else:
 
387
            renamed = False
 
388
        if renamed and not modified:
 
389
            return InventoryEntry.RENAMED
 
390
        if modified and not renamed:
 
391
            return 'modified'
 
392
        if modified and renamed:
 
393
            return InventoryEntry.MODIFIED_AND_RENAMED
 
394
        return 'unchanged'
304
395
 
305
396
    def __repr__(self):
306
 
        return ("%s(%r, %r, parent_id=%r)"
 
397
        return ("%s(%r, %r, parent_id=%r, revision=%r)"
307
398
                % (self.__class__.__name__,
308
399
                   self.file_id,
309
400
                   self.name,
310
 
                   self.parent_id))
 
401
                   self.parent_id,
 
402
                   self.revision))
311
403
 
312
404
    def snapshot(self, revision, path, previous_entries,
313
 
                 work_tree, weave_store):
 
405
                 work_tree, commit_builder):
314
406
        """Make a snapshot of this entry which may or may not have changed.
315
407
        
316
408
        This means that all its fields are populated, that it has its
318
410
        """
319
411
        mutter('new parents of %s are %r', path, previous_entries)
320
412
        self._read_tree_state(path, work_tree)
 
413
        # TODO: Where should we determine whether to reuse a
 
414
        # previous revision id or create a new revision? 20060606
321
415
        if len(previous_entries) == 1:
322
416
            # cannot be unchanged unless there is only one parent file rev.
323
417
            parent_ie = previous_entries.values()[0]
325
419
                mutter("found unchanged entry")
326
420
                self.revision = parent_ie.revision
327
421
                return "unchanged"
328
 
        return self.snapshot_revision(revision, previous_entries, 
329
 
                                      work_tree, weave_store)
330
 
 
331
 
    def snapshot_revision(self, revision, previous_entries, work_tree,
332
 
                          weave_store):
333
 
        """Record this revision unconditionally."""
334
 
        mutter('new revision for {%s}', self.file_id)
 
422
        return self._snapshot_into_revision(revision, previous_entries, 
 
423
                                            work_tree, commit_builder)
 
424
 
 
425
    def _snapshot_into_revision(self, revision, previous_entries, work_tree,
 
426
                                commit_builder):
 
427
        """Record this revision unconditionally into a store.
 
428
 
 
429
        The entry's last-changed revision property (`revision`) is updated to 
 
430
        that of the new revision.
 
431
        
 
432
        :param revision: id of the new revision that is being recorded.
 
433
 
 
434
        :returns: String description of the commit (e.g. "merged", "modified"), etc.
 
435
        """
 
436
        mutter('new revision {%s} for {%s}', revision, self.file_id)
335
437
        self.revision = revision
336
 
        change = self._get_snapshot_change(previous_entries)
337
 
        self._snapshot_text(previous_entries, work_tree, weave_store)
338
 
        return change
 
438
        self._snapshot_text(previous_entries, work_tree, commit_builder)
339
439
 
340
 
    def _snapshot_text(self, file_parents, work_tree, weave_store): 
 
440
    def _snapshot_text(self, file_parents, work_tree, commit_builder): 
341
441
        """Record the 'text' of this entry, whatever form that takes.
342
442
        
343
443
        This default implementation simply adds an empty text.
344
444
        """
345
 
        mutter('storing file {%s} in revision {%s}',
346
 
               self.file_id, self.revision)
347
 
        self._add_text_to_weave([], file_parents, weave_store)
 
445
        raise NotImplementedError(self._snapshot_text)
348
446
 
349
447
    def __eq__(self, other):
350
448
        if not isinstance(other, InventoryEntry):
371
469
    def _unchanged(self, previous_ie):
372
470
        """Has this entry changed relative to previous_ie.
373
471
 
374
 
        This method should be overriden in child classes.
 
472
        This method should be overridden in child classes.
375
473
        """
376
474
        compatible = True
377
475
        # different inv parent
388
486
        Note that this should be modified to be a noop on virtual trees
389
487
        as all entries created there are prepopulated.
390
488
        """
 
489
        # TODO: Rather than running this manually, we should check the 
 
490
        # working sha1 and other expensive properties when they're
 
491
        # first requested, or preload them if they're already known
 
492
        pass            # nothing to do by default
 
493
 
 
494
    def _forget_tree_state(self):
 
495
        pass
391
496
 
392
497
 
393
498
class RootEntry(InventoryEntry):
394
499
 
 
500
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
501
                 'text_id', 'parent_id', 'children', 'executable', 
 
502
                 'revision', 'symlink_target']
 
503
 
395
504
    def _check(self, checker, rev_id, tree):
396
505
        """See InventoryEntry._check"""
397
506
 
400
509
        self.children = {}
401
510
        self.kind = 'root_directory'
402
511
        self.parent_id = None
403
 
        self.name = ''
 
512
        self.name = u''
 
513
        self.revision = None
404
514
 
405
515
    def __eq__(self, other):
406
516
        if not isinstance(other, RootEntry):
413
523
class InventoryDirectory(InventoryEntry):
414
524
    """A directory in an inventory."""
415
525
 
 
526
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
527
                 'text_id', 'parent_id', 'children', 'executable', 
 
528
                 'revision', 'symlink_target']
 
529
 
416
530
    def _check(self, checker, rev_id, tree):
417
531
        """See InventoryEntry._check"""
418
 
        if self.text_sha1 != None or self.text_size != None or self.text_id != None:
 
532
        if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
419
533
            raise BzrCheckError('directory {%s} has text in revision {%s}'
420
534
                                % (self.file_id, rev_id))
421
535
 
448
562
        """See InventoryEntry._put_on_disk."""
449
563
        os.mkdir(fullpath)
450
564
 
 
565
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
 
566
        """See InventoryEntry._snapshot_text."""
 
567
        commit_builder.modified_directory(self.file_id, file_parents)
 
568
 
451
569
 
452
570
class InventoryFile(InventoryEntry):
453
571
    """A file in an inventory."""
454
572
 
455
 
    def _check(self, checker, rev_id, tree):
 
573
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
574
                 'text_id', 'parent_id', 'children', 'executable', 
 
575
                 'revision', 'symlink_target']
 
576
 
 
577
    def _check(self, checker, tree_revision_id, tree):
456
578
        """See InventoryEntry._check"""
457
 
        revision = self.revision
458
 
        t = (self.file_id, revision)
 
579
        t = (self.file_id, self.revision)
459
580
        if t in checker.checked_texts:
460
 
            prev_sha = checker.checked_texts[t] 
 
581
            prev_sha = checker.checked_texts[t]
461
582
            if prev_sha != self.text_sha1:
462
583
                raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
463
 
                                    (self.file_id, rev_id))
 
584
                                    (self.file_id, tree_revision_id))
464
585
            else:
465
586
                checker.repeated_text_cnt += 1
466
587
                return
467
 
        mutter('check version {%s} of {%s}', rev_id, self.file_id)
468
 
        file_lines = tree.get_file_lines(self.file_id)
469
 
        checker.checked_text_cnt += 1 
470
 
        if self.text_size != sum(map(len, file_lines)):
471
 
            raise BzrCheckError('text {%s} wrong size' % self.text_id)
472
 
        if self.text_sha1 != sha_strings(file_lines):
473
 
            raise BzrCheckError('text {%s} wrong sha1' % self.text_id)
 
588
 
 
589
        if self.file_id not in checker.checked_weaves:
 
590
            mutter('check weave {%s}', self.file_id)
 
591
            w = tree.get_weave(self.file_id)
 
592
            # Not passing a progress bar, because it creates a new
 
593
            # progress, which overwrites the current progress,
 
594
            # and doesn't look nice
 
595
            w.check()
 
596
            checker.checked_weaves[self.file_id] = True
 
597
        else:
 
598
            w = tree.get_weave(self.file_id)
 
599
 
 
600
        mutter('check version {%s} of {%s}', tree_revision_id, self.file_id)
 
601
        checker.checked_text_cnt += 1
 
602
        # We can't check the length, because Weave doesn't store that
 
603
        # information, and the whole point of looking at the weave's
 
604
        # sha1sum is that we don't have to extract the text.
 
605
        if self.text_sha1 != w.get_sha1(self.revision):
 
606
            raise BzrCheckError('text {%s} version {%s} wrong sha1' 
 
607
                                % (self.file_id, self.revision))
474
608
        checker.checked_texts[t] = self.text_sha1
475
609
 
476
610
    def copy(self):
484
618
 
485
619
    def detect_changes(self, old_entry):
486
620
        """See InventoryEntry.detect_changes."""
487
 
        assert self.text_sha1 != None
488
 
        assert old_entry.text_sha1 != None
 
621
        assert self.text_sha1 is not None
 
622
        assert old_entry.text_sha1 is not None
489
623
        text_modified = (self.text_sha1 != old_entry.text_sha1)
490
624
        meta_modified = (self.executable != old_entry.executable)
491
625
        return text_modified, meta_modified
493
627
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
494
628
             output_to, reverse=False):
495
629
        """See InventoryEntry._diff."""
496
 
        from_text = tree.get_file(self.file_id).readlines()
497
 
        if to_entry:
498
 
            to_text = to_tree.get_file(to_entry.file_id).readlines()
499
 
        else:
500
 
            to_text = []
501
 
        if not reverse:
502
 
            text_diff(from_label, from_text,
503
 
                      to_label, to_text, output_to)
504
 
        else:
505
 
            text_diff(to_label, to_text,
506
 
                      from_label, from_text, output_to)
 
630
        try:
 
631
            from_text = tree.get_file(self.file_id).readlines()
 
632
            if to_entry:
 
633
                to_text = to_tree.get_file(to_entry.file_id).readlines()
 
634
            else:
 
635
                to_text = []
 
636
            if not reverse:
 
637
                text_diff(from_label, from_text,
 
638
                          to_label, to_text, output_to)
 
639
            else:
 
640
                text_diff(to_label, to_text,
 
641
                          from_label, from_text, output_to)
 
642
        except BinaryFile:
 
643
            if reverse:
 
644
                label_pair = (to_label, from_label)
 
645
            else:
 
646
                label_pair = (from_label, to_label)
 
647
            print >> output_to, "Binary files %s and %s differ" % label_pair
507
648
 
508
649
    def has_text(self):
509
650
        """See InventoryEntry.has_text."""
536
677
 
537
678
    def _read_tree_state(self, path, work_tree):
538
679
        """See InventoryEntry._read_tree_state."""
539
 
        self.text_sha1 = work_tree.get_file_sha1(self.file_id)
540
 
        self.executable = work_tree.is_executable(self.file_id)
541
 
 
542
 
    def _snapshot_text(self, file_parents, work_tree, weave_store): 
 
680
        self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
 
681
        # FIXME: 20050930 probe for the text size when getting sha1
 
682
        # in _read_tree_state
 
683
        self.executable = work_tree.is_executable(self.file_id, path=path)
 
684
 
 
685
    def __repr__(self):
 
686
        return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s)"
 
687
                % (self.__class__.__name__,
 
688
                   self.file_id,
 
689
                   self.name,
 
690
                   self.parent_id,
 
691
                   self.text_sha1,
 
692
                   self.text_size))
 
693
 
 
694
    def _forget_tree_state(self):
 
695
        self.text_sha1 = None
 
696
 
 
697
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
543
698
        """See InventoryEntry._snapshot_text."""
544
 
        mutter('storing file {%s} in revision {%s}',
545
 
               self.file_id, self.revision)
546
 
        # special case to avoid diffing on renames or 
547
 
        # reparenting
548
 
        if (len(file_parents) == 1
549
 
            and self.text_sha1 == file_parents.values()[0].text_sha1
550
 
            and self.text_size == file_parents.values()[0].text_size):
551
 
            previous_ie = file_parents.values()[0]
552
 
            weave_store.add_identical_text(
553
 
                self.file_id, previous_ie.revision, 
554
 
                self.revision, file_parents)
555
 
        else:
556
 
            new_lines = work_tree.get_file(self.file_id).readlines()
557
 
            self._add_text_to_weave(new_lines, file_parents, weave_store)
558
 
            self.text_sha1 = sha_strings(new_lines)
559
 
            self.text_size = sum(map(len, new_lines))
560
 
 
 
699
        def get_content_byte_lines():
 
700
            return work_tree.get_file(self.file_id).readlines()
 
701
        self.text_sha1, self.text_size = commit_builder.modified_file_text(
 
702
            self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
561
703
 
562
704
    def _unchanged(self, previous_ie):
563
705
        """See InventoryEntry._unchanged."""
568
710
            # FIXME: 20050930 probe for the text size when getting sha1
569
711
            # in _read_tree_state
570
712
            self.text_size = previous_ie.text_size
 
713
        if self.executable != previous_ie.executable:
 
714
            compatible = False
571
715
        return compatible
572
716
 
573
717
 
574
718
class InventoryLink(InventoryEntry):
575
719
    """A file in an inventory."""
576
720
 
577
 
    __slots__ = ['symlink_target']
 
721
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
722
                 'text_id', 'parent_id', 'children', 'executable', 
 
723
                 'revision', 'symlink_target']
578
724
 
579
725
    def _check(self, checker, rev_id, tree):
580
726
        """See InventoryEntry._check"""
581
 
        if self.text_sha1 != None or self.text_size != None or self.text_id != None:
 
727
        if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
582
728
            raise BzrCheckError('symlink {%s} has text in revision {%s}'
583
729
                    % (self.file_id, rev_id))
584
 
        if self.symlink_target == None:
 
730
        if self.symlink_target is None:
585
731
            raise BzrCheckError('symlink {%s} has no target in revision {%s}'
586
732
                    % (self.file_id, rev_id))
587
733
 
627
773
 
628
774
    def _put_in_tar(self, item, tree):
629
775
        """See InventoryEntry._put_in_tar."""
630
 
        iterm.type = tarfile.SYMTYPE
 
776
        item.type = tarfile.SYMTYPE
631
777
        fileobj = None
632
778
        item.size = 0
633
779
        item.mode = 0755
645
791
        """See InventoryEntry._read_tree_state."""
646
792
        self.symlink_target = work_tree.get_symlink_target(self.file_id)
647
793
 
 
794
    def _forget_tree_state(self):
 
795
        self.symlink_target = None
 
796
 
648
797
    def _unchanged(self, previous_ie):
649
798
        """See InventoryEntry._unchanged."""
650
799
        compatible = super(InventoryLink, self)._unchanged(previous_ie)
652
801
            compatible = False
653
802
        return compatible
654
803
 
 
804
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
 
805
        """See InventoryEntry._snapshot_text."""
 
806
        commit_builder.modified_link(
 
807
            self.file_id, file_parents, self.symlink_target)
 
808
 
655
809
 
656
810
class Inventory(object):
657
811
    """Inventory of versioned files in a tree.
672
826
 
673
827
    >>> inv = Inventory()
674
828
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
675
 
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT')
 
829
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
676
830
    >>> inv['123-123'].name
677
831
    'hello.c'
678
832
 
686
840
    May also look up by name:
687
841
 
688
842
    >>> [x[0] for x in inv.iter_entries()]
689
 
    ['hello.c']
 
843
    [u'hello.c']
690
844
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
691
845
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
692
 
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678')
 
846
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
693
847
    """
694
 
    def __init__(self, root_id=ROOT_ID):
 
848
    def __init__(self, root_id=ROOT_ID, revision_id=None):
695
849
        """Create or read an inventory.
696
850
 
697
851
        If a working directory is specified, the inventory is read
701
855
        The inventory is created with a default root directory, with
702
856
        an id of None.
703
857
        """
704
 
        # We are letting Branch.initialize() create a unique inventory
 
858
        # We are letting Branch.create() create a unique inventory
705
859
        # root id. Rather than generating a random one here.
706
860
        #if root_id is None:
707
861
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
708
862
        self.root = RootEntry(root_id)
 
863
        # FIXME: this isn't ever used, changing it to self.revision may break
 
864
        # things. TODO make everything use self.revision_id
 
865
        self.revision_id = revision_id
709
866
        self._byid = {self.root.file_id: self.root}
710
867
 
711
 
 
712
868
    def copy(self):
 
869
        # TODO: jam 20051218 Should copy also copy the revision_id?
713
870
        other = Inventory(self.root.file_id)
714
871
        # copy recursively so we know directories will be added before
715
872
        # their children.  There are more efficient ways than this...
719
876
            other.add(entry.copy())
720
877
        return other
721
878
 
722
 
 
723
879
    def __iter__(self):
724
880
        return iter(self._byid)
725
881
 
726
 
 
727
882
    def __len__(self):
728
883
        """Returns number of entries."""
729
884
        return len(self._byid)
730
885
 
731
 
 
732
886
    def iter_entries(self, from_dir=None):
733
887
        """Return (path, entry) pairs, in order by name."""
734
 
        if from_dir == None:
735
 
            assert self.root
736
 
            from_dir = self.root
737
 
        elif isinstance(from_dir, basestring):
738
 
            from_dir = self._byid[from_dir]
739
 
            
740
 
        kids = from_dir.children.items()
741
 
        kids.sort()
742
 
        for name, ie in kids:
743
 
            yield name, ie
744
 
            if ie.kind == 'directory':
745
 
                for cn, cie in self.iter_entries(from_dir=ie.file_id):
746
 
                    yield os.path.join(name, cn), cie
747
 
 
 
888
        if from_dir is None:
 
889
            assert self.root
 
890
            from_dir = self.root
 
891
        elif isinstance(from_dir, basestring):
 
892
            from_dir = self._byid[from_dir]
 
893
            
 
894
        # unrolling the recursive called changed the time from
 
895
        # 440ms/663ms (inline/total) to 116ms/116ms
 
896
        children = from_dir.children.items()
 
897
        children.sort()
 
898
        children = collections.deque(children)
 
899
        stack = [(u'', children)]
 
900
        while stack:
 
901
            from_dir_relpath, children = stack[-1]
 
902
 
 
903
            while children:
 
904
                name, ie = children.popleft()
 
905
 
 
906
                # we know that from_dir_relpath never ends in a slash
 
907
                # and 'f' doesn't begin with one, we can do a string op, rather
 
908
                # than the checks of pathjoin(), though this means that all paths
 
909
                # start with a slash
 
910
                path = from_dir_relpath + '/' + name
 
911
 
 
912
                yield path[1:], ie
 
913
 
 
914
                if ie.kind != 'directory':
 
915
                    continue
 
916
 
 
917
                # But do this child first
 
918
                new_children = ie.children.items()
 
919
                new_children.sort()
 
920
                new_children = collections.deque(new_children)
 
921
                stack.append((path, new_children))
 
922
                # Break out of inner loop, so that we start outer loop with child
 
923
                break
 
924
            else:
 
925
                # if we finished all children, pop it off the stack
 
926
                stack.pop()
 
927
 
 
928
    def iter_entries_by_dir(self, from_dir=None):
 
929
        """Iterate over the entries in a directory first order.
 
930
 
 
931
        This returns all entries for a directory before returning
 
932
        the entries for children of a directory. This is not
 
933
        lexicographically sorted order, and is a hybrid between
 
934
        depth-first and breadth-first.
 
935
 
 
936
        :return: This yields (path, entry) pairs
 
937
        """
 
938
        # TODO? Perhaps this should return the from_dir so that the root is
 
939
        # yielded? or maybe an option?
 
940
        if from_dir is None:
 
941
            assert self.root
 
942
            from_dir = self.root
 
943
        elif isinstance(from_dir, basestring):
 
944
            from_dir = self._byid[from_dir]
 
945
            
 
946
        stack = [(u'', from_dir)]
 
947
        while stack:
 
948
            cur_relpath, cur_dir = stack.pop()
 
949
 
 
950
            child_dirs = []
 
951
            for child_name, child_ie in sorted(cur_dir.children.iteritems()):
 
952
 
 
953
                child_relpath = cur_relpath + child_name
 
954
 
 
955
                yield child_relpath, child_ie
 
956
 
 
957
                if child_ie.kind == 'directory':
 
958
                    child_dirs.append((child_relpath+'/', child_ie))
 
959
            stack.extend(reversed(child_dirs))
748
960
 
749
961
    def entries(self):
750
962
        """Return list of (path, ie) for all entries except the root.
756
968
            kids = dir_ie.children.items()
757
969
            kids.sort()
758
970
            for name, ie in kids:
759
 
                child_path = os.path.join(dir_path, name)
 
971
                child_path = pathjoin(dir_path, name)
760
972
                accum.append((child_path, ie))
761
973
                if ie.kind == 'directory':
762
974
                    descend(ie, child_path)
763
975
 
764
 
        descend(self.root, '')
 
976
        descend(self.root, u'')
765
977
        return accum
766
978
 
767
 
 
768
979
    def directories(self):
769
980
        """Return (path, entry) pairs for all directories, including the root.
770
981
        """
776
987
            kids.sort()
777
988
 
778
989
            for name, child_ie in kids:
779
 
                child_path = os.path.join(parent_path, name)
 
990
                child_path = pathjoin(parent_path, name)
780
991
                descend(child_ie, child_path)
781
 
        descend(self.root, '')
 
992
        descend(self.root, u'')
782
993
        return accum
783
994
        
784
 
 
785
 
 
786
995
    def __contains__(self, file_id):
787
996
        """True if this entry contains a file with given id.
788
997
 
789
998
        >>> inv = Inventory()
790
999
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
791
 
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
 
1000
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
792
1001
        >>> '123' in inv
793
1002
        True
794
1003
        >>> '456' in inv
796
1005
        """
797
1006
        return file_id in self._byid
798
1007
 
799
 
 
800
1008
    def __getitem__(self, file_id):
801
1009
        """Return the entry for given file_id.
802
1010
 
803
1011
        >>> inv = Inventory()
804
1012
        >>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
805
 
        InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT')
 
1013
        InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
806
1014
        >>> inv['123123'].name
807
1015
        'hello.c'
808
1016
        """
809
1017
        try:
810
1018
            return self._byid[file_id]
811
1019
        except KeyError:
812
 
            if file_id == None:
 
1020
            if file_id is None:
813
1021
                raise BzrError("can't look up file_id None")
814
1022
            else:
815
1023
                raise BzrError("file_id {%s} not in inventory" % file_id)
816
1024
 
817
 
 
818
1025
    def get_file_kind(self, file_id):
819
1026
        return self._byid[file_id].kind
820
1027
 
821
1028
    def get_child(self, parent_id, filename):
822
1029
        return self[parent_id].children.get(filename)
823
1030
 
824
 
 
825
1031
    def add(self, entry):
826
1032
        """Add entry to inventory.
827
1033
 
841
1047
        except KeyError:
842
1048
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
843
1049
 
844
 
        if parent.children.has_key(entry.name):
 
1050
        if entry.name in parent.children:
845
1051
            raise BzrError("%s is already versioned" %
846
 
                    appendpath(self.id2path(parent.file_id), entry.name))
 
1052
                    pathjoin(self.id2path(parent.file_id), entry.name))
847
1053
 
848
1054
        self._byid[entry.file_id] = entry
849
1055
        parent.children[entry.name] = entry
850
1056
        return entry
851
1057
 
852
 
 
853
 
    def add_path(self, relpath, kind, file_id=None):
 
1058
    def add_path(self, relpath, kind, file_id=None, parent_id=None):
854
1059
        """Add entry from a path.
855
1060
 
856
1061
        The immediate parent must already be versioned.
857
1062
 
858
1063
        Returns the new entry object."""
859
 
        from bzrlib.branch import gen_file_id
860
1064
        
861
 
        parts = bzrlib.osutils.splitpath(relpath)
 
1065
        parts = osutils.splitpath(relpath)
 
1066
 
862
1067
        if len(parts) == 0:
863
 
            raise BzrError("cannot re-add root of inventory")
864
 
 
865
 
        if file_id == None:
866
 
            file_id = gen_file_id(relpath)
867
 
 
868
 
        parent_path = parts[:-1]
869
 
        parent_id = self.path2id(parent_path)
870
 
        if parent_id == None:
871
 
            raise NotVersionedError(parent_path)
872
 
 
873
 
        if kind == 'directory':
874
 
            ie = InventoryDirectory(file_id, parts[-1], parent_id)
875
 
        elif kind == 'file':
876
 
            ie = InventoryFile(file_id, parts[-1], parent_id)
877
 
        elif kind == 'symlink':
878
 
            ie = InventoryLink(file_id, parts[-1], parent_id)
 
1068
            if file_id is None:
 
1069
                file_id = bzrlib.workingtree.gen_root_id()
 
1070
            self.root = RootEntry(file_id)
 
1071
            self._byid = {self.root.file_id: self.root}
 
1072
            return
879
1073
        else:
880
 
            raise BzrError("unknown kind %r" % kind)
 
1074
            parent_path = parts[:-1]
 
1075
            parent_id = self.path2id(parent_path)
 
1076
            if parent_id is None:
 
1077
                raise NotVersionedError(path=parent_path)
 
1078
        ie = make_entry(kind, parts[-1], parent_id, file_id)
881
1079
        return self.add(ie)
882
1080
 
883
 
 
884
1081
    def __delitem__(self, file_id):
885
1082
        """Remove entry by id.
886
1083
 
887
1084
        >>> inv = Inventory()
888
1085
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
889
 
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
 
1086
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
890
1087
        >>> '123' in inv
891
1088
        True
892
1089
        >>> del inv['123']
895
1092
        """
896
1093
        ie = self[file_id]
897
1094
 
898
 
        assert self[ie.parent_id].children[ie.name] == ie
 
1095
        assert ie.parent_id is None or \
 
1096
            self[ie.parent_id].children[ie.name] == ie
899
1097
        
900
 
        # TODO: Test deleting all children; maybe hoist to a separate
901
 
        # deltree method?
902
 
        if ie.kind == 'directory':
903
 
            for cie in ie.children.values():
904
 
                del self[cie.file_id]
905
 
            del ie.children
906
 
 
907
1098
        del self._byid[file_id]
908
 
        del self[ie.parent_id].children[ie.name]
909
 
 
 
1099
        if ie.parent_id is not None:
 
1100
            del self[ie.parent_id].children[ie.name]
910
1101
 
911
1102
    def __eq__(self, other):
912
1103
        """Compare two sets by comparing their contents.
916
1107
        >>> i1 == i2
917
1108
        True
918
1109
        >>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
919
 
        InventoryFile('123', 'foo', parent_id='TREE_ROOT')
 
1110
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
920
1111
        >>> i1 == i2
921
1112
        False
922
1113
        >>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
923
 
        InventoryFile('123', 'foo', parent_id='TREE_ROOT')
 
1114
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
924
1115
        >>> i1 == i2
925
1116
        True
926
1117
        """
927
1118
        if not isinstance(other, Inventory):
928
1119
            return NotImplemented
929
1120
 
930
 
        if len(self._byid) != len(other._byid):
931
 
            # shortcut: obviously not the same
932
 
            return False
933
 
 
934
1121
        return self._byid == other._byid
935
1122
 
936
 
 
937
1123
    def __ne__(self, other):
938
1124
        return not self.__eq__(other)
939
1125
 
940
 
 
941
1126
    def __hash__(self):
942
1127
        raise ValueError('not hashable')
943
1128
 
 
1129
    def _iter_file_id_parents(self, file_id):
 
1130
        """Yield the parents of file_id up to the root."""
 
1131
        while file_id is not None:
 
1132
            try:
 
1133
                ie = self._byid[file_id]
 
1134
            except KeyError:
 
1135
                raise BzrError("file_id {%s} not found in inventory" % file_id)
 
1136
            yield ie
 
1137
            file_id = ie.parent_id
944
1138
 
945
1139
    def get_idpath(self, file_id):
946
1140
        """Return a list of file_ids for the path to an entry.
951
1145
        root directory as depth 1.
952
1146
        """
953
1147
        p = []
954
 
        while file_id != None:
955
 
            try:
956
 
                ie = self._byid[file_id]
957
 
            except KeyError:
958
 
                raise BzrError("file_id {%s} not found in inventory" % file_id)
959
 
            p.insert(0, ie.file_id)
960
 
            file_id = ie.parent_id
 
1148
        for parent in self._iter_file_id_parents(file_id):
 
1149
            p.insert(0, parent.file_id)
961
1150
        return p
962
1151
 
963
 
 
964
1152
    def id2path(self, file_id):
965
 
        """Return as a list the path to file_id.
 
1153
        """Return as a string the path to file_id.
966
1154
        
967
1155
        >>> i = Inventory()
968
1156
        >>> e = i.add(InventoryDirectory('src-id', 'src', ROOT_ID))
969
1157
        >>> e = i.add(InventoryFile('foo-id', 'foo.c', parent_id='src-id'))
970
 
        >>> print i.id2path('foo-id').replace(os.sep, '/')
 
1158
        >>> print i.id2path('foo-id')
971
1159
        src/foo.c
972
1160
        """
973
1161
        # get all names, skipping root
974
 
        p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
975
 
        return os.sep.join(p)
 
1162
        return '/'.join(reversed(
 
1163
            [parent.name for parent in 
 
1164
             self._iter_file_id_parents(file_id)][:-1]))
976
1165
            
977
 
 
978
 
 
979
1166
    def path2id(self, name):
980
1167
        """Walk down through directories to return entry of last component.
981
1168
 
985
1172
        This returns the entry of the last component in the path,
986
1173
        which may be either a file or a directory.
987
1174
 
988
 
        Returns None iff the path is not found.
 
1175
        Returns None IFF the path is not found.
989
1176
        """
990
1177
        if isinstance(name, types.StringTypes):
991
1178
            name = splitpath(name)
992
1179
 
993
 
        mutter("lookup path %r" % name)
 
1180
        # mutter("lookup path %r" % name)
994
1181
 
995
1182
        parent = self.root
996
1183
        for f in name:
1005
1192
 
1006
1193
        return parent.file_id
1007
1194
 
1008
 
 
1009
1195
    def has_filename(self, names):
1010
1196
        return bool(self.path2id(names))
1011
1197
 
1012
 
 
1013
1198
    def has_id(self, file_id):
1014
1199
        return self._byid.has_key(file_id)
1015
1200
 
1016
 
 
1017
1201
    def rename(self, file_id, new_parent_id, new_name):
1018
1202
        """Move a file within the inventory.
1019
1203
 
1044
1228
        file_ie.parent_id = new_parent_id
1045
1229
 
1046
1230
 
 
1231
def make_entry(kind, name, parent_id, file_id=None):
 
1232
    """Create an inventory entry.
 
1233
 
 
1234
    :param kind: the type of inventory entry to create.
 
1235
    :param name: the basename of the entry.
 
1236
    :param parent_id: the parent_id of the entry.
 
1237
    :param file_id: the file_id to use. if None, one will be created.
 
1238
    """
 
1239
    if file_id is None:
 
1240
        file_id = bzrlib.workingtree.gen_file_id(name)
 
1241
 
 
1242
    norm_name, can_access = osutils.normalized_filename(name)
 
1243
    if norm_name != name:
 
1244
        if can_access:
 
1245
            name = norm_name
 
1246
        else:
 
1247
            # TODO: jam 20060701 This would probably be more useful
 
1248
            #       if the error was raised with the full path
 
1249
            raise errors.InvalidNormalization(name)
 
1250
 
 
1251
    if kind == 'directory':
 
1252
        return InventoryDirectory(file_id, name, parent_id)
 
1253
    elif kind == 'file':
 
1254
        return InventoryFile(file_id, name, parent_id)
 
1255
    elif kind == 'symlink':
 
1256
        return InventoryLink(file_id, name, parent_id)
 
1257
    else:
 
1258
        raise BzrError("unknown kind %r" % kind)
1047
1259
 
1048
1260
 
1049
1261
_NAME_RE = None
1050
1262
 
1051
1263
def is_valid_name(name):
1052
1264
    global _NAME_RE
1053
 
    if _NAME_RE == None:
 
1265
    if _NAME_RE is None:
1054
1266
        _NAME_RE = re.compile(r'^[^/\\]+$')
1055
1267
        
1056
1268
    return bool(_NAME_RE.match(name))