/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
138 by mbp at sourcefrog
remove parallel tree from inventory;
1
# (C) 2005 Canonical Ltd
1 by mbp at sourcefrog
import from baz patch-364
2
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
1197 by Martin Pool
doc
18
# TODO: Maybe also keep the full path of the entry, and the children?
19
# But those depend on its position within a particular inventory, and
20
# it would be nice not to need to hold the backpointer here.
21
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
22
# This should really be an id randomly assigned when the tree is
23
# created, but it's not for now.
24
ROOT_ID = "TREE_ROOT"
25
26
1185.1.41 by Robert Collins
massive patch from Alexander Belchenko - many PEP8 fixes, removes unused function uuid
27
import os.path
28
import re
29
import sys
1399.1.6 by Robert Collins
move exporting functionality into inventory.py - uncovers bug in symlink support
30
import tarfile
1185.1.41 by Robert Collins
massive patch from Alexander Belchenko - many PEP8 fixes, removes unused function uuid
31
import types
1 by mbp at sourcefrog
import from baz patch-364
32
800 by Martin Pool
Merge John's import-speedup branch:
33
import bzrlib
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
34
from bzrlib.errors import BzrError, BzrCheckError
70 by mbp at sourcefrog
Prepare for smart recursive add.
35
1399.1.6 by Robert Collins
move exporting functionality into inventory.py - uncovers bug in symlink support
36
from bzrlib.osutils import (pumpfile, quotefn, splitpath, joinpath,
37
                            appendpath, sha_strings)
70 by mbp at sourcefrog
Prepare for smart recursive add.
38
from bzrlib.trace import mutter
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
39
from bzrlib.errors import NotVersionedError
1185.1.41 by Robert Collins
massive patch from Alexander Belchenko - many PEP8 fixes, removes unused function uuid
40
1 by mbp at sourcefrog
import from baz patch-364
41
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
42
class InventoryEntry(object):
1 by mbp at sourcefrog
import from baz patch-364
43
    """Description of a versioned file.
44
45
    An InventoryEntry has the following fields, which are also
46
    present in the XML inventory-entry element:
47
1197 by Martin Pool
doc
48
    file_id
49
50
    name
51
        (within the parent directory)
52
53
    parent_id
54
        file_id of the parent directory, or ROOT_ID
55
1092.2.21 by Robert Collins
convert name_version to revision in inventory entries
56
    revision
1398 by Robert Collins
integrate in Gustavos x-bit patch
57
        the revision_id in which this variation of this file was 
1092.2.22 by Robert Collins
text_version and name_version unification looking reasonable
58
        introduced.
1197 by Martin Pool
doc
59
1398 by Robert Collins
integrate in Gustavos x-bit patch
60
    executable
61
        Indicates that this file should be executable on systems
62
        that support it.
63
1197 by Martin Pool
doc
64
    text_sha1
65
        sha-1 of the text of the file
66
        
67
    text_size
68
        size in bytes of the text of the file
69
        
70
    (reading a version 4 tree created a text_id field.)
1 by mbp at sourcefrog
import from baz patch-364
71
72
    >>> i = Inventory()
73
    >>> i.path2id('')
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
74
    'TREE_ROOT'
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
75
    >>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
76
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
77
    >>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
78
    InventoryFile('2323', 'hello.c', parent_id='123')
1 by mbp at sourcefrog
import from baz patch-364
79
    >>> for j in i.iter_entries():
80
    ...   print j
81
    ... 
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
82
    ('src', InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
83
    ('src/hello.c', InventoryFile('2323', 'hello.c', parent_id='123'))
84
    >>> i.add(InventoryFile('2323', 'bye.c', '123'))
1 by mbp at sourcefrog
import from baz patch-364
85
    Traceback (most recent call last):
86
    ...
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
87
    BzrError: inventory already contains entry with id {2323}
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
88
    >>> i.add(InventoryFile('2324', 'bye.c', '123'))
89
    InventoryFile('2324', 'bye.c', parent_id='123')
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
90
    >>> i.add(InventoryDirectory('2325', 'wibble', '123'))
91
    InventoryDirectory('2325', 'wibble', parent_id='123')
1 by mbp at sourcefrog
import from baz patch-364
92
    >>> i.path2id('src/wibble')
93
    '2325'
94
    >>> '2325' in i
95
    True
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
96
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
97
    InventoryFile('2326', 'wibble.c', parent_id='2325')
1 by mbp at sourcefrog
import from baz patch-364
98
    >>> i['2326']
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
99
    InventoryFile('2326', 'wibble.c', parent_id='2325')
1185.1.40 by Robert Collins
Merge what applied of Alexander Belchenko's win32 patch.
100
    >>> for path, entry in i.iter_entries():
101
    ...     print path.replace('\\\\', '/')     # for win32 os.sep
102
    ...     assert i.path2id(path)
1 by mbp at sourcefrog
import from baz patch-364
103
    ... 
104
    src
105
    src/bye.c
106
    src/hello.c
107
    src/wibble
108
    src/wibble/wibble.c
1185.1.40 by Robert Collins
Merge what applied of Alexander Belchenko's win32 patch.
109
    >>> i.id2path('2326').replace('\\\\', '/')
1 by mbp at sourcefrog
import from baz patch-364
110
    'src/wibble/wibble.c'
111
    """
1197 by Martin Pool
doc
112
    
955 by Martin Pool
- use __slots__ on InventoryEntry; rather faster
113
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
1398 by Robert Collins
integrate in Gustavos x-bit patch
114
                 'text_id', 'parent_id', 'children', 'executable', 
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
115
                 'revision']
1185 by Martin Pool
- add xml round-trip test for revisions
116
1092.2.23 by Robert Collins
move inventory entry centric snapshot taking logic to inventory.py
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)
119
1399.1.3 by Robert Collins
move change detection for text and metadata from delta to entry.detect_changes
120
    def detect_changes(self, old_entry):
