~bzr-pqm/bzr/bzr.dev

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
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
18
# This should really be an id randomly assigned when the tree is
19
# created, but it's not for now.
20
ROOT_ID = "TREE_ROOT"
21
22
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
23
import sys, os.path, types, re
1 by mbp at sourcefrog
import from baz patch-364
24
800 by Martin Pool
Merge John's import-speedup branch:
25
import bzrlib
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
26
from bzrlib.errors import BzrError, BzrCheckError
70 by mbp at sourcefrog
Prepare for smart recursive add.
27
28
from bzrlib.osutils import uuid, quotefn, splitpath, joinpath, appendpath
29
from bzrlib.trace import mutter
999 by Martin Pool
- remove unnecessary internal imports
30
from bzrlib.errors import NotVersionedError
31
        
1 by mbp at sourcefrog
import from baz patch-364
32
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
33
class InventoryEntry(object):
1 by mbp at sourcefrog
import from baz patch-364
34
    """Description of a versioned file.
35
36
    An InventoryEntry has the following fields, which are also
37
    present in the XML inventory-entry element:
38
39
    * *file_id*
40
    * *name*: (only the basename within the directory, must not
41
      contain slashes)
42
    * *kind*: "directory" or "file"
43
    * *directory_id*: (if absent/null means the branch root directory)
44
    * *text_sha1*: only for files
45
    * *text_size*: in bytes, only for files 
46
    * *text_id*: identifier for the text version, only for files
47
48
    InventoryEntries can also exist inside a WorkingTree
49
    inventory, in which case they are not yet bound to a
50
    particular revision of the file.  In that case the text_sha1,
51
    text_size and text_id are absent.
52
53
54
    >>> i = Inventory()
55
    >>> i.path2id('')
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
56
    'TREE_ROOT'
57
    >>> i.add(InventoryEntry('123', 'src', 'directory', ROOT_ID))
58
    >>> i.add(InventoryEntry('2323', 'hello.c', 'file', parent_id='123'))
1 by mbp at sourcefrog
import from baz patch-364
59
    >>> for j in i.iter_entries():
60
    ...   print j
61
    ... 
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
62
    ('src', InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT'))
1 by mbp at sourcefrog
import from baz patch-364
63
    ('src/hello.c', InventoryEntry('2323', 'hello.c', kind='file', parent_id='123'))
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
64
    >>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
1 by mbp at sourcefrog
import from baz patch-364
65
    Traceback (most recent call last):
66
    ...
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
67
    BzrError: inventory already contains entry with id {2323}
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
68
    >>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
69
    >>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
1 by mbp at sourcefrog
import from baz patch-364
70
    >>> i.path2id('src/wibble')
71
    '2325'
72
    >>> '2325' in i
73
    True
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
74
    >>> i.add(InventoryEntry('2326', 'wibble.c', 'file', '2325'))
1 by mbp at sourcefrog
import from baz patch-364
75
    >>> i['2326']
76
    InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
77
    >>> for j in i.iter_entries():
78
    ...     print j[0]
79
    ...     assert i.path2id(j[0])
80
    ... 
81
    src
82
    src/bye.c
83
    src/hello.c
84
    src/wibble
85
    src/wibble/wibble.c
86
    >>> i.id2path('2326')
87
    'src/wibble/wibble.c'
88
254 by Martin Pool
- Doc cleanups from Magnus Therning
89
    TODO: Maybe also keep the full path of the entry, and the children?
1 by mbp at sourcefrog
import from baz patch-364
90
           But those depend on its position within a particular inventory, and
91
           it would be nice not to need to hold the backpointer here.
92
    """
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
93
94
    # TODO: split InventoryEntry into subclasses for files,
95
    # directories, etc etc.
376 by Martin Pool
- fix slow invariant check when reading in InventoryEntry objects
96
955 by Martin Pool
- use __slots__ on InventoryEntry; rather faster
97
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
98
                 'text_id', 'parent_id', 'children', ]
99
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
100
    def __init__(self, file_id, name, kind, parent_id, text_id=None):
