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