~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: mbp at sourcefrog
  • Date: 2005-04-05 08:24:51 UTC
  • Revision ID: mbp@sourcefrog.net-20050405082451-408ebb0fd108440f
start adding quotes

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#! /usr/bin/env python
2
 
# -*- coding: UTF-8 -*-
 
1
# (C) 2005 Canonical Ltd
3
2
 
4
3
# This program is free software; you can redistribute it and/or modify
5
4
# it under the terms of the GNU General Public License as published by
17
16
 
18
17
"""Inventories map files to their name in a revision."""
19
18
 
 
19
# TODO: Maybe store inventory_id in the file?  Not really needed.
20
20
 
21
21
__copyright__ = "Copyright (C) 2005 Canonical Ltd."
22
22
__author__ = "Martin Pool <mbp@canonical.com>"
23
23
 
24
 
import sys, os.path, types
 
24
import sys, os.path, types, re
25
25
from sets import Set
26
26
 
27
27
try:
124
124
        self.parent_id = parent_id
125
125
        self.text_sha1 = None
126
126
        self.text_size = None
 
127
        if kind == 'directory':
 
128
            self.children = {}
 
129
 
 
130
 
 
131
    def sorted_children(self):
 
132
        l = self.children.items()
 
133
        l.sort()
 
134
        return l
127
135
 
128
136
 
129
137
    def copy(self):
197
205
 
198
206
 
199
207
 
 
208
class RootEntry(InventoryEntry):
 
209
    def __init__(self, file_id):
 
210
        self.file_id = file_id
 
211
        self.children = {}
 
212
        self.kind = 'root_directory'
 
213
        self.parent_id = None
 
214
        self.name = ''
 
215
 
 
216
    def __cmp__(self, other):
 
217
        if self is other:
 
218
            return 0
 
219
        if not isinstance(other, RootEntry):
 
220
            return NotImplemented
 
221
        return cmp(self.file_id, other.file_id) \
 
222
               or cmp(self.children, other.children)
 
223
 
 
224
 
 
225
 
200
226
class Inventory(XMLMixin):
201
227
    """Inventory of versioned files in a tree.
202
228
 
215
241
    returned quickly.
216
242
 
217
243
    InventoryEntry objects must not be modified after they are
218
 
    inserted.
 
244
    inserted, other than through the Inventory API.
219
245
 
220
246
    >>> inv = Inventory()
221
247
    >>> inv.write_xml(sys.stdout)
224
250
    >>> inv.add(InventoryEntry('123-123', 'hello.c'))
225
251
    >>> inv['123-123'].name
226
252
    'hello.c'
227
 
    >>> for file_id in inv: print file_id
228
 
    ...
229
 
    123-123
230
253
 
231
254
    May be treated as an iterator or set to look up file ids:
232
255
    
247
270
 
248
271
    """
249
272
 
250
 
    ## TODO: Clear up handling of files in subdirectories; we probably
251
 
    ## do want to be able to just look them up by name but this
252
 
    ## probably means gradually walking down the path, looking up as we go.
253
 
 
254
273
    ## TODO: Make sure only canonical filenames are stored.
255
274
 
256
275
    ## TODO: Do something sensible about the possible collisions on
257
276
    ## case-losing filesystems.  Perhaps we should just always forbid
258
277
    ## such collisions.
259
278
 
260
 
    ## _tree should probably just be stored as
261
 
    ## InventoryEntry._children on each directory.
 
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
262
283
 
263
284
    def __init__(self):
264
285
        """Create or read an inventory.
266
287
        If a working directory is specified, the inventory is read
267
288
        from there.  If the file is specified, read from that. If not,
268
289
        the inventory is created empty.
 
290
 
 
291
        The inventory is created with a default root directory, with
 
292
        an id of None.
269
293
        """
270
 
        self._byid = dict()
271
 
 
272
 
        # _tree is indexed by parent_id; at each level a map from name
273
 
        # to ie.  The None entry is the root.
274
 
        self._tree = {None: {}}
 
294
        self.root = RootEntry(None)
 
295
        self._byid = {None: self.root}
275
296
 
276
297
 
277
298
    def __iter__(self):
283
304
        return len(self._byid)
284
305
 
285
306
 
286
 
    def iter_entries(self, parent_id=None):
 
307
    def iter_entries(self, from_dir=None):
287
308
        """Return (path, entry) pairs, in order by name."""
288
 
        kids = self._tree[parent_id].items()
 
309
        if from_dir == None:
 
310
            assert self.root
 
311
            from_dir = self.root
 
312
        elif isinstance(from_dir, basestring):
 
313
            from_dir = self._byid[from_dir]
 
314
            
 
315
        kids = from_dir.children.items()
289
316
        kids.sort()
290
317
        for name, ie in kids:
291
318
            yield name, ie
292
319
            if ie.kind == 'directory':
293
 
                for cn, cie in self.iter_entries(parent_id=ie.file_id):
294
 
                    yield joinpath([name, cn]), cie
295
 
 
296
 
 
297
 
    def directories(self, include_root=True):
 
320
                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):
298
326
        """Return (path, entry) pairs for all directories.
299
327
        """
300
 
        if include_root:
301
 
            yield '', None
302
 
        for path, entry in self.iter_entries():
303
 
            if entry.kind == 'directory':
304
 
                yield path, entry
 
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():
 
335
                if ie.kind == 'directory':
 
336
                    dn.append((ie.name, ie))
 
337
            dn.sort()
 
338
            
 
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
 
342
 
 
343
        for name, ie in descend(self.root):
 
344
            yield name, ie
305
345
        
306
346
 