1 by mbp at sourcefrog
import from baz patch-364
101
        """Create an InventoryEntry
102
        
103
        The filename must be a single component, relative to the
104
        parent directory; it cannot be a whole path or relative name.
105
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
106
        >>> e = InventoryEntry('123', 'hello.c', 'file', ROOT_ID)
1 by mbp at sourcefrog
import from baz patch-364
107
        >>> e.name
108
        'hello.c'
109
        >>> e.file_id
110
        '123'
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
111
        >>> e = InventoryEntry('123', 'src/hello.c', 'file', ROOT_ID)
1 by mbp at sourcefrog
import from baz patch-364
112
        Traceback (most recent call last):
395 by Martin Pool
- fix error raised from invalid InventoryEntry name
113
        BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
1 by mbp at sourcefrog
import from baz patch-364
114
        """
376 by Martin Pool
- fix slow invariant check when reading in InventoryEntry objects
115
        if '/' in name or '\\' in name:
116
            raise BzrCheckError('InventoryEntry name %r is invalid' % name)
1 by mbp at sourcefrog
import from baz patch-364
117
        
955 by Martin Pool
- use __slots__ on InventoryEntry; rather faster
118
        self.text_sha1 = None
119
        self.text_size = None
120
    
1 by mbp at sourcefrog
import from baz patch-364
121
        self.file_id = file_id
122
        self.name = name
123
        self.kind = kind
124
        self.text_id = text_id
125
        self.parent_id = parent_id
138 by mbp at sourcefrog
remove parallel tree from inventory;
126
        if kind == 'directory':
127
            self.children = {}
237 by mbp at sourcefrog
- Better assertions in InventoryEntry constructor
128
        elif kind == 'file':
129
            pass
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
130
        else:
237 by mbp at sourcefrog
- Better assertions in InventoryEntry constructor
131
            raise BzrError("unhandled entry kind %r" % kind)
132
1 by mbp at sourcefrog
import from baz patch-364
133
134
156 by mbp at sourcefrog
new "directories" command
135
    def sorted_children(self):
136
        l = self.children.items()
137
        l.sort()
138
        return l
139
140
1 by mbp at sourcefrog
import from baz patch-364
141
    def copy(self):
142
        other = InventoryEntry(self.file_id, self.name, self.kind,
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
143
                               self.parent_id, text_id=self.text_id)
1 by mbp at sourcefrog
import from baz patch-364
144
        other.text_sha1 = self.text_sha1
145
        other.text_size = self.text_size
490 by Martin Pool
doc
146
        # note that children are *not* copied; they're pulled across when
147
        # others are added
1 by mbp at sourcefrog
import from baz patch-364
148
        return other
149
150
151
    def __repr__(self):
152
        return ("%s(%r, %r, kind=%r, parent_id=%r)"
153
                % (self.__class__.__name__,
154
                   self.file_id,
155
                   self.name,
156
                   self.kind,
157
                   self.parent_id))
158
159
    
160
    def to_element(self):
161
        """Convert to XML element"""
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
162
        from bzrlib.xml import Element
163
        
1 by mbp at sourcefrog
import from baz patch-364
164
        e = Element('entry')
165
166
        e.set('name', self.name)
167
        e.set('file_id', self.file_id)
168
        e.set('kind', self.kind)
169
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
170
        if self.text_size != None:
1 by mbp at sourcefrog
import from baz patch-364
171
            e.set('text_size', '%d' % self.text_size)
172
            
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
173
        for f in ['text_id', 'text_sha1']:
1 by mbp at sourcefrog
import from baz patch-364
174
            v = getattr(self, f)
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
175
            if v != None:
1 by mbp at sourcefrog
import from baz patch-364
176
                e.set(f, v)
177
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
178
        # to be conservative, we don't externalize the root pointers
179
        # for now, leaving them as null in the xml form.  in a future
180
        # version it will be implied by nested elements.
181
        if self.parent_id != ROOT_ID:
