15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
# TODO: Maybe also keep the full path of the entry, and the children?
19
# But those depend on its position within a particular inventory, and
20
# it would be nice not to need to hold the backpointer here.
22
# TODO: Perhaps split InventoryEntry into subclasses for files,
23
# directories, etc etc.
18
26
# This should really be an id randomly assigned when the tree is
19
27
# created, but it's not for now.
20
28
ROOT_ID = "TREE_ROOT"
28
36
from bzrlib.osutils import uuid, quotefn, splitpath, joinpath, appendpath
29
37
from bzrlib.trace import mutter
38
from bzrlib.errors import NotVersionedError
31
41
class InventoryEntry(object):
32
42
"""Description of a versioned file.
34
44
An InventoryEntry has the following fields, which are also
35
45
present in the XML inventory-entry element:
38
* *name*: (only the basename within the directory, must not
40
* *kind*: "directory" or "file"
41
* *directory_id*: (if absent/null means the branch root directory)
42
* *text_sha1*: only for files
43
* *text_size*: in bytes, only for files
44
* *text_id*: identifier for the text version, only for files
46
InventoryEntries can also exist inside a WorkingTree
47
inventory, in which case they are not yet bound to a
48
particular revision of the file. In that case the text_sha1,
49
text_size and text_id are absent.
50
(within the parent directory)
56
file_id of the parent directory, or ROOT_ID
59
the revision_id in which the name or parent of this file was
63
sha-1 of the text of the file
66
size in bytes of the text of the file
69
the revision_id in which the text of this file was introduced
71
(reading a version 4 tree created a text_id field.)
52
73
>>> i = Inventory()
55
76
>>> i.add(InventoryEntry('123', 'src', 'directory', ROOT_ID))
77
InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT')
56
78
>>> i.add(InventoryEntry('2323', 'hello.c', 'file', parent_id='123'))
79
InventoryEntry('2323', 'hello.c', kind='file', parent_id='123')
57
80
>>> for j in i.iter_entries():
65
88
BzrError: inventory already contains entry with id {2323}
66
89
>>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
90
InventoryEntry('2324', 'bye.c', kind='file', parent_id='123')
67
91
>>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
92
InventoryEntry('2325', 'wibble', kind='directory', parent_id='123')
68
93
>>> i.path2id('src/wibble')
72
97
>>> i.add(InventoryEntry('2326', 'wibble.c', 'file', '2325'))
98
InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
74
100
InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
75
101
>>> for j in i.iter_entries():
83
109
src/wibble/wibble.c
84
110
>>> i.id2path('2326')
85
111
'src/wibble/wibble.c'
87
TODO: Maybe also keep the full path of the entry, and the children?
88
But those depend on its position within a particular inventory, and
89
it would be nice not to need to hold the backpointer here.
92
# TODO: split InventoryEntry into subclasses for files,
93
# directories, etc etc.
114
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
115
'text_id', 'parent_id', 'children',
116
'text_version', 'entry_version', ]
98
119
def __init__(self, file_id, name, kind, parent_id, text_id=None):
99
120
"""Create an InventoryEntry
110
131
Traceback (most recent call last):
111
132
BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
134
assert isinstance(name, basestring), name
113
135
if '/' in name or '\\' in name:
114
136
raise BzrCheckError('InventoryEntry name %r is invalid' % name)
138
self.text_version = None
139
self.entry_version = None
140
self.text_sha1 = None
141
self.text_size = None
116
142
self.file_id = file_id
137
163
other = InventoryEntry(self.file_id, self.name, self.kind,
138
self.parent_id, text_id=self.text_id)
165
other.text_id = self.text_id
139
166
other.text_sha1 = self.text_sha1
140
167
other.text_size = self.text_size
168
other.text_version = self.text_version
141
169
# note that children are *not* copied; they're pulled across when
142
170
# others are added
155
def to_element(self):
156
"""Convert to XML element"""
157
from bzrlib.xml import Element
161
e.set('name', self.name)
162
e.set('file_id', self.file_id)
163
e.set('kind', self.kind)
165
if self.text_size != None:
166
e.set('text_size', '%d' % self.text_size)
168
for f in ['text_id', 'text_sha1']:
173
# to be conservative, we don't externalize the root pointers
174
# for now, leaving them as null in the xml form. in a future
175
# version it will be implied by nested elements.
176
if self.parent_id != ROOT_ID:
177
assert isinstance(self.parent_id, basestring)
178
e.set('parent_id', self.parent_id)
185
def from_element(cls, elt):
186
assert elt.tag == 'entry'
188
## original format inventories don't have a parent_id for
189
## nodes in the root directory, but it's cleaner to use one
191
parent_id = elt.get('parent_id')
192
if parent_id == None:
195
self = cls(elt.get('file_id'), elt.get('name'), elt.get('kind'), parent_id)
196
self.text_id = elt.get('text_id')
197
self.text_sha1 = elt.get('text_sha1')
199
## mutter("read inventoryentry: %r" % (elt.attrib))
201
v = elt.get('text_size')
202
self.text_size = v and int(v)
207
from_element = classmethod(from_element)
209
183
def __eq__(self, other):
210
184
if not isinstance(other, InventoryEntry):
211
185
return NotImplemented
216
190
and (self.text_size == other.text_size) \
217
191
and (self.text_id == other.text_id) \
218
192
and (self.parent_id == other.parent_id) \
219
and (self.kind == other.kind)
193
and (self.kind == other.kind) \
194
and (self.text_version == other.text_version) \
195
and (self.entry_version == other.entry_version)
222
198
def __ne__(self, other):
264
240
>>> inv = Inventory()
265
241
>>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
242
InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT')
266
243
>>> inv['123-123'].name
278
255
>>> [x[0] for x in inv.iter_entries()]
257
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
258
>>> inv.add(InventoryEntry('123-123', 'hello.c', 'file', ROOT_ID))
259
InventoryEntry('123-123', 'hello.c', kind='file', parent_id='TREE_ROOT-12345678-12345678')
261
def __init__(self, root_id=ROOT_ID):
282
262
"""Create or read an inventory.
284
264
If a working directory is specified, the inventory is read
288
268
The inventory is created with a default root directory, with
291
self.root = RootEntry(ROOT_ID)
271
# We are letting Branch(init=True) create a unique inventory
272
# root id. Rather than generating a random one here.
274
# root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
275
self.root = RootEntry(root_id)
292
276
self._byid = {self.root.file_id: self.root}
280
other = Inventory(self.root.file_id)
281
# copy recursively so we know directories will be added before
282
# their children. There are more efficient ways than this...
283
for path, entry in self.iter_entries():
284
if entry == self.root:
286
other.add(entry.copy())
295
290
def __iter__(self):
296
291
return iter(self._byid)
361
356
>>> inv = Inventory()
362
357
>>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
358
InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
374
370
>>> inv = Inventory()
375
371
>>> inv.add(InventoryEntry('123123', 'hello.c', 'file', ROOT_ID))
372
InventoryEntry('123123', 'hello.c', kind='file', parent_id='TREE_ROOT')
376
373
>>> inv['123123'].name
396
393
"""Add entry to inventory.
398
395
To add a file to a branch ready to be committed, use Branch.add,
398
Returns the new entry object.
400
400
if entry.file_id in self._byid:
401
401
raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
403
if entry.parent_id == ROOT_ID or entry.parent_id is None:
404
entry.parent_id = self.root.file_id
404
407
parent = self._byid[entry.parent_id]
412
415
self._byid[entry.file_id] = entry
413
416
parent.children[entry.name] = entry
416
420
def add_path(self, relpath, kind, file_id=None):
417
421
"""Add entry from a path.
419
The immediate parent must already be versioned"""
420
from bzrlib.errors import NotVersionedError
423
The immediate parent must already be versioned.
425
Returns the new entry object."""
426
from bzrlib.branch import gen_file_id
422
428
parts = bzrlib.osutils.splitpath(relpath)
423
429
if len(parts) == 0:
424
430
raise BzrError("cannot re-add root of inventory")
426
432
if file_id == None:
427
from bzrlib.branch import gen_file_id
428
433
file_id = gen_file_id(relpath)
430
435
parent_path = parts[:-1]
443
448
>>> inv = Inventory()
444
449
>>> inv.add(InventoryEntry('123', 'foo.c', 'file', ROOT_ID))
450
InventoryEntry('123', 'foo.c', kind='file', parent_id='TREE_ROOT')
447
453
>>> del inv['123']
463
469
del self[ie.parent_id].children[ie.name]
466
def to_element(self):
467
"""Convert to XML Element"""
468
from bzrlib.xml import Element
470
e = Element('inventory')
472
for path, ie in self.iter_entries():
473
e.append(ie.to_element())
477
def from_element(cls, elt):
478
"""Construct from XML Element
480
>>> inv = Inventory()
481
>>> inv.add(InventoryEntry('foo.c-123981239', 'foo.c', 'file', ROOT_ID))
482
>>> elt = inv.to_element()
483
>>> inv2 = Inventory.from_element(elt)
487
assert elt.tag == 'inventory'
490
o.add(InventoryEntry.from_element(e))
493
from_element = classmethod(from_element)
496
472
def __eq__(self, other):
497
473
"""Compare two sets by comparing their contents.
503
479
>>> i1.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
480
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
506
483
>>> i2.add(InventoryEntry('123', 'foo', 'file', ROOT_ID))
484
InventoryEntry('123', 'foo', kind='file', parent_id='TREE_ROOT')
549
526
"""Return as a list the path to file_id."""
551
528
# get all names, skipping root
552
p = [self[fid].name for fid in self.get_idpath(file_id)[1:]]
529
p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
553
530
return os.sep.join(p)