142
142
return False, False
144
def diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
145
output_to, reverse=False):
146
"""Perform a diff from this to to_entry.
148
text_diff will be used for textual difference calculation.
149
This is a template method, override _diff in child classes.
151
self._read_tree_state(tree.id2path(self.file_id), tree)
153
# cannot diff from one kind to another - you must do a removal
154
# and an addif they do not match.
155
assert self.kind == to_entry.kind
156
to_entry._read_tree_state(to_tree.id2path(to_entry.file_id),
158
self._diff(text_diff, from_label, tree, to_label, to_entry, to_tree,
161
144
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
162
145
output_to, reverse=False):
163
146
"""Perform a diff between two entries of the same kind."""
165
def find_previous_heads(self, previous_inventories,
166
versioned_file_store,
169
"""Return the revisions and entries that directly precede this.
171
Returned as a map from revision to inventory entry.
173
This is a map containing the file revisions in all parents
174
for which the file exists, and its revision is not a parent of
175
any other. If the file is new, the set will be empty.
177
:param versioned_file_store: A store where ancestry data on this
178
file id can be queried.
179
:param transaction: The transaction that queries to the versioned
180
file store should be completed under.
181
:param entry_vf: The entry versioned file, if its already available.
148
def parent_candidates(self, previous_inventories):
149
"""Find possible per-file graph parents.
151
This is currently defined by:
152
- Select the last changed revision in the parent inventory.
153
- Do deal with a short lived bug in bzr 0.8's development two entries
154
that have the same last changed but different 'x' bit settings are
183
def get_ancestors(weave, entry):
184
return set(weave.get_ancestry(entry.revision, topo_sorted=False))
185
157
# revision:ie mapping for each ie found in previous_inventories.
187
# revision:ie mapping with one revision for each head.
189
# revision: ancestor list for each head
191
159
# identify candidate head revision ids.
192
160
for inv in previous_inventories:
193
161
if self.file_id in inv:
194
162
ie = inv[self.file_id]
195
assert ie.file_id == self.file_id
196
if ie.kind != self.kind:
197
# Can't be a candidate if the kind has changed.
199
163
if ie.revision in candidates:
200
164
# same revision value in two different inventories:
201
165
# correct possible inconsistencies:
207
171
ie.executable = False
208
172
except AttributeError:
210
# must now be the same.
211
assert candidates[ie.revision] == ie
213
175
# add this revision as a candidate.
214
176
candidates[ie.revision] = ie
216
# common case optimisation
217
if len(candidates) == 1:
218
# if there is only one candidate revision found
219
# then we can opening the versioned file to access ancestry:
220
# there cannot be any ancestors to eliminate when there is
221
# only one revision available.
222
heads[ie.revision] = ie
225
# eliminate ancestors amongst the available candidates:
226
# heads are those that are not an ancestor of any other candidate
227
# - this provides convergence at a per-file level.
228
for ie in candidates.values():
229
# may be an ancestor of a known head:
230
already_present = 0 != len(
231
[head for head in heads
232
if ie.revision in head_ancestors[head]])
234
# an ancestor of an analyzed candidate.
236
# not an ancestor of a known head:
237
# load the versioned file for this file id if needed
239
entry_vf = versioned_file_store.get_weave_or_empty(
240
self.file_id, transaction)
241
ancestors = get_ancestors(entry_vf, ie)
242
# may knock something else out:
243
check_heads = list(heads.keys())
244
for head in check_heads:
245
if head in ancestors:
246
# this previously discovered 'head' is not
247
# really a head - its an ancestor of the newly
250
head_ancestors[ie.revision] = ancestors
251
heads[ie.revision] = ie
179
@deprecated_method(deprecated_in((1, 6, 0)))
254
180
def get_tar_item(self, root, dp, now, tree):
255
181
"""Get a tarfile item and a file stream for its content."""
256
182
item = tarfile.TarInfo(osutils.pathjoin(root, dp).encode('utf8'))
419
def snapshot(self, revision, path, previous_entries,
420
work_tree, commit_builder):
421
"""Make a snapshot of this entry which may or may not have changed.
423
This means that all its fields are populated, that it has its
424
text stored in the text store or weave.
426
# mutter('new parents of %s are %r', path, previous_entries)
427
self._read_tree_state(path, work_tree)
428
# TODO: Where should we determine whether to reuse a
429
# previous revision id or create a new revision? 20060606
430
if len(previous_entries) == 1:
431
# cannot be unchanged unless there is only one parent file rev.
432
parent_ie = previous_entries.values()[0]
433
if self._unchanged(parent_ie):
434
# mutter("found unchanged entry")
435
self.revision = parent_ie.revision
437
return self._snapshot_into_revision(revision, previous_entries,
438
work_tree, commit_builder)
440
def _snapshot_into_revision(self, revision, previous_entries, work_tree,
442
"""Record this revision unconditionally into a store.
444
The entry's last-changed revision property (`revision`) is updated to
445
that of the new revision.
447
:param revision: id of the new revision that is being recorded.
449
:returns: String description of the commit (e.g. "merged", "modified"), etc.
451
# mutter('new revision {%s} for {%s}', revision, self.file_id)
452
self.revision = revision
453
self._snapshot_text(previous_entries, work_tree, commit_builder)
455
def _snapshot_text(self, file_parents, work_tree, commit_builder):
456
"""Record the 'text' of this entry, whatever form that takes.
458
This default implementation simply adds an empty text.
460
raise NotImplementedError(self._snapshot_text)
462
343
def __eq__(self, other):
463
344
if not isinstance(other, InventoryEntry):
464
345
return NotImplemented
598
475
def _check(self, checker, tree_revision_id, tree):
599
476
"""See InventoryEntry._check"""
600
t = (self.file_id, self.revision)
601
if t in checker.checked_texts:
602
prev_sha = checker.checked_texts[t]
477
key = (self.file_id, self.revision)
478
if key in checker.checked_texts:
479
prev_sha = checker.checked_texts[key]
603
480
if prev_sha != self.text_sha1:
604
raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
605
(self.file_id, tree_revision_id))
482
'mismatched sha1 on {%s} in {%s} (%s != %s) %r' %
483
(self.file_id, tree_revision_id, prev_sha, self.text_sha1,
607
486
checker.repeated_text_cnt += 1
610
if self.file_id not in checker.checked_weaves:
611
mutter('check weave {%s}', self.file_id)
612
w = tree.get_weave(self.file_id)
613
# Not passing a progress bar, because it creates a new
614
# progress, which overwrites the current progress,
615
# and doesn't look nice
617
checker.checked_weaves[self.file_id] = True
619
w = tree.get_weave(self.file_id)
621
489
mutter('check version {%s} of {%s}', tree_revision_id, self.file_id)
622
490
checker.checked_text_cnt += 1
623
491
# We can't check the length, because Weave doesn't store that
624
492
# information, and the whole point of looking at the weave's
625
493
# sha1sum is that we don't have to extract the text.
626
if self.text_sha1 != w.get_sha1(self.revision):
627
raise BzrCheckError('text {%s} version {%s} wrong sha1'
628
% (self.file_id, self.revision))
629
checker.checked_texts[t] = self.text_sha1
494
if (self.text_sha1 != tree._repository.texts.get_sha1s([key])[key]):
495
raise BzrCheckError('text {%s} version {%s} wrong sha1' % key)
496
checker.checked_texts[key] = self.text_sha1
632
499
other = InventoryFile(self.file_id, self.name, self.parent_id)
648
513
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
649
514
output_to, reverse=False):
650
515
"""See InventoryEntry._diff."""
652
from_text = tree.get_file(self.file_id).readlines()
654
to_text = to_tree.get_file(to_entry.file_id).readlines()
658
text_diff(from_label, from_text,
659
to_label, to_text, output_to)
661
text_diff(to_label, to_text,
662
from_label, from_text, output_to)
663
except errors.BinaryFile:
665
label_pair = (to_label, from_label)
667
label_pair = (from_label, to_label)
668
print >> output_to, "Binary files %s and %s differ" % label_pair
516
from bzrlib.diff import DiffText
517
from_file_id = self.file_id
519
to_file_id = to_entry.file_id
523
to_file_id, from_file_id = from_file_id, to_file_id
524
tree, to_tree = to_tree, tree
525
from_label, to_label = to_label, from_label
526
differ = DiffText(tree, to_tree, output_to, 'utf-8', '', '',
528
return differ.diff_text(from_file_id, to_file_id, from_label, to_label)
670
530
def has_text(self):
671
531
"""See InventoryEntry.has_text."""
715
575
def _forget_tree_state(self):
716
576
self.text_sha1 = None
718
def _snapshot_text(self, file_parents, work_tree, commit_builder):
719
"""See InventoryEntry._snapshot_text."""
720
def get_content_byte_lines():
721
return work_tree.get_file(self.file_id).readlines()
722
self.text_sha1, self.text_size = commit_builder.modified_file_text(
723
self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
725
578
def _unchanged(self, previous_ie):
726
579
"""See InventoryEntry._unchanged."""
727
580
compatible = super(InventoryFile, self)._unchanged(previous_ie)
770
623
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
771
624
output_to, reverse=False):
772
625
"""See InventoryEntry._diff."""
773
from_text = self.symlink_target
626
from bzrlib.diff import DiffSymlink
627
old_target = self.symlink_target
774
628
if to_entry is not None:
775
to_text = to_entry.symlink_target
780
print >>output_to, '=== target changed %r => %r' % (from_text, to_text)
783
print >>output_to, '=== target was %r' % self.symlink_target
785
print >>output_to, '=== target is %r' % self.symlink_target
629
new_target = to_entry.symlink_target
638
new_target, old_target = old_target, new_target
639
differ = DiffSymlink(old_tree, new_tree, output_to)
640
return differ.diff_symlink(old_target, new_target)
787
642
def __init__(self, file_id, name, parent_id):
788
643
super(InventoryLink, self).__init__(file_id, name, parent_id)
909
763
if root_id is not None:
910
assert root_id.__class__ == str
911
764
self._set_root(InventoryDirectory(root_id, u'', None))
915
768
self.revision_id = revision_id
771
return "<Inventory object at %x, contents=%r>" % (id(self), self._byid)
773
def apply_delta(self, delta):
774
"""Apply a delta to this inventory.
776
:param delta: A list of changes to apply. After all the changes are
777
applied the final inventory must be internally consistent, but it
778
is ok to supply changes which, if only half-applied would have an
779
invalid result - such as supplying two changes which rename two
780
files, 'A' and 'B' with each other : [('A', 'B', 'A-id', a_entry),
781
('B', 'A', 'B-id', b_entry)].
783
Each change is a tuple, of the form (old_path, new_path, file_id,
786
When new_path is None, the change indicates the removal of an entry
787
from the inventory and new_entry will be ignored (using None is
788
appropriate). If new_path is not None, then new_entry must be an
789
InventoryEntry instance, which will be incorporated into the
790
inventory (and replace any existing entry with the same file id).
792
When old_path is None, the change indicates the addition of
793
a new entry to the inventory.
795
When neither new_path nor old_path are None, the change is a
796
modification to an entry, such as a rename, reparent, kind change
799
The children attribute of new_entry is ignored. This is because
800
this method preserves children automatically across alterations to
801
the parent of the children, and cases where the parent id of a
802
child is changing require the child to be passed in as a separate
803
change regardless. E.g. in the recursive deletion of a directory -
804
the directory's children must be included in the delta, or the
805
final inventory will be invalid.
808
# Remove all affected items which were in the original inventory,
809
# starting with the longest paths, thus ensuring parents are examined
810
# after their children, which means that everything we examine has no
811
# modified children remaining by the time we examine it.
812
for old_path, file_id in sorted(((op, f) for op, np, f, e in delta
813
if op is not None), reverse=True):
814
if file_id not in self:
817
# Preserve unaltered children of file_id for later reinsertion.
818
file_id_children = getattr(self[file_id], 'children', {})
819
if len(file_id_children):
820
children[file_id] = file_id_children
821
# Remove file_id and the unaltered children. If file_id is not
822
# being deleted it will be reinserted back later.
823
self.remove_recursive_id(file_id)
824
# Insert all affected which should be in the new inventory, reattaching
825
# their children if they had any. This is done from shortest path to
826
# longest, ensuring that items which were modified and whose parents in
827
# the resulting inventory were also modified, are inserted after their
829
for new_path, new_entry in sorted((np, e) for op, np, f, e in
830
delta if np is not None):
831
if new_entry.kind == 'directory':
832
# Pop the child which to allow detection of children whose
833
# parents were deleted and which were not reattached to a new
835
new_entry.children = children.pop(new_entry.file_id, {})
838
# Get the parent id that was deleted
839
parent_id, children = children.popitem()
840
raise errors.InconsistentDelta("<deleted>", parent_id,
841
"The file id was deleted but its children were not deleted.")
917
843
def _set_root(self, ie):
919
845
self._byid = {self.root.file_id: self.root}
987
917
lexicographically sorted order, and is a hybrid between
988
918
depth-first and breadth-first.
920
:param yield_parents: If True, yield the parents from the root leading
921
down to specific_file_ids that have been requested. This has no
922
impact if specific_file_ids is None.
990
923
:return: This yields (path, entry) pairs
992
if specific_file_ids:
993
safe = osutils.safe_file_id
994
specific_file_ids = set(safe(fid) for fid in specific_file_ids)
925
if specific_file_ids and not isinstance(specific_file_ids, set):
926
specific_file_ids = set(specific_file_ids)
995
927
# TODO? Perhaps this should return the from_dir so that the root is
996
928
# yielded? or maybe an option?
997
929
if from_dir is None:
998
930
if self.root is None:
1000
932
# Optimize a common case
1001
if specific_file_ids is not None and len(specific_file_ids) == 1:
933
if (not yield_parents and specific_file_ids is not None and
934
len(specific_file_ids) == 1):
1002
935
file_id = list(specific_file_ids)[0]
1003
936
if file_id in self:
1004
937
yield self.id2path(file_id), self[file_id]
1006
939
from_dir = self.root
1007
if (specific_file_ids is None or
940
if (specific_file_ids is None or yield_parents or
1008
941
self.root.file_id in specific_file_ids):
1009
942
yield u'', self.root
1010
943
elif isinstance(from_dir, basestring):
1392
1306
if file_id is None:
1393
1307
file_id = generate_ids.gen_file_id(name)
1395
file_id = osutils.safe_file_id(file_id)
1308
name = ensure_normalized_name(name)
1310
factory = entry_factory[kind]
1312
raise BzrError("unknown kind %r" % kind)
1313
return factory(file_id, name, parent_id)
1316
def ensure_normalized_name(name):
1319
:raises InvalidNormalization: When name is not normalized, and cannot be
1320
accessed on this platform by the normalized path.
1321
:return: The NFC normalised version of name.
1397
1323
#------- This has been copied to bzrlib.dirstate.DirState.add, please
1398
1324
# keep them synchronised.
1399
1325
# we dont import normalized_filename directly because we want to be