121
        """Return a (text_modified, meta_modified) from this to old_entry.
122
        
123
        _read_tree_state must have been called on self and old_entry prior to 
124
        calling detect_changes.
125
        """
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
126
        return False, False
1399.1.3 by Robert Collins
move change detection for text and metadata from delta to entry.detect_changes
127
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
128
    def diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
129
             output_to, reverse=False):
130
        """Perform a diff from this to to_entry.
131
132
        text_diff will be used for textual difference calculation.
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
133
        This is a template method, override _diff in child classes.
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
134
        """
135
        self._read_tree_state(tree.id2path(self.file_id), tree)
136
        if to_entry:
137
            # cannot diff from one kind to another - you must do a removal
138
            # and an addif they do not match.
139
            assert self.kind == to_entry.kind
140
            to_entry._read_tree_state(to_tree.id2path(to_entry.file_id),
141
                                      to_tree)
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
142
        self._diff(text_diff, from_label, tree, to_label, to_entry, to_tree,
143
                   output_to, reverse)
144
145
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
146
             output_to, reverse=False):
147
        """Perform a diff between two entries of the same kind."""
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
148
1399.1.6 by Robert Collins
move exporting functionality into inventory.py - uncovers bug in symlink support
149
    def get_tar_item(self, root, dp, now, tree):
1399.1.7 by Robert Collins
implement symlink exporting to tarballs
150
        """Get a tarfile item and a file stream for its content."""
1399.1.6 by Robert Collins
move exporting functionality into inventory.py - uncovers bug in symlink support
151
        item = tarfile.TarInfo(os.path.join(root, dp))
152
        # TODO: would be cool to actually set it to the timestamp of the
153
        # revision it was last changed
154
        item.mtime = now
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
155
        fileobj = self._put_in_tar(item, tree)
1399.1.6 by Robert Collins
move exporting functionality into inventory.py - uncovers bug in symlink support
156
        return item, fileobj
157
1399.1.5 by Robert Collins
move checking whether an entry stores text into inventory.py from fetch,py
158
    def has_text(self):
159
        """Return true if the object this entry represents has textual data.
160
161
        Note that textual data includes binary content.
162
        """
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
163
        return False
1399.1.5 by Robert Collins
move checking whether an entry stores text into inventory.py from fetch,py
164
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
165
    def __init__(self, file_id, name, parent_id, text_id=None):
1 by mbp at sourcefrog
import from baz patch-364
166
        """Create an InventoryEntry
167
        
168
        The filename must be a single component, relative to the
169
        parent directory; it cannot be a whole path or relative name.
170
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
171
        >>> e = InventoryFile('123', 'hello.c', ROOT_ID)
1 by mbp at sourcefrog
import from baz patch-364
172
        >>> e.name
173
        'hello.c'
174
        >>> e.file_id
175
        '123'
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
176
        >>> e = InventoryFile('123', 'src/hello.c', ROOT_ID)
1 by mbp at sourcefrog
import from baz patch-364
177
        Traceback (most recent call last):
395 by Martin Pool
- fix error raised from invalid InventoryEntry name
178
        BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
1 by mbp at sourcefrog
import from baz patch-364
179
        """
1183 by Martin Pool
- implement version 5 xml storage, and tests
180
        assert isinstance(name, basestring), name
376 by Martin Pool
- fix slow invariant check when reading in InventoryEntry objects
181
        if '/' in name or '\\' in name:
182
            raise BzrCheckError('InventoryEntry name %r is invalid' % name)
1 by mbp at sourcefrog
import from baz patch-364
183
        
1398 by Robert Collins
integrate in Gustavos x-bit patch
184
        self.executable = False
1092.2.21 by Robert Collins
convert name_version to revision in inventory entries
185
        self.revision = None
955 by Martin Pool
- use __slots__ on InventoryEntry; rather faster
186
        self.text_sha1 = None
187
        self.text_size = None
1 by mbp at sourcefrog
import from baz patch-364
188
        self.file_id = file_id
189
        self.name = name
190
        self.text_id = text_id
191
        self.parent_id = parent_id
1092.2.6 by Robert Collins
symlink support updated to work
192
        self.symlink_target = None
237 by mbp at sourcefrog
- Better assertions in InventoryEntry constructor
193
1399.1.2 by Robert Collins
push kind character creation into InventoryEntry and TreeEntry
194
    def kind_character(self):
195
        """Return a short kind indicator useful for appending to names."""
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
196
        raise BzrError('unknown kind %r' % self.kind)
1399.1.2 by Robert Collins
push kind character creation into InventoryEntry and TreeEntry
197
1399.1.3 by Robert Collins
move change detection for text and metadata from delta to entry.detect_changes
198
    known_kinds = ('file', 'directory', 'symlink', 'root_directory')
1 by mbp at sourcefrog
import from baz patch-364
199
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
200
    def _put_in_tar(self, item, tree):
201
        """populate item for stashing in a tar, and return the content stream.
202
203
        If no content is available, return None.
204
        """
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
205
        raise BzrError("don't know how to export {%s} of kind %r" %
206
                       (self.file_id, self.kind))
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
207
1399.1.6 by Robert Collins
move exporting functionality into inventory.py - uncovers bug in symlink support
208
    def put_on_disk(self, dest, dp, tree):
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
209
        """Create a representation of self on disk in the prefix dest.
210
        
211
        This is a template method - implement _put_on_disk in subclasses.
212
        """
1399.1.6 by Robert Collins
move exporting functionality into inventory.py - uncovers bug in symlink support
213
        fullpath = appendpath(dest, dp)
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
214
        self._put_on_disk(fullpath, tree)
215
        mutter("  export {%s} kind %s to %s" % (self.file_id, self.kind, fullpath))
216
217
    def _put_on_disk(self, fullpath, tree):