182
            assert isinstance(self.parent_id, basestring)
183
            e.set('parent_id', self.parent_id)
184
1 by mbp at sourcefrog
import from baz patch-364
185
        e.tail = '\n'
186
            
187
        return e
188
189
190
    def from_element(cls, elt):
191
        assert elt.tag == 'entry'
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
192
193
        ## original format inventories don't have a parent_id for
194
        ## nodes in the root directory, but it's cleaner to use one
195
        ## internally.
196
        parent_id = elt.get('parent_id')
197
        if parent_id == None:
198
            parent_id = ROOT_ID
199
200
        self = cls(elt.get('file_id'), elt.get('name'), elt.get('kind'), parent_id)
1 by mbp at sourcefrog
import from baz patch-364
201
        self.text_id = elt.get('text_id')
202
        self.text_sha1 = elt.get('text_sha1')
203
        
204
        ## mutter("read inventoryentry: %r" % (elt.attrib))
205
206
        v = elt.get('text_size')
207
        self.text_size = v and int(v)
208
209
        return self
210
            
211
212
    from_element = classmethod(from_element)
213
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
214
    def __eq__(self, other):
1 by mbp at sourcefrog
import from baz patch-364
215
        if not isinstance(other, InventoryEntry):
216
            return NotImplemented
217
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
218
        return (self.file_id == other.file_id) \
219
               and (self.name == other.name) \
220
               and (self.text_sha1 == other.text_sha1) \
221
               and (self.text_size == other.text_size) \
222
               and (self.text_id == other.text_id) \
223
               and (self.parent_id == other.parent_id) \
224
               and (self.kind == other.kind)
225
226
227
    def __ne__(self, other):
228
        return not (self == other)
229
230
    def __hash__(self):
231
        raise ValueError('not hashable')
1 by mbp at sourcefrog
import from baz patch-364
232
233
234
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
235
class RootEntry(InventoryEntry):
236
    def __init__(self, file_id):
237
        self.file_id = file_id
238
        self.children = {}
239
        self.kind = 'root_directory'
240
        self.parent_id = None
156 by mbp at sourcefrog
new "directories" command
241
        self.name = ''
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
242
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
243
    def __eq__(self, other):
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
244
        if not isinstance(other, RootEntry):
245
            return NotImplemented
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
246
        
247
        return (self.file_id == other.file_id) \
248
               and (self.children == other.children)
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
249
250
251
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
252
class Inventory(object):
1 by mbp at sourcefrog
import from baz patch-364
253
    """Inventory of versioned files in a tree.
254
240 by mbp at sourcefrog
doc
255
    This describes which file_id is present at each point in the tree,
256
    and possibly the SHA-1 or other information about the file.
257
    Entries can be looked up either by path or by file_id.
1 by mbp at sourcefrog
import from baz patch-364
258
259
    The inventory represents a typical unix file tree, with
260
    directories containing files and subdirectories.  We never store
261
    the full path to a file, because renaming a directory implicitly
262
    moves all of its contents.  This class internally maintains a
263
    lookup tree that allows the children under a directory to be
264
    returned quickly.
265
266
    InventoryEntry objects must not be modified after they are
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
267
    inserted, other than through the Inventory API.
1 by mbp at sourcefrog
import from baz patch-364
268
269
    >>> inv = Inventory()
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
270
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
1 by mbp at sourcefrog
import from baz patch-364
271
    >>> inv['123-123'].name
272
    'hello.c'
273
274
    May be treated as an iterator or set to look up file ids:
275
    
276
    >>> bool(inv.path2id('hello.c'))
277
    True
278
    >>> '123-123' in inv
279
    True
280
281
    May also look up by name:
282
283
    >>> [x[0] for x in inv.iter_entries()]
284
    ['hello.c']
909 by Martin Pool
- merge John's code to give the tree root an explicit file id
285
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
286
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
1 by mbp at sourcefrog
import from baz patch-364
287
    """
909 by Martin Pool
- merge John's code to give the tree root an explicit file id
288
    def __init__(self, root_id=ROOT_ID):
