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}
286
329
if ie.kind == 'directory':
287
330
for cn, cie in self.iter_entries(from_dir=ie.file_id):
288
331
yield os.path.join(name, cn), cie
292
"""Return list of (path, ie) for all entries except the root.
294
This may be faster than iter_entries.
335
def directories(self):
336
"""Return (path, entry) pairs for all directories.
297
def descend(dir_ie, dir_path):
298
kids = dir_ie.children.items()
300
for name, ie in kids:
301
child_path = os.path.join(dir_path, name)
302
accum.append((child_path, ie))
338
def descend(parent_ie):
339
parent_name = parent_ie.name
340
yield parent_name, parent_ie
342
# directory children in sorted order
344
for ie in parent_ie.children.itervalues():
303
345
if ie.kind == 'directory':
304
descend(ie, child_path)
306
descend(self.root, '')
310
def directories(self):
311
"""Return (path, entry) pairs for all directories, including the root.
314
def descend(parent_ie, parent_path):
315
accum.append((parent_path, parent_ie))
346
dn.append((ie.name, ie))
317
kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
349
for name, child_ie in dn:
350
for sub_name, sub_ie in descend(child_ie):
351
yield appendpath(parent_name, sub_name), sub_ie
320
for name, child_ie in kids:
321
child_path = os.path.join(parent_path, name)
322
descend(child_ie, child_path)
323
descend(self.root, '')
353
for name, ie in descend(self.root):
331
361
>>> inv = Inventory()
332
362
>>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
333
InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
345
374
>>> inv = Inventory()
346
375
>>> inv.add(InventoryEntry('123123', 'hello.c', 'file', ROOT_ID))
347
InventoryEntry('123123', 'hello.c', kind='file', parent_id='TREE_ROOT')
348
376
>>> inv['123123'].name
368
396
"""Add entry to inventory.
370
398
To add a file to a branch ready to be committed, use Branch.add,
373
Returns the new entry object.
375
400
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
401
bailout("inventory already contains entry with id {%s}" % entry.file_id)
382
404
parent = self._byid[entry.parent_id]
384
raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
406
bailout("parent_id {%s} not in inventory" % entry.parent_id)
386
408
if parent.children.has_key(entry.name):
387
raise BzrError("%s is already versioned" %
409
bailout("%s is already versioned" %
388
410
appendpath(self.id2path(parent.file_id), entry.name))
390
412
self._byid[entry.file_id] = entry
391
413
parent.children[entry.name] = entry
395
416
def add_path(self, relpath, kind, file_id=None):
396
417
"""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
419
The immediate parent must already be versioned"""
403
420
parts = bzrlib.osutils.splitpath(relpath)
404
421
if len(parts) == 0:
405
raise BzrError("cannot re-add root of inventory")
422
bailout("cannot re-add root of inventory")
407
424
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)
425
file_id = bzrlib.branch.gen_file_id(relpath)
427
parent_id = self.path2id(parts[:-1])
428
assert parent_id != None
415
429
ie = InventoryEntry(file_id, parts[-1],
416
430
kind=kind, parent_id=parent_id)
417
431
return self.add(ie)
423
437
>>> inv = Inventory()
424
438
>>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
425
InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
428
441
>>> del inv['123']
444
457
del self[ie.parent_id].children[ie.name]
460
def to_element(self):
461
"""Convert to XML Element"""
462
e = Element('inventory')
464
for path, ie in self.iter_entries():
465
e.append(ie.to_element())
469
def from_element(cls, elt):
470
"""Construct from XML Element
472
>>> inv = Inventory()
473
>>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
474
>>> elt = inv.to_element()
475
>>> inv2 = Inventory.from_element(elt)
479
assert elt.tag == 'inventory'
482
o.add(InventoryEntry.from_element(e))
485
from_element = classmethod(from_element)
447
488
def __eq__(self, other):
448
489
"""Compare two sets by comparing their contents.
454
495
>>> i1.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
455
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
458
498
>>> i2.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
459
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
501
541
"""Return as a list the path to file_id."""
503
543
# get all names, skipping root
504
p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
544
p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
505
545
return os.sep.join(p)
552
592
This does not move the working file."""
553
593
if not is_valid_name(new_name):
554
raise BzrError("not an acceptable filename: %r" % new_name)
594
bailout("not an acceptable filename: %r" % new_name)
556
596
new_parent = self._byid[new_parent_id]
557
597
if new_name in new_parent.children:
558
raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
598
bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
560
600
new_parent_idpath = self.get_idpath(new_parent_id)
561
601
if file_id in new_parent_idpath:
562
raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
602
bailout("cannot move directory %r into a subdirectory of itself, %r"
563
603
% (self.id2path(file_id), self.id2path(new_parent_id)))
565
605
file_ie = self._byid[file_id]