218
        """Put this entry onto disk at fullpath, from tree tree."""
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
219
        raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
1399.1.6 by Robert Collins
move exporting functionality into inventory.py - uncovers bug in symlink support
220
156 by mbp at sourcefrog
new "directories" command
221
    def sorted_children(self):
222
        l = self.children.items()
223
        l.sort()
224
        return l
225
1399.1.1 by Robert Collins
move checks for versionability of file kinds into InventoryEntry
226
    @staticmethod
227
    def versionable_kind(kind):
228
        return kind in ('file', 'directory', 'symlink')
229
1092.2.20 by Robert Collins
symlink and weaves, whaddya know
230
    def check(self, checker, rev_id, inv, tree):
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
231
        """Check this inventory entry is intact.
232
233
        This is a template method, override _check for kind specific
234
        tests.
235
        """
1092.2.20 by Robert Collins
symlink and weaves, whaddya know
236
        if self.parent_id != None:
237
            if not inv.has_id(self.parent_id):
238
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
239
                        % (self.parent_id, rev_id))
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
240
        self._check(checker, rev_id, tree)
241
242
    def _check(self, checker, rev_id, tree):
243
        """Check this inventory entry for kind specific errors."""
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
244
        raise BzrCheckError('unknown entry kind %r in revision {%s}' % 
245
                            (self.kind, rev_id))
1092.2.20 by Robert Collins
symlink and weaves, whaddya know
246
156 by mbp at sourcefrog
new "directories" command
247
1 by mbp at sourcefrog
import from baz patch-364
248
    def copy(self):
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
249
        """Clone this inventory entry."""
250
        raise NotImplementedError
1 by mbp at sourcefrog
import from baz patch-364
251
1092.2.23 by Robert Collins
move inventory entry centric snapshot taking logic to inventory.py
252
    def _get_snapshot_change(self, previous_entries):
253
        if len(previous_entries) > 1:
254
            return 'merged'
255
        elif len(previous_entries) == 0:
256
            return 'added'
257
        else:
258
            return 'modified/renamed/reparented'
1 by mbp at sourcefrog
import from baz patch-364
259
260
    def __repr__(self):
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
261
        return ("%s(%r, %r, parent_id=%r)"
1 by mbp at sourcefrog
import from baz patch-364
262
                % (self.__class__.__name__,
263
                   self.file_id,
264
                   self.name,
265
                   self.parent_id))
266
1092.2.23 by Robert Collins
move inventory entry centric snapshot taking logic to inventory.py
267
    def snapshot(self, revision, path, previous_entries, work_tree, 
268
                 weave_store):
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
269
        """Make a snapshot of this entry which may or may not have changed.
1092.2.23 by Robert Collins
move inventory entry centric snapshot taking logic to inventory.py
270
        
271
        This means that all its fields are populated, that it has its
272
        text stored in the text store or weave.
273
        """
274
        mutter('new parents of %s are %r', path, previous_entries)
275
        self._read_tree_state(path, work_tree)
276
        if len(previous_entries) == 1:
277
            # cannot be unchanged unless there is only one parent file rev.
278
            parent_ie = previous_entries.values()[0]
279
            if self._unchanged(path, parent_ie, work_tree):
280
                mutter("found unchanged entry")
281
                self.revision = parent_ie.revision
282
                return "unchanged"
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
283
        return self.snapshot_revision(revision, previous_entries, 
284
                                      work_tree, weave_store)
285
286
    def snapshot_revision(self, revision, previous_entries, work_tree,
287
                          weave_store):
288
        """Record this revision unconditionally."""
1092.2.23 by Robert Collins
move inventory entry centric snapshot taking logic to inventory.py
289
        mutter('new revision for {%s}', self.file_id)
290
        self.revision = revision
291
        change = self._get_snapshot_change(previous_entries)
292
        return change
293
294
    def _snapshot_text(self, file_parents, work_tree, weave_store): 
295
        mutter('storing file {%s} in revision {%s}',
296
               self.file_id, self.revision)
297
        # special case to avoid diffing on renames or 
298
        # reparenting
299
        if (len(file_parents) == 1
300
            and self.text_sha1 == file_parents.values()[0].text_sha1
301
            and self.text_size == file_parents.values()[0].text_size):
302
            previous_ie = file_parents.values()[0]
303
            weave_store.add_identical_text(
304
                self.file_id, previous_ie.revision, 
305
                self.revision, file_parents)
306
        else:
307
            new_lines = work_tree.get_file(self.file_id).readlines()
308
            self._add_text_to_weave(new_lines, file_parents, weave_store)
309
            self.text_sha1 = sha_strings(new_lines)
310
            self.text_size = sum(map(len, new_lines))
311
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
312
    def __eq__(self, other):
1 by mbp at sourcefrog
import from baz patch-364
313
        if not isinstance(other, InventoryEntry):
314
            return NotImplemented
315
1398 by Robert Collins
integrate in Gustavos x-bit patch
316
        return ((self.file_id == other.file_id)
317
                and (self.name == other.name)
318
                and (other.symlink_target == self.symlink_target)
319
                and (self.text_sha1 == other.text_sha1)
320
                and (self.text_size == other.text_size)
321
                and (self.text_id == other.text_id)
322
                and (self.parent_id == other.parent_id)
323
                and (self.kind == other.kind)
324
                and (self.revision == other.revision)
325
                and (self.executable == other.executable)
326
                )
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
327
328
    def __ne__(self, other):
329
        return not (self == other)
330
331
    def __hash__(self):
332
        raise ValueError('not hashable')
1 by mbp at sourcefrog
import from baz patch-364
333
1092.2.23 by Robert Collins
move inventory entry centric snapshot taking logic to inventory.py
334
    def _unchanged(self, path, previous_ie, work_tree):
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
335
        """Has this entry changed relative to previous_ie.
336
337
        This method should be overriden in child classes.
338
        """
