23
23
import sys, os.path, types, re
27
from cElementTree import Element, ElementTree, SubElement
29
from elementtree.ElementTree import Element, ElementTree, SubElement
31
from xml import XMLMixin
32
from errors import bailout, BzrError, BzrCheckError
26
from bzrlib.errors import BzrError, BzrCheckError
28
35
from bzrlib.osutils import uuid, quotefn, splitpath, joinpath, appendpath
29
36
from bzrlib.trace import mutter
31
class InventoryEntry(object):
38
class InventoryEntry(XMLMixin):
32
39
"""Description of a versioned file.
34
41
An InventoryEntry has the following fields, which are also
62
69
>>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
63
70
Traceback (most recent call last):
65
BzrError: inventory already contains entry with id {2323}
72
BzrError: ('inventory already contains entry with id {2323}', [])
66
73
>>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
67
74
>>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
68
75
>>> i.path2id('src/wibble')
207
212
from_element = classmethod(from_element)
209
def __eq__(self, other):
214
def __cmp__(self, other):
210
217
if not isinstance(other, InventoryEntry):
211
218
return NotImplemented
213
return (self.file_id == other.file_id) \
214
and (self.name == other.name) \
215
and (self.text_sha1 == other.text_sha1) \
216
and (self.text_size == other.text_size) \
217
and (self.text_id == other.text_id) \
218
and (self.parent_id == other.parent_id) \
219
and (self.kind == other.kind)
222
def __ne__(self, other):
223
return not (self == other)
226
raise ValueError('not hashable')
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)
235
235
self.parent_id = None
238
def __eq__(self, other):
238
def __cmp__(self, other):
239
241
if not isinstance(other, RootEntry):
240
242
return NotImplemented
242
return (self.file_id == other.file_id) \
243
and (self.children == other.children)
247
class Inventory(object):
243
return cmp(self.file_id, other.file_id) \
244
or cmp(self.children, other.children)
248
class Inventory(XMLMixin):
248
249
"""Inventory of versioned files in a tree.
250
251
This describes which file_id is present at each point in the tree,
262
263
inserted, other than through the Inventory API.
264
265
>>> inv = Inventory()
266
>>> inv.write_xml(sys.stdout)
265
269
>>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
266
270
>>> inv['123-123'].name
278
282
>>> [x[0] for x in inv.iter_entries()]
280
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
281
>>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
285
>>> inv.write_xml(sys.stdout)
287
<entry file_id="123-123" kind="file" name="hello.c" />
283
def __init__(self, root_id=ROOT_ID):
284
292
"""Create or read an inventory.
286
294
If a working directory is specified, the inventory is read
290
298
The inventory is created with a default root directory, with
293
# We are letting Branch(init=True) create a unique inventory
294
# root id. Rather than generating a random one here.
296
# root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
297
self.root = RootEntry(root_id)
301
self.root = RootEntry(ROOT_ID)
298
302
self._byid = {self.root.file_id: self.root}
322
326
if ie.kind == 'directory':
323
327
for cn, cie in self.iter_entries(from_dir=ie.file_id):
324
328
yield os.path.join(name, cn), cie
328
"""Return list of (path, ie) for all entries except the root.
330
This may be faster than iter_entries.
332
def directories(self):
333
"""Return (path, entry) pairs for all directories.
333
def descend(dir_ie, dir_path):
334
kids = dir_ie.children.items()
336
for name, ie in kids:
337
child_path = os.path.join(dir_path, name)
338
accum.append((child_path, ie))
335
def descend(parent_ie):
336
parent_name = parent_ie.name
337
yield parent_name, parent_ie
339
# directory children in sorted order
341
for ie in parent_ie.children.itervalues():
339
342
if ie.kind == 'directory':
340
descend(ie, child_path)
342
descend(self.root, '')
346
def directories(self):
347
"""Return (path, entry) pairs for all directories, including the root.
350
def descend(parent_ie, parent_path):
351
accum.append((parent_path, parent_ie))
343
dn.append((ie.name, ie))
353
kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
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
356
for name, child_ie in kids:
357
child_path = os.path.join(parent_path, name)
358
descend(child_ie, child_path)
359
descend(self.root, '')
350
for name, ie in descend(self.root):
404
395
To add a file to a branch ready to be committed, use Branch.add,
405
396
which calls this."""
406
397
if entry.file_id in self._byid:
407
raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
409
if entry.parent_id == ROOT_ID or entry.parent_id is None:
410
entry.parent_id = self.root.file_id
398
bailout("inventory already contains entry with id {%s}" % entry.file_id)
413
401
parent = self._byid[entry.parent_id]
415
raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
403
bailout("parent_id {%s} not in inventory" % entry.parent_id)
417
405
if parent.children.has_key(entry.name):
418
raise BzrError("%s is already versioned" %
406
bailout("%s is already versioned" %
419
407
appendpath(self.id2path(parent.file_id), entry.name))
421
409
self._byid[entry.file_id] = entry
426
414
"""Add entry from a path.
428
416
The immediate parent must already be versioned"""
429
from bzrlib.errors import NotVersionedError
431
417
parts = bzrlib.osutils.splitpath(relpath)
432
418
if len(parts) == 0:
433
raise BzrError("cannot re-add root of inventory")
419
bailout("cannot re-add root of inventory")
435
421
if file_id == None:
436
from bzrlib.branch import gen_file_id
437
file_id = gen_file_id(relpath)
439
parent_path = parts[:-1]
440
parent_id = self.path2id(parent_path)
441
if parent_id == None:
442
raise NotVersionedError(parent_path)
422
file_id = bzrlib.branch.gen_file_id(relpath)
424
parent_id = self.path2id(parts[:-1])
425
assert parent_id != None
444
426
ie = InventoryEntry(file_id, parts[-1],
445
427
kind=kind, parent_id=parent_id)
446
428
return self.add(ie)
472
454
del self[ie.parent_id].children[ie.name]
458
return Set(self._byid)
475
461
def to_element(self):
476
462
"""Convert to XML Element"""
477
from bzrlib.xml import Element
479
463
e = Element('inventory')
481
if self.root.file_id not in (None, ROOT_ID):
482
e.set('file_id', self.root.file_id)
483
465
for path, ie in self.iter_entries():
484
466
e.append(ie.to_element())
488
470
def from_element(cls, elt):
489
471
"""Construct from XML Element
491
473
>>> inv = Inventory()
492
474
>>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
493
475
>>> elt = inv.to_element()
498
# XXXX: doctest doesn't run this properly under python2.3
499
480
assert elt.tag == 'inventory'
500
root_id = elt.get('file_id') or ROOT_ID
503
ie = InventoryEntry.from_element(e)
504
if ie.parent_id == ROOT_ID:
505
ie.parent_id = root_id
483
o.add(InventoryEntry.from_element(e))
509
486
from_element = classmethod(from_element)
512
def __eq__(self, other):
489
def __cmp__(self, other):
513
490
"""Compare two sets by comparing their contents.
515
492
>>> i1 = Inventory()
526
506
if not isinstance(other, Inventory):
527
507
return NotImplemented
529
if len(self._byid) != len(other._byid):
530
# shortcut: obviously not the same
533
return self._byid == other._byid
536
def __ne__(self, other):
537
return not (self == other)
541
raise ValueError('not hashable')
509
if self.id_set() ^ other.id_set():
512
for file_id in self._byid:
513
c = cmp(self[file_id], other[file_id])
545
519
def get_idpath(self, file_id):
565
539
"""Return as a list the path to file_id."""
567
541
# get all names, skipping root
568
p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
542
p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
569
543
return os.sep.join(p)
616
590
This does not move the working file."""
617
591
if not is_valid_name(new_name):
618
raise BzrError("not an acceptable filename: %r" % new_name)
592
bailout("not an acceptable filename: %r" % new_name)
620
594
new_parent = self._byid[new_parent_id]
621
595
if new_name in new_parent.children:
622
raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
596
bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
624
598
new_parent_idpath = self.get_idpath(new_parent_id)
625
599
if file_id in new_parent_idpath:
626
raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
600
bailout("cannot move directory %r into a subdirectory of itself, %r"
627
601
% (self.id2path(file_id), self.id2path(new_parent_id)))
629
603
file_ie = self._byid[file_id]