~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Aaron Bentley
  • Date: 2005-09-14 16:54:44 UTC
  • mto: (1185.1.16)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: abentley@panoramicfeedback.com-20050914165444-335a16dc80f7c129
Handled NotBranchError like a CommandError in main()

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
"""Inventories map files to their name in a revision."""
18
 
 
19
 
# TODO: Maybe store inventory_id in the file?  Not really needed.
20
 
 
21
 
__copyright__ = "Copyright (C) 2005 Canonical Ltd."
22
 
__author__ = "Martin Pool <mbp@canonical.com>"
 
17
 
 
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
 
23
22
 
24
23
import sys, os.path, types, re
25
 
from sets import Set
26
 
 
27
 
try:
28
 
    from cElementTree import Element, ElementTree, SubElement
29
 
except ImportError:
30
 
    from elementtree.ElementTree import Element, ElementTree, SubElement
31
 
 
32
 
from xml import XMLMixin
33
 
from errors import bailout
34
24
 
35
25
import bzrlib
 
26
from bzrlib.errors import BzrError, BzrCheckError
 
27
 
36
28
from bzrlib.osutils import uuid, quotefn, splitpath, joinpath, appendpath
37
29
from bzrlib.trace import mutter
 
30
from bzrlib.errors import NotVersionedError
 
31
        
38
32
 
39
 
class InventoryEntry(XMLMixin):
 
33
class InventoryEntry(object):
40
34
    """Description of a versioned file.
41
35
 
42
36
    An InventoryEntry has the following fields, which are also
59
53
 
60
54
    >>> i = Inventory()
61
55
    >>> i.path2id('')
62
 
    >>> i.add(InventoryEntry('123', 'src', kind='directory'))
63
 
    >>> i.add(InventoryEntry('2323', 'hello.c', parent_id='123'))
 
56
    'TREE_ROOT'
 
57
    >>> i.add(InventoryEntry('123', 'src', 'directory', ROOT_ID))
 
58
    InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT')
 
59
    >>> i.add(InventoryEntry('2323', 'hello.c', 'file', parent_id='123'))
 
60
    InventoryEntry('2323', 'hello.c', kind='file', parent_id='123')
64
61
    >>> for j in i.iter_entries():
65
62
    ...   print j
66
63
    ... 
67
 
    ('src', InventoryEntry('123', 'src', kind='directory', parent_id=None))
 
64
    ('src', InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT'))
68
65
    ('src/hello.c', InventoryEntry('2323', 'hello.c', kind='file', parent_id='123'))
69
 
    >>> i.add(InventoryEntry('2323', 'bye.c', parent_id='123'))
 
66
    >>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
70
67
    Traceback (most recent call last):
71
68
    ...
72
 
    BzrError: ('inventory already contains entry with id {2323}', [])
73
 
    >>> i.add(InventoryEntry('2324', 'bye.c', parent_id='123'))
74
 
    >>> i.add(InventoryEntry('2325', 'wibble', parent_id='123', kind='directory'))
 
69
    BzrError: inventory already contains entry with id {2323}
 
70
    >>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
 
71
    InventoryEntry('2324', 'bye.c', kind='file', parent_id='123')
 
72
    >>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
 
73
    InventoryEntry('2325', 'wibble', kind='directory', parent_id='123')
75
74
    >>> i.path2id('src/wibble')
76
75
    '2325'
77
76
    >>> '2325' in i
78
77
    True
79
 
    >>> i.add(InventoryEntry('2326', 'wibble.c', parent_id='2325'))
 
78
    >>> i.add(InventoryEntry('2326', 'wibble.c', 'file', '2325'))
 
79
    InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
80
80
    >>> i['2326']
81
81
    InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
82
82
    >>> for j in i.iter_entries():
91
91
    >>> i.id2path('2326')
92
92
    'src/wibble/wibble.c'
93
93
 
94
 
    :todo: Maybe also keep the full path of the entry, and the children?
 
94
    TODO: Maybe also keep the full path of the entry, and the children?
95
95
           But those depend on its position within a particular inventory, and
96
96
           it would be nice not to need to hold the backpointer here.
97
97
    """
98
 
    def __init__(self, file_id, name, kind='file', text_id=None,
99
 
                 parent_id=None):
 