1092.2.22 by Robert Collins
text_version and name_version unification looking reasonable
339
        compatible = True
340
        # different inv parent
341
        if previous_ie.parent_id != self.parent_id:
342
            compatible = False
343
        # renamed
344
        elif previous_ie.name != self.name:
345
            compatible = False
346
        return compatible
347
1092.2.23 by Robert Collins
move inventory entry centric snapshot taking logic to inventory.py
348
    def _read_tree_state(self, path, work_tree):
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
349
        """Populate fields in the inventory entry from the given tree.
350
        
351
        Note that this should be modified to be a noop on virtual trees
352
        as all entries created there are prepopulated.
353
        """
1092.2.23 by Robert Collins
move inventory entry centric snapshot taking logic to inventory.py
354
1 by mbp at sourcefrog
import from baz patch-364
355
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
356
class RootEntry(InventoryEntry):
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
357
358
    def _check(self, checker, rev_id, tree):
359
        """See InventoryEntry._check"""
360
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
361
    def __init__(self, file_id):
362
        self.file_id = file_id
363
        self.children = {}
364
        self.kind = 'root_directory'
365
        self.parent_id = None
156 by mbp at sourcefrog
new "directories" command
366
        self.name = ''
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
367
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
368
    def __eq__(self, other):
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
369
        if not isinstance(other, RootEntry):
370
            return NotImplemented
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
371
        
372
        return (self.file_id == other.file_id) \
373
               and (self.children == other.children)
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
374
375
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
376
class InventoryDirectory(InventoryEntry):
377
    """A directory in an inventory."""
378
379
    def _check(self, checker, rev_id, tree):
380
        """See InventoryEntry._check"""
381
        if self.text_sha1 != None or self.text_size != None or self.text_id != None:
382
            raise BzrCheckError('directory {%s} has text in revision {%s}'
383
                                % (self.file_id, rev_id))
384
385
    def copy(self):
386
        other = InventoryDirectory(self.file_id, self.name, self.parent_id)
387
        other.revision = self.revision
388
        # note that children are *not* copied; they're pulled across when
389
        # others are added
390
        return other
391
392
    def __init__(self, file_id, name, parent_id):
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
393
        super(InventoryDirectory, self).__init__(file_id, name, parent_id)
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
394
        self.children = {}
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
395
        self.kind = 'directory'
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
396
397
    def kind_character(self):
398
        """See InventoryEntry.kind_character."""
399
        return '/'
400
401
    def _put_in_tar(self, item, tree):
402
        """See InventoryEntry._put_in_tar."""
403
        item.type = tarfile.DIRTYPE
404
        fileobj = None
405
        item.name += '/'
406
        item.size = 0
407
        item.mode = 0755
408
        return fileobj
409
410
    def _put_on_disk(self, fullpath, tree):
411
        """See InventoryEntry._put_on_disk."""
412
        os.mkdir(fullpath)
413
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
414
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
415
class InventoryFile(InventoryEntry):
416
    """A file in an inventory."""
417
418
    def _check(self, checker, rev_id, tree):
419
        """See InventoryEntry._check"""
420
        revision = self.revision
421
        t = (self.file_id, revision)
422
        if t in checker.checked_texts:
423
            prev_sha = checker.checked_texts[t] 
424
            if prev_sha != self.text_sha1:
425
                raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
426
                                    (self.file_id, rev_id))
427
            else:
428
                checker.repeated_text_cnt += 1
429
                return
430
        mutter('check version {%s} of {%s}', rev_id, self.file_id)
431
        file_lines = tree.get_file_lines(self.file_id)
432
        checker.checked_text_cnt += 1 
433
        if self.text_size != sum(map(len, file_lines)):
434
            raise BzrCheckError('text {%s} wrong size' % self.text_id)
435
        if self.text_sha1 != sha_strings(file_lines):
436
            raise BzrCheckError('text {%s} wrong sha1' % self.text_id)
437
        checker.checked_texts[t] = self.text_sha1
438
439
    def copy(self):
440
        other = InventoryFile(self.file_id, self.name, self.parent_id)
441
        other.executable = self.executable
442
        other.text_id = self.text_id
443
        other.text_sha1 = self.text_sha1
444
        other.text_size = self.text_size
445
        other.revision = self.revision
446
        return other
447
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
448
    def detect_changes(self, old_entry):
449
        """See InventoryEntry.detect_changes."""
450
        assert self.text_sha1 != None
451
        assert old_entry.text_sha1 != None
452
        text_modified = (self.text_sha1 != old_entry.text_sha1)
453
        meta_modified = (self.executable != old_entry.executable)
454
        return text_modified, meta_modified
455
456
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
457
             output_to, reverse=False):
458
        """See InventoryEntry._diff."""
459
        from_text = tree.get_file(self.file_id).readlines()
460
        if to_entry:
461
            to_text = to_tree.get_file(to_entry.file_id).readlines()
462
        else:
463
            to_text = []
464
        if not reverse:
465
            text_diff(from_label, from_text,
466
                      to_label, to_text, output_to)
467
        else:
468
            text_diff(to_label, to_text,
469
                      from_label, from_text, output_to)
470
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
471
    def has_text(self):
472
        """See InventoryEntry.has_text."""
473
        return True
474
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
475
    def __init__(self, file_id, name, parent_id):
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
476
        super(InventoryFile, self).__init__(file_id, name, parent_id)
477
        self.kind = 'file'
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
478
479
    def kind_character(self):
480
        """See InventoryEntry.kind_character."""
481
        return ''
482
483
    def _put_in_tar(self, item, tree):
484
        """See InventoryEntry._put_in_tar."""
485
        item.type = tarfile.REGTYPE
486
        fileobj = tree.get_file(self.file_id)
487
        item.size = self.text_size
488
        if tree.is_executable(self.file_id):
