78
73
>>> i = Inventory()
81
>>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
82
InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
83
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
84
InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
85
>>> shouldbe = {0: '', 1: 'src', 2: pathjoin('src','hello.c')}
86
>>> for ix, j in enumerate(i.iter_entries()):
87
... print (j[0] == shouldbe[ix], j[1])
76
>>> i.add(InventoryEntry('123', 'src', 'directory', ROOT_ID))
77
InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT')
78
>>> i.add(InventoryEntry('2323', 'hello.c', 'file', parent_id='123'))
79
InventoryEntry('2323', 'hello.c', kind='file', parent_id='123')
80
>>> for j in i.iter_entries():
89
(True, InventoryDirectory('TREE_ROOT', '', parent_id=None, revision=None))
90
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
91
(True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
92
>>> i.add(InventoryFile('2323', 'bye.c', '123'))
83
('src', InventoryEntry('123', 'src', kind='directory', parent_id='TREE_ROOT'))
84
('src/hello.c', InventoryEntry('2323', 'hello.c', kind='file', parent_id='123'))
85
>>> i.add(InventoryEntry('2323', 'bye.c', 'file', '123'))
93
86
Traceback (most recent call last):
95
88
BzrError: inventory already contains entry with id {2323}
96
>>> i.add(InventoryFile('2324', 'bye.c', '123'))
97
InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
98
>>> i.add(InventoryDirectory('2325', 'wibble', '123'))
99
InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
89
>>> i.add(InventoryEntry('2324', 'bye.c', 'file', '123'))
90
InventoryEntry('2324', 'bye.c', kind='file', parent_id='123')
91
>>> i.add(InventoryEntry('2325', 'wibble', 'directory', '123'))
92
InventoryEntry('2325', 'wibble', kind='directory', parent_id='123')
100
93
>>> i.path2id('src/wibble')
104
>>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
105
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
97
>>> i.add(InventoryEntry('2326', 'wibble.c', 'file', '2325'))
98
InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
107
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
100
InventoryEntry('2326', 'wibble.c', kind='file', parent_id='2325')
108
101
>>> for path, entry in i.iter_entries():
102
... print path.replace('\\\\', '/') # for win32 os.sep
110
103
... assert i.path2id(path)
117
109
src/wibble/wibble.c
118
>>> i.id2path('2326')
110
>>> i.id2path('2326').replace('\\\\', '/')
119
111
'src/wibble/wibble.c'
122
# Constants returned by describe_change()
124
# TODO: These should probably move to some kind of FileChangeDescription
125
# class; that's like what's inside a TreeDelta but we want to be able to
126
# generate them just for one file at a time.
128
MODIFIED_AND_RENAMED = 'modified and renamed'
132
def detect_changes(self, old_entry):
133
"""Return a (text_modified, meta_modified) from this to old_entry.
135
_read_tree_state must have been called on self and old_entry prior to
136
calling detect_changes.
140
def diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
141
output_to, reverse=False):
142
"""Perform a diff from this to to_entry.
144
text_diff will be used for textual difference calculation.
145
This is a template method, override _diff in child classes.
147
self._read_tree_state(tree.id2path(self.file_id), tree)
149
# cannot diff from one kind to another - you must do a removal
150
# and an addif they do not match.
151
assert self.kind == to_entry.kind
152
to_entry._read_tree_state(to_tree.id2path(to_entry.file_id),
154
self._diff(text_diff, from_label, tree, to_label, to_entry, to_tree,
157
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
158
output_to, reverse=False):
159
"""Perform a diff between two entries of the same kind."""
161
def find_previous_heads(self, previous_inventories,
162
versioned_file_store,
165
"""Return the revisions and entries that directly precede this.
167
Returned as a map from revision to inventory entry.
169
This is a map containing the file revisions in all parents
170
for which the file exists, and its revision is not a parent of
171
any other. If the file is new, the set will be empty.
173
:param versioned_file_store: A store where ancestry data on this
174
file id can be queried.
175
:param transaction: The transaction that queries to the versioned
176
file store should be completed under.
177
:param entry_vf: The entry versioned file, if its already available.
179
def get_ancestors(weave, entry):
180
return set(weave.get_ancestry(entry.revision))
181
# revision:ie mapping for each ie found in previous_inventories.
183
# revision:ie mapping with one revision for each head.
185
# revision: ancestor list for each head
187
# identify candidate head revision ids.
188
for inv in previous_inventories:
189
if self.file_id in inv:
190
ie = inv[self.file_id]
191
assert ie.file_id == self.file_id
192
if ie.revision in candidates:
193
# same revision value in two different inventories:
194
# correct possible inconsistencies:
195
# * there was a bug in revision updates with 'x' bit
198
if candidates[ie.revision].executable != ie.executable:
199
candidates[ie.revision].executable = False
200
ie.executable = False
201
except AttributeError:
203
# must now be the same.
204
assert candidates[ie.revision] == ie
206
# add this revision as a candidate.
207
candidates[ie.revision] = ie
209
# common case optimisation
210
if len(candidates) == 1:
211
# if there is only one candidate revision found
212
# then we can opening the versioned file to access ancestry:
213
# there cannot be any ancestors to eliminate when there is
214
# only one revision available.
215
heads[ie.revision] = ie
218
# eliminate ancestors amongst the available candidates:
219
# heads are those that are not an ancestor of any other candidate
220
# - this provides convergence at a per-file level.
221
for ie in candidates.values():
222
# may be an ancestor of a known head:
223
already_present = 0 != len(
224
[head for head in heads
225
if ie.revision in head_ancestors[head]])
227
# an ancestor of an analyzed candidate.
229
# not an ancestor of a known head:
230
# load the versioned file for this file id if needed
232
entry_vf = versioned_file_store.get_weave_or_empty(
233
self.file_id, transaction)
234
ancestors = get_ancestors(entry_vf, ie)
235
# may knock something else out:
236
check_heads = list(heads.keys())
237
for head in check_heads:
238
if head in ancestors:
239
# this previously discovered 'head' is not
240
# really a head - its an ancestor of the newly
243
head_ancestors[ie.revision] = ancestors
244
heads[ie.revision] = ie
247
def get_tar_item(self, root, dp, now, tree):
248
"""Get a tarfile item and a file stream for its content."""
249
item = tarfile.TarInfo(pathjoin(root, dp))
250
# TODO: would be cool to actually set it to the timestamp of the
251
# revision it was last changed
253
fileobj = self._put_in_tar(item, tree)
257
"""Return true if the object this entry represents has textual data.
259
Note that textual data includes binary content.
261
Also note that all entries get weave files created for them.
262
This attribute is primarily used when upgrading from old trees that
263
did not have the weave index for all inventory entries.
267
def __init__(self, file_id, name, parent_id, text_id=None):
114
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
115
'text_id', 'parent_id', 'children',
116
'revision', 'symlink_target']
118
def _add_text_to_weave(self, new_lines, parents, weave_store):
119
weave_store.add_text(self.file_id, self.revision, new_lines, parents)
121
def __init__(self, file_id, name, kind, parent_id, text_id=None):
268
122
"""Create an InventoryEntry
270
124
The filename must be a single component, relative to the
271
125
parent directory; it cannot be a whole path or relative name.
273
>>> e = InventoryFile('123', 'hello.c', ROOT_ID)
127
>>> e = InventoryEntry('123', 'hello.c', 'file', ROOT_ID)
278
>>> e = InventoryFile('123', 'src/hello.c', ROOT_ID)
132
>>> e = InventoryEntry('123', 'src/hello.c', 'file', ROOT_ID)
279
133
Traceback (most recent call last):
280
InvalidEntryName: Invalid entry name: src/hello.c
134
BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
282
136
assert isinstance(name, basestring), name
283
137
if '/' in name or '\\' in name:
284
raise InvalidEntryName(name=name)
285
self.executable = False
138
raise BzrCheckError('InventoryEntry name %r is invalid' % name)
286
140
self.revision = None
287
141
self.text_sha1 = None
288
142
self.text_size = None
289
143
self.file_id = file_id
291
146
self.text_id = text_id
292
147
self.parent_id = parent_id
293
148
self.symlink_target = None
295
def kind_character(self):
296
"""Return a short kind indicator useful for appending to names."""
297
raise BzrError('unknown kind %r' % self.kind)
299
known_kinds = ('file', 'directory', 'symlink')
301
def _put_in_tar(self, item, tree):
302
"""populate item for stashing in a tar, and return the content stream.
304
If no content is available, return None.
306
raise BzrError("don't know how to export {%s} of kind %r" %
307
(self.file_id, self.kind))
309
def put_on_disk(self, dest, dp, tree):
310
"""Create a representation of self on disk in the prefix dest.
312
This is a template method - implement _put_on_disk in subclasses.
314
fullpath = pathjoin(dest, dp)
315
self._put_on_disk(fullpath, tree)
316
# mutter(" export {%s} kind %s to %s", self.file_id,
317
# self.kind, fullpath)
319
def _put_on_disk(self, fullpath, tree):
320
"""Put this entry onto disk at fullpath, from tree tree."""
321
raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
149
if kind == 'directory':
153
elif kind == 'symlink':
156
raise BzrError("unhandled entry kind %r" % kind)
158
def read_symlink_target(self, path):
159
if self.kind == 'symlink':
161
self.symlink_target = os.readlink(path)
163
raise BzrError("os.readlink error, %s" % e)
323
165
def sorted_children(self):
324
return sorted(self.children.items())
327
def versionable_kind(kind):
328
return kind in ('file', 'directory', 'symlink')
166
l = self.children.items()
330
170
def check(self, checker, rev_id, inv, tree):
331
"""Check this inventory entry is intact.
333
This is a template method, override _check for kind specific
336
:param checker: Check object providing context for the checks;
337
can be used to find out what parts of the repository have already
339
:param rev_id: Revision id from which this InventoryEntry was loaded.
340
Not necessarily the last-changed revision for this file.
341
:param inv: Inventory from which the entry was loaded.
342
:param tree: RevisionTree for this entry.
344
if self.parent_id is not None:
171
if self.parent_id != None:
345
172
if not inv.has_id(self.parent_id):
346
173
raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
347
174
% (self.parent_id, rev_id))
348
self._check(checker, rev_id, tree)
175
if self.kind == 'file':
176
revision = self.revision
177
t = (self.file_id, revision)
178
if t in checker.checked_texts:
179
prev_sha = checker.checked_texts[t]
180
if prev_sha != self.text_sha1:
181
raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
182
(self.file_id, rev_id))
184
checker.repeated_text_cnt += 1
186
mutter('check version {%s} of {%s}', rev_id, self.file_id)
187
file_lines = tree.get_file_lines(self.file_id)
188
checker.checked_text_cnt += 1
189
if self.text_size != sum(map(len, file_lines)):
190
raise BzrCheckError('text {%s} wrong size' % self.text_id)
191
if self.text_sha1 != sha_strings(file_lines):
192
raise BzrCheckError('text {%s} wrong sha1' % self.text_id)
193
checker.checked_texts[t] = self.text_sha1
194
elif self.kind == 'directory':
195
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
196
raise BzrCheckError('directory {%s} has text in revision {%s}'
197
% (self.file_id, rev_id))
198
elif self.kind == 'root_directory':
200
elif self.kind == 'symlink':
201
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
202
raise BzrCheckError('symlink {%s} has text in revision {%s}'
203
% (self.file_id, rev_id))
204
if self.symlink_target == None:
205
raise BzrCheckError('symlink {%s} has no target in revision {%s}'
206
% (self.file_id, rev_id))
208
raise BzrCheckError('unknown entry kind %r in revision {%s}' %
350
def _check(self, checker, rev_id, tree):
351
"""Check this inventory entry for kind specific errors."""
352
raise BzrCheckError('unknown entry kind %r in revision {%s}' %
356
"""Clone this inventory entry."""
357
raise NotImplementedError
360
def describe_change(old_entry, new_entry):
361
"""Describe the change between old_entry and this.
363
This smells of being an InterInventoryEntry situation, but as its
364
the first one, we're making it a static method for now.
366
An entry with a different parent, or different name is considered
367
to be renamed. Reparenting is an internal detail.
368
Note that renaming the parent does not trigger a rename for the
371
# TODO: Perhaps return an object rather than just a string
372
if old_entry is new_entry:
373
# also the case of both being None
375
elif old_entry is None:
213
other = InventoryEntry(self.file_id, self.name, self.kind,
215
other.text_id = self.text_id
216
other.text_sha1 = self.text_sha1
217
other.text_size = self.text_size
218
other.symlink_target = self.symlink_target
219
other.revision = self.revision
220
# note that children are *not* copied; they're pulled across when
224
def _get_snapshot_change(self, previous_entries):
225
if len(previous_entries) > 1:
227
elif len(previous_entries) == 0:
377
elif new_entry is None:
379
text_modified, meta_modified = new_entry.detect_changes(old_entry)
380
if text_modified or meta_modified:
384
# TODO 20060511 (mbp, rbc) factor out 'detect_rename' here.
385
if old_entry.parent_id != new_entry.parent_id:
387
elif old_entry.name != new_entry.name:
391
if renamed and not modified:
392
return InventoryEntry.RENAMED
393
if modified and not renamed:
395
if modified and renamed:
396
return InventoryEntry.MODIFIED_AND_RENAMED
230
return 'modified/renamed/reparented'
399
232
def __repr__(self):
400
return ("%s(%r, %r, parent_id=%r, revision=%r)"
233
return ("%s(%r, %r, kind=%r, parent_id=%r)"
401
234
% (self.__class__.__name__,
407
def snapshot(self, revision, path, previous_entries,
408
work_tree, commit_builder):
409
"""Make a snapshot of this entry which may or may not have changed.
240
def snapshot(self, revision, path, previous_entries, work_tree,
242
"""Make a snapshot of this entry.
411
244
This means that all its fields are populated, that it has its
412
245
text stored in the text store or weave.
414
# mutter('new parents of %s are %r', path, previous_entries)
247
mutter('new parents of %s are %r', path, previous_entries)
415
248
self._read_tree_state(path, work_tree)
416
# TODO: Where should we determine whether to reuse a
417
# previous revision id or create a new revision? 20060606
418
249
if len(previous_entries) == 1:
419
250
# cannot be unchanged unless there is only one parent file rev.
420
251
parent_ie = previous_entries.values()[0]
421
if self._unchanged(parent_ie):
422
# mutter("found unchanged entry")
252
if self._unchanged(path, parent_ie, work_tree):
253
mutter("found unchanged entry")
423
254
self.revision = parent_ie.revision
424
255
return "unchanged"
425
return self._snapshot_into_revision(revision, previous_entries,
426
work_tree, commit_builder)
428
def _snapshot_into_revision(self, revision, previous_entries, work_tree,
430
"""Record this revision unconditionally into a store.
432
The entry's last-changed revision property (`revision`) is updated to
433
that of the new revision.
435
:param revision: id of the new revision that is being recorded.
437
:returns: String description of the commit (e.g. "merged", "modified"), etc.
439
# mutter('new revision {%s} for {%s}', revision, self.file_id)
256
mutter('new revision for {%s}', self.file_id)
440
257
self.revision = revision
441
self._snapshot_text(previous_entries, work_tree, commit_builder)
258
change = self._get_snapshot_change(previous_entries)
259
if self.kind != 'file':
261
self._snapshot_text(previous_entries, work_tree, weave_store)
443
def _snapshot_text(self, file_parents, work_tree, commit_builder):
444
"""Record the 'text' of this entry, whatever form that takes.
446
This default implementation simply adds an empty text.
448
raise NotImplementedError(self._snapshot_text)
264
def _snapshot_text(self, file_parents, work_tree, weave_store):
265
mutter('storing file {%s} in revision {%s}',
266
self.file_id, self.revision)
267
# special case to avoid diffing on renames or
269
if (len(file_parents) == 1
270
and self.text_sha1 == file_parents.values()[0].text_sha1
271
and self.text_size == file_parents.values()[0].text_size):
272
previous_ie = file_parents.values()[0]
273
weave_store.add_identical_text(
274
self.file_id, previous_ie.revision,
275
self.revision, file_parents)
277
new_lines = work_tree.get_file(self.file_id).readlines()
278
self._add_text_to_weave(new_lines, file_parents, weave_store)
279
self.text_sha1 = sha_strings(new_lines)
280
self.text_size = sum(map(len, new_lines))
450
282
def __eq__(self, other):
451
283
if not isinstance(other, InventoryEntry):
452
284
return NotImplemented
454
return ((self.file_id == other.file_id)
455
and (self.name == other.name)
456
and (other.symlink_target == self.symlink_target)
457
and (self.text_sha1 == other.text_sha1)
458
and (self.text_size == other.text_size)
459
and (self.text_id == other.text_id)
460
and (self.parent_id == other.parent_id)
461
and (self.kind == other.kind)
462
and (self.revision == other.revision)
463
and (self.executable == other.executable)
286
return (self.file_id == other.file_id) \
287
and (self.name == other.name) \
288
and (other.symlink_target == self.symlink_target) \
289
and (self.text_sha1 == other.text_sha1) \
290
and (self.text_size == other.text_size) \
291
and (self.text_id == other.text_id) \
292
and (self.parent_id == other.parent_id) \
293
and (self.kind == other.kind) \
294
and (self.revision == other.revision)
466
296
def __ne__(self, other):
467
297
return not (self == other)
526
342
and (self.children == other.children)
529
class InventoryDirectory(InventoryEntry):
530
"""A directory in an inventory."""
532
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
533
'text_id', 'parent_id', 'children', 'executable',
534
'revision', 'symlink_target']
536
def _check(self, checker, rev_id, tree):
537
"""See InventoryEntry._check"""
538
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
539
raise BzrCheckError('directory {%s} has text in revision {%s}'
540
% (self.file_id, rev_id))
543
other = InventoryDirectory(self.file_id, self.name, self.parent_id)
544
other.revision = self.revision
545
# note that children are *not* copied; they're pulled across when
549
def __init__(self, file_id, name, parent_id):
550
super(InventoryDirectory, self).__init__(file_id, name, parent_id)
552
self.kind = 'directory'
554
def kind_character(self):
555
"""See InventoryEntry.kind_character."""
558
def _put_in_tar(self, item, tree):
559
"""See InventoryEntry._put_in_tar."""
560
item.type = tarfile.DIRTYPE
567
def _put_on_disk(self, fullpath, tree):
568
"""See InventoryEntry._put_on_disk."""
571
def _snapshot_text(self, file_parents, work_tree, commit_builder):
572
"""See InventoryEntry._snapshot_text."""
573
commit_builder.modified_directory(self.file_id, file_parents)
576
class InventoryFile(InventoryEntry):
577
"""A file in an inventory."""
579
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
580
'text_id', 'parent_id', 'children', 'executable',
581
'revision', 'symlink_target']
583
def _check(self, checker, tree_revision_id, tree):
584
"""See InventoryEntry._check"""
585
t = (self.file_id, self.revision)
586
if t in checker.checked_texts:
587
prev_sha = checker.checked_texts[t]
588
if prev_sha != self.text_sha1:
589
raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
590
(self.file_id, tree_revision_id))
592
checker.repeated_text_cnt += 1
595
if self.file_id not in checker.checked_weaves:
596
mutter('check weave {%s}', self.file_id)
597
w = tree.get_weave(self.file_id)
598
# Not passing a progress bar, because it creates a new
599
# progress, which overwrites the current progress,
600
# and doesn't look nice
602
checker.checked_weaves[self.file_id] = True
604
w = tree.get_weave(self.file_id)
606
mutter('check version {%s} of {%s}', tree_revision_id, self.file_id)
607
checker.checked_text_cnt += 1
608
# We can't check the length, because Weave doesn't store that
609
# information, and the whole point of looking at the weave's
610
# sha1sum is that we don't have to extract the text.
611
if self.text_sha1 != w.get_sha1(self.revision):
612
raise BzrCheckError('text {%s} version {%s} wrong sha1'
613
% (self.file_id, self.revision))
614
checker.checked_texts[t] = self.text_sha1
617
other = InventoryFile(self.file_id, self.name, self.parent_id)
618
other.executable = self.executable
619
other.text_id = self.text_id
620
other.text_sha1 = self.text_sha1
621
other.text_size = self.text_size
622
other.revision = self.revision
625
def detect_changes(self, old_entry):
626
"""See InventoryEntry.detect_changes."""
627
assert self.text_sha1 is not None
628
assert old_entry.text_sha1 is not None
629
text_modified = (self.text_sha1 != old_entry.text_sha1)
630
meta_modified = (self.executable != old_entry.executable)
631
return text_modified, meta_modified
633
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
634
output_to, reverse=False):
635
"""See InventoryEntry._diff."""
637
from_text = tree.get_file(self.file_id).readlines()
639
to_text = to_tree.get_file(to_entry.file_id).readlines()
643
text_diff(from_label, from_text,
644
to_label, to_text, output_to)
646
text_diff(to_label, to_text,
647
from_label, from_text, output_to)
650
label_pair = (to_label, from_label)
652
label_pair = (from_label, to_label)
653
print >> output_to, "Binary files %s and %s differ" % label_pair
656
"""See InventoryEntry.has_text."""
659
def __init__(self, file_id, name, parent_id):
660
super(InventoryFile, self).__init__(file_id, name, parent_id)
663
def kind_character(self):
664
"""See InventoryEntry.kind_character."""
667
def _put_in_tar(self, item, tree):
668
"""See InventoryEntry._put_in_tar."""
669
item.type = tarfile.REGTYPE
670
fileobj = tree.get_file(self.file_id)
671
item.size = self.text_size
672
if tree.is_executable(self.file_id):
678
def _put_on_disk(self, fullpath, tree):
679
"""See InventoryEntry._put_on_disk."""
680
pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
681
if tree.is_executable(self.file_id):
682
os.chmod(fullpath, 0755)
684
def _read_tree_state(self, path, work_tree):
685
"""See InventoryEntry._read_tree_state."""
686
self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
687
# FIXME: 20050930 probe for the text size when getting sha1
688
# in _read_tree_state
689
self.executable = work_tree.is_executable(self.file_id, path=path)
692
return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s)"
693
% (self.__class__.__name__,
700
def _forget_tree_state(self):
701
self.text_sha1 = None
703
def _snapshot_text(self, file_parents, work_tree, commit_builder):
704
"""See InventoryEntry._snapshot_text."""
705
def get_content_byte_lines():
706
return work_tree.get_file(self.file_id).readlines()
707
self.text_sha1, self.text_size = commit_builder.modified_file_text(
708
self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
710
def _unchanged(self, previous_ie):
711
"""See InventoryEntry._unchanged."""
712
compatible = super(InventoryFile, self)._unchanged(previous_ie)
713
if self.text_sha1 != previous_ie.text_sha1:
716
# FIXME: 20050930 probe for the text size when getting sha1
717
# in _read_tree_state
718
self.text_size = previous_ie.text_size
719
if self.executable != previous_ie.executable:
724
class InventoryLink(InventoryEntry):
725
"""A file in an inventory."""
727
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
728
'text_id', 'parent_id', 'children', 'executable',
729
'revision', 'symlink_target']
731
def _check(self, checker, rev_id, tree):
732
"""See InventoryEntry._check"""
733
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
734
raise BzrCheckError('symlink {%s} has text in revision {%s}'
735
% (self.file_id, rev_id))
736
if self.symlink_target is None:
737
raise BzrCheckError('symlink {%s} has no target in revision {%s}'
738
% (self.file_id, rev_id))
741
other = InventoryLink(self.file_id, self.name, self.parent_id)
742
other.symlink_target = self.symlink_target
743
other.revision = self.revision
746
def detect_changes(self, old_entry):
747
"""See InventoryEntry.detect_changes."""
748
# FIXME: which _modified field should we use ? RBC 20051003
749
text_modified = (self.symlink_target != old_entry.symlink_target)
751
mutter(" symlink target changed")
752
meta_modified = False
753
return text_modified, meta_modified
755
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
756
output_to, reverse=False):
757
"""See InventoryEntry._diff."""
758
from_text = self.symlink_target
759
if to_entry is not None:
760
to_text = to_entry.symlink_target
765
print >>output_to, '=== target changed %r => %r' % (from_text, to_text)
768
print >>output_to, '=== target was %r' % self.symlink_target
770
print >>output_to, '=== target is %r' % self.symlink_target
772
def __init__(self, file_id, name, parent_id):
773
super(InventoryLink, self).__init__(file_id, name, parent_id)
774
self.kind = 'symlink'
776
def kind_character(self):
777
"""See InventoryEntry.kind_character."""
780
def _put_in_tar(self, item, tree):
781
"""See InventoryEntry._put_in_tar."""
782
item.type = tarfile.SYMTYPE
786
item.linkname = self.symlink_target
789
def _put_on_disk(self, fullpath, tree):
790
"""See InventoryEntry._put_on_disk."""
792
os.symlink(self.symlink_target, fullpath)
794
raise BzrError("Failed to create symlink %r -> %r, error: %s" % (fullpath, self.symlink_target, e))
796
def _read_tree_state(self, path, work_tree):
797
"""See InventoryEntry._read_tree_state."""
798
self.symlink_target = work_tree.get_symlink_target(self.file_id)
800
def _forget_tree_state(self):
801
self.symlink_target = None
803
def _unchanged(self, previous_ie):
804
"""See InventoryEntry._unchanged."""
805
compatible = super(InventoryLink, self)._unchanged(previous_ie)
806
if self.symlink_target != previous_ie.symlink_target:
810
def _snapshot_text(self, file_parents, work_tree, commit_builder):
811
"""See InventoryEntry._snapshot_text."""
812
commit_builder.modified_link(
813
self.file_id, file_parents, self.symlink_target)
816
346
class Inventory(object):
817
347
"""Inventory of versioned files in a tree.