98
 
 
99
    # TODO: split InventoryEntry into subclasses for files,
 
100
    # directories, etc etc.
 
101
 
 
102
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
103
                 'text_id', 'parent_id', 'children',
 
104
                 'text_version', 'entry_version', ]
 
105
 
 
106
 
 
107
    def __init__(self, file_id, name, kind, parent_id, text_id=None):
100
108
        """Create an InventoryEntry
101
109
        
102
110
        The filename must be a single component, relative to the
103
111
        parent directory; it cannot be a whole path or relative name.
104
112
 
105
 
        >>> e = InventoryEntry('123', 'hello.c')
 
113
        >>> e = InventoryEntry('123', 'hello.c', 'file', ROOT_ID)
106
114
        >>> e.name
107
115
        'hello.c'
108
116
        >>> e.file_id
109
117
        '123'
110
 
        >>> e = InventoryEntry('123', 'src/hello.c')
 
118
        >>> e = InventoryEntry('123', 'src/hello.c', 'file', ROOT_ID)
111
119
        Traceback (most recent call last):
112
 
        BzrError: ("InventoryEntry name is not a simple filename: 'src/hello.c'", [])
 
120
        BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
113
121
        """
114
 
        
115
 
        if len(splitpath(name)) != 1:
116
 
            bailout('InventoryEntry name is not a simple filename: %r'
117
 
                    % name)
118
 
        
 
122
        assert isinstance(name, basestring), name
 
123
        if '/' in name or '\\' in name:
 
124
            raise BzrCheckError('InventoryEntry name %r is invalid' % name)
 
125
        
 
126
        self.text_version = None
 
127
        self.entry_version = None
 
128
        self.text_sha1 = None
 
129
        self.text_size = None
119
130
        self.file_id = file_id
120
131
        self.name = name
121
 
        assert kind in ['file', 'directory']
122
132
        self.kind = kind
123
133
        self.text_id = text_id
124
134
        self.parent_id = parent_id
125
 
        self.text_sha1 = None
126
 
        self.text_size = None
127
135
        if kind == 'directory':
128
136
            self.children = {}
 
137
        elif kind == 'file':
 
138
            pass
 
139
        else:
 
140
            raise BzrError("unhandled entry kind %r" % kind)
 
141
 
129
142
 
130
143
 
131
144
    def sorted_children(self):
136
149
 
137
150
    def copy(self):
138
151
        other = InventoryEntry(self.file_id, self.name, self.kind,
139
 
                               self.text_id, self.parent_id)
 
152
                               self.parent_id, text_id=self.text_id)
140
153
        other.text_sha1 = self.text_sha1
141
154
        other.text_size = self.text_size
 
155
        # note that children are *not* copied; they're pulled across when
 
156
        # others are added
142
157
        return other
143
158
 
144
159
 
151
166
                   self.parent_id))
152
167
 
153
168
    
154
 
    def to_element(self):
155
 
        """Convert to XML element"""
156
 
        e = Element('entry')
157
 
 
158
 
        e.set('name', self.name)
159
 
        e.set('file_id', self.file_id)
160
 
        e.set('kind', self.kind)
161
 
 
162
 
        if self.text_size is not None:
163
 
            e.set('text_size', '%d' % self.text_size)
164
 
            
165
 
        for f in ['text_id', 'text_sha1', 'parent_id']:
166
 
            v = getattr(self, f)
167
 
            if v is not None:
168
 
                e.set(f, v)
169
 
 
170
 
        e.tail = '\n'
171
 
            
172
 
        return e
173
 
 
174
 
 
175
 
    def from_element(cls, elt):
176
 
        assert elt.tag == 'entry'
177
 
        self = cls(elt.get('file_id'), elt.get('name'), elt.get('kind'))
178
 
        self.text_id = elt.get('text_id')
179
 
        self.text_sha1 = elt.get('text_sha1')
180
 
        self.parent_id = elt.get('parent_id')
181
 
        
182
 
        ## mutter("read inventoryentry: %r" % (elt.attrib))
183
 
 
184
 
        v = elt.get('text_size')
185
 
        self.text_size = v and int(v)
186
 
 
187
 
        return self
188
 
            
189
 
 
190
 
    from_element = classmethod(from_element)
191
 
 
192
 
    def __cmp__(self, other):