489
            item.mode = 0755
490
        else:
491
            item.mode = 0644
492
        return fileobj
493
494
    def _put_on_disk(self, fullpath, tree):
495
        """See InventoryEntry._put_on_disk."""
496
        pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
497
        if tree.is_executable(self.file_id):
498
            os.chmod(fullpath, 0755)
499
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
500
    def _read_tree_state(self, path, work_tree):
501
        """See InventoryEntry._read_tree_state."""
502
        self.text_sha1 = work_tree.get_file_sha1(self.file_id)
503
        self.executable = work_tree.is_executable(self.file_id)
504
505
    def snapshot_revision(self, revision, previous_entries, work_tree,
506
                          weave_store):
507
        """See InventoryEntry.snapshot_revision."""
508
        change = super(InventoryFile, self).snapshot_revision(revision, 
509
            previous_entries, work_tree, weave_store)
510
        self._snapshot_text(previous_entries, work_tree, weave_store)
511
        return change
512
513
    def _unchanged(self, path, previous_ie, work_tree):
514
        """See InventoryEntry._unchanged."""
515
        compatible = super(InventoryFile, self)._unchanged(path, previous_ie, 
516
            work_tree)
517
        if self.text_sha1 != previous_ie.text_sha1:
518
            compatible = False
519
        else:
520
            # FIXME: 20050930 probe for the text size when getting sha1
521
            # in _read_tree_state
522
            self.text_size = previous_ie.text_size
523
        return compatible
524
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
525
526
class InventoryLink(InventoryEntry):
527
    """A file in an inventory."""
528
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
529
    __slots__ = ['symlink_target']
530
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
531
    def _check(self, checker, rev_id, tree):
532
        """See InventoryEntry._check"""
533
        if self.text_sha1 != None or self.text_size != None or self.text_id != None:
534
            raise BzrCheckError('symlink {%s} has text in revision {%s}'
535
                    % (self.file_id, rev_id))
536
        if self.symlink_target == None:
537
            raise BzrCheckError('symlink {%s} has no target in revision {%s}'
538
                    % (self.file_id, rev_id))
539
540
    def copy(self):
541
        other = InventoryLink(self.file_id, self.name, self.parent_id)
542
        other.symlink_target = self.symlink_target
543
        other.revision = self.revision
544
        return other
545
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
546
    def detect_changes(self, old_entry):
547
        """See InventoryEntry.detect_changes."""
548
        # FIXME: which _modified field should we use ? RBC 20051003
549
        text_modified = (self.symlink_target != old_entry.symlink_target)
550
        if text_modified:
551
            mutter("    symlink target changed")
552
        meta_modified = False
553
        return text_modified, meta_modified
554
555
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
556
             output_to, reverse=False):
557
        """See InventoryEntry._diff."""
558
        from_text = self.symlink_target
559
        if to_entry is not None:
560
            to_text = to_entry.symlink_target
561
            if reverse:
562
                temp = from_text
563
                from_text = to_text
564
                to_text = temp
565
            print >>output_to, '=== target changed %r => %r' % (from_text, to_text)
566
        else:
567
            if not reverse:
568
                print >>output_to, '=== target was %r' % self.symlink_target
569
            else:
570
                print >>output_to, '=== target is %r' % self.symlink_target
571
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
572
    def __init__(self, file_id, name, parent_id):
573
        super(InventoryLink, self).__init__(file_id, name, parent_id)
574
        self.kind = 'symlink'
575
576
    def kind_character(self):
577
        """See InventoryEntry.kind_character."""
578
        return ''
579
580
    def _put_in_tar(self, item, tree):
581
        """See InventoryEntry._put_in_tar."""
582
        iterm.type = tarfile.SYMTYPE
583
        fileobj = None
584
        item.size = 0
585
        item.mode = 0755
586
        item.linkname = self.symlink_target
587
        return fileobj
588
589
    def _put_on_disk(self, fullpath, tree):
590
        """See InventoryEntry._put_on_disk."""
591
        try:
592
            os.symlink(self.symlink_target, fullpath)
593
        except OSError,e:
594
            raise BzrError("Failed to create symlink %r -> %r, error: %s" % (fullpath, self.symlink_target, e))
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
595
1399.1.11 by Robert Collins
finish off nearly all of the kind separations
596
    def _read_tree_state(self, path, work_tree):
597
        """See InventoryEntry._read_tree_state."""
598
        self.symlink_target = work_tree.get_symlink_target(self.file_id)
599
600
    def _unchanged(self, path, previous_ie, work_tree):
601
        """See InventoryEntry._unchanged."""
602
        compatible = super(InventoryLink, self)._unchanged(path, previous_ie, 
603
            work_tree)
604
        if self.symlink_target != previous_ie.symlink_target:
605
            compatible = False
606
        return compatible
607
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
608
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
609
class Inventory(object):
1 by mbp at sourcefrog
import from baz patch-364
610
    """Inventory of versioned files in a tree.
611
240 by mbp at sourcefrog
doc
612
    This describes which file_id is present at each point in the tree,
613
    and possibly the SHA-1 or other information about the file.
614
    Entries can be looked up either by path or by file_id.
1 by mbp at sourcefrog
import from baz patch-364
615
616
    The inventory represents a typical unix file tree, with
617
    directories containing files and subdirectories.  We never store
618
    the full path to a file, because renaming a directory implicitly
619
    moves all of its contents.  This class internally maintains a
620
    lookup tree that allows the children under a directory to be
621
    returned quickly.
622
623
    InventoryEntry objects must not be modified after they are
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
624
    inserted, other than through the Inventory API.
1 by mbp at sourcefrog
import from baz patch-364
625
626
    >>> inv = Inventory()
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
627
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
628
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT')
1 by mbp at sourcefrog
import from baz patch-364
629
    >>> inv['123-123'].name
630
    'hello.c'
631
632
    May be treated as an iterator or set to look up file ids:
633
    
634
    >>> bool(inv.path2id('hello.c'))
635
    True
636
    >>> '123-123' in inv
637
    True
638
639
    May also look up by name:
640
641
    >>> [x[0] for x in inv.iter_entries()]
642
    ['hello.c']
909 by Martin Pool
- merge John's code to give the tree root an explicit file id
643
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
644
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
645
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678')
1 by mbp at sourcefrog
import from baz patch-364
646
    """