1 by mbp at sourcefrog
import from baz patch-364
289
        """Create or read an inventory.
290
291
        If a working directory is specified, the inventory is read
292
        from there.  If the file is specified, read from that. If not,
293
        the inventory is created empty.
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
294
295
        The inventory is created with a default root directory, with
296
        an id of None.
1 by mbp at sourcefrog
import from baz patch-364
297
        """
909 by Martin Pool
- merge John's code to give the tree root an explicit file id
298
        # We are letting Branch(init=True) create a unique inventory
299
        # root id. Rather than generating a random one here.
300
        #if root_id is None:
301
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
302
        self.root = RootEntry(root_id)
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
303
        self._byid = {self.root.file_id: self.root}
1 by mbp at sourcefrog
import from baz patch-364
304
305
306
    def __iter__(self):
307
        return iter(self._byid)
308
309
310
    def __len__(self):
311
        """Returns number of entries."""
312
        return len(self._byid)
313
314
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
315
    def iter_entries(self, from_dir=None):
1 by mbp at sourcefrog
import from baz patch-364
316
        """Return (path, entry) pairs, in order by name."""
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
317
        if from_dir == None:
318
            assert self.root
319
            from_dir = self.root
320
        elif isinstance(from_dir, basestring):
321
            from_dir = self._byid[from_dir]
322
            
323
        kids = from_dir.children.items()
1 by mbp at sourcefrog
import from baz patch-364
324
        kids.sort()
325
        for name, ie in kids:
326
            yield name, ie
327
            if ie.kind == 'directory':
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
328
                for cn, cie in self.iter_entries(from_dir=ie.file_id):
271 by Martin Pool
- Windows path fixes
329
                    yield os.path.join(name, cn), cie
555 by Martin Pool
- New Inventory.entries() method
330
331
332
    def entries(self):
333
        """Return list of (path, ie) for all entries except the root.
334
335
        This may be faster than iter_entries.
336
        """
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
337
        accum = []
338
        def descend(dir_ie, dir_path):
556 by Martin Pool
- fix up Inventory.entries()
339
            kids = dir_ie.children.items()
555 by Martin Pool
- New Inventory.entries() method
340
            kids.sort()
341
            for name, ie in kids:
342
                child_path = os.path.join(dir_path, name)
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
343
                accum.append((child_path, ie))
555 by Martin Pool
- New Inventory.entries() method
344
                if ie.kind == 'directory':
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
345
                    descend(ie, child_path)
555 by Martin Pool
- New Inventory.entries() method
346
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
347
        descend(self.root, '')
348
        return accum
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
349
350
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
351
    def directories(self):
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
352
        """Return (path, entry) pairs for all directories, including the root.
1 by mbp at sourcefrog
import from baz patch-364
353
        """
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
354
        accum = []
355
        def descend(parent_ie, parent_path):
356
            accum.append((parent_path, parent_ie))
156 by mbp at sourcefrog
new "directories" command
357
            
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
358
            kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
359
            kids.sort()
156 by mbp at sourcefrog
new "directories" command
360
557 by Martin Pool
- Refactor/cleanup Inventory.entries()
361
            for name, child_ie in kids:
362
                child_path = os.path.join(parent_path, name)
363
                descend(child_ie, child_path)
364
        descend(self.root, '')
365
        return accum
1 by mbp at sourcefrog
import from baz patch-364
366
        
367
368
369
    def __contains__(self, file_id):
370
        """True if this entry contains a file with given id.
371
372
        >>> inv = Inventory()
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
373
        >>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
1 by mbp at sourcefrog
import from baz patch-364
374
        >>> '123' in inv
375
        True
376
        >>> '456' in inv
377
        False
378
        """
379
        return file_id in self._byid
380
381
382
    def __getitem__(self, file_id):
383
        """Return the entry for given file_id.
384
385
        >>> inv = Inventory()
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
386
        >>> inv.add(InventoryEntry('123123', 'hello.c', 'file', ROOT_ID))
1 by mbp at sourcefrog
import from baz patch-364
387
        >>> inv['123123'].name
388
        'hello.c'
389
        """
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
390
        try:
391
            return self._byid[file_id]
392
        except KeyError:
380 by Martin Pool
- Slight optimization for Inventory.__getitem__
393
            if file_id == None:
394
                raise BzrError("can't look up file_id None")
395
            else:
396
                raise BzrError("file_id {%s} not in inventory" % file_id)
1 by mbp at sourcefrog
import from baz patch-364
397
398
460 by Martin Pool
- new testing command compare-trees
399
    def get_file_kind(self, file_id):
400
        return self._byid[file_id].kind
401
138 by mbp at sourcefrog
remove parallel tree from inventory;
402
    def get_child(self, parent_id, filename):
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
403
        return self[parent_id].children.get(filename)
138 by mbp at sourcefrog
remove parallel tree from inventory;
404
405
1 by mbp at sourcefrog
import from baz patch-364
406
    def add(self, entry):
407
        """Add entry to inventory.
408
409
        To add  a file to a branch ready to be committed, use Branch.add,
410
        which calls this."""
139 by mbp at sourcefrog
simplified/faster Inventory.add
411
        if entry.file_id in self._byid:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
412
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1 by mbp at sourcefrog
import from baz patch-364
413
909 by Martin Pool
- merge John's code to give the tree root an explicit file id
414
        if entry.parent_id == ROOT_ID or entry.parent_id is None:
415
            entry.parent_id = self.root.file_id
416
155 by mbp at sourcefrog
add new explicit RootEntry to inventory (in-core only)
417
        try:
418
            parent = self._byid[entry.parent_id]
419
        except KeyError:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
420
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
139 by mbp at sourcefrog
simplified/faster Inventory.add
421
422
        if parent.children.has_key(entry.name):
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
423
            raise BzrError("%s is already versioned" %
140 by mbp at sourcefrog
fix error message for repeated add
424
                    appendpath(self.id2path(parent.file_id), entry.name))
1 by mbp at sourcefrog
import from baz patch-364
425
426
        self._byid[entry.file_id] = entry
139 by mbp at sourcefrog
simplified/faster Inventory.add
427
        parent.children[entry.name] = entry
1 by mbp at sourcefrog
import from baz patch-364
428
429
70 by mbp at sourcefrog
Prepare for smart recursive add.
430
    def add_path(self, relpath, kind, file_id=None):
431
        """Add entry from a path.
432
433
        The immediate parent must already be versioned"""
1015 by Martin Pool
- fix circular imports
434
        from bzrlib.branch import gen_file_id
435
        
70 by mbp at sourcefrog
Prepare for smart recursive add.
436
        parts = bzrlib.osutils.splitpath(relpath)
437
        if len(parts) == 0:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
438
            raise BzrError("cannot re-add root of inventory")
70 by mbp at sourcefrog
Prepare for smart recursive add.
439
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
440
        if file_id == None:
800 by Martin Pool
Merge John's import-speedup branch:
441
            file_id = gen_file_id(relpath)
70 by mbp at sourcefrog
Prepare for smart recursive add.
442
753 by Martin Pool
- new exception NotVersionedError
443
        parent_path = parts[:-1]
444
        parent_id = self.path2id(parent_path)
445
        if parent_id == None:
446
            raise NotVersionedError(parent_path)
447
70 by mbp at sourcefrog
Prepare for smart recursive add.
448
        ie = InventoryEntry(file_id, parts[-1],
449
                            kind=kind, parent_id=parent_id)
450
        return self.add(ie)
451
452
1 by mbp at sourcefrog
import from baz patch-364
453
    def __delitem__(self, file_id):
454
        """Remove entry by id.
455
456
        >>> inv = Inventory()
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
457
        >>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
1 by mbp at sourcefrog
import from baz patch-364
458
        >>> '123' in inv
459
        True
460
        >>> del inv['123']
461
        >>> '123' in inv
462
        False
463
        """