193
 
        if self is other:
194
 
            return 0
 
169
    def __eq__(self, other):
195
170
        if not isinstance(other, InventoryEntry):
196
171
            return NotImplemented
197
172
 
198
 
        return cmp(self.file_id, other.file_id) \
199
 
               or cmp(self.name, other.name) \
200
 
               or cmp(self.text_sha1, other.text_sha1) \
201
 
               or cmp(self.text_size, other.text_size) \
202
 
               or cmp(self.text_id, other.text_id) \
203
 
               or cmp(self.parent_id, other.parent_id) \
204
 
               or cmp(self.kind, other.kind)
 
173
        return (self.file_id == other.file_id) \
 
174
               and (self.name == other.name) \
 
175
               and (self.text_sha1 == other.text_sha1) \
 
176
               and (self.text_size == other.text_size) \
 
177
               and (self.text_id == other.text_id) \
 
178
               and (self.parent_id == other.parent_id) \
 
179
               and (self.kind == other.kind) \
 
180
               and (self.text_version == other.text_version) \
 
181
               and (self.entry_version == other.entry_version)
 
182
 
 
183
 
 
184
    def __ne__(self, other):
 
185
        return not (self == other)
 
186
 
 
187
    def __hash__(self):
 
188
        raise ValueError('not hashable')
205
189
 
206
190
 
207
191
 
213
197
        self.parent_id = None
214
198
        self.name = ''
215
199
 
216
 
    def __cmp__(self, other):
217
 
        if self is other:
218
 
            return 0
 
200
    def __eq__(self, other):
219
201
        if not isinstance(other, RootEntry):
220
202
            return NotImplemented
221
 
        return cmp(self.file_id, other.file_id) \
222
 
               or cmp(self.children, other.children)
223
 
 
224
 
 
225
 
 
226
 
class Inventory(XMLMixin):
 
203
        
 
204
        return (self.file_id == other.file_id) \
 
205
               and (self.children == other.children)
 
206
 
 
207
 
 
208
 
 
209
class Inventory(object):
227
210
    """Inventory of versioned files in a tree.
228
211
 
229
 
    An Inventory acts like a set of InventoryEntry items.  You can
230
 
    also look files up by their file_id or name.
231
 
    
232
 
    May be read from and written to a metadata file in a tree.  To
233
 
    manipulate the inventory (for example to add a file), it is read
234
 
    in, modified, and then written back out.
 
212
    This describes which file_id is present at each point in the tree,
 
213
    and possibly the SHA-1 or other information about the file.
 
214
    Entries can be looked up either by path or by file_id.
235
215
 
236
216
    The inventory represents a typical unix file tree, with
237
217
    directories containing files and subdirectories.  We never store
244
224
    inserted, other than through the Inventory API.
245
225
 
246
226
    >>> inv = Inventory()
247
 
    >>> inv.write_xml(sys.stdout)
248
 
    <inventory>
249
 
    </inventory>
250
 
    >>> inv.add(InventoryEntry('123-123', 'hello.c'))
 
227
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
 
228
    InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT')
251
229
    >>> inv['123-123'].name
252
230
    'hello.c'
253
231
 
262
240
 
263
241
    >>> [x[0] for x in inv.iter_entries()]
264
242
    ['hello.c']
265
 
    
266
 
    >>> inv.write_xml(sys.stdout)
267
 
    <inventory>
268
 
    <entry file_id="123-123" kind="file" name="hello.c" />
269
 
    </inventory>
270
 
 
 
243
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
 
244
    >>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
 
245
    InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT-12345678-12345678')
271
246
    """
272
 
 
273
 
    ## TODO: Make sure only canonical filenames are stored.
274
 
 
275
 
    ## TODO: Do something sensible about the possible collisions on
276
 
    ## case-losing filesystems.  Perhaps we should just always forbid
277
 
    ## such collisions.
278
 
 
279
 
    ## TODO: No special cases for root, rather just give it a file id
280
 
    ## like everything else.
281
 
 
282
 
    ## TODO: Probably change XML serialization to use nesting
283
 
 
284
 
    def __init__(self):
 
247
    def __init__(self, root_id=ROOT_ID):
285
248
        """Create or read an inventory.
286
249
 
287
250
        If a working directory is specified, the inventory is read
291
254
        The inventory is created with a default root directory, with
292
255
        an id of None.
293
256
        """
