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
35
28
from bzrlib.osutils import uuid, quotefn, splitpath, joinpath, appendpath
36
29
from bzrlib.trace import mutter
38
class InventoryEntry(XMLMixin):
31
class InventoryEntry(object):
39
32
"""Description of a versioned file.
41
34
An InventoryEntry has the following fields, which are also
69
62
>>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
70
63
Traceback (most recent call last):
72
BzrError: ('inventory already contains entry with id {2323}', [])
65
BzrError: inventory already contains entry with id {2323}
73
66
>>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
74
67
>>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
75
68
>>> i.path2id('src/wibble')
145
138
self.parent_id, text_id=self.text_id)
146
139
other.text_sha1 = self.text_sha1
147
140
other.text_size = self.text_size
141
# note that children are *not* copied; they're pulled across when
210
207
from_element = classmethod(from_element)
212
def __cmp__(self, other):
209
def __eq__(self, other):
215
210
if not isinstance(other, InventoryEntry):
216
211
return NotImplemented
218
return cmp(self.file_id, other.file_id) \
219
or cmp(self.name, other.name) \
220
or cmp(self.text_sha1, other.text_sha1) \
221
or cmp(self.text_size, other.text_size) \
222
or cmp(self.text_id, other.text_id) \
223
or cmp(self.parent_id, other.parent_id) \
224
or cmp(self.kind, other.kind)
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')
233
235
self.parent_id = None
236
def __cmp__(self, other):
238
def __eq__(self, other):
239
239
if not isinstance(other, RootEntry):
240
240
return NotImplemented
241
return cmp(self.file_id, other.file_id) \
242
or cmp(self.children, other.children)
246
class Inventory(XMLMixin):
242
return (self.file_id == other.file_id) \
243
and (self.children == other.children)
247
class Inventory(object):
247
248
"""Inventory of versioned files in a tree.
249
250
This describes which file_id is present at each point in the tree,
261
262
inserted, other than through the Inventory API.
263
264
>>> inv = Inventory()
264
>>> inv.write_xml(sys.stdout)
267
265
>>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
268
266
>>> inv['123-123'].name
280
278
>>> [x[0] for x in inv.iter_entries()]
283
>>> inv.write_xml(sys.stdout)
285
<entry file_id="123-123" kind="file" name="hello.c" />
280
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
281
>>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
283
def __init__(self, root_id=ROOT_ID):
290
284
"""Create or read an inventory.
292
286
If a working directory is specified, the inventory is read
296
290
The inventory is created with a default root directory, with
299
self.root = RootEntry(ROOT_ID)
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)
300
298
self._byid = {self.root.file_id: self.root}
324
322
if ie.kind == 'directory':
325
323
for cn, cie in self.iter_entries(from_dir=ie.file_id):
326
324
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.
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))
339
if ie.kind == 'directory':
340
descend(ie, child_path)
342
descend(self.root, '')
330
346
def directories(self):
331
"""Return (path, entry) pairs for all directories.
347
"""Return (path, entry) pairs for all directories, including the root.
333
def descend(parent_ie):
334
parent_name = parent_ie.name
335
yield parent_name, parent_ie
337
# directory children in sorted order
339
for ie in parent_ie.children.itervalues():
340
if ie.kind == 'directory':
341
dn.append((ie.name, ie))
350
def descend(parent_ie, parent_path):
351
accum.append((parent_path, parent_ie))
344
for name, child_ie in dn:
345
for sub_name, sub_ie in descend(child_ie):
346
yield appendpath(parent_name, sub_name), sub_ie
353
kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
348
for name, ie in descend(self.root):
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, '')
380
391
raise BzrError("file_id {%s} not in inventory" % file_id)
394
def get_file_kind(self, file_id):
395
return self._byid[file_id].kind
383
397
def get_child(self, parent_id, filename):
384
398
return self[parent_id].children.get(filename)
390
404
To add a file to a branch ready to be committed, use Branch.add,
391
405
which calls this."""
392
406
if entry.file_id in self._byid:
393
bailout("inventory already contains entry with id {%s}" % entry.file_id)
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
396
413
parent = self._byid[entry.parent_id]
398
bailout("parent_id {%s} not in inventory" % entry.parent_id)
415
raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
400
417
if parent.children.has_key(entry.name):
401
bailout("%s is already versioned" %
418
raise BzrError("%s is already versioned" %
402
419
appendpath(self.id2path(parent.file_id), entry.name))
404
421
self._byid[entry.file_id] = entry
409
426
"""Add entry from a path.
411
428
The immediate parent must already be versioned"""
429
from bzrlib.errors import NotVersionedError
412
431
parts = bzrlib.osutils.splitpath(relpath)
413
432
if len(parts) == 0:
414
bailout("cannot re-add root of inventory")
433
raise BzrError("cannot re-add root of inventory")
416
435
if file_id == None:
417
file_id = bzrlib.branch.gen_file_id(relpath)
419
parent_id = self.path2id(parts[:-1])
420
assert parent_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)
421
444
ie = InventoryEntry(file_id, parts[-1],
422
445
kind=kind, parent_id=parent_id)
423
446
return self.add(ie)
449
472
del self[ie.parent_id].children[ie.name]
453
return Set(self._byid)
456
475
def to_element(self):
457
476
"""Convert to XML Element"""
477
from bzrlib.xml import Element
458
479
e = Element('inventory')
481
if self.root.file_id not in (None, ROOT_ID):
482
e.set('file_id', self.root.file_id)
460
483
for path, ie in self.iter_entries():
461
484
e.append(ie.to_element())
465
488
def from_element(cls, elt):
466
489
"""Construct from XML Element
468
491
>>> inv = Inventory()
469
492
>>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
470
493
>>> elt = inv.to_element()
498
# XXXX: doctest doesn't run this properly under python2.3
475
499
assert elt.tag == 'inventory'
500
root_id = elt.get('file_id') or ROOT_ID
478
o.add(InventoryEntry.from_element(e))
503
ie = InventoryEntry.from_element(e)
504
if ie.parent_id == ROOT_ID:
505
ie.parent_id = root_id
481
509
from_element = classmethod(from_element)
484
def __cmp__(self, other):
512
def __eq__(self, other):
485
513
"""Compare two sets by comparing their contents.
487
515
>>> i1 = Inventory()
501
526
if not isinstance(other, Inventory):
502
527
return NotImplemented
504
if self.id_set() ^ other.id_set():
507
for file_id in self._byid:
508
c = cmp(self[file_id], other[file_id])
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')
514
545
def get_idpath(self, file_id):
534
565
"""Return as a list the path to file_id."""
536
567
# get all names, skipping root
537
p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
568
p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
538
569
return os.sep.join(p)
585
616
This does not move the working file."""
586
617
if not is_valid_name(new_name):
587
bailout("not an acceptable filename: %r" % new_name)
618
raise BzrError("not an acceptable filename: %r" % new_name)
589
620
new_parent = self._byid[new_parent_id]
590
621
if new_name in new_parent.children:
591
bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
622
raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
593
624
new_parent_idpath = self.get_idpath(new_parent_id)
594
625
if file_id in new_parent_idpath:
595
bailout("cannot move directory %r into a subdirectory of itself, %r"
626
raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
596
627
% (self.id2path(file_id), self.id2path(new_parent_id)))
598
629
file_ie = self._byid[file_id]