909 by Martin Pool
- merge John's code to give the tree root an explicit file id
647
    def __init__(self, root_id=ROOT_ID):
1 by mbp at sourcefrog
import from baz patch-364
648
        """Create or read an inventory.
649
650
        If a working directory is specified, the inventory is read
651
        from there.  If the file is specified, read from that. If not,
652
        the inventory is created empty.
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
653
654
        The inventory is created with a default root directory, with
655
        an id of None.
1 by mbp at sourcefrog
import from baz patch-364
656
        """
1185.11.5 by John Arbash Meinel
Merged up-to-date against mainline, still broken.
657
        # We are letting Branch.initialize() create a unique inventory
909 by Martin Pool
- merge John's code to give the tree root an explicit file id
658
        # root id. Rather than generating a random one here.
659
        #if root_id is None:
660
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
661
        self.root = RootEntry(root_id)
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
662
        self._byid = {self.root.file_id: self.root}
1 by mbp at sourcefrog
import from baz patch-364
663
664
1189 by Martin Pool
- BROKEN: partial support for commit into weave
665
    def copy(self):
666
        other = Inventory(self.root.file_id)
1206 by Martin Pool
- fix bug in Inventory.copy()
667
        # copy recursively so we know directories will be added before
668
        # their children.  There are more efficient ways than this...
669
        for path, entry in self.iter_entries():
1189 by Martin Pool
- BROKEN: partial support for commit into weave
670
            if entry == self.root:
671
                continue
672
            other.add(entry.copy())
673
        return other
674
675
1 by mbp at sourcefrog
import from baz patch-364
676
    def __iter__(self):
677
        return iter(self._byid)
678
679
680
    def __len__(self):
681
        """Returns number of entries."""
682
        return len(self._byid)
683
684
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
685
    def iter_entries(self, from_dir=None):
1 by mbp at sourcefrog
import from baz patch-364
686
        """Return (path, entry) pairs, in order by name."""
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
687
        if from_dir == None:
688
            assert self.root
689
            from_dir = self.root
690
        elif isinstance(from_dir, basestring):
691
            from_dir = self._byid[from_dir]
692
            
693
        kids = from_dir.children.items()
1 by mbp at sourcefrog
import from baz patch-364
694
        kids.sort()
695
        for name, ie in kids:
696
            yield name, ie
697
            if ie.kind == 'directory':
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
698
                for cn, cie in self.iter_entries(from_dir=ie.file_id):
271 by Martin Pool
- Windows path fixes
699
                    yield os.path.join(name, cn), cie
555 by Martin Pool
- New Inventory.entries() method
700
701
702
    def entries(self):
703
        """Return list of (path, ie) for all entries except the root.
704
705
        This may be faster than iter_entries.
706
        """
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
707
        accum = []
708
        def descend(dir_ie, dir_path):
556 by Martin Pool
- fix up Inventory.entries()
709
            kids = dir_ie.children.items()
555 by Martin Pool
- New Inventory.entries() method
710
            kids.sort()
711
            for name, ie in kids:
712
                child_path = os.path.join(dir_path, name)
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
713
                accum.append((child_path, ie))
555 by Martin Pool
- New Inventory.entries() method
714
                if ie.kind == 'directory':
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
715
                    descend(ie, child_path)
555 by Martin Pool
- New Inventory.entries() method
716
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
717
        descend(self.root, '')
718
        return accum
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
719
720
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
721
    def directories(self):
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
722
        """Return (path, entry) pairs for all directories, including the root.
1 by mbp at sourcefrog
import from baz patch-364
723
        """
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
724
        accum = []
725
        def descend(parent_ie, parent_path):
726
            accum.append((parent_path, parent_ie))
156 by mbp at sourcefrog
new "directories" command
727
            
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
728
            kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
729
            kids.sort()
156 by mbp at sourcefrog
new "directories" command
730
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
731
            for name, child_ie in kids:
732
                child_path = os.path.join(parent_path, name)
733
                descend(child_ie, child_path)
734
        descend(self.root, '')
735
        return accum
1 by mbp at sourcefrog
import from baz patch-364
736
        
737
738
739
    def __contains__(self, file_id):
740
        """True if this entry contains a file with given id.
741
742
        >>> inv = Inventory()
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
743
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
744
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
1 by mbp at sourcefrog
import from baz patch-364
745
        >>> '123' in inv
746
        True
747
        >>> '456' in inv
748
        False
749
        """
750
        return file_id in self._byid
751
752
753
    def __getitem__(self, file_id):
754
        """Return the entry for given file_id.
755
756
        >>> inv = Inventory()
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
757
        >>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
758
        InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT')
1 by mbp at sourcefrog
import from baz patch-364
759
        >>> inv['123123'].name
760
        'hello.c'
761
        """
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
762
        try:
763
            return self._byid[file_id]
764
        except KeyError:
380 by Martin Pool
- Slight optimization for Inventory.__getitem__
765
            if file_id == None:
766
                raise BzrError("can't look up file_id None")
767
            else:
768
                raise BzrError("file_id {%s} not in inventory" % file_id)
1 by mbp at sourcefrog
import from baz patch-364
769
770
460 by Martin Pool
- new testing command compare-trees
771
    def get_file_kind(self, file_id):
772
        return self._byid[file_id].kind