294
 
        self.root = RootEntry(None)
295
 
        self._byid = {None: self.root}
 
257
        # We are letting Branch(init=True) create a unique inventory
 
258
        # root id. Rather than generating a random one here.
 
259
        #if root_id is None:
 
260
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
 
261
        self.root = RootEntry(root_id)
 
262
        self._byid = {self.root.file_id: self.root}
296
263
 
297
264
 
298
265
    def __iter__(self):
318
285
            yield name, ie
319
286
            if ie.kind == 'directory':
320
287
                for cn, cie in self.iter_entries(from_dir=ie.file_id):
321
 
                    yield '/'.join((name, cn)), cie
322
 
                    
323
 
 
324
 
 
325
 
    def directories(self, from_dir=None):
326
 
        """Return (path, entry) pairs for all directories.
 
288
                    yield os.path.join(name, cn), cie
 
289
 
 
290
 
 
291
    def entries(self):
 
292
        """Return list of (path, ie) for all entries except the root.
 
293
 
 
294
        This may be faster than iter_entries.
327
295
        """
328
 
        def descend(parent_ie):
329
 
            parent_name = parent_ie.name
330
 
            yield parent_name, parent_ie
331
 
 
332
 
            # directory children in sorted order
333
 
            dn = []
334
 
            for ie in parent_ie.children.itervalues():
 
296
        accum = []
 
297
        def descend(dir_ie, dir_path):
 
298
            kids = dir_ie.children.items()
 
299
            kids.sort()
 
300
            for name, ie in kids:
 
301
                child_path = os.path.join(dir_path, name)
 
302
                accum.append((child_path, ie))
335
303
                if ie.kind == 'directory':
336
 
                    dn.append((ie.name, ie))
337
 
            dn.sort()
 
304
                    descend(ie, child_path)
 
305
 
 
306
        descend(self.root, '')
 
307
        return accum
 
308
 
 
309
 
 
310
    def directories(self):
 
311
        """Return (path, entry) pairs for all directories, including the root.
 
312
        """
 
313
        accum = []
 
314
        def descend(parent_ie, parent_path):
 
315
            accum.append((parent_path, parent_ie))
338
316
            
339
 
            for name, child_ie in dn:
340
 
                for sub_name, sub_ie in descend(child_ie):
341
 
                    yield appendpath(parent_name, sub_name), sub_ie
 
317
            kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
 
318
            kids.sort()
342
319
 
343
 
        for name, ie in descend(self.root):
344
 
            yield name, ie
 
320
            for name, child_ie in kids:
 
321
                child_path = os.path.join(parent_path, name)
 
322
                descend(child_ie, child_path)
 
323
        descend(self.root, '')
 
324
        return accum
345
325
        
346
326
 
347
327
 
349
329
        """True if this entry contains a file with given id.
350
330
 
351
331
        >>> inv = Inventory()
352
 
        >>> inv.add(InventoryEntry('123', 'foo.c'))
 
332
        >>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
 
333
        InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
353
334
        >>> '123' in inv
354
335
        True
355
336
        >>> '456' in inv
362
343
        """Return the entry for given file_id.
363
344
 
364
345
        >>> inv = Inventory()
365
 
        >>> inv.add(InventoryEntry('123123', 'hello.c'))
 
346
        >>> inv.add(InventoryEntry('123123', 'hello.c', 'file', ROOT_ID))
 
347
        InventoryEntry('123123', 'hello.c', kind='file', parent_id='TREE_ROOT')
366
348
        >>> inv['123123'].name
367
349
        'hello.c'
368
350
        """
369
 
        return self._byid[file_id]
370
 
 
 
351
        try:
 
352
            return self._byid[file_id]
 
353
        except KeyError:
 
354
            if file_id == None:
 
355
                raise BzrError("can't look up file_id None")
 
356
            else:
 
357
                raise BzrError("file_id {%s} not in inventory" % file_id)
 
358
 
 
359
 
 
360
    def get_file_kind(self, file_id):
 
361
        return self._byid[file_id].kind
371
362
 
372
363
    def get_child(self, parent_id, filename):
373
364
        return self[parent_id].children.get(filename)
