77
72
>>> i = Inventory()
80
>>> i.add(InventoryEntry('123', 'src', 'directory', ROOT_ID))
81
InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT')
82
>>> i.add(InventoryEntry('2323', 'hello.c', 'file', parent_id='123'))
83
InventoryEntry('2323', 'hello.c', kind='file', parent_id='123')
75
>>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
76
InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
77
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
78
InventoryFile('2323', 'hello.c', parent_id='123')
84
79
>>> for j in i.iter_entries():
87
('src', InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT'))
88
('src/hello.c', InventoryEntry('2323', 'hello.c', kind='file', parent_id='123'))
89
>>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
82
('src', InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
83
('src/hello.c', InventoryFile('2323', 'hello.c', parent_id='123'))
84
>>> i.add(InventoryFile('2323', 'bye.c', '123'))
90
85
Traceback (most recent call last):
92
87
BzrError: inventory already contains entry with id {2323}
93
>>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
94
InventoryEntry('2324', 'bye.c', kind='file', parent_id='123')
95
>>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
96
InventoryEntry('2325', 'wibble', kind='directory', parent_id='123')
88
>>> i.add(InventoryFile('2324', 'bye.c', '123'))
89
InventoryFile('2324', 'bye.c', parent_id='123')
90
>>> i.add(InventoryDirectory('2325', 'wibble', '123'))
91
InventoryDirectory('2325', 'wibble', parent_id='123')
97
92
>>> i.path2id('src/wibble')
101
>>> i.add(InventoryEntry('2326', 'wibble.c', 'file', '2325'))
102
InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
96
>>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
97
InventoryFile('2326', 'wibble.c', parent_id='2325')
104
InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
99
InventoryFile('2326', 'wibble.c', parent_id='2325')
105
100
>>> for path, entry in i.iter_entries():
106
101
... print path.replace('\\\\', '/') # for win32 os.sep
107
102
... assert i.path2id(path)
118
113
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
119
114
'text_id', 'parent_id', 'children', 'executable',
120
'revision', 'symlink_target']
122
117
def _add_text_to_weave(self, new_lines, parents, weave_store):
123
118
weave_store.add_text(self.file_id, self.revision, new_lines, parents)
125
def __init__(self, file_id, name, kind, parent_id, text_id=None):
120
def detect_changes(self, old_entry):
121
"""Return a (text_modified, meta_modified) from this to old_entry.
123
_read_tree_state must have been called on self and old_entry prior to
124
calling detect_changes.
128
def diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
129
output_to, reverse=False):
130
"""Perform a diff from this to to_entry.
132
text_diff will be used for textual difference calculation.
133
This is a template method, override _diff in child classes.
135
self._read_tree_state(tree.id2path(self.file_id), tree)
137
# cannot diff from one kind to another - you must do a removal
138
# and an addif they do not match.
139
assert self.kind == to_entry.kind
140
to_entry._read_tree_state(to_tree.id2path(to_entry.file_id),
142
self._diff(text_diff, from_label, tree, to_label, to_entry, to_tree,
145
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
146
output_to, reverse=False):
147
"""Perform a diff between two entries of the same kind."""
149
def get_tar_item(self, root, dp, now, tree):
150
"""Get a tarfile item and a file stream for its content."""
151
item = tarfile.TarInfo(os.path.join(root, dp))
152
# TODO: would be cool to actually set it to the timestamp of the
153
# revision it was last changed
155
fileobj = self._put_in_tar(item, tree)
159
"""Return true if the object this entry represents has textual data.
161
Note that textual data includes binary content.
165
def __init__(self, file_id, name, parent_id, text_id=None):
126
166
"""Create an InventoryEntry
128
168
The filename must be a single component, relative to the
129
169
parent directory; it cannot be a whole path or relative name.
131
>>> e = InventoryEntry('123', 'hello.c', 'file', ROOT_ID)
171
>>> e = InventoryFile('123', 'hello.c', ROOT_ID)
136
>>> e = InventoryEntry('123', 'src/hello.c', 'file', ROOT_ID)
176
>>> e = InventoryFile('123', 'src/hello.c', ROOT_ID)
137
177
Traceback (most recent call last):
138
178
BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
147
187
self.text_size = None
148
188
self.file_id = file_id
151
190
self.text_id = text_id
152
191
self.parent_id = parent_id
153
192
self.symlink_target = None
154
if kind == 'directory':
158
elif kind == 'symlink':
161
raise BzrError("unhandled entry kind %r" % kind)
163
def read_symlink_target(self, path):
164
if self.kind == 'symlink':
166
self.symlink_target = os.readlink(path)
168
raise BzrError("os.readlink error, %s" % e)
194
def kind_character(self):
195
"""Return a short kind indicator useful for appending to names."""
196
raise BzrError('unknown kind %r' % self.kind)
198
known_kinds = ('file', 'directory', 'symlink', 'root_directory')
200
def _put_in_tar(self, item, tree):
201
"""populate item for stashing in a tar, and return the content stream.
203
If no content is available, return None.
205
raise BzrError("don't know how to export {%s} of kind %r" %
206
(self.file_id, self.kind))
208
def put_on_disk(self, dest, dp, tree):
209
"""Create a representation of self on disk in the prefix dest.
211
This is a template method - implement _put_on_disk in subclasses.
213
fullpath = appendpath(dest, dp)
214
self._put_on_disk(fullpath, tree)
215
mutter(" export {%s} kind %s to %s" % (self.file_id, self.kind, fullpath))
217
def _put_on_disk(self, fullpath, tree):
218
"""Put this entry onto disk at fullpath, from tree tree."""
219
raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
170
221
def sorted_children(self):
171
222
l = self.children.items()
227
def versionable_kind(kind):
228
return kind in ('file', 'directory', 'symlink')
175
230
def check(self, checker, rev_id, inv, tree):
231
"""Check this inventory entry is intact.
233
This is a template method, override _check for kind specific
176
236
if self.parent_id != None:
177
237
if not inv.has_id(self.parent_id):
178
238
raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
179
239
% (self.parent_id, rev_id))
180
if self.kind == 'file':
181
revision = self.revision
182
t = (self.file_id, revision)
183
if t in checker.checked_texts:
184
prev_sha = checker.checked_texts[t]
185
if prev_sha != self.text_sha1:
186
raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
187
(self.file_id, rev_id))
189
checker.repeated_text_cnt += 1
191
mutter('check version {%s} of {%s}', rev_id, self.file_id)
192
file_lines = tree.get_file_lines(self.file_id)
193
checker.checked_text_cnt += 1
194
if self.text_size != sum(map(len, file_lines)):
195
raise BzrCheckError('text {%s} wrong size' % self.text_id)
196
if self.text_sha1 != sha_strings(file_lines):
197
raise BzrCheckError('text {%s} wrong sha1' % self.text_id)
198
checker.checked_texts[t] = self.text_sha1
199
elif self.kind == 'directory':
200
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
201
raise BzrCheckError('directory {%s} has text in revision {%s}'
202
% (self.file_id, rev_id))
203
elif self.kind == 'root_directory':
205
elif self.kind == 'symlink':
206
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
207
raise BzrCheckError('symlink {%s} has text in revision {%s}'
208
% (self.file_id, rev_id))
209
if self.symlink_target == None:
210
raise BzrCheckError('symlink {%s} has no target in revision {%s}'
211
% (self.file_id, rev_id))
213
raise BzrCheckError('unknown entry kind %r in revision {%s}' %
240
self._check(checker, rev_id, tree)
242
def _check(self, checker, rev_id, tree):
243
"""Check this inventory entry for kind specific errors."""
244
raise BzrCheckError('unknown entry kind %r in revision {%s}' %
218
other = InventoryEntry(self.file_id, self.name, self.kind,
220
other.executable = self.executable
221
other.text_id = self.text_id
222
other.text_sha1 = self.text_sha1
223
other.text_size = self.text_size
224
other.symlink_target = self.symlink_target
225
other.revision = self.revision
226
# note that children are *not* copied; they're pulled across when
249
"""Clone this inventory entry."""
250
raise NotImplementedError
230
252
def _get_snapshot_change(self, previous_entries):
231
253
if len(previous_entries) > 1:
351
373
and (self.children == other.children)
376
class InventoryDirectory(InventoryEntry):
377
"""A directory in an inventory."""
379
def _check(self, checker, rev_id, tree):
380
"""See InventoryEntry._check"""
381
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
382
raise BzrCheckError('directory {%s} has text in revision {%s}'
383
% (self.file_id, rev_id))
386
other = InventoryDirectory(self.file_id, self.name, self.parent_id)
387
other.revision = self.revision
388
# note that children are *not* copied; they're pulled across when
392
def __init__(self, file_id, name, parent_id):
393
super(InventoryDirectory, self).__init__(file_id, name, parent_id)
395
self.kind = 'directory'
397
def kind_character(self):
398
"""See InventoryEntry.kind_character."""
401
def _put_in_tar(self, item, tree):
402
"""See InventoryEntry._put_in_tar."""
403
item.type = tarfile.DIRTYPE
410
def _put_on_disk(self, fullpath, tree):
411
"""See InventoryEntry._put_on_disk."""
415
class InventoryFile(InventoryEntry):
416
"""A file in an inventory."""
418
def _check(self, checker, rev_id, tree):
419
"""See InventoryEntry._check"""
420
revision = self.revision
421
t = (self.file_id, revision)
422
if t in checker.checked_texts:
423
prev_sha = checker.checked_texts[t]
424
if prev_sha != self.text_sha1:
425
raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
426
(self.file_id, rev_id))
428
checker.repeated_text_cnt += 1
430
mutter('check version {%s} of {%s}', rev_id, self.file_id)
431
file_lines = tree.get_file_lines(self.file_id)
432
checker.checked_text_cnt += 1
433
if self.text_size != sum(map(len, file_lines)):
434
raise BzrCheckError('text {%s} wrong size' % self.text_id)
435
if self.text_sha1 != sha_strings(file_lines):
436
raise BzrCheckError('text {%s} wrong sha1' % self.text_id)
437
checker.checked_texts[t] = self.text_sha1
440
other = InventoryFile(self.file_id, self.name, self.parent_id)
441
other.executable = self.executable
442
other.text_id = self.text_id
443
other.text_sha1 = self.text_sha1
444
other.text_size = self.text_size
445
other.revision = self.revision
448
def detect_changes(self, old_entry):
449
"""See InventoryEntry.detect_changes."""
450
assert self.text_sha1 != None
451
assert old_entry.text_sha1 != None
452
text_modified = (self.text_sha1 != old_entry.text_sha1)
453
meta_modified = (self.executable != old_entry.executable)
454
return text_modified, meta_modified
456
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
457
output_to, reverse=False):
458
"""See InventoryEntry._diff."""
459
from_text = tree.get_file(self.file_id).readlines()
461
to_text = to_tree.get_file(to_entry.file_id).readlines()
465
text_diff(from_label, from_text,
466
to_label, to_text, output_to)
468
text_diff(to_label, to_text,
469
from_label, from_text, output_to)
472
"""See InventoryEntry.has_text."""
475
def __init__(self, file_id, name, parent_id):
476
super(InventoryFile, self).__init__(file_id, name, parent_id)
479
def kind_character(self):
480
"""See InventoryEntry.kind_character."""
483
def _put_in_tar(self, item, tree):
484
"""See InventoryEntry._put_in_tar."""
485
item.type = tarfile.REGTYPE
486
fileobj = tree.get_file(self.file_id)
487
item.size = self.text_size
488
if tree.is_executable(self.file_id):
494
def _put_on_disk(self, fullpath, tree):
495
"""See InventoryEntry._put_on_disk."""
496
pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
497
if tree.is_executable(self.file_id):
498
os.chmod(fullpath, 0755)
500
def _read_tree_state(self, path, work_tree):
501
"""See InventoryEntry._read_tree_state."""
502
self.text_sha1 = work_tree.get_file_sha1(self.file_id)
503
self.executable = work_tree.is_executable(self.file_id)
505
def snapshot_revision(self, revision, previous_entries, work_tree,
507
"""See InventoryEntry.snapshot_revision."""
508
change = super(InventoryFile, self).snapshot_revision(revision,
509
previous_entries, work_tree, weave_store)
510
self._snapshot_text(previous_entries, work_tree, weave_store)
513
def _unchanged(self, path, previous_ie, work_tree):
514
"""See InventoryEntry._unchanged."""
515
compatible = super(InventoryFile, self)._unchanged(path, previous_ie,
517
if self.text_sha1 != previous_ie.text_sha1:
520
# FIXME: 20050930 probe for the text size when getting sha1
521
# in _read_tree_state
522
self.text_size = previous_ie.text_size
526
class InventoryLink(InventoryEntry):
527
"""A file in an inventory."""
529
__slots__ = ['symlink_target']
531
def _check(self, checker, rev_id, tree):
532
"""See InventoryEntry._check"""
533
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
534
raise BzrCheckError('symlink {%s} has text in revision {%s}'
535
% (self.file_id, rev_id))
536
if self.symlink_target == None:
537
raise BzrCheckError('symlink {%s} has no target in revision {%s}'
538
% (self.file_id, rev_id))
541
other = InventoryLink(self.file_id, self.name, self.parent_id)
542
other.symlink_target = self.symlink_target
543
other.revision = self.revision
546
def detect_changes(self, old_entry):
547
"""See InventoryEntry.detect_changes."""
548
# FIXME: which _modified field should we use ? RBC 20051003
549
text_modified = (self.symlink_target != old_entry.symlink_target)
551
mutter(" symlink target changed")
552
meta_modified = False
553
return text_modified, meta_modified
555
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
556
output_to, reverse=False):
557
"""See InventoryEntry._diff."""
558
from_text = self.symlink_target
559
if to_entry is not None:
560
to_text = to_entry.symlink_target
565
print >>output_to, '=== target changed %r => %r' % (from_text, to_text)
568
print >>output_to, '=== target was %r' % self.symlink_target
570
print >>output_to, '=== target is %r' % self.symlink_target
572
def __init__(self, file_id, name, parent_id):
573
super(InventoryLink, self).__init__(file_id, name, parent_id)
574
self.kind = 'symlink'
576
def kind_character(self):
577
"""See InventoryEntry.kind_character."""
580
def _put_in_tar(self, item, tree):
581
"""See InventoryEntry._put_in_tar."""
582
iterm.type = tarfile.SYMTYPE
586
item.linkname = self.symlink_target
589
def _put_on_disk(self, fullpath, tree):
590
"""See InventoryEntry._put_on_disk."""
592
os.symlink(self.symlink_target, fullpath)
594
raise BzrError("Failed to create symlink %r -> %r, error: %s" % (fullpath, self.symlink_target, e))
596
def _read_tree_state(self, path, work_tree):
597
"""See InventoryEntry._read_tree_state."""
598
self.symlink_target = work_tree.get_symlink_target(self.file_id)
600
def _unchanged(self, path, previous_ie, work_tree):
601
"""See InventoryEntry._unchanged."""
602
compatible = super(InventoryLink, self)._unchanged(path, previous_ie,
604
if self.symlink_target != previous_ie.symlink_target:
355
609
class Inventory(object):
356
610
"""Inventory of versioned files in a tree.