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
34
28
from bzrlib.osutils import uuid, quotefn, splitpath, joinpath, appendpath
35
29
from bzrlib.trace import mutter
30
from bzrlib.errors import NotVersionedError
37
class InventoryEntry(XMLMixin):
33
class InventoryEntry(object):
38
34
"""Description of a versioned file.
40
36
An InventoryEntry has the following fields, which are also
61
57
>>> i.add(InventoryEntry('123', 'src', 'directory', ROOT_ID))
58
InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT')
62
59
>>> i.add(InventoryEntry('2323', 'hello.c', 'file', parent_id='123'))
60
InventoryEntry('2323', 'hello.c', kind='file', parent_id='123')
63
61
>>> for j in i.iter_entries():
68
66
>>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
69
67
Traceback (most recent call last):
71
BzrError: ('inventory already contains entry with id {2323}', [])
69
BzrError: inventory already contains entry with id {2323}
72
70
>>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
71
InventoryEntry('2324', 'bye.c', kind='file', parent_id='123')
73
72
>>> 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')
80
81
InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
81
82
>>> for j in i.iter_entries():
98
99
# TODO: split InventoryEntry into subclasses for files,
99
100
# directories, etc etc.
102
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
103
'text_id', 'parent_id', 'children',
104
'text_version', 'entry_version', ]
104
107
def __init__(self, file_id, name, kind, parent_id, text_id=None):
105
108
"""Create an InventoryEntry
116
119
Traceback (most recent call last):
117
120
BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
122
assert isinstance(name, basestring), name
119
123
if '/' in name or '\\' in name:
120
124
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
122
130
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)
213
169
def __eq__(self, other):
214
170
if not isinstance(other, InventoryEntry):
215
171
return NotImplemented
220
176
and (self.text_size == other.text_size) \
221
177
and (self.text_id == other.text_id) \
222
178
and (self.parent_id == other.parent_id) \
223
and (self.kind == other.kind)
179
and (self.kind == other.kind) \
180
and (self.text_version == other.text_version) \
181
and (self.entry_version == other.entry_version)
226
184
def __ne__(self, other):
266
224
inserted, other than through the Inventory API.
268
226
>>> inv = Inventory()
269
>>> inv.write_xml(sys.stdout)
272
227
>>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
228
InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT')
273
229
>>> inv['123-123'].name
285
241
>>> [x[0] for x in inv.iter_entries()]
288
>>> inv.write_xml(sys.stdout)
290
<entry file_id="123-123" kind="file" name="hello.c" />
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')
247
def __init__(self, root_id=ROOT_ID):
295
248
"""Create or read an inventory.
297
250
If a working directory is specified, the inventory is read
301
254
The inventory is created with a default root directory, with
304
self.root = RootEntry(ROOT_ID)
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)
305
262
self._byid = {self.root.file_id: self.root}
374
331
>>> inv = Inventory()
375
332
>>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
333
InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
387
345
>>> inv = Inventory()
388
346
>>> inv.add(InventoryEntry('123123', 'hello.c', 'file', ROOT_ID))
347
InventoryEntry('123123', 'hello.c', kind='file', parent_id='TREE_ROOT')
389
348
>>> inv['123123'].name
409
368
"""Add entry to inventory.
411
370
To add a file to a branch ready to be committed, use Branch.add,
373
Returns the new entry object.
413
375
if entry.file_id in self._byid:
414
bailout("inventory already contains entry with id {%s}" % entry.file_id)
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
417
382
parent = self._byid[entry.parent_id]
419
bailout("parent_id {%s} not in inventory" % entry.parent_id)
384
raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
421
386
if parent.children.has_key(entry.name):
422
bailout("%s is already versioned" %
387
raise BzrError("%s is already versioned" %
423
388
appendpath(self.id2path(parent.file_id), entry.name))
425
390
self._byid[entry.file_id] = entry
426
391
parent.children[entry.name] = entry
429
395
def add_path(self, relpath, kind, file_id=None):
430
396
"""Add entry from a path.
432
The immediate parent must already be versioned"""
398
The immediate parent must already be versioned.
400
Returns the new entry object."""
401
from bzrlib.branch import gen_file_id
433
403
parts = bzrlib.osutils.splitpath(relpath)
434
404
if len(parts) == 0:
435
bailout("cannot re-add root of inventory")
405
raise BzrError("cannot re-add root of inventory")
437
407
if file_id == None:
438
file_id = bzrlib.branch.gen_file_id(relpath)
440
parent_id = self.path2id(parts[:-1])
441
assert parent_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)
442
415
ie = InventoryEntry(file_id, parts[-1],
443
416
kind=kind, parent_id=parent_id)
444
417
return self.add(ie)
450
423
>>> inv = Inventory()
451
424
>>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
425
InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
454
428
>>> del inv['123']
470
444
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)
501
447
def __eq__(self, other):
502
448
"""Compare two sets by comparing their contents.
508
454
>>> i1.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
455
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
511
458
>>> i2.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
459
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
554
501
"""Return as a list the path to file_id."""
556
503
# get all names, skipping root
557
p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
504
p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
558
505
return os.sep.join(p)
605
552
This does not move the working file."""
606
553
if not is_valid_name(new_name):
607
bailout("not an acceptable filename: %r" % new_name)
554
raise BzrError("not an acceptable filename: %r" % new_name)
609
556
new_parent = self._byid[new_parent_id]
610
557
if new_name in new_parent.children:
611
bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
558
raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
613
560
new_parent_idpath = self.get_idpath(new_parent_id)
614
561
if file_id in new_parent_idpath:
615
bailout("cannot move directory %r into a subdirectory of itself, %r"
562
raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
616
563
% (self.id2path(file_id), self.id2path(new_parent_id)))
618
565
file_ie = self._byid[file_id]