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
30
from bzrlib.errors import NotVersionedError
33
class InventoryEntry(object):
38
class InventoryEntry(XMLMixin):
34
39
"""Description of a versioned file.
36
41
An InventoryEntry has the following fields, which are also
57
62
>>> i.add(InventoryEntry('123', 'src', 'directory', ROOT_ID))
58
InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT')
59
63
>>> i.add(InventoryEntry('2323', 'hello.c', 'file', parent_id='123'))
60
InventoryEntry('2323', 'hello.c', kind='file', parent_id='123')
61
64
>>> for j in i.iter_entries():
66
69
>>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
67
70
Traceback (most recent call last):
69
BzrError: inventory already contains entry with id {2323}
72
BzrError: ('inventory already contains entry with id {2323}', [])
70
73
>>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
71
InventoryEntry('2324', 'bye.c', kind='file', parent_id='123')
72
74
>>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
73
InventoryEntry('2325', 'wibble', kind='directory', parent_id='123')
74
75
>>> i.path2id('src/wibble')
78
79
>>> i.add(InventoryEntry('2326', 'wibble.c', 'file', '2325'))
79
InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
81
81
InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
82
82
>>> for j in i.iter_entries():
99
99
# TODO: split InventoryEntry into subclasses for files,
100
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', ]
107
105
def __init__(self, file_id, name, kind, parent_id, text_id=None):
108
106
"""Create an InventoryEntry
119
117
Traceback (most recent call last):
120
118
BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
122
assert isinstance(name, basestring), name
123
120
if '/' in name or '\\' in name:
124
121
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
123
self.file_id = file_id
169
def __eq__(self, other):
162
def to_element(self):
163
"""Convert to XML element"""
166
e.set('name', self.name)
167
e.set('file_id', self.file_id)
168
e.set('kind', self.kind)
170
if self.text_size != None:
171
e.set('text_size', '%d' % self.text_size)
173
for f in ['text_id', 'text_sha1']:
178
# to be conservative, we don't externalize the root pointers
179
# for now, leaving them as null in the xml form. in a future
180
# version it will be implied by nested elements.
181
if self.parent_id != ROOT_ID:
182
assert isinstance(self.parent_id, basestring)
183
e.set('parent_id', self.parent_id)
190
def from_element(cls, elt):
191
assert elt.tag == 'entry'
193
## original format inventories don't have a parent_id for
194
## nodes in the root directory, but it's cleaner to use one
196
parent_id = elt.get('parent_id')
197
if parent_id == None:
200
self = cls(elt.get('file_id'), elt.get('name'), elt.get('kind'), parent_id)
201
self.text_id = elt.get('text_id')
202
self.text_sha1 = elt.get('text_sha1')
204
## mutter("read inventoryentry: %r" % (elt.attrib))
206
v = elt.get('text_size')
207
self.text_size = v and int(v)
212
from_element = classmethod(from_element)
214
def __cmp__(self, other):
170
217
if not isinstance(other, InventoryEntry):
171
218
return NotImplemented
173
return (self.file_id == other.file_id) \
174
and (self.name == other.name) \
175
and (self.text_sha1 == other.text_sha1) \
176
and (self.text_size == other.text_size) \
177
and (self.text_id == other.text_id) \
178
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)
184
def __ne__(self, other):
185
return not (self == other)
188
raise ValueError('not hashable')
220
return cmp(self.file_id, other.file_id) \
221
or cmp(self.name, other.name) \
222
or cmp(self.text_sha1, other.text_sha1) \
223
or cmp(self.text_size, other.text_size) \
224
or cmp(self.text_id, other.text_id) \
225
or cmp(self.parent_id, other.parent_id) \
226
or cmp(self.kind, other.kind)
197
235
self.parent_id = None
200
def __eq__(self, other):
238
def __cmp__(self, other):
201
241
if not isinstance(other, RootEntry):
202
242
return NotImplemented
204
return (self.file_id == other.file_id) \
205
and (self.children == other.children)
209
class Inventory(object):
243
return cmp(self.file_id, other.file_id) \
244
or cmp(self.children, other.children)
248
class Inventory(XMLMixin):
210
249
"""Inventory of versioned files in a tree.
212
251
This describes which file_id is present at each point in the tree,
224
263
inserted, other than through the Inventory API.
226
265
>>> inv = Inventory()
266
>>> inv.write_xml(sys.stdout)
227
269
>>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
228
InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT')
229
270
>>> inv['123-123'].name
241
282
>>> [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')
285
>>> inv.write_xml(sys.stdout)
287
<entry file_id="123-123" kind="file" name="hello.c" />
247
def __init__(self, root_id=ROOT_ID):
248
292
"""Create or read an inventory.
250
294
If a working directory is specified, the inventory is read
254
298
The inventory is created with a default root directory, with
257
# We are letting Branch.initialize() 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)
301
self.root = RootEntry(ROOT_ID)
262
302
self._byid = {self.root.file_id: self.root}
286
326
if ie.kind == 'directory':
287
327
for cn, cie in self.iter_entries(from_dir=ie.file_id):
288
328
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.
332
def directories(self):
333
"""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))
335
def descend(parent_ie):
336
parent_name = parent_ie.name
337
yield parent_name, parent_ie
339
# directory children in sorted order
341
for ie in parent_ie.children.itervalues():
303
342
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))
343
dn.append((ie.name, ie))
317
kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
346
for name, child_ie in dn:
347
for sub_name, sub_ie in descend(child_ie):
348
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, '')
350
for name, ie in descend(self.root):
368
393
"""Add entry to inventory.
370
395
To add a file to a branch ready to be committed, use Branch.add,
373
Returns the new entry object.
375
397
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
398
bailout("inventory already contains entry with id {%s}" % entry.file_id)
382
401
parent = self._byid[entry.parent_id]
384
raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
403
bailout("parent_id {%s} not in inventory" % entry.parent_id)
386
405
if parent.children.has_key(entry.name):
387
raise BzrError("%s is already versioned" %
406
bailout("%s is already versioned" %
388
407
appendpath(self.id2path(parent.file_id), entry.name))
390
409
self._byid[entry.file_id] = entry
391
410
parent.children[entry.name] = entry
395
413
def add_path(self, relpath, kind, file_id=None):
396
414
"""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
416
The immediate parent must already be versioned"""
403
417
parts = bzrlib.osutils.splitpath(relpath)
404
418
if len(parts) == 0:
405
raise BzrError("cannot re-add root of inventory")
419
bailout("cannot re-add root of inventory")
407
421
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)
422
file_id = bzrlib.branch.gen_file_id(relpath)
424
parent_id = self.path2id(parts[:-1])
425
assert parent_id != None
415
426
ie = InventoryEntry(file_id, parts[-1],
416
427
kind=kind, parent_id=parent_id)
417
428
return self.add(ie)
444
454
del self[ie.parent_id].children[ie.name]
447
def __eq__(self, other):
458
return Set(self._byid)
461
def to_element(self):
462
"""Convert to XML Element"""
463
e = Element('inventory')
465
for path, ie in self.iter_entries():
466
e.append(ie.to_element())
470
def from_element(cls, elt):
471
"""Construct from XML Element
473
>>> inv = Inventory()
474
>>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
475
>>> elt = inv.to_element()
476
>>> inv2 = Inventory.from_element(elt)
480
assert elt.tag == 'inventory'
483
o.add(InventoryEntry.from_element(e))
486
from_element = classmethod(from_element)
489
def __cmp__(self, other):
448
490
"""Compare two sets by comparing their contents.
450
492
>>> i1 = Inventory()
454
496
>>> i1.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
455
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
458
499
>>> i2.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
459
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
463
506
if not isinstance(other, Inventory):
464
507
return NotImplemented
466
if len(self._byid) != len(other._byid):
467
# shortcut: obviously not the same
470
return self._byid == other._byid
473
def __ne__(self, other):
474
return not (self == other)
478
raise ValueError('not hashable')
509
if self.id_set() ^ other.id_set():
512
for file_id in self._byid:
513
c = cmp(self[file_id], other[file_id])
481
519
def get_idpath(self, file_id):
552
590
This does not move the working file."""
553
591
if not is_valid_name(new_name):
554
raise BzrError("not an acceptable filename: %r" % new_name)
592
bailout("not an acceptable filename: %r" % new_name)
556
594
new_parent = self._byid[new_parent_id]
557
595
if new_name in new_parent.children:
558
raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
596
bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
560
598
new_parent_idpath = self.get_idpath(new_parent_id)
561
599
if file_id in new_parent_idpath:
562
raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
600
bailout("cannot move directory %r into a subdirectory of itself, %r"
563
601
% (self.id2path(file_id), self.id2path(new_parent_id)))
565
603
file_ie = self._byid[file_id]