118
127
class _TransformResults(object):
120
128
def __init__(self, modified_paths, rename_count):
121
129
object.__init__(self)
122
130
self.modified_paths = modified_paths
123
131
self.rename_count = rename_count
126
class TreeTransform(object):
127
"""Represent a tree transformation.
129
This object is designed to support incremental generation of the transform,
132
However, it gives optimum performance when parent directories are created
133
before their contents. The transform is then able to put child files
134
directly in their parent directory, avoiding later renames.
136
It is easy to produce malformed transforms, but they are generally
137
harmless. Attempting to apply a malformed transform will cause an
138
exception to be raised before any modifications are made to the tree.
140
Many kinds of malformed transforms can be corrected with the
141
resolve_conflicts function. The remaining ones indicate programming error,
142
such as trying to create a file with no path.
144
Two sets of file creation methods are supplied. Convenience methods are:
149
These are composed of the low-level methods:
151
* create_file or create_directory or create_symlink
155
Transform/Transaction ids
156
-------------------------
157
trans_ids are temporary ids assigned to all files involved in a transform.
158
It's possible, even common, that not all files in the Tree have trans_ids.
160
trans_ids are only valid for the TreeTransform that generated them.
163
def __init__(self, tree, pb=None):
134
class TreeTransformBase(object):
135
"""The base class for TreeTransform and its kin."""
137
def __init__(self, tree, pb=None, case_sensitive=True):
140
:param tree: The tree that will be transformed, but not necessarily
143
:param case_sensitive: If True, the target of the transform is
144
case sensitive, not just case preserving.
146
object.__init__(self)
164
147
self._tree = tree
167
148
self._id_number = 0
168
# Mapping of path in old tree -> trans_id
169
self._tree_path_ids = {}
170
# Mapping trans_id -> path in old tree
171
self._tree_id_paths = {}
172
149
# mapping of trans_id -> new basename
173
150
self._new_name = {}
174
151
# mapping of trans_id -> new parent trans_id
175
152
self._new_parent = {}
176
153
# mapping of trans_id with new contents -> new file_kind
177
154
self._new_contents = {}
155
# mapping of trans_id => (sha1 of content, stat_value)
156
self._observed_sha1s = {}
178
157
# Set of trans_ids whose contents will be removed
179
158
self._removed_contents = set()
180
159
# Mapping of trans_id -> new execute-bit value
181
160
self._new_executability = {}
182
161
# Mapping of trans_id -> new tree-reference value
183
162
self._new_reference_revision = {}
163
# Mapping of trans_id -> new file_id
165
# Mapping of old file-id -> trans_id
166
self._non_present_ids = {}
167
# Mapping of new file_id -> trans_id
184
169
# Set of trans_ids that will be removed
185
170
self._removed_id = set()
171
# Mapping of path in old tree -> trans_id
172
self._tree_path_ids = {}
173
# Mapping trans_id -> path in old tree
174
self._tree_id_paths = {}
175
# The trans_id that will be used as the tree root
176
if tree.is_versioned(''):
177
self._new_root = self.trans_id_tree_path('')
179
self._new_root = None
186
180
# Indicator of whether the transform has been applied
187
181
self._done = False
184
# Whether the target is case sensitive
185
self._case_sensitive_target = case_sensitive
186
# A counter of how many files have been renamed
187
self.rename_count = 0
189
189
def __enter__(self):
190
190
"""Support Context Manager API."""
268
293
This means that the old root trans-id becomes obsolete, so it is
269
294
recommended only to invoke this after the root trans-id has become
272
raise NotImplementedError(self.fixup_new_roots)
275
"""Produce a new tranform id"""
276
new_id = "new-%s" % self._id_number
298
new_roots = [k for k, v in viewitems(self._new_parent)
300
if len(new_roots) < 1:
302
if len(new_roots) != 1:
303
raise ValueError('A tree cannot have two roots!')
304
if self._new_root is None:
305
self._new_root = new_roots[0]
307
old_new_root = new_roots[0]
308
# unversion the new root's directory.
309
if self.final_kind(self._new_root) is None:
310
file_id = self.final_file_id(old_new_root)
312
file_id = self.final_file_id(self._new_root)
313
if old_new_root in self._new_id:
314
self.cancel_versioning(old_new_root)
316
self.unversion_file(old_new_root)
317
# if, at this stage, root still has an old file_id, zap it so we can
318
# stick a new one in.
319
if (self.tree_file_id(self._new_root) is not None
320
and self._new_root not in self._removed_id):
321
self.unversion_file(self._new_root)
322
if file_id is not None:
323
self.version_file(self._new_root, file_id=file_id)
325
# Now move children of new root into old root directory.
326
# Ensure all children are registered with the transaction, but don't
327
# use directly-- some tree children have new parents
328
list(self.iter_tree_children(old_new_root))
329
# Move all children of new root into old root directory.
330
for child in self.by_parent().get(old_new_root, []):
331
self.adjust_path(self.final_name(child), self._new_root, child)
333
# Ensure old_new_root has no directory.
334
if old_new_root in self._new_contents:
335
self.cancel_creation(old_new_root)
337
self.delete_contents(old_new_root)
339
# prevent deletion of root directory.
340
if self._new_root in self._removed_contents:
341
self.cancel_deletion(self._new_root)
343
# destroy path info for old_new_root.
344
del self._new_parent[old_new_root]
345
del self._new_name[old_new_root]
347
def trans_id_file_id(self, file_id):
348
"""Determine or set the transaction id associated with a file ID.
349
A new id is only created for file_ids that were never present. If
350
a transaction has been unversioned, it is deliberately still returned.
351
(this will likely lead to an unversioned parent conflict.)
354
raise ValueError('None is not a valid file id')
355
if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
356
return self._r_new_id[file_id]
359
path = self._tree.id2path(file_id)
360
except errors.NoSuchId:
361
if file_id in self._non_present_ids:
362
return self._non_present_ids[file_id]
364
trans_id = self._assign_id()
365
self._non_present_ids[file_id] = trans_id
368
return self.trans_id_tree_path(path)
280
370
def trans_id_tree_path(self, path):
281
371
"""Determine (and maybe set) the transaction ID for a tree path."""
282
372
path = self.canonical_path(path)
283
373
if path not in self._tree_path_ids:
284
self._tree_path_ids[path] = self.assign_id()
374
self._tree_path_ids[path] = self._assign_id()
285
375
self._tree_id_paths[self._tree_path_ids[path]] = path
286
376
return self._tree_path_ids[path]
390
538
def new_contents(self, trans_id):
391
539
return (trans_id in self._new_contents)
393
def find_raw_conflicts(self):
541
def find_conflicts(self):
394
542
"""Find any violations of inventory or filesystem invariants"""
395
raise NotImplementedError(self.find_raw_conflicts)
543
if self._done is True:
544
raise ReusingTransform()
546
# ensure all children of all existent parents are known
547
# all children of non-existent parents are known, by definition.
548
self._add_tree_children()
549
by_parent = self.by_parent()
550
conflicts.extend(self._unversioned_parents(by_parent))
551
conflicts.extend(self._parent_loops())
552
conflicts.extend(self._duplicate_entries(by_parent))
553
conflicts.extend(self._parent_type_conflicts(by_parent))
554
conflicts.extend(self._improper_versioning())
555
conflicts.extend(self._executability_conflicts())
556
conflicts.extend(self._overwrite_conflicts())
559
def _check_malformed(self):
560
conflicts = self.find_conflicts()
561
if len(conflicts) != 0:
562
raise MalformedTransform(conflicts=conflicts)
564
def _add_tree_children(self):
565
"""Add all the children of all active parents to the known paths.
567
Active parents are those which gain children, and those which are
568
removed. This is a necessary first step in detecting conflicts.
570
parents = list(self.by_parent())
571
parents.extend([t for t in self._removed_contents if
572
self.tree_kind(t) == 'directory'])
573
for trans_id in self._removed_id:
574
path = self.tree_path(trans_id)
576
if self._tree.stored_kind(path) == 'directory':
577
parents.append(trans_id)
578
elif self.tree_kind(trans_id) == 'directory':
579
parents.append(trans_id)
581
for parent_id in parents:
582
# ensure that all children are registered with the transaction
583
list(self.iter_tree_children(parent_id))
585
def _has_named_child(self, name, parent_id, known_children):
586
"""Does a parent already have a name child.
588
:param name: The searched for name.
590
:param parent_id: The parent for which the check is made.
592
:param known_children: The already known children. This should have
593
been recently obtained from `self.by_parent.get(parent_id)`
594
(or will be if None is passed).
596
if known_children is None:
597
known_children = self.by_parent().get(parent_id, [])
598
for child in known_children:
599
if self.final_name(child) == name:
601
parent_path = self._tree_id_paths.get(parent_id, None)
602
if parent_path is None:
603
# No parent... no children
605
child_path = joinpath(parent_path, name)
606
child_id = self._tree_path_ids.get(child_path, None)
608
# Not known by the tree transform yet, check the filesystem
609
return osutils.lexists(self._tree.abspath(child_path))
611
raise AssertionError('child_id is missing: %s, %s, %s'
612
% (name, parent_id, child_id))
614
def _available_backup_name(self, name, target_id):
615
"""Find an available backup name.
617
:param name: The basename of the file.
619
:param target_id: The directory trans_id where the backup should
622
known_children = self.by_parent().get(target_id, [])
623
return osutils.available_backup_name(
625
lambda base: self._has_named_child(
626
base, target_id, known_children))
628
def _parent_loops(self):
629
"""No entry should be its own ancestor"""
631
for trans_id in self._new_parent:
634
while parent_id != ROOT_PARENT:
637
parent_id = self.final_parent(parent_id)
640
if parent_id == trans_id:
641
conflicts.append(('parent loop', trans_id))
642
if parent_id in seen:
646
def _unversioned_parents(self, by_parent):
647
"""If parent directories are versioned, children must be versioned."""
649
for parent_id, children in viewitems(by_parent):
650
if parent_id == ROOT_PARENT:
652
if self.final_file_id(parent_id) is not None:
654
for child_id in children:
655
if self.final_file_id(child_id) is not None:
656
conflicts.append(('unversioned parent', parent_id))
660
def _improper_versioning(self):
661
"""Cannot version a file with no contents, or a bad type.
663
However, existing entries with no contents are okay.
666
for trans_id in self._new_id:
667
kind = self.final_kind(trans_id)
668
if kind == 'symlink' and not self._tree.supports_symlinks():
669
# Ignore symlinks as they are not supported on this platform
672
conflicts.append(('versioning no contents', trans_id))
674
if not self._tree.versionable_kind(kind):
675
conflicts.append(('versioning bad kind', trans_id, kind))
678
def _executability_conflicts(self):
679
"""Check for bad executability changes.
681
Only versioned files may have their executability set, because
682
1. only versioned entries can have executability under windows
683
2. only files can be executable. (The execute bit on a directory
684
does not indicate searchability)
687
for trans_id in self._new_executability:
688
if self.final_file_id(trans_id) is None:
689
conflicts.append(('unversioned executability', trans_id))
691
if self.final_kind(trans_id) != "file":
692
conflicts.append(('non-file executability', trans_id))
695
def _overwrite_conflicts(self):
696
"""Check for overwrites (not permitted on Win32)"""
698
for trans_id in self._new_contents:
699
if self.tree_kind(trans_id) is None:
701
if trans_id not in self._removed_contents:
702
conflicts.append(('overwrite', trans_id,
703
self.final_name(trans_id)))
706
def _duplicate_entries(self, by_parent):
707
"""No directory may have two entries with the same name."""
709
if (self._new_name, self._new_parent) == ({}, {}):
711
for children in viewvalues(by_parent):
713
for child_tid in children:
714
name = self.final_name(child_tid)
716
# Keep children only if they still exist in the end
717
if not self._case_sensitive_target:
719
name_ids.append((name, child_tid))
723
for name, trans_id in name_ids:
724
kind = self.final_kind(trans_id)
725
file_id = self.final_file_id(trans_id)
726
if kind is None and file_id is None:
728
if name == last_name:
729
conflicts.append(('duplicate', last_trans_id, trans_id,
732
last_trans_id = trans_id
735
def _parent_type_conflicts(self, by_parent):
736
"""Children must have a directory parent"""
738
for parent_id, children in viewitems(by_parent):
739
if parent_id == ROOT_PARENT:
742
for child_id in children:
743
if self.final_kind(child_id) is not None:
748
# There is at least a child, so we need an existing directory to
750
kind = self.final_kind(parent_id)
752
# The directory will be deleted
753
conflicts.append(('missing parent', parent_id))
754
elif kind != "directory":
755
# Meh, we need a *directory* to put something in it
756
conflicts.append(('non-directory parent', parent_id))
759
def _set_executability(self, path, trans_id):
760
"""Set the executability of versioned files """
761
if self._tree._supports_executable():
762
new_executability = self._new_executability[trans_id]
763
abspath = self._tree.abspath(path)
764
current_mode = os.stat(abspath).st_mode
765
if new_executability:
768
to_mode = current_mode | (0o100 & ~umask)
769
# Enable x-bit for others only if they can read it.
770
if current_mode & 0o004:
771
to_mode |= 0o001 & ~umask
772
if current_mode & 0o040:
773
to_mode |= 0o010 & ~umask
775
to_mode = current_mode & ~0o111
776
osutils.chmod_if_possible(abspath, to_mode)
778
def _new_entry(self, name, parent_id, file_id):
779
"""Helper function to create a new filesystem entry."""
780
trans_id = self.create_path(name, parent_id)
781
if file_id is not None:
782
self.version_file(trans_id, file_id=file_id)
397
785
def new_file(self, name, parent_id, contents, file_id=None,
398
786
executable=None, sha1=None):
439
837
raise NotImplementedError(self.new_orphan)
839
def _get_potential_orphans(self, dir_id):
840
"""Find the potential orphans in a directory.
842
A directory can't be safely deleted if there are versioned files in it.
843
If all the contained files are unversioned then they can be orphaned.
845
The 'None' return value means that the directory contains at least one
846
versioned file and should not be deleted.
848
:param dir_id: The directory trans id.
850
:return: A list of the orphan trans ids or None if at least one
851
versioned file is present.
854
# Find the potential orphans, stop if one item should be kept
855
for child_tid in self.by_parent()[dir_id]:
856
if child_tid in self._removed_contents:
857
# The child is removed as part of the transform. Since it was
858
# versioned before, it's not an orphan
860
if self.final_file_id(child_tid) is None:
861
# The child is not versioned
862
orphans.append(child_tid)
864
# We have a versioned file here, searching for orphans is
870
def _affected_ids(self):
871
"""Return the set of transform ids affected by the transform"""
872
trans_ids = set(self._removed_id)
873
trans_ids.update(self._new_id)
874
trans_ids.update(self._removed_contents)
875
trans_ids.update(self._new_contents)
876
trans_ids.update(self._new_executability)
877
trans_ids.update(self._new_name)
878
trans_ids.update(self._new_parent)
881
def _get_file_id_maps(self):
882
"""Return mapping of file_ids to trans_ids in the to and from states"""
883
trans_ids = self._affected_ids()
886
# Build up two dicts: trans_ids associated with file ids in the
887
# FROM state, vs the TO state.
888
for trans_id in trans_ids:
889
from_file_id = self.tree_file_id(trans_id)
890
if from_file_id is not None:
891
from_trans_ids[from_file_id] = trans_id
892
to_file_id = self.final_file_id(trans_id)
893
if to_file_id is not None:
894
to_trans_ids[to_file_id] = trans_id
895
return from_trans_ids, to_trans_ids
897
def _from_file_data(self, from_trans_id, from_versioned, from_path):
898
"""Get data about a file in the from (tree) state
900
Return a (name, parent, kind, executable) tuple
902
from_path = self._tree_id_paths.get(from_trans_id)
904
# get data from working tree if versioned
905
from_entry = next(self._tree.iter_entries_by_dir(
906
specific_files=[from_path]))[1]
907
from_name = from_entry.name
908
from_parent = from_entry.parent_id
911
if from_path is None:
912
# File does not exist in FROM state
916
# File exists, but is not versioned. Have to use path-
918
from_name = os.path.basename(from_path)
919
tree_parent = self.get_tree_parent(from_trans_id)
920
from_parent = self.tree_file_id(tree_parent)
921
if from_path is not None:
922
from_kind, from_executable, from_stats = \
923
self._tree._comparison_data(from_entry, from_path)
926
from_executable = False
927
return from_name, from_parent, from_kind, from_executable
929
def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
930
"""Get data about a file in the to (target) state
932
Return a (name, parent, kind, executable) tuple
934
to_name = self.final_name(to_trans_id)
935
to_kind = self.final_kind(to_trans_id)
936
to_parent = self.final_file_id(self.final_parent(to_trans_id))
937
if to_trans_id in self._new_executability:
938
to_executable = self._new_executability[to_trans_id]
939
elif to_trans_id == from_trans_id:
940
to_executable = from_executable
942
to_executable = False
943
return to_name, to_parent, to_kind, to_executable
441
945
def iter_changes(self):
442
946
"""Produce output in the same format as Tree.iter_changes.
444
948
Will produce nonsensical results if invoked while inventory/filesystem
445
conflicts (as reported by TreeTransform.find_raw_conflicts()) are present.
949
conflicts (as reported by TreeTransform.find_conflicts()) are present.
447
951
This reads the Transform, but only reproduces changes involving a
448
952
file_id. Files that are not versioned in either of the FROM or TO
449
953
states are not reflected.
451
raise NotImplementedError(self.iter_changes)
955
final_paths = FinalPaths(self)
956
from_trans_ids, to_trans_ids = self._get_file_id_maps()
958
# Now iterate through all active file_ids
959
for file_id in set(from_trans_ids).union(to_trans_ids):
961
from_trans_id = from_trans_ids.get(file_id)
962
# find file ids, and determine versioning state
963
if from_trans_id is None:
964
from_versioned = False
965
from_trans_id = to_trans_ids[file_id]
967
from_versioned = True
968
to_trans_id = to_trans_ids.get(file_id)
969
if to_trans_id is None:
971
to_trans_id = from_trans_id
975
if not from_versioned:
978
from_path = self._tree_id_paths.get(from_trans_id)
982
to_path = final_paths.get_path(to_trans_id)
984
from_name, from_parent, from_kind, from_executable = \
985
self._from_file_data(from_trans_id, from_versioned, from_path)
987
to_name, to_parent, to_kind, to_executable = \
988
self._to_file_data(to_trans_id, from_trans_id, from_executable)
990
if from_kind != to_kind:
992
elif to_kind in ('file', 'symlink') and (
993
to_trans_id != from_trans_id
994
or to_trans_id in self._new_contents):
996
if (not modified and from_versioned == to_versioned
997
and from_parent == to_parent and from_name == to_name
998
and from_executable == to_executable):
1002
file_id, (from_path, to_path), modified,
1003
(from_versioned, to_versioned),
1004
(from_parent, to_parent),
1005
(from_name, to_name),
1006
(from_kind, to_kind),
1007
(from_executable, to_executable)))
1010
return (c.path[0] or '', c.path[1] or '')
1011
return iter(sorted(results, key=path_key))
453
1013
def get_preview_tree(self):
454
1014
"""Return a tree representing the result of the transform.
481
1041
may reduce performance for some non-native formats.)
482
1042
:return: The revision_id of the revision committed.
484
raise NotImplementedError(self.commit)
1044
self._check_malformed()
1046
unversioned = set(self._new_contents).difference(set(self._new_id))
1047
for trans_id in unversioned:
1048
if self.final_file_id(trans_id) is None:
1049
raise errors.StrictCommitFailed()
1051
revno, last_rev_id = branch.last_revision_info()
1052
if last_rev_id == _mod_revision.NULL_REVISION:
1053
if merge_parents is not None:
1054
raise ValueError('Cannot supply merge parents for first'
1058
parent_ids = [last_rev_id]
1059
if merge_parents is not None:
1060
parent_ids.extend(merge_parents)
1061
if self._tree.get_revision_id() != last_rev_id:
1062
raise ValueError('TreeTransform not based on branch basis: %s' %
1063
self._tree.get_revision_id().decode('utf-8'))
1064
from . import commit
1065
revprops = commit.Commit.update_revprops(revprops, branch, authors)
1066
builder = branch.get_commit_builder(parent_ids,
1067
timestamp=timestamp,
1069
committer=committer,
1071
revision_id=revision_id)
1072
preview = self.get_preview_tree()
1073
list(builder.record_iter_changes(preview, last_rev_id,
1074
self.iter_changes()))
1075
builder.finish_inventory()
1076
revision_id = builder.commit(message)
1077
branch.set_last_revision_info(revno + 1, revision_id)
1080
def _text_parent(self, trans_id):
1081
path = self.tree_path(trans_id)
1083
if path is None or self._tree.kind(path) != 'file':
1085
except errors.NoSuchFile:
1089
def _get_parents_texts(self, trans_id):
1090
"""Get texts for compression parents of this file."""
1091
path = self._text_parent(trans_id)
1094
return (self._tree.get_file_text(path),)
1096
def _get_parents_lines(self, trans_id):
1097
"""Get lines for compression parents of this file."""
1098
path = self._text_parent(trans_id)
1101
return (self._tree.get_file_lines(path),)
1103
def serialize(self, serializer):
1104
"""Serialize this TreeTransform.
1106
:param serializer: A Serialiser like pack.ContainerSerializer.
1108
from . import bencode
1109
new_name = {k.encode('utf-8'): v.encode('utf-8')
1110
for k, v in viewitems(self._new_name)}
1111
new_parent = {k.encode('utf-8'): v.encode('utf-8')
1112
for k, v in viewitems(self._new_parent)}
1113
new_id = {k.encode('utf-8'): v
1114
for k, v in viewitems(self._new_id)}
1115
new_executability = {k.encode('utf-8'): int(v)
1116
for k, v in viewitems(self._new_executability)}
1117
tree_path_ids = {k.encode('utf-8'): v.encode('utf-8')
1118
for k, v in viewitems(self._tree_path_ids)}
1119
non_present_ids = {k: v.encode('utf-8')
1120
for k, v in viewitems(self._non_present_ids)}
1121
removed_contents = [trans_id.encode('utf-8')
1122
for trans_id in self._removed_contents]
1123
removed_id = [trans_id.encode('utf-8')
1124
for trans_id in self._removed_id]
1126
b'_id_number': self._id_number,
1127
b'_new_name': new_name,
1128
b'_new_parent': new_parent,
1129
b'_new_executability': new_executability,
1131
b'_tree_path_ids': tree_path_ids,
1132
b'_removed_id': removed_id,
1133
b'_removed_contents': removed_contents,
1134
b'_non_present_ids': non_present_ids,
1136
yield serializer.bytes_record(bencode.bencode(attribs),
1138
for trans_id, kind in sorted(viewitems(self._new_contents)):
1140
with open(self._limbo_name(trans_id), 'rb') as cur_file:
1141
lines = cur_file.readlines()
1142
parents = self._get_parents_lines(trans_id)
1143
mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1144
content = b''.join(mpdiff.to_patch())
1145
if kind == 'directory':
1147
if kind == 'symlink':
1148
content = self._read_symlink_target(trans_id)
1149
if not isinstance(content, bytes):
1150
content = content.encode('utf-8')
1151
yield serializer.bytes_record(
1152
content, ((trans_id.encode('utf-8'), kind.encode('ascii')),))
1154
def deserialize(self, records):
1155
"""Deserialize a stored TreeTransform.
1157
:param records: An iterable of (names, content) tuples, as per
1158
pack.ContainerPushParser.
1160
from . import bencode
1161
names, content = next(records)
1162
attribs = bencode.bdecode(content)
1163
self._id_number = attribs[b'_id_number']
1164
self._new_name = {k.decode('utf-8'): v.decode('utf-8')
1165
for k, v in viewitems(attribs[b'_new_name'])}
1166
self._new_parent = {k.decode('utf-8'): v.decode('utf-8')
1167
for k, v in viewitems(attribs[b'_new_parent'])}
1168
self._new_executability = {
1169
k.decode('utf-8'): bool(v)
1170
for k, v in viewitems(attribs[b'_new_executability'])}
1171
self._new_id = {k.decode('utf-8'): v
1172
for k, v in viewitems(attribs[b'_new_id'])}
1173
self._r_new_id = {v: k for k, v in viewitems(self._new_id)}
1174
self._tree_path_ids = {}
1175
self._tree_id_paths = {}
1176
for bytepath, trans_id in viewitems(attribs[b'_tree_path_ids']):
1177
path = bytepath.decode('utf-8')
1178
trans_id = trans_id.decode('utf-8')
1179
self._tree_path_ids[path] = trans_id
1180
self._tree_id_paths[trans_id] = path
1181
self._removed_id = {trans_id.decode('utf-8')
1182
for trans_id in attribs[b'_removed_id']}
1183
self._removed_contents = set(
1184
trans_id.decode('utf-8')
1185
for trans_id in attribs[b'_removed_contents'])
1186
self._non_present_ids = {
1187
k: v.decode('utf-8')
1188
for k, v in viewitems(attribs[b'_non_present_ids'])}
1189
for ((trans_id, kind),), content in records:
1190
trans_id = trans_id.decode('utf-8')
1191
kind = kind.decode('ascii')
1193
mpdiff = multiparent.MultiParent.from_patch(content)
1194
lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1195
self.create_file(lines, trans_id)
1196
if kind == 'directory':
1197
self.create_directory(trans_id)
1198
if kind == 'symlink':
1199
self.create_symlink(content.decode('utf-8'), trans_id)
486
1201
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
487
1202
"""Schedule creation of a new file.
522
1237
"""Cancel the creation of new file contents."""
523
1238
raise NotImplementedError(self.cancel_creation)
525
def cook_conflicts(self, raw_conflicts):
528
raise NotImplementedError(self.cook_conflicts)
1241
class DiskTreeTransform(TreeTransformBase):
1242
"""Tree transform storing its contents on disk."""
1244
def __init__(self, tree, limbodir, pb=None, case_sensitive=True):
1246
:param tree: The tree that will be transformed, but not necessarily
1248
:param limbodir: A directory where new files can be stored until
1249
they are installed in their proper places
1251
:param case_sensitive: If True, the target of the transform is
1252
case sensitive, not just case preserving.
1254
TreeTransformBase.__init__(self, tree, pb, case_sensitive)
1255
self._limbodir = limbodir
1256
self._deletiondir = None
1257
# A mapping of transform ids to their limbo filename
1258
self._limbo_files = {}
1259
self._possibly_stale_limbo_files = set()
1260
# A mapping of transform ids to a set of the transform ids of children
1261
# that their limbo directory has
1262
self._limbo_children = {}
1263
# Map transform ids to maps of child filename to child transform id
1264
self._limbo_children_names = {}
1265
# List of transform ids that need to be renamed from limbo into place
1266
self._needs_rename = set()
1267
self._creation_mtime = None
1268
self._create_symlinks = osutils.supports_symlinks(self._limbodir)
1271
"""Release the working tree lock, if held, clean up limbo dir.
1273
This is required if apply has not been invoked, but can be invoked
1276
if self._tree is None:
1279
limbo_paths = list(viewvalues(self._limbo_files))
1280
limbo_paths.extend(self._possibly_stale_limbo_files)
1281
limbo_paths.sort(reverse=True)
1282
for path in limbo_paths:
1285
except OSError as e:
1286
if e.errno != errno.ENOENT:
1288
# XXX: warn? perhaps we just got interrupted at an
1289
# inconvenient moment, but perhaps files are disappearing
1292
delete_any(self._limbodir)
1294
# We don't especially care *why* the dir is immortal.
1295
raise ImmortalLimbo(self._limbodir)
1297
if self._deletiondir is not None:
1298
delete_any(self._deletiondir)
1300
raise errors.ImmortalPendingDeletion(self._deletiondir)
1302
TreeTransformBase.finalize(self)
1304
def _limbo_supports_executable(self):
1305
"""Check if the limbo path supports the executable bit."""
1306
return osutils.supports_executable(self._limbodir)
1308
def _limbo_name(self, trans_id):
1309
"""Generate the limbo name of a file"""
1310
limbo_name = self._limbo_files.get(trans_id)
1311
if limbo_name is None:
1312
limbo_name = self._generate_limbo_path(trans_id)
1313
self._limbo_files[trans_id] = limbo_name
1316
def _generate_limbo_path(self, trans_id):
1317
"""Generate a limbo path using the trans_id as the relative path.
1319
This is suitable as a fallback, and when the transform should not be
1320
sensitive to the path encoding of the limbo directory.
1322
self._needs_rename.add(trans_id)
1323
return pathjoin(self._limbodir, trans_id)
1325
def adjust_path(self, name, parent, trans_id):
1326
previous_parent = self._new_parent.get(trans_id)
1327
previous_name = self._new_name.get(trans_id)
1328
TreeTransformBase.adjust_path(self, name, parent, trans_id)
1329
if (trans_id in self._limbo_files
1330
and trans_id not in self._needs_rename):
1331
self._rename_in_limbo([trans_id])
1332
if previous_parent != parent:
1333
self._limbo_children[previous_parent].remove(trans_id)
1334
if previous_parent != parent or previous_name != name:
1335
del self._limbo_children_names[previous_parent][previous_name]
1337
def _rename_in_limbo(self, trans_ids):
1338
"""Fix limbo names so that the right final path is produced.
1340
This means we outsmarted ourselves-- we tried to avoid renaming
1341
these files later by creating them with their final names in their
1342
final parents. But now the previous name or parent is no longer
1343
suitable, so we have to rename them.
1345
Even for trans_ids that have no new contents, we must remove their
1346
entries from _limbo_files, because they are now stale.
1348
for trans_id in trans_ids:
1349
old_path = self._limbo_files[trans_id]
1350
self._possibly_stale_limbo_files.add(old_path)
1351
del self._limbo_files[trans_id]
1352
if trans_id not in self._new_contents:
1354
new_path = self._limbo_name(trans_id)
1355
os.rename(old_path, new_path)
1356
self._possibly_stale_limbo_files.remove(old_path)
1357
for descendant in self._limbo_descendants(trans_id):
1358
desc_path = self._limbo_files[descendant]
1359
desc_path = new_path + desc_path[len(old_path):]
1360
self._limbo_files[descendant] = desc_path
1362
def _limbo_descendants(self, trans_id):
1363
"""Return the set of trans_ids whose limbo paths descend from this."""
1364
descendants = set(self._limbo_children.get(trans_id, []))
1365
for descendant in list(descendants):
1366
descendants.update(self._limbo_descendants(descendant))
1369
def _set_mode(self, trans_id, mode_id, typefunc):
1370
raise NotImplementedError(self._set_mode)
1372
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1373
"""Schedule creation of a new file.
1377
:param contents: an iterator of strings, all of which will be written
1378
to the target destination.
1379
:param trans_id: TreeTransform handle
1380
:param mode_id: If not None, force the mode of the target file to match
1381
the mode of the object referenced by mode_id.
1382
Otherwise, we will try to preserve mode bits of an existing file.
1383
:param sha1: If the sha1 of this content is already known, pass it in.
1384
We can use it to prevent future sha1 computations.
1386
name = self._limbo_name(trans_id)
1387
with open(name, 'wb') as f:
1388
unique_add(self._new_contents, trans_id, 'file')
1389
f.writelines(contents)
1390
self._set_mtime(name)
1391
self._set_mode(trans_id, mode_id, S_ISREG)
1392
# It is unfortunate we have to use lstat instead of fstat, but we just
1393
# used utime and chmod on the file, so we need the accurate final
1395
if sha1 is not None:
1396
self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
1398
def _read_symlink_target(self, trans_id):
1399
return os.readlink(self._limbo_name(trans_id))
1401
def _set_mtime(self, path):
1402
"""All files that are created get the same mtime.
1404
This time is set by the first object to be created.
1406
if self._creation_mtime is None:
1407
self._creation_mtime = time.time()
1408
os.utime(path, (self._creation_mtime, self._creation_mtime))
1410
def create_hardlink(self, path, trans_id):
1411
"""Schedule creation of a hard link"""
1412
name = self._limbo_name(trans_id)
1415
except OSError as e:
1416
if e.errno != errno.EPERM:
1418
raise errors.HardLinkNotSupported(path)
1420
unique_add(self._new_contents, trans_id, 'file')
1421
except BaseException:
1422
# Clean up the file, it never got registered so
1423
# TreeTransform.finalize() won't clean it up.
1427
def create_directory(self, trans_id):
1428
"""Schedule creation of a new directory.
1430
See also new_directory.
1432
os.mkdir(self._limbo_name(trans_id))
1433
unique_add(self._new_contents, trans_id, 'directory')
1435
def create_symlink(self, target, trans_id):
1436
"""Schedule creation of a new symbolic link.
1438
target is a bytestring.
1439
See also new_symlink.
1441
if self._create_symlinks:
1442
os.symlink(target, self._limbo_name(trans_id))
1445
path = FinalPaths(self).get_path(trans_id)
1449
'Unable to create symlink "%s" on this filesystem.' % (path,))
1450
# We add symlink to _new_contents even if they are unsupported
1451
# and not created. These entries are subsequently used to avoid
1452
# conflicts on platforms that don't support symlink
1453
unique_add(self._new_contents, trans_id, 'symlink')
1455
def cancel_creation(self, trans_id):
1456
"""Cancel the creation of new file contents."""
1457
del self._new_contents[trans_id]
1458
if trans_id in self._observed_sha1s:
1459
del self._observed_sha1s[trans_id]
1460
children = self._limbo_children.get(trans_id)
1461
# if this is a limbo directory with children, move them before removing
1463
if children is not None:
1464
self._rename_in_limbo(children)
1465
del self._limbo_children[trans_id]
1466
del self._limbo_children_names[trans_id]
1467
delete_any(self._limbo_name(trans_id))
1469
def new_orphan(self, trans_id, parent_id):
1470
conf = self._tree.get_config_stack()
1471
handle_orphan = conf.get('transform.orphan_policy')
1472
handle_orphan(self, trans_id, parent_id)
531
1475
class OrphaningError(errors.BzrError):
599
1543
invalid='warning')
1546
class TreeTransform(DiskTreeTransform):
1547
"""Represent a tree transformation.
1549
This object is designed to support incremental generation of the transform,
1552
However, it gives optimum performance when parent directories are created
1553
before their contents. The transform is then able to put child files
1554
directly in their parent directory, avoiding later renames.
1556
It is easy to produce malformed transforms, but they are generally
1557
harmless. Attempting to apply a malformed transform will cause an
1558
exception to be raised before any modifications are made to the tree.
1560
Many kinds of malformed transforms can be corrected with the
1561
resolve_conflicts function. The remaining ones indicate programming error,
1562
such as trying to create a file with no path.
1564
Two sets of file creation methods are supplied. Convenience methods are:
1569
These are composed of the low-level methods:
1571
* create_file or create_directory or create_symlink
1575
Transform/Transaction ids
1576
-------------------------
1577
trans_ids are temporary ids assigned to all files involved in a transform.
1578
It's possible, even common, that not all files in the Tree have trans_ids.
1580
trans_ids are used because filenames and file_ids are not good enough
1581
identifiers; filenames change, and not all files have file_ids. File-ids
1582
are also associated with trans-ids, so that moving a file moves its
1585
trans_ids are only valid for the TreeTransform that generated them.
1589
Limbo is a temporary directory use to hold new versions of files.
1590
Files are added to limbo by create_file, create_directory, create_symlink,
1591
and their convenience variants (new_*). Files may be removed from limbo
1592
using cancel_creation. Files are renamed from limbo into their final
1593
location as part of TreeTransform.apply
1595
Limbo must be cleaned up, by either calling TreeTransform.apply or
1596
calling TreeTransform.finalize.
1598
Files are placed into limbo inside their parent directories, where
1599
possible. This reduces subsequent renames, and makes operations involving
1600
lots of files faster. This optimization is only possible if the parent
1601
directory is created *before* creating any of its children, so avoid
1602
creating children before parents, where possible.
1606
This temporary directory is used by _FileMover for storing files that are
1607
about to be deleted. In case of rollback, the files will be restored.
1608
FileMover does not delete files until it is sure that a rollback will not
1612
def __init__(self, tree, pb=None):
1613
"""Note: a tree_write lock is taken on the tree.
1615
Use TreeTransform.finalize() to release the lock (can be omitted if
1616
TreeTransform.apply() called).
1618
tree.lock_tree_write()
1620
limbodir = urlutils.local_path_from_url(
1621
tree._transport.abspath('limbo'))
1622
osutils.ensure_empty_directory_exists(
1624
errors.ExistingLimbo)
1625
deletiondir = urlutils.local_path_from_url(
1626
tree._transport.abspath('pending-deletion'))
1627
osutils.ensure_empty_directory_exists(
1629
errors.ExistingPendingDeletion)
1630
except BaseException:
1634
# Cache of realpath results, to speed up canonical_path
1635
self._realpaths = {}
1636
# Cache of relpath results, to speed up canonical_path
1638
DiskTreeTransform.__init__(self, tree, limbodir, pb,
1639
tree.case_sensitive)
1640
self._deletiondir = deletiondir
1642
def canonical_path(self, path):
1643
"""Get the canonical tree-relative path"""
1644
# don't follow final symlinks
1645
abs = self._tree.abspath(path)
1646
if abs in self._relpaths:
1647
return self._relpaths[abs]
1648
dirname, basename = os.path.split(abs)
1649
if dirname not in self._realpaths:
1650
self._realpaths[dirname] = os.path.realpath(dirname)
1651
dirname = self._realpaths[dirname]
1652
abs = pathjoin(dirname, basename)
1653
if dirname in self._relpaths:
1654
relpath = pathjoin(self._relpaths[dirname], basename)
1655
relpath = relpath.rstrip('/\\')
1657
relpath = self._tree.relpath(abs)
1658
self._relpaths[abs] = relpath
1661
def tree_kind(self, trans_id):
1662
"""Determine the file kind in the working tree.
1664
:returns: The file kind or None if the file does not exist
1666
path = self._tree_id_paths.get(trans_id)
1670
return file_kind(self._tree.abspath(path))
1671
except errors.NoSuchFile:
1674
def _set_mode(self, trans_id, mode_id, typefunc):
1675
"""Set the mode of new file contents.
1676
The mode_id is the existing file to get the mode from (often the same
1677
as trans_id). The operation is only performed if there's a mode match
1678
according to typefunc.
1683
old_path = self._tree_id_paths[mode_id]
1687
mode = os.stat(self._tree.abspath(old_path)).st_mode
1688
except OSError as e:
1689
if e.errno in (errno.ENOENT, errno.ENOTDIR):
1690
# Either old_path doesn't exist, or the parent of the
1691
# target is not a directory (but will be one eventually)
1692
# Either way, we know it doesn't exist *right now*
1693
# See also bug #248448
1698
osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
1700
def iter_tree_children(self, parent_id):
1701
"""Iterate through the entry's tree children, if any"""
1703
path = self._tree_id_paths[parent_id]
1707
children = os.listdir(self._tree.abspath(path))
1708
except OSError as e:
1709
if not (osutils._is_error_enotdir(e) or
1710
e.errno in (errno.ENOENT, errno.ESRCH)):
1714
for child in children:
1715
childpath = joinpath(path, child)
1716
if self._tree.is_control_filename(childpath):
1718
yield self.trans_id_tree_path(childpath)
1720
def _generate_limbo_path(self, trans_id):
1721
"""Generate a limbo path using the final path if possible.
1723
This optimizes the performance of applying the tree transform by
1724
avoiding renames. These renames can be avoided only when the parent
1725
directory is already scheduled for creation.
1727
If the final path cannot be used, falls back to using the trans_id as
1730
parent = self._new_parent.get(trans_id)
1731
# if the parent directory is already in limbo (e.g. when building a
1732
# tree), choose a limbo name inside the parent, to reduce further
1734
use_direct_path = False
1735
if self._new_contents.get(parent) == 'directory':
1736
filename = self._new_name.get(trans_id)
1737
if filename is not None:
1738
if parent not in self._limbo_children:
1739
self._limbo_children[parent] = set()
1740
self._limbo_children_names[parent] = {}
1741
use_direct_path = True
1742
# the direct path can only be used if no other file has
1743
# already taken this pathname, i.e. if the name is unused, or
1744
# if it is already associated with this trans_id.
1745
elif self._case_sensitive_target:
1746
if (self._limbo_children_names[parent].get(filename)
1747
in (trans_id, None)):
1748
use_direct_path = True
1750
for l_filename, l_trans_id in viewitems(
1751
self._limbo_children_names[parent]):
1752
if l_trans_id == trans_id:
1754
if l_filename.lower() == filename.lower():
1757
use_direct_path = True
1759
if not use_direct_path:
1760
return DiskTreeTransform._generate_limbo_path(self, trans_id)
1762
limbo_name = pathjoin(self._limbo_files[parent], filename)
1763
self._limbo_children[parent].add(trans_id)
1764
self._limbo_children_names[parent][filename] = trans_id
1767
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1768
"""Apply all changes to the inventory and filesystem.
1770
If filesystem or inventory conflicts are present, MalformedTransform
1773
If apply succeeds, finalize is not necessary.
1775
:param no_conflicts: if True, the caller guarantees there are no
1776
conflicts, so no check is made.
1777
:param precomputed_delta: An inventory delta to use instead of
1779
:param _mover: Supply an alternate FileMover, for testing
1781
raise NotImplementedError(self.apply)
1783
def _apply_removals(self, mover):
1784
"""Perform tree operations that remove directory/inventory names.
1786
That is, delete files that are to be deleted, and put any files that
1787
need renaming into limbo. This must be done in strict child-to-parent
1790
If inventory_delta is None, no inventory delta generation is performed.
1792
tree_paths = sorted(viewitems(self._tree_path_ids), reverse=True)
1793
with ui.ui_factory.nested_progress_bar() as child_pb:
1794
for num, (path, trans_id) in enumerate(tree_paths):
1795
# do not attempt to move root into a subdirectory of itself.
1798
child_pb.update(gettext('removing file'), num, len(tree_paths))
1799
full_path = self._tree.abspath(path)
1800
if trans_id in self._removed_contents:
1801
delete_path = os.path.join(self._deletiondir, trans_id)
1802
mover.pre_delete(full_path, delete_path)
1803
elif (trans_id in self._new_name or
1804
trans_id in self._new_parent):
1806
mover.rename(full_path, self._limbo_name(trans_id))
1807
except TransformRenameFailed as e:
1808
if e.errno != errno.ENOENT:
1811
self.rename_count += 1
1813
def _apply_insertions(self, mover):
1814
"""Perform tree operations that insert directory/inventory names.
1816
That is, create any files that need to be created, and restore from
1817
limbo any files that needed renaming. This must be done in strict
1818
parent-to-child order.
1820
If inventory_delta is None, no inventory delta is calculated, and
1821
no list of modified paths is returned.
1823
new_paths = self.new_paths(filesystem_only=True)
1825
with ui.ui_factory.nested_progress_bar() as child_pb:
1826
for num, (path, trans_id) in enumerate(new_paths):
1828
child_pb.update(gettext('adding file'),
1829
num, len(new_paths))
1830
full_path = self._tree.abspath(path)
1831
if trans_id in self._needs_rename:
1833
mover.rename(self._limbo_name(trans_id), full_path)
1834
except TransformRenameFailed as e:
1835
# We may be renaming a dangling inventory id
1836
if e.errno != errno.ENOENT:
1839
self.rename_count += 1
1840
# TODO: if trans_id in self._observed_sha1s, we should
1841
# re-stat the final target, since ctime will be
1842
# updated by the change.
1843
if (trans_id in self._new_contents
1844
or self.path_changed(trans_id)):
1845
if trans_id in self._new_contents:
1846
modified_paths.append(full_path)
1847
if trans_id in self._new_executability:
1848
self._set_executability(path, trans_id)
1849
if trans_id in self._observed_sha1s:
1850
o_sha1, o_st_val = self._observed_sha1s[trans_id]
1851
st = osutils.lstat(full_path)
1852
self._observed_sha1s[trans_id] = (o_sha1, st)
1853
for path, trans_id in new_paths:
1854
# new_paths includes stuff like workingtree conflicts. Only the
1855
# stuff in new_contents actually comes from limbo.
1856
if trans_id in self._limbo_files:
1857
del self._limbo_files[trans_id]
1858
self._new_contents.clear()
1859
return modified_paths
1861
def _apply_observed_sha1s(self):
1862
"""After we have finished renaming everything, update observed sha1s
1864
This has to be done after self._tree.apply_inventory_delta, otherwise
1865
it doesn't know anything about the files we are updating. Also, we want
1866
to do this as late as possible, so that most entries end up cached.
1868
# TODO: this doesn't update the stat information for directories. So
1869
# the first 'bzr status' will still need to rewrite
1870
# .bzr/checkout/dirstate. However, we at least don't need to
1871
# re-read all of the files.
1872
# TODO: If the operation took a while, we could do a time.sleep(3) here
1873
# to allow the clock to tick over and ensure we won't have any
1874
# problems. (we could observe start time, and finish time, and if
1875
# it is less than eg 10% overhead, add a sleep call.)
1876
paths = FinalPaths(self)
1877
for trans_id, observed in viewitems(self._observed_sha1s):
1878
path = paths.get_path(trans_id)
1879
self._tree._observed_sha1(path, observed)
602
1882
def joinpath(parent, child):
603
1883
"""Join tree-relative paths, handling the tree root specially"""
604
1884
if parent is None or parent == "":
639
1919
return [(self.get_path(t), t) for t in trans_ids]
1922
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
1923
delta_from_tree=False):
1924
"""Create working tree for a branch, using a TreeTransform.
1926
This function should be used on empty trees, having a tree root at most.
1927
(see merge and revert functionality for working with existing trees)
1929
Existing files are handled like so:
1931
- Existing bzrdirs take precedence over creating new items. They are
1932
created as '%s.diverted' % name.
1933
- Otherwise, if the content on disk matches the content we are building,
1934
it is silently replaced.
1935
- Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1937
:param tree: The tree to convert wt into a copy of
1938
:param wt: The working tree that files will be placed into
1939
:param accelerator_tree: A tree which can be used for retrieving file
1940
contents more quickly than tree itself, i.e. a workingtree. tree
1941
will be used for cases where accelerator_tree's content is different.
1942
:param hardlink: If true, hard-link files to accelerator_tree, where
1943
possible. accelerator_tree must implement abspath, i.e. be a
1945
:param delta_from_tree: If true, build_tree may use the input Tree to
1946
generate the inventory delta.
1948
with cleanup.ExitStack() as exit_stack:
1949
exit_stack.enter_context(wt.lock_tree_write())
1950
exit_stack.enter_context(tree.lock_read())
1951
if accelerator_tree is not None:
1952
exit_stack.enter_context(accelerator_tree.lock_read())
1953
return _build_tree(tree, wt, accelerator_tree, hardlink,
1957
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
1958
"""See build_tree."""
1959
for num, _unused in enumerate(wt.all_versioned_paths()):
1960
if num > 0: # more than just a root
1961
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
1963
top_pb = ui.ui_factory.nested_progress_bar()
1964
pp = ProgressPhase("Build phase", 2, top_pb)
1965
if tree.path2id('') is not None:
1966
# This is kind of a hack: we should be altering the root
1967
# as part of the regular tree shape diff logic.
1968
# The conditional test here is to avoid doing an
1969
# expensive operation (flush) every time the root id
1970
# is set within the tree, nor setting the root and thus
1971
# marking the tree as dirty, because we use two different
1972
# idioms here: tree interfaces and inventory interfaces.
1973
if wt.path2id('') != tree.path2id(''):
1974
wt.set_root_id(tree.path2id(''))
1980
file_trans_id[wt.path2id('')] = tt.trans_id_tree_path('')
1981
with ui.ui_factory.nested_progress_bar() as pb:
1982
deferred_contents = []
1984
total = len(tree.all_versioned_paths())
1986
precomputed_delta = []
1988
precomputed_delta = None
1989
# Check if tree inventory has content. If so, we populate
1990
# existing_files with the directory content. If there are no
1991
# entries we skip populating existing_files as its not used.
1992
# This improves performance and unncessary work on large
1993
# directory trees. (#501307)
1995
existing_files = set()
1996
for dir, files in wt.walkdirs():
1997
existing_files.update(f[0] for f in files)
1998
for num, (tree_path, entry) in \
1999
enumerate(tree.iter_entries_by_dir()):
2000
pb.update(gettext("Building tree"), num
2001
- len(deferred_contents), total)
2002
if entry.parent_id is None:
2005
file_id = entry.file_id
2007
precomputed_delta.append((None, tree_path, file_id, entry))
2008
if tree_path in existing_files:
2009
target_path = wt.abspath(tree_path)
2010
kind = file_kind(target_path)
2011
if kind == "directory":
2013
controldir.ControlDir.open(target_path)
2014
except errors.NotBranchError:
2018
if (file_id not in divert
2020
tree, entry, tree_path, kind, target_path)):
2021
tt.delete_contents(tt.trans_id_tree_path(tree_path))
2022
if kind == 'directory':
2024
parent_id = file_trans_id[entry.parent_id]
2025
if entry.kind == 'file':
2026
# We *almost* replicate new_by_entry, so that we can defer
2027
# getting the file text, and get them all at once.
2028
trans_id = tt.create_path(entry.name, parent_id)
2029
file_trans_id[file_id] = trans_id
2030
tt.version_file(trans_id, file_id=file_id)
2031
executable = tree.is_executable(tree_path)
2033
tt.set_executability(executable, trans_id)
2034
trans_data = (trans_id, tree_path, entry.text_sha1)
2035
deferred_contents.append((tree_path, trans_data))
2037
file_trans_id[file_id] = new_by_entry(
2038
tree_path, tt, entry, parent_id, tree)
2040
new_trans_id = file_trans_id[file_id]
2041
old_parent = tt.trans_id_tree_path(tree_path)
2042
_reparent_children(tt, old_parent, new_trans_id)
2043
offset = num + 1 - len(deferred_contents)
2044
_create_files(tt, tree, deferred_contents, pb, offset,
2045
accelerator_tree, hardlink)
2047
divert_trans = set(file_trans_id[f] for f in divert)
2050
return resolve_checkout(t, c, divert_trans)
2051
raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
2052
if len(raw_conflicts) > 0:
2053
precomputed_delta = None
2054
conflicts = cook_conflicts(raw_conflicts, tt)
2055
for conflict in conflicts:
2056
trace.warning(text_type(conflict))
2058
wt.add_conflicts(conflicts)
2059
except errors.UnsupportedOperation:
2061
result = tt.apply(no_conflicts=True,
2062
precomputed_delta=precomputed_delta)
2069
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
2071
total = len(desired_files) + offset
2073
if accelerator_tree is None:
2074
new_desired_files = desired_files
2076
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2078
change.path for change in iter
2079
if not (change.changed_content or change.executable[0] != change.executable[1])]
2080
if accelerator_tree.supports_content_filtering():
2081
unchanged = [(tp, ap) for (tp, ap) in unchanged
2082
if not next(accelerator_tree.iter_search_rules([ap]))]
2083
unchanged = dict(unchanged)
2084
new_desired_files = []
2086
for unused_tree_path, (trans_id, tree_path, text_sha1) in desired_files:
2087
accelerator_path = unchanged.get(tree_path)
2088
if accelerator_path is None:
2089
new_desired_files.append((tree_path,
2090
(trans_id, tree_path, text_sha1)))
2092
pb.update(gettext('Adding file contents'), count + offset, total)
2094
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2097
with accelerator_tree.get_file(accelerator_path) as f:
2098
chunks = osutils.file_iterator(f)
2099
if wt.supports_content_filtering():
2100
filters = wt._content_filter_stack(tree_path)
2101
chunks = filtered_output_bytes(chunks, filters,
2102
ContentFilterContext(tree_path, tree))
2103
tt.create_file(chunks, trans_id, sha1=text_sha1)
2106
for count, ((trans_id, tree_path, text_sha1), contents) in enumerate(
2107
tree.iter_files_bytes(new_desired_files)):
2108
if wt.supports_content_filtering():
2109
filters = wt._content_filter_stack(tree_path)
2110
contents = filtered_output_bytes(contents, filters,
2111
ContentFilterContext(tree_path, tree))
2112
tt.create_file(contents, trans_id, sha1=text_sha1)
2113
pb.update(gettext('Adding file contents'), count + offset, total)
642
2116
def _reparent_children(tt, old_parent, new_parent):
643
2117
for child in tt.iter_tree_children(old_parent):
644
2118
tt.adjust_path(tt.final_name(child), new_parent, child)
713
2233
tt.set_executability(entry.executable, trans_id)
716
def _prepare_revert_transform(es, working_tree, target_tree, tt, filenames,
2236
def revert(working_tree, target_tree, filenames, backups=False,
2237
pb=None, change_reporter=None):
2238
"""Revert a working tree's contents to those of a target tree."""
2239
pb = ui.ui_factory.nested_progress_bar()
2241
with target_tree.lock_read(), working_tree.transform(pb) as tt:
2242
pp = ProgressPhase("Revert phase", 3, pb)
2243
conflicts, merge_modified = _prepare_revert_transform(
2244
working_tree, target_tree, tt, filenames, backups, pp)
2247
change_reporter = delta._ChangeReporter(
2248
unversioned_filter=working_tree.is_ignored)
2249
delta.report_changes(tt.iter_changes(), change_reporter)
2250
for conflict in conflicts:
2251
trace.warning(text_type(conflict))
2254
if working_tree.supports_merge_modified():
2255
working_tree.set_merge_modified(merge_modified)
2261
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
717
2262
backups, pp, basis_tree=None,
718
2263
merge_modified=None):
719
2264
with ui.ui_factory.nested_progress_bar() as child_pb:
720
2265
if merge_modified is None:
721
2266
merge_modified = working_tree.merge_modified()
722
merge_modified = _alter_files(es, working_tree, target_tree, tt,
2267
merge_modified = _alter_files(working_tree, target_tree, tt,
723
2268
child_pb, filenames, backups,
724
2269
merge_modified, basis_tree)
725
2270
with ui.ui_factory.nested_progress_bar() as child_pb:
726
2271
raw_conflicts = resolve_conflicts(
727
2272
tt, child_pb, lambda t, c: conflict_pass(t, c, target_tree))
728
conflicts = tt.cook_conflicts(raw_conflicts)
2273
conflicts = cook_conflicts(raw_conflicts, tt)
729
2274
return conflicts, merge_modified
732
def revert(working_tree, target_tree, filenames, backups=False,
733
pb=None, change_reporter=None, merge_modified=None, basis_tree=None):
734
"""Revert a working tree's contents to those of a target tree."""
735
with contextlib.ExitStack() as es:
736
pb = es.enter_context(ui.ui_factory.nested_progress_bar())
737
es.enter_context(target_tree.lock_read())
738
tt = es.enter_context(working_tree.transform(pb))
739
pp = ProgressPhase("Revert phase", 3, pb)
740
conflicts, merge_modified = _prepare_revert_transform(
741
es, working_tree, target_tree, tt, filenames, backups, pp)
744
change_reporter = delta._ChangeReporter(
745
unversioned_filter=working_tree.is_ignored)
746
delta.report_changes(tt.iter_changes(), change_reporter)
747
for conflict in conflicts:
748
trace.warning(str(conflict))
751
if working_tree.supports_merge_modified():
752
working_tree.set_merge_modified(merge_modified)
756
def _alter_files(es, working_tree, target_tree, tt, pb, specific_files,
2277
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
757
2278
backups, merge_modified, basis_tree=None):
758
2279
if basis_tree is not None:
759
es.enter_context(basis_tree.lock_read())
2280
basis_tree.lock_read()
760
2281
# We ask the working_tree for its changes relative to the target, rather
761
2282
# than the target changes relative to the working tree. Because WT4 has an
762
2283
# optimizer to compare itself to a target, but no optimizer for the
767
2288
skip_root = True
769
2290
skip_root = False
771
for id_num, change in enumerate(change_list):
772
target_path, wt_path = change.path
773
target_versioned, wt_versioned = change.versioned
774
target_parent = change.parent_id[0]
775
target_name, wt_name = change.name
776
target_kind, wt_kind = change.kind
777
target_executable, wt_executable = change.executable
778
if skip_root and wt_path == '':
781
if wt_path is not None:
782
trans_id = tt.trans_id_tree_path(wt_path)
784
trans_id = tt.assign_id()
785
if change.changed_content:
787
if wt_kind == 'file' and (backups or target_kind is None):
788
wt_sha1 = working_tree.get_file_sha1(wt_path)
789
if merge_modified.get(wt_path) != wt_sha1:
790
# acquire the basis tree lazily to prevent the
791
# expense of accessing it when it's not needed ?
792
# (Guessing, RBC, 200702)
2293
for id_num, change in enumerate(change_list):
2294
file_id = change.file_id
2295
target_path, wt_path = change.path
2296
target_versioned, wt_versioned = change.versioned
2297
target_parent, wt_parent = change.parent_id
2298
target_name, wt_name = change.name
2299
target_kind, wt_kind = change.kind
2300
target_executable, wt_executable = change.executable
2301
if skip_root and wt_parent is None:
2303
trans_id = tt.trans_id_file_id(file_id)
2305
if change.changed_content:
2306
keep_content = False
2307
if wt_kind == 'file' and (backups or target_kind is None):
2308
wt_sha1 = working_tree.get_file_sha1(wt_path)
2309
if merge_modified.get(wt_path) != wt_sha1:
2310
# acquire the basis tree lazily to prevent the
2311
# expense of accessing it when it's not needed ?
2312
# (Guessing, RBC, 200702)
2313
if basis_tree is None:
2314
basis_tree = working_tree.basis_tree()
2315
basis_tree.lock_read()
2316
basis_inter = InterTree.get(basis_tree, working_tree)
2317
basis_path = basis_inter.find_source_path(wt_path)
2318
if basis_path is None:
2319
if target_kind is None and not target_versioned:
2322
if wt_sha1 != basis_tree.get_file_sha1(basis_path):
2324
if wt_kind is not None:
2325
if not keep_content:
2326
tt.delete_contents(trans_id)
2327
elif target_kind is not None:
2328
parent_trans_id = tt.trans_id_file_id(wt_parent)
2329
backup_name = tt._available_backup_name(
2330
wt_name, parent_trans_id)
2331
tt.adjust_path(backup_name, parent_trans_id, trans_id)
2332
new_trans_id = tt.create_path(wt_name, parent_trans_id)
2333
if wt_versioned and target_versioned:
2334
tt.unversion_file(trans_id)
2335
tt.version_file(new_trans_id, file_id=file_id)
2336
# New contents should have the same unix perms as old
2339
trans_id = new_trans_id
2340
if target_kind in ('directory', 'tree-reference'):
2341
tt.create_directory(trans_id)
2342
if target_kind == 'tree-reference':
2343
revision = target_tree.get_reference_revision(
2345
tt.set_tree_reference(revision, trans_id)
2346
elif target_kind == 'symlink':
2347
tt.create_symlink(target_tree.get_symlink_target(
2348
target_path), trans_id)
2349
elif target_kind == 'file':
2350
deferred_files.append(
2351
(target_path, (trans_id, mode_id, file_id)))
793
2352
if basis_tree is None:
794
2353
basis_tree = working_tree.basis_tree()
795
es.enter_context(basis_tree.lock_read())
796
basis_inter = InterTree.get(basis_tree, working_tree)
797
basis_path = basis_inter.find_source_path(wt_path)
798
if basis_path is None:
799
if target_kind is None and not target_versioned:
2354
basis_tree.lock_read()
2355
new_sha1 = target_tree.get_file_sha1(target_path)
2356
basis_inter = InterTree.get(basis_tree, target_tree)
2357
basis_path = basis_inter.find_source_path(target_path)
2358
if (basis_path is not None and
2359
new_sha1 == basis_tree.get_file_sha1(basis_path)):
2360
# If the new contents of the file match what is in basis,
2361
# then there is no need to store in merge_modified.
2362
if basis_path in merge_modified:
2363
del merge_modified[basis_path]
802
if wt_sha1 != basis_tree.get_file_sha1(basis_path):
804
if wt_kind is not None:
806
tt.delete_contents(trans_id)
2365
merge_modified[target_path] = new_sha1
2367
# preserve the execute bit when backing up
2368
if keep_content and wt_executable == target_executable:
2369
tt.set_executability(target_executable, trans_id)
807
2370
elif target_kind is not None:
808
parent_trans_id = tt.trans_id_tree_path(osutils.dirname(wt_path))
809
backup_name = tt._available_backup_name(
810
wt_name, parent_trans_id)
811
tt.adjust_path(backup_name, parent_trans_id, trans_id)
812
new_trans_id = tt.create_path(wt_name, parent_trans_id)
813
if wt_versioned and target_versioned:
814
tt.unversion_file(trans_id)
816
new_trans_id, file_id=getattr(change, 'file_id', None))
817
# New contents should have the same unix perms as old
820
trans_id = new_trans_id
821
if target_kind in ('directory', 'tree-reference'):
822
tt.create_directory(trans_id)
823
if target_kind == 'tree-reference':
824
revision = target_tree.get_reference_revision(
826
tt.set_tree_reference(revision, trans_id)
827
elif target_kind == 'symlink':
828
tt.create_symlink(target_tree.get_symlink_target(
829
target_path), trans_id)
830
elif target_kind == 'file':
831
deferred_files.append(
832
(target_path, (trans_id, mode_id, target_path)))
833
if basis_tree is None:
834
basis_tree = working_tree.basis_tree()
835
es.enter_context(basis_tree.lock_read())
836
new_sha1 = target_tree.get_file_sha1(target_path)
837
basis_inter = InterTree.get(basis_tree, target_tree)
838
basis_path = basis_inter.find_source_path(target_path)
839
if (basis_path is not None and
840
new_sha1 == basis_tree.get_file_sha1(basis_path)):
841
# If the new contents of the file match what is in basis,
842
# then there is no need to store in merge_modified.
843
if basis_path in merge_modified:
844
del merge_modified[basis_path]
846
merge_modified[target_path] = new_sha1
848
# preserve the execute bit when backing up
849
if keep_content and wt_executable == target_executable:
850
tt.set_executability(target_executable, trans_id)
851
elif target_kind is not None:
852
raise AssertionError(target_kind)
853
if not wt_versioned and target_versioned:
855
trans_id, file_id=getattr(change, 'file_id', None))
856
if wt_versioned and not target_versioned:
857
tt.unversion_file(trans_id)
858
if (target_name is not None
859
and (wt_name != target_name or change.is_reparented())):
860
if target_path == '':
861
parent_trans = ROOT_PARENT
863
parent_trans = tt.trans_id_file_id(target_parent)
864
if wt_path == '' and wt_versioned:
865
tt.adjust_root_path(target_name, parent_trans)
867
tt.adjust_path(target_name, parent_trans, trans_id)
868
if wt_executable != target_executable and target_kind == "file":
869
tt.set_executability(target_executable, trans_id)
870
if working_tree.supports_content_filtering():
871
for (trans_id, mode_id, target_path), bytes in (
872
target_tree.iter_files_bytes(deferred_files)):
873
# We're reverting a tree to the target tree so using the
874
# target tree to find the file path seems the best choice
875
# here IMO - Ian C 27/Oct/2009
876
filters = working_tree._content_filter_stack(target_path)
877
bytes = filtered_output_bytes(
879
ContentFilterContext(target_path, working_tree))
880
tt.create_file(bytes, trans_id, mode_id)
882
for (trans_id, mode_id, target_path), bytes in target_tree.iter_files_bytes(
884
tt.create_file(bytes, trans_id, mode_id)
2371
raise AssertionError(target_kind)
2372
if not wt_versioned and target_versioned:
2373
tt.version_file(trans_id, file_id=file_id)
2374
if wt_versioned and not target_versioned:
2375
tt.unversion_file(trans_id)
2376
if (target_name is not None
2377
and (wt_name != target_name or wt_parent != target_parent)):
2378
if target_name == '' and target_parent is None:
2379
parent_trans = ROOT_PARENT
2381
parent_trans = tt.trans_id_file_id(target_parent)
2382
if wt_parent is None and wt_versioned:
2383
tt.adjust_root_path(target_name, parent_trans)
2385
tt.adjust_path(target_name, parent_trans, trans_id)
2386
if wt_executable != target_executable and target_kind == "file":
2387
tt.set_executability(target_executable, trans_id)
2388
if working_tree.supports_content_filtering():
2389
for (trans_id, mode_id, file_id), bytes in (
2390
target_tree.iter_files_bytes(deferred_files)):
2391
# We're reverting a tree to the target tree so using the
2392
# target tree to find the file path seems the best choice
2393
# here IMO - Ian C 27/Oct/2009
2394
filter_tree_path = target_tree.id2path(file_id)
2395
filters = working_tree._content_filter_stack(filter_tree_path)
2396
bytes = filtered_output_bytes(
2398
ContentFilterContext(filter_tree_path, working_tree))
2399
tt.create_file(bytes, trans_id, mode_id)
2401
for (trans_id, mode_id, file_id), bytes in target_tree.iter_files_bytes(
2403
tt.create_file(bytes, trans_id, mode_id)
2404
tt.fixup_new_roots()
2406
if basis_tree is not None:
886
2408
return merge_modified
894
2416
with ui.ui_factory.nested_progress_bar() as pb:
895
2417
for n in range(10):
896
2418
pb.update(gettext('Resolution pass'), n + 1, 10)
897
conflicts = tt.find_raw_conflicts()
2419
conflicts = tt.find_conflicts()
898
2420
if len(conflicts) == 0:
899
2421
return new_conflicts
900
2422
new_conflicts.update(pass_func(tt, conflicts))
901
2423
raise MalformedTransform(conflicts=conflicts)
904
def resolve_duplicate_id(tt, path_tree, c_type, old_trans_id, trans_id):
905
tt.unversion_file(old_trans_id)
906
yield (c_type, 'Unversioned existing file', old_trans_id, trans_id)
909
def resolve_duplicate(tt, path_tree, c_type, last_trans_id, trans_id, name):
910
# files that were renamed take precedence
911
final_parent = tt.final_parent(last_trans_id)
912
if tt.path_changed(last_trans_id):
913
existing_file, new_file = trans_id, last_trans_id
915
existing_file, new_file = last_trans_id, trans_id
916
if (not tt._tree.has_versioned_directories() and
917
tt.final_kind(trans_id) == 'directory' and
918
tt.final_kind(last_trans_id) == 'directory'):
919
_reparent_transform_children(tt, existing_file, new_file)
920
tt.delete_contents(existing_file)
921
tt.unversion_file(existing_file)
922
tt.cancel_creation(existing_file)
924
new_name = tt.final_name(existing_file) + '.moved'
925
tt.adjust_path(new_name, final_parent, existing_file)
926
yield (c_type, 'Moved existing file to', existing_file, new_file)
929
def resolve_parent_loop(tt, path_tree, c_type, cur):
930
# break the loop by undoing one of the ops that caused the loop
931
while not tt.path_changed(cur):
932
cur = tt.final_parent(cur)
933
yield (c_type, 'Cancelled move', cur, tt.final_parent(cur),)
934
tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
937
def resolve_missing_parent(tt, path_tree, c_type, trans_id):
938
if trans_id in tt._removed_contents:
939
cancel_deletion = True
940
orphans = tt._get_potential_orphans(trans_id)
942
cancel_deletion = False
943
# All children are orphans
946
tt.new_orphan(o, trans_id)
947
except OrphaningError:
948
# Something bad happened so we cancel the directory
949
# deletion which will leave it in place with a
950
# conflict. The user can deal with it from there.
951
# Note that this also catch the case where we don't
952
# want to create orphans and leave the directory in
954
cancel_deletion = True
957
# Cancel the directory deletion
958
tt.cancel_deletion(trans_id)
959
yield ('deleting parent', 'Not deleting', trans_id)
963
tt.final_name(trans_id)
965
if path_tree is not None:
966
file_id = tt.final_file_id(trans_id)
968
file_id = tt.inactive_file_id(trans_id)
969
_, entry = next(path_tree.iter_entries_by_dir(
970
specific_files=[path_tree.id2path(file_id)]))
971
# special-case the other tree root (move its
972
# children to current root)
973
if entry.parent_id is None:
975
moved = _reparent_transform_children(
976
tt, trans_id, tt.root)
978
yield (c_type, 'Moved to root', child)
980
parent_trans_id = tt.trans_id_file_id(
982
tt.adjust_path(entry.name, parent_trans_id,
985
tt.create_directory(trans_id)
986
yield (c_type, 'Created directory', trans_id)
989
def resolve_unversioned_parent(tt, path_tree, c_type, trans_id):
990
file_id = tt.inactive_file_id(trans_id)
991
# special-case the other tree root (move its children instead)
992
if path_tree and path_tree.path2id('') == file_id:
993
# This is the root entry, skip it
995
tt.version_file(trans_id, file_id=file_id)
996
yield (c_type, 'Versioned directory', trans_id)
999
def resolve_non_directory_parent(tt, path_tree, c_type, parent_id):
1000
parent_parent = tt.final_parent(parent_id)
1001
parent_name = tt.final_name(parent_id)
1002
# TODO(jelmer): Make this code transform-specific
1003
if tt._tree.supports_setting_file_ids():
1004
parent_file_id = tt.final_file_id(parent_id)
1006
parent_file_id = b'DUMMY'
1007
new_parent_id = tt.new_directory(parent_name + '.new',
1008
parent_parent, parent_file_id)
1009
_reparent_transform_children(tt, parent_id, new_parent_id)
1010
if parent_file_id is not None:
1011
tt.unversion_file(parent_id)
1012
yield (c_type, 'Created directory', new_parent_id)
1015
def resolve_versioning_no_contents(tt, path_tree, c_type, trans_id):
1016
tt.cancel_versioning(trans_id)
1020
CONFLICT_RESOLVERS = {
1021
'duplicate id': resolve_duplicate_id,
1022
'duplicate': resolve_duplicate,
1023
'parent loop': resolve_parent_loop,
1024
'missing parent': resolve_missing_parent,
1025
'unversioned parent': resolve_unversioned_parent,
1026
'non-directory parent': resolve_non_directory_parent,
1027
'versioning no contents': resolve_versioning_no_contents,
1031
2426
def conflict_pass(tt, conflicts, path_tree=None):
1032
2427
"""Resolve some classes of conflicts.
1036
2431
:param path_tree: A Tree to get supplemental paths from
1038
2433
new_conflicts = set()
1039
for conflict in conflicts:
1040
resolver = CONFLICT_RESOLVERS.get(conflict[0])
1041
if resolver is None:
1043
new_conflicts.update(resolver(tt, path_tree, *conflict))
2434
for c_type, conflict in ((c[0], c) for c in conflicts):
2435
if c_type == 'duplicate id':
2436
tt.unversion_file(conflict[1])
2437
new_conflicts.add((c_type, 'Unversioned existing file',
2438
conflict[1], conflict[2], ))
2439
elif c_type == 'duplicate':
2440
# files that were renamed take precedence
2441
final_parent = tt.final_parent(conflict[1])
2442
if tt.path_changed(conflict[1]):
2443
existing_file, new_file = conflict[2], conflict[1]
2445
existing_file, new_file = conflict[1], conflict[2]
2446
new_name = tt.final_name(existing_file) + '.moved'
2447
tt.adjust_path(new_name, final_parent, existing_file)
2448
new_conflicts.add((c_type, 'Moved existing file to',
2449
existing_file, new_file))
2450
elif c_type == 'parent loop':
2451
# break the loop by undoing one of the ops that caused the loop
2453
while not tt.path_changed(cur):
2454
cur = tt.final_parent(cur)
2455
new_conflicts.add((c_type, 'Cancelled move', cur,
2456
tt.final_parent(cur),))
2457
tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
2459
elif c_type == 'missing parent':
2460
trans_id = conflict[1]
2461
if trans_id in tt._removed_contents:
2462
cancel_deletion = True
2463
orphans = tt._get_potential_orphans(trans_id)
2465
cancel_deletion = False
2466
# All children are orphans
2469
tt.new_orphan(o, trans_id)
2470
except OrphaningError:
2471
# Something bad happened so we cancel the directory
2472
# deletion which will leave it in place with a
2473
# conflict. The user can deal with it from there.
2474
# Note that this also catch the case where we don't
2475
# want to create orphans and leave the directory in
2477
cancel_deletion = True
2480
# Cancel the directory deletion
2481
tt.cancel_deletion(trans_id)
2482
new_conflicts.add(('deleting parent', 'Not deleting',
2487
tt.final_name(trans_id)
2489
if path_tree is not None:
2490
file_id = tt.final_file_id(trans_id)
2492
file_id = tt.inactive_file_id(trans_id)
2493
_, entry = next(path_tree.iter_entries_by_dir(
2494
specific_files=[path_tree.id2path(file_id)]))
2495
# special-case the other tree root (move its
2496
# children to current root)
2497
if entry.parent_id is None:
2499
moved = _reparent_transform_children(
2500
tt, trans_id, tt.root)
2502
new_conflicts.add((c_type, 'Moved to root',
2505
parent_trans_id = tt.trans_id_file_id(
2507
tt.adjust_path(entry.name, parent_trans_id,
2510
tt.create_directory(trans_id)
2511
new_conflicts.add((c_type, 'Created directory', trans_id))
2512
elif c_type == 'unversioned parent':
2513
file_id = tt.inactive_file_id(conflict[1])
2514
# special-case the other tree root (move its children instead)
2515
if path_tree and path_tree.path2id('') == file_id:
2516
# This is the root entry, skip it
2518
tt.version_file(conflict[1], file_id=file_id)
2519
new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
2520
elif c_type == 'non-directory parent':
2521
parent_id = conflict[1]
2522
parent_parent = tt.final_parent(parent_id)
2523
parent_name = tt.final_name(parent_id)
2524
parent_file_id = tt.final_file_id(parent_id)
2525
new_parent_id = tt.new_directory(parent_name + '.new',
2526
parent_parent, parent_file_id)
2527
_reparent_transform_children(tt, parent_id, new_parent_id)
2528
if parent_file_id is not None:
2529
tt.unversion_file(parent_id)
2530
new_conflicts.add((c_type, 'Created directory', new_parent_id))
2531
elif c_type == 'versioning no contents':
2532
tt.cancel_versioning(conflict[1])
1044
2533
return new_conflicts
2536
def cook_conflicts(raw_conflicts, tt):
2537
"""Generate a list of cooked conflicts, sorted by file path"""
2538
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
2539
return sorted(conflict_iter, key=conflicts.Conflict.sort_key)
2542
def iter_cook_conflicts(raw_conflicts, tt):
2544
for conflict in raw_conflicts:
2545
c_type = conflict[0]
2546
action = conflict[1]
2547
modified_path = fp.get_path(conflict[2])
2548
modified_id = tt.final_file_id(conflict[2])
2549
if len(conflict) == 3:
2550
yield conflicts.Conflict.factory(
2551
c_type, action=action, path=modified_path, file_id=modified_id)
2554
conflicting_path = fp.get_path(conflict[3])
2555
conflicting_id = tt.final_file_id(conflict[3])
2556
yield conflicts.Conflict.factory(
2557
c_type, action=action, path=modified_path,
2558
file_id=modified_id,
2559
conflict_path=conflicting_path,
2560
conflict_file_id=conflicting_id)
1047
2563
class _FileMover(object):
1048
2564
"""Moves and deletes files for TreeTransform, tracking operations"""
1111
2627
tt.delete_contents(trans_id)
1112
2628
tt.create_hardlink(source_tree.abspath(change.path[0]), trans_id)
1116
class PreviewTree(object):
1119
def __init__(self, transform):
1120
self._transform = transform
1121
self._parent_ids = []
1122
self.__by_parent = None
1123
self._path2trans_id_cache = {}
1124
self._all_children_cache = {}
1125
self._final_name_cache = {}
1127
def supports_setting_file_ids(self):
1128
raise NotImplementedError(self.supports_setting_file_ids)
1131
def _by_parent(self):
1132
if self.__by_parent is None:
1133
self.__by_parent = self._transform.by_parent()
1134
return self.__by_parent
1136
def get_parent_ids(self):
1137
return self._parent_ids
1139
def set_parent_ids(self, parent_ids):
1140
self._parent_ids = parent_ids
1142
def get_revision_tree(self, revision_id):
1143
return self._transform._tree.get_revision_tree(revision_id)
1145
def is_locked(self):
1148
def lock_read(self):
1149
# Perhaps in theory, this should lock the TreeTransform?
1150
return lock.LogicalLockResult(self.unlock)
1155
def _path2trans_id(self, path):
1156
"""Look up the trans id associated with a path.
1158
:param path: path to look up, None when the path does not exist
1161
# We must not use None here, because that is a valid value to store.
1162
trans_id = self._path2trans_id_cache.get(path, object)
1163
if trans_id is not object:
1165
segments = osutils.splitpath(path)
1166
cur_parent = self._transform.root
1167
for cur_segment in segments:
1168
for child in self._all_children(cur_parent):
1169
final_name = self._final_name_cache.get(child)
1170
if final_name is None:
1171
final_name = self._transform.final_name(child)
1172
self._final_name_cache[child] = final_name
1173
if final_name == cur_segment:
1177
self._path2trans_id_cache[path] = None
1179
self._path2trans_id_cache[path] = cur_parent
1182
def _all_children(self, trans_id):
1183
children = self._all_children_cache.get(trans_id)
1184
if children is not None:
1186
children = set(self._transform.iter_tree_children(trans_id))
1187
# children in the _new_parent set are provided by _by_parent.
1188
children.difference_update(self._transform._new_parent)
1189
children.update(self._by_parent.get(trans_id, []))
1190
self._all_children_cache[trans_id] = children
1193
def get_file_with_stat(self, path):
1194
return self.get_file(path), None
1196
def is_executable(self, path):
1197
trans_id = self._path2trans_id(path)
1198
if trans_id is None:
1201
return self._transform._new_executability[trans_id]
1204
return self._transform._tree.is_executable(path)
1205
except OSError as e:
1206
if e.errno == errno.ENOENT:
1209
except errors.NoSuchFile:
1212
def has_filename(self, path):
1213
trans_id = self._path2trans_id(path)
1214
if trans_id in self._transform._new_contents:
1216
elif trans_id in self._transform._removed_contents:
1219
return self._transform._tree.has_filename(path)
1221
def get_file_sha1(self, path, stat_value=None):
1222
trans_id = self._path2trans_id(path)
1223
if trans_id is None:
1224
raise errors.NoSuchFile(path)
1225
kind = self._transform._new_contents.get(trans_id)
1227
return self._transform._tree.get_file_sha1(path)
1229
with self.get_file(path) as fileobj:
1230
return osutils.sha_file(fileobj)
1232
def get_file_verifier(self, path, stat_value=None):
1233
trans_id = self._path2trans_id(path)
1234
if trans_id is None:
1235
raise errors.NoSuchFile(path)
1236
kind = self._transform._new_contents.get(trans_id)
1238
return self._transform._tree.get_file_verifier(path)
1240
with self.get_file(path) as fileobj:
1241
return ("SHA1", osutils.sha_file(fileobj))
1243
def kind(self, path):
1244
trans_id = self._path2trans_id(path)
1245
if trans_id is None:
1246
raise errors.NoSuchFile(path)
1247
return self._transform.final_kind(trans_id)
1249
def stored_kind(self, path):
1250
trans_id = self._path2trans_id(path)
1251
if trans_id is None:
1252
raise errors.NoSuchFile(path)
1254
return self._transform._new_contents[trans_id]
1256
return self._transform._tree.stored_kind(path)
1258
def _get_repository(self):
1259
repo = getattr(self._transform._tree, '_repository', None)
1261
repo = self._transform._tree.branch.repository
1264
def _iter_parent_trees(self):
1265
for revision_id in self.get_parent_ids():
1267
yield self.revision_tree(revision_id)
1268
except errors.NoSuchRevisionInTree:
1269
yield self._get_repository().revision_tree(revision_id)
1271
def get_file_size(self, path):
1272
"""See Tree.get_file_size"""
1273
trans_id = self._path2trans_id(path)
1274
if trans_id is None:
1275
raise errors.NoSuchFile(path)
1276
kind = self._transform.final_kind(trans_id)
1279
if trans_id in self._transform._new_contents:
1280
return self._stat_limbo_file(trans_id).st_size
1281
if self.kind(path) == 'file':
1282
return self._transform._tree.get_file_size(path)
1286
def get_reference_revision(self, path):
1287
trans_id = self._path2trans_id(path)
1288
if trans_id is None:
1289
raise errors.NoSuchFile(path)
1290
reference_revision = self._transform._new_reference_revision.get(trans_id)
1291
if reference_revision is None:
1292
return self._transform._tree.get_reference_revision(path)
1293
return reference_revision
1295
def tree_kind(self, trans_id):
1296
path = self._tree_id_paths.get(trans_id)
1299
kind = self._tree.path_content_summary(path)[0]
1300
if kind == 'missing':