23
23
import sys, os.path, types, re
26
from cElementTree import Element, ElementTree, SubElement
28
from elementtree.ElementTree import Element, ElementTree, SubElement
30
from xml import XMLMixin
31
from errors import bailout, BzrError, BzrCheckError
26
from bzrlib.errors import BzrError, BzrCheckError
28
34
from bzrlib.osutils import uuid, quotefn, splitpath, joinpath, appendpath
29
35
from bzrlib.trace import mutter
30
from bzrlib.errors import NotVersionedError
33
class InventoryEntry(object):
37
class InventoryEntry(XMLMixin):
34
38
"""Description of a versioned file.
36
40
An InventoryEntry has the following fields, which are also
57
61
>>> i.add(InventoryEntry('123', 'src', 'directory', ROOT_ID))
58
InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT')
59
62
>>> i.add(InventoryEntry('2323', 'hello.c', 'file', parent_id='123'))
60
InventoryEntry('2323', 'hello.c', kind='file', parent_id='123')
61
63
>>> for j in i.iter_entries():
66
68
>>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
67
69
Traceback (most recent call last):
69
BzrError: inventory already contains entry with id {2323}
71
BzrError: ('inventory already contains entry with id {2323}', [])
70
72
>>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
71
InventoryEntry('2324', 'bye.c', kind='file', parent_id='123')
72
73
>>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
73
InventoryEntry('2325', 'wibble', kind='directory', parent_id='123')
74
74
>>> i.path2id('src/wibble')
78
78
>>> i.add(InventoryEntry('2326', 'wibble.c', 'file', '2325'))
79
InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
81
80
InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
82
81
>>> for j in i.iter_entries():
99
98
# TODO: split InventoryEntry into subclasses for files,
100
99
# directories, etc etc.
102
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
103
'text_id', 'parent_id', 'children',
104
'text_version', 'entry_version', ]
107
104
def __init__(self, file_id, name, kind, parent_id, text_id=None):
108
105
"""Create an InventoryEntry
119
116
Traceback (most recent call last):
120
117
BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
122
assert isinstance(name, basestring), name
123
119
if '/' in name or '\\' in name:
124
120
raise BzrCheckError('InventoryEntry name %r is invalid' % name)
126
self.text_version = None
127
self.entry_version = None
128
self.text_sha1 = None
129
self.text_size = None
130
122
self.file_id = file_id
161
def to_element(self):
162
"""Convert to XML element"""
165
e.set('name', self.name)
166
e.set('file_id', self.file_id)
167
e.set('kind', self.kind)
169
if self.text_size != None:
170
e.set('text_size', '%d' % self.text_size)
172
for f in ['text_id', 'text_sha1']:
177
# to be conservative, we don't externalize the root pointers
178
# for now, leaving them as null in the xml form. in a future
179
# version it will be implied by nested elements.
180
if self.parent_id != ROOT_ID:
181
assert isinstance(self.parent_id, basestring)
182
e.set('parent_id', self.parent_id)
189
def from_element(cls, elt):
190
assert elt.tag == 'entry'
192
## original format inventories don't have a parent_id for
193
## nodes in the root directory, but it's cleaner to use one
195
parent_id = elt.get('parent_id')
196
if parent_id == None:
199
self = cls(elt.get('file_id'), elt.get('name'), elt.get('kind'), parent_id)
200
self.text_id = elt.get('text_id')
201
self.text_sha1 = elt.get('text_sha1')
203
## mutter("read inventoryentry: %r" % (elt.attrib))
205
v = elt.get('text_size')
206
self.text_size = v and int(v)
211
from_element = classmethod(from_element)
169
213
def __eq__(self, other):
170
214
if not isinstance(other, InventoryEntry):
171
215
return NotImplemented
176
220
and (self.text_size == other.text_size) \
177
221
and (self.text_id == other.text_id) \
178
222
and (self.parent_id == other.parent_id) \
179
and (self.kind == other.kind) \
180
and (self.text_version == other.text_version) \
181
and (self.entry_version == other.entry_version)
223
and (self.kind == other.kind)
184
226
def __ne__(self, other):
224
266
inserted, other than through the Inventory API.
226
268
>>> inv = Inventory()
269
>>> inv.write_xml(sys.stdout)
227
272
>>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
228
InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT')
229
273
>>> inv['123-123'].name
241
285
>>> [x[0] for x in inv.iter_entries()]
243
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
244
>>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
245
InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT-12345678-12345678')
288
>>> inv.write_xml(sys.stdout)
290
<entry file_id="123-123" kind="file" name="hello.c" />
247
def __init__(self, root_id=ROOT_ID):
248
295
"""Create or read an inventory.
250
297
If a working directory is specified, the inventory is read
254
301
The inventory is created with a default root directory, with
257
# We are letting Branch(init=True) create a unique inventory
258
# root id. Rather than generating a random one here.
260
# root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
261
self.root = RootEntry(root_id)
304
self.root = RootEntry(ROOT_ID)
262
305
self._byid = {self.root.file_id: self.root}
331
374
>>> inv = Inventory()
332
375
>>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
333
InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
345
387
>>> inv = Inventory()
346
388
>>> inv.add(InventoryEntry('123123', 'hello.c', 'file', ROOT_ID))
347
InventoryEntry('123123', 'hello.c', kind='file', parent_id='TREE_ROOT')
348
389
>>> inv['123123'].name
368
409
"""Add entry to inventory.
370
411
To add a file to a branch ready to be committed, use Branch.add,
373
Returns the new entry object.
375
413
if entry.file_id in self._byid:
376
raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
378
if entry.parent_id == ROOT_ID or entry.parent_id is None:
379
entry.parent_id = self.root.file_id
414
bailout("inventory already contains entry with id {%s}" % entry.file_id)
382
417
parent = self._byid[entry.parent_id]
384
raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
419
bailout("parent_id {%s} not in inventory" % entry.parent_id)
386
421
if parent.children.has_key(entry.name):
387
raise BzrError("%s is already versioned" %
422
bailout("%s is already versioned" %
388
423
appendpath(self.id2path(parent.file_id), entry.name))
390
425
self._byid[entry.file_id] = entry
391
426
parent.children[entry.name] = entry
395
429
def add_path(self, relpath, kind, file_id=None):
396
430
"""Add entry from a path.
398
The immediate parent must already be versioned.
400
Returns the new entry object."""
401
from bzrlib.branch import gen_file_id
432
The immediate parent must already be versioned"""
403
433
parts = bzrlib.osutils.splitpath(relpath)
404
434
if len(parts) == 0:
405
raise BzrError("cannot re-add root of inventory")
435
bailout("cannot re-add root of inventory")
407
437
if file_id == None:
408
file_id = gen_file_id(relpath)
410
parent_path = parts[:-1]
411
parent_id = self.path2id(parent_path)
412
if parent_id == None:
413
raise NotVersionedError(parent_path)
438
file_id = bzrlib.branch.gen_file_id(relpath)
440
parent_id = self.path2id(parts[:-1])
441
assert parent_id != None
415
442
ie = InventoryEntry(file_id, parts[-1],
416
443
kind=kind, parent_id=parent_id)
417
444
return self.add(ie)
423
450
>>> inv = Inventory()
424
451
>>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
425
InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
428
454
>>> del inv['123']
444
470
del self[ie.parent_id].children[ie.name]
473
def to_element(self):
474
"""Convert to XML Element"""
475
e = Element('inventory')
477
for path, ie in self.iter_entries():
478
e.append(ie.to_element())
482
def from_element(cls, elt):
483
"""Construct from XML Element
485
>>> inv = Inventory()
486
>>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
487
>>> elt = inv.to_element()
488
>>> inv2 = Inventory.from_element(elt)
492
assert elt.tag == 'inventory'
495
o.add(InventoryEntry.from_element(e))
498
from_element = classmethod(from_element)
447
501
def __eq__(self, other):
448
502
"""Compare two sets by comparing their contents.
454
508
>>> i1.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
455
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
458
511
>>> i2.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
459
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
501
554
"""Return as a list the path to file_id."""
503
556
# get all names, skipping root
504
p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
557
p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
505
558
return os.sep.join(p)
552
605
This does not move the working file."""
553
606
if not is_valid_name(new_name):
554
raise BzrError("not an acceptable filename: %r" % new_name)
607
bailout("not an acceptable filename: %r" % new_name)
556
609
new_parent = self._byid[new_parent_id]
557
610
if new_name in new_parent.children:
558
raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
611
bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
560
613
new_parent_idpath = self.get_idpath(new_parent_id)
561
614
if file_id in new_parent_idpath:
562
raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
615
bailout("cannot move directory %r into a subdirectory of itself, %r"
563
616
% (self.id2path(file_id), self.id2path(new_parent_id)))
565
618
file_ie = self._byid[file_id]