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