307
347
 
308
 
    def children(self, parent_id):
309
 
        """Return entries that are direct children of parent_id."""
310
 
        return self._tree[parent_id]
311
 
                    
312
 
 
313
 
 
314
 
    # TODO: return all paths and entries
315
 
 
316
 
 
317
348
    def __contains__(self, file_id):
318
349
        """True if this entry contains a file with given id.
319
350
 
338
369
        return self._byid[file_id]
339
370
 
340
371
 
 
372
    def get_child(self, parent_id, filename):
 
373
        return self[parent_id].children.get(filename)
 
374
 
 
375
 
341
376
    def add(self, entry):
342
377
        """Add entry to inventory.
343
378
 
344
379
        To add  a file to a branch ready to be committed, use Branch.add,
345
380
        which calls this."""
346
 
        if entry.file_id in self:
 
381
        if entry.file_id in self._byid:
347
382
            bailout("inventory already contains entry with id {%s}" % entry.file_id)
348
383
 
349
 
        if entry.parent_id != None:
350
 
            if entry.parent_id not in self:
351
 
                bailout("parent_id %s of new entry not found in inventory"
352
 
                        % entry.parent_id)
353
 
            
354
 
        if self._tree[entry.parent_id].has_key(entry.name):
355
 
            bailout("%s is already versioned"
356
 
                    % appendpath(self.id2path(entry.parent_id), entry.name))
 
384
        try:
 
385
            parent = self._byid[entry.parent_id]
 
386
        except KeyError:
 
387
            bailout("parent_id %r not in inventory" % entry.parent_id)
 
388
 
 
389
        if parent.children.has_key(entry.name):
 
390
            bailout("%s is already versioned" %
 
391
                    appendpath(self.id2path(parent.file_id), entry.name))
357
392
 
358
393
        self._byid[entry.file_id] = entry
359
 
        self._tree[entry.parent_id][entry.name] = entry
360
 
 
361
 
        if entry.kind == 'directory':
362
 
            self._tree[entry.file_id] = {}
 
394
        parent.children[entry.name] = entry
363
395
 
364
396
 
365
397
    def add_path(self, relpath, kind, file_id=None):
392
424
        """
393
425
        ie = self[file_id]
394
426
 
395
 
        assert self._tree[ie.parent_id][ie.name] == ie
 
427
        assert self[ie.parent_id].children[ie.name] == ie
396
428
        
397
429
        # TODO: Test deleting all children; maybe hoist to a separate
398
430
        # deltree method?
399
431
        if ie.kind == 'directory':
400
 
            for cie in self._tree[file_id].values():
 
432
            for cie in ie.children.values():
401
433
                del self[cie.file_id]
402
 
            del self._tree[file_id]
 
434
            del ie.children
403
435
 
404
436
        del self._byid[file_id]
405
 
        del self._tree[ie.parent_id][ie.name]
 
437
        del self[ie.parent_id].children[ie.name]
406
438
 
407
439
 
408
440
    def id_set(self):
471
503
        """Return as a list the path to file_id."""
472
504
        p = []
473
505
        while file_id != None:
474
 
            ie = self[file_id]
475
 
            p = [ie.name] + p
 
506
            ie = self._byid[file_id]
 
507
            p.insert(0, ie.name)
476
508
            file_id = ie.parent_id
477
 
        return joinpath(p)
 
509
        return '/'.join(p)
478
510
            
479
511
 
480
512
 
490
522
        if isinstance(name, types.StringTypes):
491
523
            name = splitpath(name)
492
524
 
493
 
        parent_id = None
 
525
        parent = self[None]
494
526
        for f in name:
495
527
            try:
496
 
                cie = self._tree[parent_id][f]
 
528
                cie = parent.children[f]
497
529
                assert cie.name == f
498
 
                parent_id = cie.file_id
 
530
                parent = cie
499
531
            except KeyError:
500
532
                # or raise an error?
501
533
                return None
502
534
 
503
 
        return parent_id
504
 
 
505
 
 
506
 
    def get_child(self, parent_id, child_name):
507
 
        return self._tree[parent_id].get(child_name)
 
535
        return parent.file_id
508
536
 
509
537
 
510
538
    def has_filename(self, names):
512
540
 
513
541
 
514
542
    def has_id(self, file_id):
515
 
        assert isinstance(file_id, str)
516
543
        return self._byid.has_key(file_id)
517
544
 
518
545
 
 
546
    def rename(self, file_id, new_parent_id, new_name):
 
547
        """Move a file within the inventory.
 
548
 
 
549
        This can change either the name, or the parent, or both.
 
550
 
 
551
        This does not move the working file."""
 
552
        if not is_valid_name(new_name):
 
553
            bailout("not an acceptable filename: %r" % new_name)
 
554
 
 
555
        new_parent = self._byid[new_parent_id]
 
556
        if new_name in new_parent.children:
 
557
            bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
 
558
 
 
559
        file_ie = self._byid[file_id]
 
560
        old_parent = self._byid[file_ie.parent_id]
 
561
 
 
562
        # TODO: Don't leave things messed up if this fails
 
563
 
 
564
        del old_parent.children[file_ie.name]
 
565
        new_parent.children[new_name] = file_ie
 
566
        
 
567
        file_ie.name = new_name
 
568
        file_ie.parent_id = new_parent_id
 
569
 
 
570
 
 
571
 
 
572
 
 
573
_NAME_RE = re.compile(r'^[^/\\]+$')
 
574
 
 
575
def is_valid_name(name):
 
576
    return bool(_NAME_RE.match(name))
519
577
 
520
578
 
521
579