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, topo_sorted=False))
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
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, \
669
("Binary files %s and %s differ" % label_pair).encode('utf8')
637
671
def has_text(self):
638
672
"""See InventoryEntry.has_text."""
682
716
def _forget_tree_state(self):
683
717
self.text_sha1 = None
719
def _snapshot_text(self, file_parents, work_tree, commit_builder):
720
"""See InventoryEntry._snapshot_text."""
721
def get_content_byte_lines():
722
return work_tree.get_file(self.file_id).readlines()
723
self.text_sha1, self.text_size = commit_builder.modified_file_text(
724
self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
685
726
def _unchanged(self, previous_ie):
686
727
"""See InventoryEntry._unchanged."""
687
728
compatible = super(InventoryFile, self)._unchanged(previous_ie)
730
771
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
731
772
output_to, reverse=False):
732
773
"""See InventoryEntry._diff."""
733
from bzrlib.diff import DiffSymlink
734
old_target = self.symlink_target
774
from_text = self.symlink_target
735
775
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)
776
to_text = to_entry.symlink_target
781
print >>output_to, '=== target changed %r => %r' % (from_text, to_text)
784
print >>output_to, '=== target was %r' % self.symlink_target
786
print >>output_to, '=== target is %r' % self.symlink_target
749
788
def __init__(self, file_id, name, parent_id):
750
789
super(InventoryLink, self).__init__(file_id, name, parent_id)
876
916
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
918
def _set_root(self, ie):
943
920
self._byid = {self.root.file_id: self.root}
1014
988
lexicographically sorted order, and is a hybrid between
1015
989
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
991
: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)
993
if specific_file_ids:
994
safe = osutils.safe_file_id
995
specific_file_ids = set(safe(fid) for fid in specific_file_ids)
1024
996
# TODO? Perhaps this should return the from_dir so that the root is
1025
997
# yielded? or maybe an option?
1026
998
if from_dir is None:
1027
999
if self.root is None:
1029
1001
# Optimize a common case
1030
if (not yield_parents and specific_file_ids is not None and
1031
len(specific_file_ids) == 1):
1002
if specific_file_ids is not None and len(specific_file_ids) == 1:
1032
1003
file_id = list(specific_file_ids)[0]
1033
1004
if file_id in self:
1034
1005
yield self.id2path(file_id), self[file_id]
1036
1007
from_dir = self.root
1037
if (specific_file_ids is None or yield_parents or
1008
if (specific_file_ids is None or
1038
1009
self.root.file_id in specific_file_ids):
1039
1010
yield u'', self.root
1040
1011
elif isinstance(from_dir, basestring):
1410
1393
if file_id is None:
1411
1394
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.
1396
file_id = osutils.safe_file_id(file_id)
1427
1398
#------- This has been copied to bzrlib.dirstate.DirState.add, please
1428
1399
# keep them synchronised.
1429
1400
# we dont import normalized_filename directly because we want to be