163
161
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
164
162
output_to, reverse=False):
165
163
"""Perform a diff between two entries of the same kind."""
167
def parent_candidates(self, previous_inventories):
168
"""Find possible per-file graph parents.
170
This is currently defined by:
171
- Select the last changed revision in the parent inventory.
172
- Do deal with a short lived bug in bzr 0.8's development two entries
173
that have the same last changed but different 'x' bit settings are
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.
183
def get_ancestors(weave, entry):
184
return set(weave.get_ancestry(entry.revision))
176
185
# 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
178
191
# identify candidate head revision ids.
179
192
for inv in previous_inventories:
180
193
if self.file_id in inv:
181
194
ie = inv[self.file_id]
182
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.
183
199
if ie.revision in candidates:
184
200
# same revision value in two different inventories:
185
201
# correct possible inconsistencies:
197
213
# add this revision as a candidate.
198
214
candidates[ie.revision] = ie
201
@deprecated_method(symbol_versioning.zero_ninetyone)
202
def find_previous_heads(self, previous_inventories,
203
versioned_file_store,
206
"""Return the revisions and entries that directly precede this.
208
Returned as a map from revision to inventory entry.
210
This is a map containing the file revisions in all parents
211
for which the file exists, and its revision is not a parent of
212
any other. If the file is new, the set will be empty.
214
:param versioned_file_store: A store where ancestry data on this
215
file id can be queried.
216
:param transaction: The transaction that queries to the versioned
217
file store should be completed under.
218
:param entry_vf: The entry versioned file, if its already available.
220
candidates = self.parent_candidates(previous_inventories)
222
# revision:ie mapping with one revision for each head.
224
216
# common case optimisation
225
217
if len(candidates) == 1:
226
218
# if there is only one candidate revision found
227
# then we can avoid opening the versioned file to access ancestry:
219
# then we can opening the versioned file to access ancestry:
228
220
# there cannot be any ancestors to eliminate when there is
229
221
# only one revision available.
232
# --- what follows is now encapsulated in repository.get_graph.heads(),
233
# but that is not accessible from here as we have no repository
234
# pointer. Note that the repository.get_graph.heads() call can return
235
# different results *at the moment* because of the kind-changing check
236
# we have in parent_candidates().
222
heads[ie.revision] = ie
238
225
# eliminate ancestors amongst the available candidates:
239
226
# heads are those that are not an ancestor of any other candidate
240
227
# - this provides convergence at a per-file level.
241
def get_ancestors(weave, entry):
242
return set(weave.get_ancestry(entry.revision, topo_sorted=False))
243
# revision: ancestor list for each head
245
228
for ie in candidates.values():
246
229
# may be an ancestor of a known head:
247
230
already_present = 0 != len(
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)
436
462
def __eq__(self, other):
437
463
if not isinstance(other, InventoryEntry):
438
464
return NotImplemented
571
601
if t in checker.checked_texts:
572
602
prev_sha = checker.checked_texts[t]
573
603
if prev_sha != self.text_sha1:
575
'mismatched sha1 on {%s} in {%s} (%s != %s) %r' %
576
(self.file_id, tree_revision_id, prev_sha, self.text_sha1,
604
raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
605
(self.file_id, tree_revision_id))
579
607
checker.repeated_text_cnt += 1
582
610
if self.file_id not in checker.checked_weaves:
583
611
mutter('check weave {%s}', self.file_id)
584
w = tree._get_weave(self.file_id)
612
w = tree.get_weave(self.file_id)
585
613
# Not passing a progress bar, because it creates a new
586
614
# progress, which overwrites the current progress,
587
615
# and doesn't look nice
589
617
checker.checked_weaves[self.file_id] = True
591
w = tree._get_weave(self.file_id)
619
w = tree.get_weave(self.file_id)
593
621
mutter('check version {%s} of {%s}', tree_revision_id, self.file_id)
594
622
checker.checked_text_cnt += 1
620
648
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
621
649
output_to, reverse=False):
622
650
"""See InventoryEntry._diff."""
623
from bzrlib.diff import DiffText
624
from_file_id = self.file_id
626
to_file_id = to_entry.file_id
630
to_file_id, from_file_id = from_file_id, to_file_id
631
tree, to_tree = to_tree, tree
632
from_label, to_label = to_label, from_label
633
differ = DiffText(tree, to_tree, output_to, 'utf-8', '', '',
635
return differ.diff_text(from_file_id, to_file_id, from_label, to_label)
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
637
670
def has_text(self):
638
671
"""See InventoryEntry.has_text."""
682
715
def _forget_tree_state(self):
683
716
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)
685
725
def _unchanged(self, previous_ie):
686
726
"""See InventoryEntry._unchanged."""
687
727
compatible = super(InventoryFile, self)._unchanged(previous_ie)
730
770
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
731
771
output_to, reverse=False):
732
772
"""See InventoryEntry._diff."""
733
from bzrlib.diff import DiffSymlink
734
old_target = self.symlink_target
773
from_text = self.symlink_target
735
774
if to_entry is not None:
736
new_target = to_entry.symlink_target
745
new_target, old_target = old_target, new_target
746
differ = DiffSymlink(old_tree, new_tree, output_to)
747
return differ.diff_symlink(old_target, new_target)
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
749
787
def __init__(self, file_id, name, parent_id):
750
788
super(InventoryLink, self).__init__(file_id, name, parent_id)
876
915
self.revision_id = revision_id
879
return "<Inventory object at %x, contents=%r>" % (id(self), self._byid)
881
def apply_delta(self, delta):
882
"""Apply a delta to this inventory.
884
:param delta: A list of changes to apply. After all the changes are
885
applied the final inventory must be internally consistent, but it
886
is ok to supply changes which, if only half-applied would have an
887
invalid result - such as supplying two changes which rename two
888
files, 'A' and 'B' with each other : [('A', 'B', 'A-id', a_entry),
889
('B', 'A', 'B-id', b_entry)].
891
Each change is a tuple, of the form (old_path, new_path, file_id,
894
When new_path is None, the change indicates the removal of an entry
895
from the inventory and new_entry will be ignored (using None is
896
appropriate). If new_path is not None, then new_entry must be an
897
InventoryEntry instance, which will be incorporated into the
898
inventory (and replace any existing entry with the same file id).
900
When old_path is None, the change indicates the addition of
901
a new entry to the inventory.
903
When neither new_path nor old_path are None, the change is a
904
modification to an entry, such as a rename, reparent, kind change
907
The children attribute of new_entry is ignored. This is because
908
this method preserves children automatically across alterations to
909
the parent of the children, and cases where the parent id of a
910
child is changing require the child to be passed in as a separate
911
change regardless. E.g. in the recursive deletion of a directory -
912
the directory's children must be included in the delta, or the
913
final inventory will be invalid.
916
# Remove all affected items which were in the original inventory,
917
# starting with the longest paths, thus ensuring parents are examined
918
# after their children, which means that everything we examine has no
919
# modified children remaining by the time we examine it.
920
for old_path, file_id in sorted(((op, f) for op, np, f, e in delta
921
if op is not None), reverse=True):
922
if file_id not in self:
925
# Preserve unaltered children of file_id for later reinsertion.
926
children[file_id] = getattr(self[file_id], 'children', {})
927
# Remove file_id and the unaltered children. If file_id is not
928
# being deleted it will be reinserted back later.
929
self.remove_recursive_id(file_id)
930
# Insert all affected which should be in the new inventory, reattaching
931
# their children if they had any. This is done from shortest path to
932
# longest, ensuring that items which were modified and whose parents in
933
# the resulting inventory were also modified, are inserted after their
935
for new_path, new_entry in sorted((np, e) for op, np, f, e in
936
delta if np is not None):
937
if new_entry.kind == 'directory':
938
new_entry.children = children.get(new_entry.file_id, {})
941
917
def _set_root(self, ie):
943
919
self._byid = {self.root.file_id: self.root}
1014
987
lexicographically sorted order, and is a hybrid between
1015
988
depth-first and breadth-first.
1017
:param yield_parents: If True, yield the parents from the root leading
1018
down to specific_file_ids that have been requested. This has no
1019
impact if specific_file_ids is None.
1020
990
:return: This yields (path, entry) pairs
1022
if specific_file_ids and not isinstance(specific_file_ids, set):
1023
specific_file_ids = set(specific_file_ids)
992
if specific_file_ids:
993
safe = osutils.safe_file_id
994
specific_file_ids = set(safe(fid) for fid in specific_file_ids)
1024
995
# TODO? Perhaps this should return the from_dir so that the root is
1025
996
# yielded? or maybe an option?
1026
997
if from_dir is None:
1027
998
if self.root is None:
1029
1000
# Optimize a common case
1030
if (not yield_parents and specific_file_ids is not None and
1031
len(specific_file_ids) == 1):
1001
if specific_file_ids is not None and len(specific_file_ids) == 1:
1032
1002
file_id = list(specific_file_ids)[0]
1033
1003
if file_id in self:
1034
1004
yield self.id2path(file_id), self[file_id]
1036
1006
from_dir = self.root
1037
if (specific_file_ids is None or yield_parents or
1007
if (specific_file_ids is None or
1038
1008
self.root.file_id in specific_file_ids):
1039
1009
yield u'', self.root
1040
1010
elif isinstance(from_dir, basestring):
1410
1388
if file_id is None:
1411
1389
file_id = generate_ids.gen_file_id(name)
1412
name = ensure_normalized_name(name)
1414
factory = entry_factory[kind]
1416
raise BzrError("unknown kind %r" % kind)
1417
return factory(file_id, name, parent_id)
1420
def ensure_normalized_name(name):
1423
:raises InvalidNormalization: When name is not normalized, and cannot be
1424
accessed on this platform by the normalized path.
1425
:return: The NFC normalised version of name.
1391
file_id = osutils.safe_file_id(file_id)
1427
1393
#------- This has been copied to bzrlib.dirstate.DirState.add, please
1428
1394
# keep them synchronised.
1429
1395
# we dont import normalized_filename directly because we want to be