377
368
        """Add entry to inventory.
378
369
 
379
370
        To add  a file to a branch ready to be committed, use Branch.add,
380
 
        which calls this."""
 
371
        which calls this.
 
372
 
 
373
        Returns the new entry object.
 
374
        """
381
375
        if entry.file_id in self._byid:
382
 
            bailout("inventory already contains entry with id {%s}" % entry.file_id)
 
376
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
 
377
 
 
378
        if entry.parent_id == ROOT_ID or entry.parent_id is None:
 
379
            entry.parent_id = self.root.file_id
383
380
 
384
381
        try:
385
382
            parent = self._byid[entry.parent_id]
386
383
        except KeyError:
387
 
            bailout("parent_id %r not in inventory" % entry.parent_id)
 
384
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
388
385
 
389
386
        if parent.children.has_key(entry.name):
390
 
            bailout("%s is already versioned" %
 
387
            raise BzrError("%s is already versioned" %
391
388
                    appendpath(self.id2path(parent.file_id), entry.name))
392
389
 
393
390
        self._byid[entry.file_id] = entry
394
391
        parent.children[entry.name] = entry
 
392
        return entry
395
393
 
396
394
 
397
395
    def add_path(self, relpath, kind, file_id=None):
398
396
        """Add entry from a path.
399
397
 
400
 
        The immediate parent must already be versioned"""
 
398
        The immediate parent must already be versioned.
 
399
 
 
400
        Returns the new entry object."""
 
401
        from bzrlib.branch import gen_file_id
 
402
        
401
403
        parts = bzrlib.osutils.splitpath(relpath)
402
404
        if len(parts) == 0:
403
 
            bailout("cannot re-add root of inventory")
404
 
 
405
 
        if file_id is None:
406
 
            file_id = bzrlib.branch.gen_file_id(relpath)
407
 
 
408
 
        parent_id = self.path2id(parts[:-1])
 
405
            raise BzrError("cannot re-add root of inventory")
 
406
 
 
407
        if file_id == None:
 
408
            file_id = gen_file_id(relpath)
 
409
 
 
410
        parent_path = parts[:-1]
 
411
        parent_id = self.path2id(parent_path)
 
412
        if parent_id == None:
 
413
            raise NotVersionedError(parent_path)
 
414
 
409
415
        ie = InventoryEntry(file_id, parts[-1],
410
416
                            kind=kind, parent_id=parent_id)
411
417
        return self.add(ie)
415
421
        """Remove entry by id.
416
422
 
417
423
        >>> inv = Inventory()
418
 
        >>> inv.add(InventoryEntry('123', 'foo.c'))
 
424
        >>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
 
425
        InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
419
426
        >>> '123' in inv
420
427
        True
421
428
        >>> del inv['123']
437
444
        del self[ie.parent_id].children[ie.name]
438
445
 
439
446
 
440
 
    def id_set(self):
441
 
        return Set(self._byid)
442
 
 
443
 
 
444
 
    def to_element(self):
445
 
        """Convert to XML Element"""
446
 
        e = Element('inventory')
447
 
        e.text = '\n'
448
 
        for path, ie in self.iter_entries():
449
 
            e.append(ie.to_element())
450
 
        return e
451
 
    
452
 
 
453
 
    def from_element(cls, elt):
454
 
        """Construct from XML Element
455
 
 
456
 
        >>> inv = Inventory()
457
 
        >>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c'))
458
 
        >>> elt = inv.to_element()
459
 
        >>> inv2 = Inventory.from_element(elt)
460
 
        >>> inv2 == inv
461
 
        True
462
 
        """
463
 
        assert elt.tag == 'inventory'
464
 
        o = cls()
465
 
        for e in elt:
466
 
            o.add(InventoryEntry.from_element(e))
467
 
        return o
468
 
        
469
 
    from_element = classmethod(from_element)
470
 
 
471
 
 
472
 
    def __cmp__(self, other):
 
447
    def __eq__(self, other):
473
448
        """Compare two sets by comparing their contents.
474
449
 
475
450
        >>> i1 = Inventory()
476
451
        >>> i2 = Inventory()
477
452
        >>> i1 == i2
478
453
        True
479
 
        >>> i1.add(InventoryEntry('123', 'foo'))
 
454
        >>> i1.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
 
455
        InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
480
456
        >>> i1 == i2