464
        ie = self[file_id]
465
138 by mbp at sourcefrog
remove parallel tree from inventory;
466
        assert self[ie.parent_id].children[ie.name] == ie
1 by mbp at sourcefrog
import from baz patch-364
467
        
468
        # TODO: Test deleting all children; maybe hoist to a separate
469
        # deltree method?
470
        if ie.kind == 'directory':
138 by mbp at sourcefrog
remove parallel tree from inventory;
471
            for cie in ie.children.values():
1 by mbp at sourcefrog
import from baz patch-364
472
                del self[cie.file_id]
138 by mbp at sourcefrog
remove parallel tree from inventory;
473
            del ie.children
1 by mbp at sourcefrog
import from baz patch-364
474
475
        del self._byid[file_id]
138 by mbp at sourcefrog
remove parallel tree from inventory;
476
        del self[ie.parent_id].children[ie.name]
1 by mbp at sourcefrog
import from baz patch-364
477
478
479
    def to_element(self):
480
        """Convert to XML Element"""
802 by Martin Pool
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
481
        from bzrlib.xml import Element
482
        
1 by mbp at sourcefrog
import from baz patch-364
483
        e = Element('inventory')
484
        e.text = '\n'
909 by Martin Pool
- merge John's code to give the tree root an explicit file id
485
        if self.root.file_id not in (None, ROOT_ID):
486
            e.set('file_id', self.root.file_id)
1 by mbp at sourcefrog
import from baz patch-364
487
        for path, ie in self.iter_entries():
488
            e.append(ie.to_element())
489
        return e
490
    
491
492
    def from_element(cls, elt):
493
        """Construct from XML Element
848 by Martin Pool
doc
494
        
1 by mbp at sourcefrog
import from baz patch-364
495
        >>> inv = Inventory()
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
496
        >>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
1 by mbp at sourcefrog
import from baz patch-364
497
        >>> elt = inv.to_element()
498
        >>> inv2 = Inventory.from_element(elt)
499
        >>> inv2 == inv
500
        True
501
        """
848 by Martin Pool
doc
502
        # XXXX: doctest doesn't run this properly under python2.3
1 by mbp at sourcefrog
import from baz patch-364
503
        assert elt.tag == 'inventory'
909 by Martin Pool
- merge John's code to give the tree root an explicit file id
504
        root_id = elt.get('file_id') or ROOT_ID
505
        o = cls(root_id)
1 by mbp at sourcefrog
import from baz patch-364
506
        for e in elt:
909 by Martin Pool
- merge John's code to give the tree root an explicit file id
507
            ie = InventoryEntry.from_element(e)
508
            if ie.parent_id == ROOT_ID:
509
                ie.parent_id = root_id
510
            o.add(ie)
1 by mbp at sourcefrog
import from baz patch-364
511
        return o
512
        
513
    from_element = classmethod(from_element)
514
515
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
516
    def __eq__(self, other):
1 by mbp at sourcefrog
import from baz patch-364
517
        """Compare two sets by comparing their contents.
518
519
        >>> i1 = Inventory()
520
        >>> i2 = Inventory()
521
        >>> i1 == i2
522
        True
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
523
        >>> i1.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
1 by mbp at sourcefrog
import from baz patch-364
524
        >>> i1 == i2
525
        False
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
526
        >>> i2.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
1 by mbp at sourcefrog
import from baz patch-364
527
        >>> i1 == i2
528
        True
529
        """
530
        if not isinstance(other, Inventory):
531
            return NotImplemented
532
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
533
        if len(self._byid) != len(other._byid):
543 by Martin Pool
- More cleanups for set type
534
            # shortcut: obviously not the same
544 by Martin Pool
- Define __eq__ and __ne__ for Inventory and InventoryEntry objects,
535
            return False
536
537
        return self._byid == other._byid
538
539
540
    def __ne__(self, other):
541
        return not (self == other)
542
543
544
    def __hash__(self):
545
        raise ValueError('not hashable')
