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):
160
def to_element(self):
161
"""Convert to XML element"""
164
e.set('name', self.name)
165
e.set('file_id', self.file_id)
166
e.set('kind', self.kind)
168
if self.text_size != None:
169
e.set('text_size', '%d' % self.text_size)
171
for f in ['text_id', 'text_sha1']:
176
# to be conservative, we don't externalize the root pointers
177
# for now, leaving them as null in the xml form. in a future
178
# version it will be implied by nested elements.
179
if self.parent_id != ROOT_ID:
180
assert isinstance(self.parent_id, basestring)
181
e.set('parent_id', self.parent_id)
188
def from_element(cls, elt):
189
assert elt.tag == 'entry'
191
## original format inventories don't have a parent_id for
192
## nodes in the root directory, but it's cleaner to use one
194
parent_id = elt.get('parent_id')
195
if parent_id == None:
198
self = cls(elt.get('file_id'), elt.get('name'), elt.get('kind'), parent_id)
199
self.text_id = elt.get('text_id')
200
self.text_sha1 = elt.get('text_sha1')
202
## mutter("read inventoryentry: %r" % (elt.attrib))
204
v = elt.get('text_size')
205
self.text_size = v and int(v)
210
from_element = classmethod(from_element)
212
def __cmp__(self, other):
170
215
if not isinstance(other, InventoryEntry):
171
216
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')
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)
197
233
self.parent_id = None
200
def __eq__(self, other):
236
def __cmp__(self, other):
201
239
if not isinstance(other, RootEntry):
202
240
return NotImplemented
204
return (self.file_id == other.file_id) \
205
and (self.children == other.children)
209
class Inventory(object):
241
return cmp(self.file_id, other.file_id) \
242
or cmp(self.children, other.children)
246
class Inventory(XMLMixin):
210
247
"""Inventory of versioned files in a tree.
212
249
This describes which file_id is present at each point in the tree,
224
261
inserted, other than through the Inventory API.
226
263
>>> inv = Inventory()
264
>>> inv.write_xml(sys.stdout)
227
267
>>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
228
InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT')
229
268
>>> inv['123-123'].name
241
280
>>> [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')
283
>>> inv.write_xml(sys.stdout)
285
<entry file_id="123-123" kind="file" name="hello.c" />
247
def __init__(self, root_id=ROOT_ID):
248
290
"""Create or read an inventory.
250
292
If a working directory is specified, the inventory is read
254
296
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)
299
self.root = RootEntry(ROOT_ID)
262
300
self._byid = {self.root.file_id: self.root}
286
324
if ie.kind == 'directory':
287
325
for cn, cie in self.iter_entries(from_dir=ie.file_id):
288
326
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.
330
def directories(self):
331
"""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))
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():
303
340
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))
341
dn.append((ie.name, ie))
317
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
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, '')
348
for name, ie in descend(self.root):
368
391
"""Add entry to inventory.
370
393
To add a file to a branch ready to be committed, use Branch.add,
373
Returns the new entry object.
375
395
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
396
bailout("inventory already contains entry with id {%s}" % entry.file_id)
382
399
parent = self._byid[entry.parent_id]
384
raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
401
bailout("parent_id {%s} not in inventory" % entry.parent_id)
386
403
if parent.children.has_key(entry.name):
387
raise BzrError("%s is already versioned" %
404
bailout("%s is already versioned" %
388
405
appendpath(self.id2path(parent.file_id), entry.name))
390
407
self._byid[entry.file_id] = entry
391
408
parent.children[entry.name] = entry
395
411
def add_path(self, relpath, kind, file_id=None):
396
412
"""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
414
The immediate parent must already be versioned"""
403
415
parts = bzrlib.osutils.splitpath(relpath)
404
416
if len(parts) == 0:
405
raise BzrError("cannot re-add root of inventory")
417
bailout("cannot re-add root of inventory")
407
419
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)
420
file_id = bzrlib.branch.gen_file_id(relpath)
422
parent_id = self.path2id(parts[:-1])
423
assert parent_id != None
415
424
ie = InventoryEntry(file_id, parts[-1],
416
425
kind=kind, parent_id=parent_id)
417
426
return self.add(ie)
444
452
del self[ie.parent_id].children[ie.name]
447
def __eq__(self, other):
456
return Set(self._byid)
459
def to_element(self):
460
"""Convert to XML Element"""
461
e = Element('inventory')
463
for path, ie in self.iter_entries():
464
e.append(ie.to_element())
468
def from_element(cls, elt):
469
"""Construct from XML Element
471
>>> inv = Inventory()
472
>>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
473
>>> elt = inv.to_element()
474
>>> inv2 = Inventory.from_element(elt)
478
assert elt.tag == 'inventory'
481
o.add(InventoryEntry.from_element(e))
484
from_element = classmethod(from_element)
487
def __cmp__(self, other):
448
488
"""Compare two sets by comparing their contents.
450
490
>>> i1 = Inventory()
454
494
>>> i1.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
455
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
458
497
>>> i2.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
459
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
463
504
if not isinstance(other, Inventory):
464
505
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')
507
if self.id_set() ^ other.id_set():
510
for file_id in self._byid:
511
c = cmp(self[file_id], other[file_id])
481
517
def get_idpath(self, file_id):
552
588
This does not move the working file."""
553
589
if not is_valid_name(new_name):
554
raise BzrError("not an acceptable filename: %r" % new_name)
590
bailout("not an acceptable filename: %r" % new_name)
556
592
new_parent = self._byid[new_parent_id]
557
593
if new_name in new_parent.children:
558
raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
594
bailout("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
560
596
new_parent_idpath = self.get_idpath(new_parent_id)
561
597
if file_id in new_parent_idpath:
562
raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
598
bailout("cannot move directory %r into a subdirectory of itself, %r"
563
599
% (self.id2path(file_id), self.id2path(new_parent_id)))
565
601
file_ie = self._byid[file_id]