773
138 by mbp at sourcefrog
remove parallel tree from inventory;
774
    def get_child(self, parent_id, filename):
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
775
        return self[parent_id].children.get(filename)
138 by mbp at sourcefrog
remove parallel tree from inventory;
776
777
1 by mbp at sourcefrog
import from baz patch-364
778
    def add(self, entry):
779
        """Add entry to inventory.
780
781
        To add  a file to a branch ready to be committed, use Branch.add,
1180 by Martin Pool
- start splitting code for xml (de)serialization away from objects
782
        which calls this.
783
784
        Returns the new entry object.
785
        """
139 by mbp at sourcefrog
simplified/faster Inventory.add
786
        if entry.file_id in self._byid:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
787
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1 by mbp at sourcefrog
import from baz patch-364
788
909 by Martin Pool
- merge John's code to give the tree root an explicit file id
789
        if entry.parent_id == ROOT_ID or entry.parent_id is None:
790
            entry.parent_id = self.root.file_id
791
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
792
        try:
793
            parent = self._byid[entry.parent_id]
794
        except KeyError:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
795
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
139 by mbp at sourcefrog
simplified/faster Inventory.add
796
797
        if parent.children.has_key(entry.name):
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
798
            raise BzrError("%s is already versioned" %
140 by mbp at sourcefrog
fix error message for repeated add
799
                    appendpath(self.id2path(parent.file_id), entry.name))
1 by mbp at sourcefrog
import from baz patch-364
800
801
        self._byid[entry.file_id] = entry
139 by mbp at sourcefrog
simplified/faster Inventory.add
802
        parent.children[entry.name] = entry
1092.1.30 by Robert Collins
change smart_add reporting of added files to callback with the entry, and change the inventory.add signature to return the added entry
803
        return entry
1 by mbp at sourcefrog
import from baz patch-364
804
805
70 by mbp at sourcefrog
Prepare for smart recursive add.
806
    def add_path(self, relpath, kind, file_id=None):
807
        """Add entry from a path.
808
1180 by Martin Pool
- start splitting code for xml (de)serialization away from objects
809
        The immediate parent must already be versioned.
810
811
        Returns the new entry object."""
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
812
        from bzrlib.branch import gen_file_id
1015 by Martin Pool
- fix circular imports
813
        
70 by mbp at sourcefrog
Prepare for smart recursive add.
814
        parts = bzrlib.osutils.splitpath(relpath)
815
        if len(parts) == 0:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
816
            raise BzrError("cannot re-add root of inventory")
70 by mbp at sourcefrog
Prepare for smart recursive add.
817
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
818
        if file_id == None:
800 by Martin Pool
Merge John's import-speedup branch:
819
            file_id = gen_file_id(relpath)
70 by mbp at sourcefrog
Prepare for smart recursive add.
820
753 by Martin Pool
- new exception NotVersionedError
821
        parent_path = parts[:-1]
822
        parent_id = self.path2id(parent_path)
823
        if parent_id == None:
824
            raise NotVersionedError(parent_path)
825
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
826
        if kind == 'directory':
827
            ie = InventoryDirectory(file_id, parts[-1], parent_id)
1399.1.9 by Robert Collins
factor out file related logic from InventoryEntry to InventoryFile
828
        elif kind == 'file':
829
            ie = InventoryFile(file_id, parts[-1], parent_id)
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
830
        elif kind == 'symlink':
831
            ie = InventoryLink(file_id, parts[-1], parent_id)
1399.1.8 by Robert Collins
factor out inventory directory logic into 'InventoryDirectory' class
832
        else:
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
833
            raise BzrError("unknown kind %r" % kind)
70 by mbp at sourcefrog
Prepare for smart recursive add.
834
        return self.add(ie)
835
836
1 by mbp at sourcefrog
import from baz patch-364
837
    def __delitem__(self, file_id):
838
        """Remove entry by id.
839
840
        >>> inv = Inventory()
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
841
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
842
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
1 by mbp at sourcefrog
import from baz patch-364
843
        >>> '123' in inv
844
        True
845
        >>> del inv['123']
846
        >>> '123' in inv
847
        False
848
        """
849
        ie = self[file_id]
850
138 by mbp at sourcefrog
remove parallel tree from inventory;
851
        assert self[ie.parent_id].children[ie.name] == ie
1 by mbp at sourcefrog
import from baz patch-364
852
        
853
        # TODO: Test deleting all children; maybe hoist to a separate
854
        # deltree method?
855
        if ie.kind == 'directory':
138 by mbp at sourcefrog
remove parallel tree from inventory;
856
            for cie in ie.children.values():
1 by mbp at sourcefrog
import from baz patch-364
857
                del self[cie.file_id]
138 by mbp at sourcefrog
remove parallel tree from inventory;
858
            del ie.children
1 by mbp at sourcefrog
import from baz patch-364
859
860
        del self._byid[file_id]
138 by mbp at sourcefrog
remove parallel tree from inventory;
861
        del self[ie.parent_id].children[ie.name]
1 by mbp at sourcefrog
import from baz patch-364
862
863
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
864
    def __eq__(self, other):
1 by mbp at sourcefrog
import from baz patch-364
865
        """Compare two sets by comparing their contents.
866
867
        >>> i1 = Inventory()
868
        >>> i2 = Inventory()
869
        >>> i1 == i2
870
        True
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
871
        >>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
872
        InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1 by mbp at sourcefrog
import from baz patch-364
873
        >>> i1 == i2
874
        False
1399.1.10 by Robert Collins
remove kind from the InventoryEntry constructor - only child classes should be created now
875
        >>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
876
        InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1 by mbp at sourcefrog
import from baz patch-364
877
        >>> i1 == i2
878
        True
879
        """
880
        if not isinstance(other, Inventory):
881
            return NotImplemented
882
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
883
        if len(self._byid) != len(other._byid):
543 by Martin Pool
- More cleanups for set type
884
            # shortcut: obviously not the same
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
885
            return False