546
1 by mbp at sourcefrog
import from baz patch-364
547
548
172 by mbp at sourcefrog
- clearer check against attempts to introduce directory loops in the inventory
549
    def get_idpath(self, file_id):
550
        """Return a list of file_ids for the path to an entry.
551
552
        The list contains one element for each directory followed by
553
        the id of the file itself.  So the length of the returned list
554
        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
555
        root directory as depth 1.
172 by mbp at sourcefrog
- clearer check against attempts to introduce directory loops in the inventory
556
        """
557
        p = []
558
        while file_id != None:
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
559
            try:
560
                ie = self._byid[file_id]
561
            except KeyError:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
562
                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
563
            p.insert(0, ie.file_id)
564
            file_id = ie.parent_id
565
        return p
566
567
1 by mbp at sourcefrog
import from baz patch-364
568
    def id2path(self, file_id):
569
        """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
570
571
        # get all names, skipping root
922 by Martin Pool
- optimization for Inventory.id2path; access byid map directly rather than
572
        p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
271 by Martin Pool
- Windows path fixes
573
        return os.sep.join(p)
1 by mbp at sourcefrog
import from baz patch-364
574
            
575
576
577
    def path2id(self, name):
578
        """Walk down through directories to return entry of last component.
579
580
        names may be either a list of path components, or a single
581
        string, in which case it is automatically split.
582
583
        This returns the entry of the last component in the path,
584
        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
585
586
        Returns None iff the path is not found.
1 by mbp at sourcefrog
import from baz patch-364
587
        """
70 by mbp at sourcefrog
Prepare for smart recursive add.
588
        if isinstance(name, types.StringTypes):
589
            name = splitpath(name)
1 by mbp at sourcefrog
import from baz patch-364
590
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
591
        mutter("lookup path %r" % name)
592
593
        parent = self.root
70 by mbp at sourcefrog
Prepare for smart recursive add.
594
        for f in name:
1 by mbp at sourcefrog
import from baz patch-364
595
            try:
138 by mbp at sourcefrog
remove parallel tree from inventory;
596
                cie = parent.children[f]
1 by mbp at sourcefrog
import from baz patch-364
597
                assert cie.name == f
178 by mbp at sourcefrog
- Use a non-null file_id for the branch root directory. At the moment
598
                assert cie.parent_id == parent.file_id
138 by mbp at sourcefrog
remove parallel tree from inventory;
599
                parent = cie
1 by mbp at sourcefrog
import from baz patch-364
600
            except KeyError:
601
                # or raise an error?
602
                return None
603
138 by mbp at sourcefrog
remove parallel tree from inventory;
604
        return parent.file_id
1 by mbp at sourcefrog
import from baz patch-364
605
606
607
    def has_filename(self, names):
608
        return bool(self.path2id(names))
609
610
611
    def has_id(self, file_id):
612
        return self._byid.has_key(file_id)
613
614
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
615
    def rename(self, file_id, new_parent_id, new_name):
616
        """Move a file within the inventory.
617
618
        This can change either the name, or the parent, or both.
619
620
        This does not move the working file."""
621
        if not is_valid_name(new_name):
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
622
            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
623
624
        new_parent = self._byid[new_parent_id]
625
        if new_name in new_parent.children:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
626
            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
627
172 by mbp at sourcefrog
- clearer check against attempts to introduce directory loops in the inventory
628
        new_parent_idpath = self.get_idpath(new_parent_id)
629
        if file_id in new_parent_idpath:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
630
            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
631
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
632
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
633
        file_ie = self._byid[file_id]
634
        old_parent = self._byid[file_ie.parent_id]
635
636
        # TODO: Don't leave things messed up if this fails
637
638
        del old_parent.children[file_ie.name]
639
        new_parent.children[new_name] = file_ie
640
        
641
        file_ie.name = new_name
642
        file_ie.parent_id = new_parent_id
643
644
645
646
647
_NAME_RE = re.compile(r'^[^/\\]+$')
648
649
def is_valid_name(name):
650
    return bool(_NAME_RE.match(name))