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', ]
105
105
def __init__(self, file_id, name, kind, parent_id, text_id=None):
106
106
"""Create an InventoryEntry
148
145
self.parent_id, text_id=self.text_id)
149
146
other.text_sha1 = self.text_sha1
150
147
other.text_size = self.text_size
151
# note that children are *not* copied; they're pulled across when
217
210
from_element = classmethod(from_element)
219
def __eq__(self, other):
212
def __cmp__(self, other):
220
215
if not isinstance(other, InventoryEntry):
221
216
return NotImplemented
223
return (self.file_id == other.file_id) \
224
and (self.name == other.name) \
225
and (self.text_sha1 == other.text_sha1) \
226
and (self.text_size == other.text_size) \
227
and (self.text_id == other.text_id) \
228
and (self.parent_id == other.parent_id) \
229
and (self.kind == other.kind)
232
def __ne__(self, other):
233
return not (self == other)
236
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)
245
233
self.parent_id = None
248
def __eq__(self, other):
236
def __cmp__(self, other):
249
239
if not isinstance(other, RootEntry):
250
240
return NotImplemented
252
return (self.file_id == other.file_id) \
253
and (self.children == other.children)
257
class Inventory(object):
241
return cmp(self.file_id, other.file_id) \
242
or cmp(self.children, other.children)
246
class Inventory(XMLMixin):
258
247
"""Inventory of versioned files in a tree.
260
249
This describes which file_id is present at each point in the tree,
272
261
inserted, other than through the Inventory API.
274
263
>>> inv = Inventory()
264
>>> inv.write_xml(sys.stdout)
275
267
>>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
276
InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT')
277
268
>>> inv['123-123'].name
289
280
>>> [x[0] for x in inv.iter_entries()]
291
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
292
>>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
293
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" />
295
def __init__(self, root_id=ROOT_ID):
296
290
"""Create or read an inventory.
298
292
If a working directory is specified, the inventory is read
302
296
The inventory is created with a default root directory, with
305
# We are letting Branch(init=True) create a unique inventory
306
# root id. Rather than generating a random one here.
308
# root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
309
self.root = RootEntry(root_id)
299
self.root = RootEntry(ROOT_ID)
310
300
self._byid = {self.root.file_id: self.root}
334
324
if ie.kind == 'directory':
335
325
for cn, cie in self.iter_entries(from_dir=ie.file_id):
336
326
yield os.path.join(name, cn), cie
340
"""Return list of (path, ie) for all entries except the root.
342
This may be faster than iter_entries.
330
def directories(self):
331
"""Return (path, entry) pairs for all directories.
345
def descend(dir_ie, dir_path):
346
kids = dir_ie.children.items()
348
for name, ie in kids:
349
child_path = os.path.join(dir_path, name)
350
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():
351
340
if ie.kind == 'directory':
352
descend(ie, child_path)
354
descend(self.root, '')
358
def directories(self):
359
"""Return (path, entry) pairs for all directories, including the root.
362
def descend(parent_ie, parent_path):
363
accum.append((parent_path, parent_ie))
341
dn.append((ie.name, ie))
365
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
368
for name, child_ie in kids:
369
child_path = os.path.join(parent_path, name)
370
descend(child_ie, child_path)
371
descend(self.root, '')
348
for name, ie in descend(self.root):
379
356
>>> inv = Inventory()
380
357
>>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
381
InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
393
369
>>> inv = Inventory()
394
370
>>> inv.add(InventoryEntry('123123', 'hello.c', 'file', ROOT_ID))
395
InventoryEntry('123123', 'hello.c', kind='file', parent_id='TREE_ROOT')
396
371
>>> inv['123123'].name
418
393
To add a file to a branch ready to be committed, use Branch.add,
419
394
which calls this."""
420
395
if entry.file_id in self._byid:
421
raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
423
if entry.parent_id == ROOT_ID or entry.parent_id is None:
424
entry.parent_id = self.root.file_id
396
bailout("inventory already contains entry with id {%s}" % entry.file_id)
427
399
parent = self._byid[entry.parent_id]
429
raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
401
bailout("parent_id {%s} not in inventory" % entry.parent_id)
431
403
if parent.children.has_key(entry.name):
432
raise BzrError("%s is already versioned" %
404
bailout("%s is already versioned" %
433
405
appendpath(self.id2path(parent.file_id), entry.name))
435
407
self._byid[entry.file_id] = entry
436
408
parent.children[entry.name] = entry
440
411
def add_path(self, relpath, kind, file_id=None):
441
412
"""Add entry from a path.
443
414
The immediate parent must already be versioned"""
444
from bzrlib.branch import gen_file_id
446
415
parts = bzrlib.osutils.splitpath(relpath)
447
416
if len(parts) == 0:
448
raise BzrError("cannot re-add root of inventory")
417
bailout("cannot re-add root of inventory")
450
419
if file_id == None:
451
file_id = gen_file_id(relpath)
453
parent_path = parts[:-1]
454
parent_id = self.path2id(parent_path)
455
if parent_id == None:
456
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
458
424
ie = InventoryEntry(file_id, parts[-1],
459
425
kind=kind, parent_id=parent_id)
460
426
return self.add(ie)
466
432
>>> inv = Inventory()
467
433
>>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
468
InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
471
436
>>> del inv['123']
487
452
del self[ie.parent_id].children[ie.name]
456
return Set(self._byid)
490
459
def to_element(self):
491
460
"""Convert to XML Element"""
492
from bzrlib.xml import Element
494
461
e = Element('inventory')
496
if self.root.file_id not in (None, ROOT_ID):
497
e.set('file_id', self.root.file_id)
498
463
for path, ie in self.iter_entries():
499
464
e.append(ie.to_element())
503
468
def from_element(cls, elt):
504
469
"""Construct from XML Element
506
471
>>> inv = Inventory()
507
472
>>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
508
InventoryEntry('foo.c-123981239', 'foo.c', kind='file', parent_id='TREE_ROOT')
509
473
>>> elt = inv.to_element()
510
474
>>> inv2 = Inventory.from_element(elt)
514
# XXXX: doctest doesn't run this properly under python2.3
515
478
assert elt.tag == 'inventory'
516
root_id = elt.get('file_id') or ROOT_ID
519
ie = InventoryEntry.from_element(e)
520
if ie.parent_id == ROOT_ID:
521
ie.parent_id = root_id
481
o.add(InventoryEntry.from_element(e))
525
484
from_element = classmethod(from_element)
528
def __eq__(self, other):
487
def __cmp__(self, other):
529
488
"""Compare two sets by comparing their contents.
531
490
>>> i1 = Inventory()
535
494
>>> i1.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
536
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
539
497
>>> i2.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
540
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
544
504
if not isinstance(other, Inventory):
545
505
return NotImplemented
547
if len(self._byid) != len(other._byid):
548
# shortcut: obviously not the same
551
return self._byid == other._byid
554
def __ne__(self, other):
555
return not (self == other)
559
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])
563
517
def get_idpath(self, file_id):
583
537
"""Return as a list the path to file_id."""
585
539
# get all names, skipping root
586
p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
540
p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
587
541
return os.sep.join(p)
634
588
This does not move the working file."""
635
589
if not is_valid_name(new_name):
636
raise BzrError("not an acceptable filename: %r" % new_name)
590
bailout("not an acceptable filename: %r" % new_name)
638
592
new_parent = self._byid[new_parent_id]
639
593
if new_name in new_parent.children:
640
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)))
642
596
new_parent_idpath = self.get_idpath(new_parent_id)
643
597
if file_id in new_parent_idpath:
644
raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
598
bailout("cannot move directory %r into a subdirectory of itself, %r"
645
599
% (self.id2path(file_id), self.id2path(new_parent_id)))
647
601
file_ie = self._byid[file_id]