886
887
        return self._byid == other._byid
888
889
890
    def __ne__(self, other):
1249 by Martin Pool
- improvements to weave commit [broken]
891
        return not self.__eq__(other)
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
892
893
894
    def __hash__(self):
895
        raise ValueError('not hashable')
896
1 by mbp at sourcefrog
import from baz patch-364
897
172 by mbp at sourcefrog
- clearer check against attempts to introduce directory loops in the inventory
898
    def get_idpath(self, file_id):
899
        """Return a list of file_ids for the path to an entry.
900
901
        The list contains one element for each directory followed by
902
        the id of the file itself.  So the length of the returned list
903
        is equal to the depth of the file in the tree, counting the
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
904
        root directory as depth 1.
172 by mbp at sourcefrog
- clearer check against attempts to introduce directory loops in the inventory
905
        """
906
        p = []
907
        while file_id != None:
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
908
            try:
909
                ie = self._byid[file_id]
910
            except KeyError:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
911
                raise BzrError("file_id {%s} not found in inventory" % file_id)
172 by mbp at sourcefrog
- clearer check against attempts to introduce directory loops in the inventory
912
            p.insert(0, ie.file_id)
913
            file_id = ie.parent_id
914
        return p
915
916
1 by mbp at sourcefrog
import from baz patch-364
917
    def id2path(self, file_id):
918
        """Return as a list the path to file_id."""
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
919
920
        # get all names, skipping root
922 by Martin Pool
- optimization for Inventory.id2path; access byid map directly rather than
921
        p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
271 by Martin Pool
- Windows path fixes
922
        return os.sep.join(p)
1 by mbp at sourcefrog
import from baz patch-364
923
            
924
925
926
    def path2id(self, name):
927
        """Walk down through directories to return entry of last component.
928
929
        names may be either a list of path components, or a single
930
        string, in which case it is automatically split.
931
932
        This returns the entry of the last component in the path,
933
        which may be either a file or a directory.
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
934
935
        Returns None iff the path is not found.
1 by mbp at sourcefrog
import from baz patch-364
936
        """
70 by mbp at sourcefrog
Prepare for smart recursive add.
937
        if isinstance(name, types.StringTypes):
938
            name = splitpath(name)
1 by mbp at sourcefrog
import from baz patch-364
939
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
940
        mutter("lookup path %r" % name)
941
942
        parent = self.root
70 by mbp at sourcefrog
Prepare for smart recursive add.
943
        for f in name:
1 by mbp at sourcefrog
import from baz patch-364
944
            try:
138 by mbp at sourcefrog
remove parallel tree from inventory;
945
                cie = parent.children[f]
1 by mbp at sourcefrog
import from baz patch-364
946
                assert cie.name == f
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
947
                assert cie.parent_id == parent.file_id
138 by mbp at sourcefrog
remove parallel tree from inventory;
948
                parent = cie
1 by mbp at sourcefrog
import from baz patch-364
949
            except KeyError:
950
                # or raise an error?
951
                return None
952
138 by mbp at sourcefrog
remove parallel tree from inventory;
953
        return parent.file_id
1 by mbp at sourcefrog
import from baz patch-364
954
955
956
    def has_filename(self, names):
957
        return bool(self.path2id(names))
958
959
960
    def has_id(self, file_id):
961
        return self._byid.has_key(file_id)
962
963
160 by mbp at sourcefrog
- basic support for moving files to different directories - have not done support for renaming them yet, but should be straightforward - some tests, but many cases are not handled yet i think
964
    def rename(self, file_id, new_parent_id, new_name):
965
        """Move a file within the inventory.
966
967
        This can change either the name, or the parent, or both.
968
969
        This does not move the working file."""
970
        if not is_valid_name(new_name):
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
971
            raise BzrError("not an acceptable filename: %r" % new_name)
160 by mbp at sourcefrog
- basic support for moving files to different directories - have not done support for renaming them yet, but should be straightforward - some tests, but many cases are not handled yet i think
972
973
        new_parent = self._byid[new_parent_id]
974
        if new_name in new_parent.children:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
975
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
160 by mbp at sourcefrog
- basic support for moving files to different directories - have not done support for renaming them yet, but should be straightforward - some tests, but many cases are not handled yet i think
976
172 by mbp at sourcefrog
- clearer check against attempts to introduce directory loops in the inventory
977
        new_parent_idpath = self.get_idpath(new_parent_id)
978
        if file_id in new_parent_idpath:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
979
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
172 by mbp at sourcefrog
- clearer check against attempts to introduce directory loops in the inventory
980
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
981
160 by mbp at sourcefrog
- basic support for moving files to different directories - have not done support for renaming them yet, but should be straightforward - some tests, but many cases are not handled yet i think
982
        file_ie = self._byid[file_id]
983
        old_parent = self._byid[file_ie.parent_id]
984
985
        # TODO: Don't leave things messed up if this fails
986
987
        del old_parent.children[file_ie.name]
988
        new_parent.children[new_name] = file_ie
989
        
990
        file_ie.name = new_name
991
        file_ie.parent_id = new_parent_id
992
993
994
995
1077 by Martin Pool
- avoid compiling REs at module load time
996
_NAME_RE = None
160 by mbp at sourcefrog
- basic support for moving files to different directories - have not done support for renaming them yet, but should be straightforward - some tests, but many cases are not handled yet i think
997
998
def is_valid_name(name):
1077 by Martin Pool
- avoid compiling REs at module load time
999
    global _NAME_RE
1000
    if _NAME_RE == None:
1001
        _NAME_RE = re.compile(r'^[^/\\]+$')
1002
        
160 by mbp at sourcefrog
- basic support for moving files to different directories - have not done support for renaming them yet, but should be straightforward - some tests, but many cases are not handled yet i think
1003
    return bool(_NAME_RE.match(name))