481
457
        False
482
 
        >>> i2.add(InventoryEntry('123', 'foo'))
 
458
        >>> i2.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
 
459
        InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
483
460
        >>> i1 == i2
484
461
        True
485
462
        """
486
 
        if self is other:
487
 
            return 0
488
 
        
489
463
        if not isinstance(other, Inventory):
490
464
            return NotImplemented
491
465
 
492
 
        if self.id_set() ^ other.id_set():
493
 
            return 1
494
 
 
495
 
        for file_id in self._byid:
496
 
            c = cmp(self[file_id], other[file_id])
497
 
            if c: return c
498
 
 
499
 
        return 0
500
 
 
501
 
 
502
 
    def id2path(self, file_id):
503
 
        """Return as a list the path to file_id."""
 
466
        if len(self._byid) != len(other._byid):
 
467
            # shortcut: obviously not the same
 
468
            return False
 
469
 
 
470
        return self._byid == other._byid
 
471
 
 
472
 
 
473
    def __ne__(self, other):
 
474
        return not (self == other)
 
475
 
 
476
 
 
477
    def __hash__(self):
 
478
        raise ValueError('not hashable')
 
479
 
 
480
 
 
481
    def get_idpath(self, file_id):
 
482
        """Return a list of file_ids for the path to an entry.
 
483
 
 
484
        The list contains one element for each directory followed by
 
485
        the id of the file itself.  So the length of the returned list
 
486
        is equal to the depth of the file in the tree, counting the
 
487
        root directory as depth 1.
 
488
        """
504
489
        p = []
505
490
        while file_id != None:
506
 
            ie = self._byid[file_id]
507
 
            p.insert(0, ie.name)
 
491
            try:
 
492
                ie = self._byid[file_id]
 
493
            except KeyError:
 
494
                raise BzrError("file_id {%s} not found in inventory" % file_id)
 
495
            p.insert(0, ie.file_id)
508
496
            file_id = ie.parent_id
509
 
        return '/'.join(p)
 
497
        return p
 
498
 
 
499
 
 
500
    def id2path(self, file_id):
 
501
        """Return as a list the path to file_id."""
 
502
 
 
503
        # get all names, skipping root
 
504
        p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
 
505
        return os.sep.join(p)
510
506
            
511
507
 
512
508
 
518
514
 
519
515
        This returns the entry of the last component in the path,
520
516
        which may be either a file or a directory.
 
517
 
 
518
        Returns None iff the path is not found.
521
519
        """
522
520
        if isinstance(name, types.StringTypes):
523
521
            name = splitpath(name)
524
522
 
525
 
        parent = self[None]
 
523
        mutter("lookup path %r" % name)
 
524
 
 
525
        parent = self.root
526
526
        for f in name:
527
527
            try:
528
528
                cie = parent.children[f]
529
529
                assert cie.name == f
 
530
                assert cie.parent_id == parent.file_id
530
531
                parent = cie
531
532
            except KeyError:
532
533
                # or raise an error?
550
551
 
551
552
        This does not move the working file."""
552
553
        if not is_valid_name(new_name):
553
 
            bailout("not an acceptable filename: %r" % new_name)
 
554
            raise BzrError("not an acceptable filename: %r" % new_name)
554
555
 
555
556
        new_parent = self._byid[new_parent_id]
556
557
        if new_name in new_parent.children:
557
 
            bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
 
558
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
 
559
 
 
560
        new_parent_idpath = self.get_idpath(new_parent_id)
 
561
        if file_id in new_parent_idpath:
 
562
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
 
563
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
558
564
 
559
565
        file_ie = self._byid[file_id]
560
566
        old_parent = self._byid[file_ie.parent_id]
570
576
 
571
577
 
572
578
 
573
 
_NAME_RE = re.compile(r'^[^/\\]+$')
 
579
_NAME_RE = None
574
580
 
575
581
def is_valid_name(name):
 
582
    global _NAME_RE
 
583
    if _NAME_RE == None:
 
584
        _NAME_RE = re.compile(r'^[^/\\]+$')
 
585
        
576
586
    return bool(_NAME_RE.match(name))
577
 
 
578
 
 
579
 
 
580
 
if __name__ == '__main__':
581
 
    import doctest, inventory
582
 
    doctest.testmod(inventory)