14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
19
21
from stat import S_ISREG, S_IEXEC
22
from bzrlib.lazy_import import lazy_import
23
lazy_import(globals(), """
25
config as _mod_config,
32
lazy_import.lazy_import(globals(), """
34
44
revision as _mod_revision,
48
from breezy.bzr import (
52
from breezy.i18n import gettext
38
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
39
ReusingTransform, CantMoveRoot,
40
ExistingLimbo, ImmortalLimbo, NoFinalPath,
42
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
43
from bzrlib.inventory import InventoryEntry
44
from bzrlib.osutils import (
54
from .errors import (DuplicateKey, MalformedTransform,
55
ReusingTransform, CantMoveRoot,
56
ImmortalLimbo, NoFinalPath)
57
from .filters import filtered_output_bytes, ContentFilterContext
58
from .mutabletree import MutableTree
59
from .osutils import (
54
from bzrlib.progress import ProgressPhase
55
from bzrlib.symbol_versioning import (
59
from bzrlib.trace import mutter, warning
60
from bzrlib import tree
62
import bzrlib.urlutils as urlutils
67
from .progress import ProgressPhase
65
79
ROOT_PARENT = "root-parent"
381
393
return sorted(FinalPaths(self).get_paths(new_ids))
383
395
def _inventory_altered(self):
384
"""Get the trans_ids and paths of files needing new inv entries."""
386
for id_set in [self._new_name, self._new_parent, self._new_id,
396
"""Determine which trans_ids need new Inventory entries.
398
An new entry is needed when anything that would be reflected by an
399
inventory entry changes, including file name, file_id, parent file_id,
400
file kind, and the execute bit.
402
Some care is taken to return entries with real changes, not cases
403
where the value is deleted and then restored to its original value,
404
but some actually unchanged values may be returned.
406
:returns: A list of (path, trans_id) for all items requiring an
407
inventory change. Ordered by path.
410
# Find entries whose file_ids are new (or changed).
411
new_file_id = set(t for t in self._new_id
412
if self._new_id[t] != self.tree_file_id(t))
413
for id_set in [self._new_name, self._new_parent, new_file_id,
387
414
self._new_executability]:
388
new_ids.update(id_set)
415
changed_ids.update(id_set)
416
# removing implies a kind change
389
417
changed_kind = set(self._removed_contents)
390
419
changed_kind.intersection_update(self._new_contents)
391
changed_kind.difference_update(new_ids)
392
changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
394
new_ids.update(changed_kind)
395
return sorted(FinalPaths(self).get_paths(new_ids))
420
# Ignore entries that are already known to have changed.
421
changed_kind.difference_update(changed_ids)
422
# to keep only the truly changed ones
423
changed_kind = (t for t in changed_kind
424
if self.tree_kind(t) != self.final_kind(t))
425
# all kind changes will alter the inventory
426
changed_ids.update(changed_kind)
427
# To find entries with changed parent_ids, find parents which existed,
428
# but changed file_id.
429
# Now add all their children to the set.
430
for parent_trans_id in new_file_id:
431
changed_ids.update(self.iter_tree_children(parent_trans_id))
432
return sorted(FinalPaths(self).get_paths(changed_ids))
397
434
def final_kind(self, trans_id):
398
435
"""Determine the final file kind, after any changes applied.
400
Raises NoSuchFile if the file does not exist/has no contents.
401
(It is conceivable that a path would be created without the
402
corresponding contents insertion command)
437
:return: None if the file does not exist/has no contents. (It is
438
conceivable that a path would be created without the corresponding
439
contents insertion command)
404
441
if trans_id in self._new_contents:
405
442
return self._new_contents[trans_id]
406
443
elif trans_id in self._removed_contents:
407
raise NoSuchFile(None)
409
446
return self.tree_kind(trans_id)
448
def tree_path(self, trans_id):
449
"""Determine the tree path associated with the trans_id."""
450
return self._tree_id_paths.get(trans_id)
411
452
def tree_file_id(self, trans_id):
412
453
"""Determine the file id associated with the trans_id in the tree"""
414
path = self._tree_id_paths[trans_id]
416
# the file is a new, unversioned file, or invalid trans_id
454
path = self.tree_path(trans_id)
418
457
# the file is old; the old id is still valid
419
458
if self._new_root == trans_id:
420
return self._tree.get_root_id()
459
return self._tree.path2id('')
421
460
return self._tree.path2id(path)
423
462
def final_file_id(self, trans_id):
532
571
# ensure that all children are registered with the transaction
533
572
list(self.iter_tree_children(parent_id))
535
def has_named_child(self, by_parent, parent_id, name):
537
children = by_parent[parent_id]
540
for child in children:
574
def _has_named_child(self, name, parent_id, known_children):
575
"""Does a parent already have a name child.
577
:param name: The searched for name.
579
:param parent_id: The parent for which the check is made.
581
:param known_children: The already known children. This should have
582
been recently obtained from `self.by_parent.get(parent_id)`
583
(or will be if None is passed).
585
if known_children is None:
586
known_children = self.by_parent().get(parent_id, [])
587
for child in known_children:
541
588
if self.final_name(child) == name:
544
path = self._tree_id_paths[parent_id]
590
parent_path = self._tree_id_paths.get(parent_id, None)
591
if parent_path is None:
592
# No parent... no children
547
childpath = joinpath(path, name)
548
child_id = self._tree_path_ids.get(childpath)
594
child_path = joinpath(parent_path, name)
595
child_id = self._tree_path_ids.get(child_path, None)
549
596
if child_id is None:
550
return lexists(self._tree.abspath(childpath))
597
# Not known by the tree transform yet, check the filesystem
598
return osutils.lexists(self._tree.abspath(child_path))
552
if self.final_parent(child_id) != parent_id:
554
if child_id in self._removed_contents:
555
# XXX What about dangling file-ids?
600
raise AssertionError('child_id is missing: %s, %s, %s'
601
% (name, parent_id, child_id))
603
def _available_backup_name(self, name, target_id):
604
"""Find an available backup name.
606
:param name: The basename of the file.
608
:param target_id: The directory trans_id where the backup should
611
known_children = self.by_parent().get(target_id, [])
612
return osutils.available_backup_name(
614
lambda base: self._has_named_child(
615
base, target_id, known_children))
560
617
def _parent_loops(self):
561
618
"""No entry should be its own ancestor"""
669
724
def _duplicate_ids(self):
670
725
"""Each inventory id may only be used once"""
728
all_ids = self._tree.all_file_ids()
729
except errors.UnsupportedOperation:
730
# it's okay for non-file-id trees to raise UnsupportedOperation.
672
732
removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
673
733
self._removed_id))
674
all_ids = self._tree.all_file_ids()
675
734
active_tree_ids = all_ids.difference(removed_tree_ids)
676
for trans_id, file_id in self._new_id.iteritems():
735
for trans_id, file_id in viewitems(self._new_id):
677
736
if file_id in active_tree_ids:
678
old_trans_id = self.trans_id_tree_file_id(file_id)
737
path = self._tree.id2path(file_id)
738
old_trans_id = self.trans_id_tree_path(path)
679
739
conflicts.append(('duplicate id', old_trans_id, trans_id))
682
742
def _parent_type_conflicts(self, by_parent):
683
"""parents must have directory 'contents'."""
743
"""Children must have a directory parent"""
685
for parent_id, children in by_parent.iteritems():
686
if parent_id is ROOT_PARENT:
688
if not self._any_contents(children):
690
for child in children:
692
self.final_kind(child)
696
kind = self.final_kind(parent_id)
745
for parent_id, children in viewitems(by_parent):
746
if parent_id == ROOT_PARENT:
749
for child_id in children:
750
if self.final_kind(child_id) is not None:
755
# There is at least a child, so we need an existing directory to
757
kind = self.final_kind(parent_id)
759
# The directory will be deleted
700
760
conflicts.append(('missing parent', parent_id))
701
761
elif kind != "directory":
762
# Meh, we need a *directory* to put something in it
702
763
conflicts.append(('non-directory parent', parent_id))
705
def _any_contents(self, trans_ids):
706
"""Return true if any of the trans_ids, will have contents."""
707
for trans_id in trans_ids:
709
kind = self.final_kind(trans_id)
715
766
def _set_executability(self, path, trans_id):
716
767
"""Set the executability of versioned files """
717
if supports_executable():
768
if self._tree._supports_executable():
718
769
new_executability = self._new_executability[trans_id]
719
770
abspath = self._tree.abspath(path)
720
771
current_mode = os.stat(abspath).st_mode
721
772
if new_executability:
722
773
umask = os.umask(0)
724
to_mode = current_mode | (0100 & ~umask)
775
to_mode = current_mode | (0o100 & ~umask)
725
776
# Enable x-bit for others only if they can read it.
726
if current_mode & 0004:
727
to_mode |= 0001 & ~umask
728
if current_mode & 0040:
729
to_mode |= 0010 & ~umask
777
if current_mode & 0o004:
778
to_mode |= 0o001 & ~umask
779
if current_mode & 0o040:
780
to_mode |= 0o010 & ~umask
731
to_mode = current_mode & ~0111
732
os.chmod(abspath, to_mode)
782
to_mode = current_mode & ~0o111
783
osutils.chmod_if_possible(abspath, to_mode)
734
785
def _new_entry(self, name, parent_id, file_id):
735
786
"""Helper function to create a new filesystem entry."""
781
832
self.create_symlink(target, trans_id)
835
def new_orphan(self, trans_id, parent_id):
836
"""Schedule an item to be orphaned.
838
When a directory is about to be removed, its children, if they are not
839
versioned are moved out of the way: they don't have a parent anymore.
841
:param trans_id: The trans_id of the existing item.
842
:param parent_id: The parent trans_id of the item.
844
raise NotImplementedError(self.new_orphan)
846
def _get_potential_orphans(self, dir_id):
847
"""Find the potential orphans in a directory.
849
A directory can't be safely deleted if there are versioned files in it.
850
If all the contained files are unversioned then they can be orphaned.
852
The 'None' return value means that the directory contains at least one
853
versioned file and should not be deleted.
855
:param dir_id: The directory trans id.
857
:return: A list of the orphan trans ids or None if at least one
858
versioned file is present.
861
# Find the potential orphans, stop if one item should be kept
862
for child_tid in self.by_parent()[dir_id]:
863
if child_tid in self._removed_contents:
864
# The child is removed as part of the transform. Since it was
865
# versioned before, it's not an orphan
867
elif self.final_file_id(child_tid) is None:
868
# The child is not versioned
869
orphans.append(child_tid)
871
# We have a versioned file here, searching for orphans is
784
877
def _affected_ids(self):
785
878
"""Return the set of transform ids affected by the transform"""
786
879
trans_ids = set(self._removed_id)
787
trans_ids.update(self._new_id.keys())
880
trans_ids.update(self._new_id)
788
881
trans_ids.update(self._removed_contents)
789
trans_ids.update(self._new_contents.keys())
790
trans_ids.update(self._new_executability.keys())
791
trans_ids.update(self._new_name.keys())
792
trans_ids.update(self._new_parent.keys())
882
trans_ids.update(self._new_contents)
883
trans_ids.update(self._new_executability)
884
trans_ids.update(self._new_name)
885
trans_ids.update(self._new_parent)
795
888
def _get_file_id_maps(self):
904
989
to_path = final_paths.get_path(to_trans_id)
991
from_name, from_parent, from_kind, from_executable = \
992
self._from_file_data(from_trans_id, from_versioned, from_path)
994
to_name, to_parent, to_kind, to_executable = \
995
self._to_file_data(to_trans_id, from_trans_id, from_executable)
905
997
if from_kind != to_kind:
907
999
elif to_kind in ('file', 'symlink') and (
908
to_trans_id != from_trans_id or
909
to_trans_id in self._new_contents):
1000
to_trans_id != from_trans_id
1001
or to_trans_id in self._new_contents):
911
if (not modified and from_versioned == to_versioned and
912
from_parent==to_parent and from_name == to_name and
913
from_executable == to_executable):
1003
if (not modified and from_versioned == to_versioned
1004
and from_parent == to_parent and from_name == to_name
1005
and from_executable == to_executable):
915
results.append((file_id, (from_path, to_path), modified,
916
(from_versioned, to_versioned),
917
(from_parent, to_parent),
918
(from_name, to_name),
919
(from_kind, to_kind),
920
(from_executable, to_executable)))
921
return iter(sorted(results, key=lambda x:x[1]))
1009
file_id, (from_path, to_path), modified,
1010
(from_versioned, to_versioned),
1011
(from_parent, to_parent),
1012
(from_name, to_name),
1013
(from_kind, to_kind),
1014
(from_executable, to_executable)))
1017
return (c.path[0] or '', c.path[1] or '')
1018
return iter(sorted(results, key=path_key))
923
1020
def get_preview_tree(self):
924
1021
"""Return a tree representing the result of the transform.
987
1084
return revision_id
989
1086
def _text_parent(self, trans_id):
990
file_id = self.tree_file_id(trans_id)
1087
path = self.tree_path(trans_id)
992
if file_id is None or self._tree.kind(file_id) != 'file':
1089
if path is None or self._tree.kind(path) != 'file':
994
1091
except errors.NoSuchFile:
998
1095
def _get_parents_texts(self, trans_id):
999
1096
"""Get texts for compression parents of this file."""
1000
file_id = self._text_parent(trans_id)
1097
path = self._text_parent(trans_id)
1003
return (self._tree.get_file_text(file_id),)
1100
return (self._tree.get_file_text(path),)
1005
1102
def _get_parents_lines(self, trans_id):
1006
1103
"""Get lines for compression parents of this file."""
1007
file_id = self._text_parent(trans_id)
1104
path = self._text_parent(trans_id)
1010
return (self._tree.get_file_lines(file_id),)
1107
return (self._tree.get_file_lines(path),)
1012
1109
def serialize(self, serializer):
1013
1110
"""Serialize this TreeTransform.
1015
1112
:param serializer: A Serialiser like pack.ContainerSerializer.
1017
new_name = dict((k, v.encode('utf-8')) for k, v in
1018
self._new_name.items())
1019
new_executability = dict((k, int(v)) for k, v in
1020
self._new_executability.items())
1021
tree_path_ids = dict((k.encode('utf-8'), v)
1022
for k, v in self._tree_path_ids.items())
1114
new_name = {k.encode('utf-8'): v.encode('utf-8')
1115
for k, v in viewitems(self._new_name)}
1116
new_parent = {k.encode('utf-8'): v.encode('utf-8')
1117
for k, v in viewitems(self._new_parent)}
1118
new_id = {k.encode('utf-8'): v
1119
for k, v in viewitems(self._new_id)}
1120
new_executability = {k.encode('utf-8'): int(v)
1121
for k, v in viewitems(self._new_executability)}
1122
tree_path_ids = {k.encode('utf-8'): v.encode('utf-8')
1123
for k, v in viewitems(self._tree_path_ids)}
1124
non_present_ids = {k: v.encode('utf-8')
1125
for k, v in viewitems(self._non_present_ids)}
1126
removed_contents = [trans_id.encode('utf-8')
1127
for trans_id in self._removed_contents]
1128
removed_id = [trans_id.encode('utf-8')
1129
for trans_id in self._removed_id]
1024
'_id_number': self._id_number,
1025
'_new_name': new_name,
1026
'_new_parent': self._new_parent,
1027
'_new_executability': new_executability,
1028
'_new_id': self._new_id,
1029
'_tree_path_ids': tree_path_ids,
1030
'_removed_id': list(self._removed_id),
1031
'_removed_contents': list(self._removed_contents),
1032
'_non_present_ids': self._non_present_ids,
1131
b'_id_number': self._id_number,
1132
b'_new_name': new_name,
1133
b'_new_parent': new_parent,
1134
b'_new_executability': new_executability,
1136
b'_tree_path_ids': tree_path_ids,
1137
b'_removed_id': removed_id,
1138
b'_removed_contents': removed_contents,
1139
b'_non_present_ids': non_present_ids,
1034
1141
yield serializer.bytes_record(bencode.bencode(attribs),
1036
for trans_id, kind in self._new_contents.items():
1143
for trans_id, kind in sorted(viewitems(self._new_contents)):
1037
1144
if kind == 'file':
1038
lines = osutils.chunks_to_lines(
1039
self._read_file_chunks(trans_id))
1145
with open(self._limbo_name(trans_id), 'rb') as cur_file:
1146
lines = cur_file.readlines()
1040
1147
parents = self._get_parents_lines(trans_id)
1041
1148
mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1042
content = ''.join(mpdiff.to_patch())
1149
content = b''.join(mpdiff.to_patch())
1043
1150
if kind == 'directory':
1045
1152
if kind == 'symlink':
1046
1153
content = self._read_symlink_target(trans_id)
1047
yield serializer.bytes_record(content, ((trans_id, kind),))
1154
if not isinstance(content, bytes):
1155
content = content.encode('utf-8')
1156
yield serializer.bytes_record(
1157
content, ((trans_id.encode('utf-8'), kind.encode('ascii')),))
1049
1159
def deserialize(self, records):
1050
1160
"""Deserialize a stored TreeTransform.
1052
1162
:param records: An iterable of (names, content) tuples, as per
1053
1163
pack.ContainerPushParser.
1055
names, content = records.next()
1165
names, content = next(records)
1056
1166
attribs = bencode.bdecode(content)
1057
self._id_number = attribs['_id_number']
1058
self._new_name = dict((k, v.decode('utf-8'))
1059
for k, v in attribs['_new_name'].items())
1060
self._new_parent = attribs['_new_parent']
1061
self._new_executability = dict((k, bool(v)) for k, v in
1062
attribs['_new_executability'].items())
1063
self._new_id = attribs['_new_id']
1064
self._r_new_id = dict((v, k) for k, v in self._new_id.items())
1167
self._id_number = attribs[b'_id_number']
1168
self._new_name = {k.decode('utf-8'): v.decode('utf-8')
1169
for k, v in viewitems(attribs[b'_new_name'])}
1170
self._new_parent = {k.decode('utf-8'): v.decode('utf-8')
1171
for k, v in viewitems(attribs[b'_new_parent'])}
1172
self._new_executability = {
1173
k.decode('utf-8'): bool(v)
1174
for k, v in viewitems(attribs[b'_new_executability'])}
1175
self._new_id = {k.decode('utf-8'): v
1176
for k, v in viewitems(attribs[b'_new_id'])}
1177
self._r_new_id = {v: k for k, v in viewitems(self._new_id)}
1065
1178
self._tree_path_ids = {}
1066
1179
self._tree_id_paths = {}
1067
for bytepath, trans_id in attribs['_tree_path_ids'].items():
1180
for bytepath, trans_id in viewitems(attribs[b'_tree_path_ids']):
1068
1181
path = bytepath.decode('utf-8')
1182
trans_id = trans_id.decode('utf-8')
1069
1183
self._tree_path_ids[path] = trans_id
1070
1184
self._tree_id_paths[trans_id] = path
1071
self._removed_id = set(attribs['_removed_id'])
1072
self._removed_contents = set(attribs['_removed_contents'])
1073
self._non_present_ids = attribs['_non_present_ids']
1185
self._removed_id = {trans_id.decode('utf-8')
1186
for trans_id in attribs[b'_removed_id']}
1187
self._removed_contents = set(
1188
trans_id.decode('utf-8')
1189
for trans_id in attribs[b'_removed_contents'])
1190
self._non_present_ids = {
1191
k: v.decode('utf-8')
1192
for k, v in viewitems(attribs[b'_non_present_ids'])}
1074
1193
for ((trans_id, kind),), content in records:
1194
trans_id = trans_id.decode('utf-8')
1195
kind = kind.decode('ascii')
1075
1196
if kind == 'file':
1076
1197
mpdiff = multiparent.MultiParent.from_patch(content)
1077
1198
lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1081
1202
if kind == 'symlink':
1082
1203
self.create_symlink(content.decode('utf-8'), trans_id)
1205
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1206
"""Schedule creation of a new file.
1210
:param contents: an iterator of strings, all of which will be written
1211
to the target destination.
1212
:param trans_id: TreeTransform handle
1213
:param mode_id: If not None, force the mode of the target file to match
1214
the mode of the object referenced by mode_id.
1215
Otherwise, we will try to preserve mode bits of an existing file.
1216
:param sha1: If the sha1 of this content is already known, pass it in.
1217
We can use it to prevent future sha1 computations.
1219
raise NotImplementedError(self.create_file)
1221
def create_directory(self, trans_id):
1222
"""Schedule creation of a new directory.
1224
See also new_directory.
1226
raise NotImplementedError(self.create_directory)
1228
def create_symlink(self, target, trans_id):
1229
"""Schedule creation of a new symbolic link.
1231
target is a bytestring.
1232
See also new_symlink.
1234
raise NotImplementedError(self.create_symlink)
1236
def create_hardlink(self, path, trans_id):
1237
"""Schedule creation of a hard link"""
1238
raise NotImplementedError(self.create_hardlink)
1240
def cancel_creation(self, trans_id):
1241
"""Cancel the creation of new file contents."""
1242
raise NotImplementedError(self.cancel_creation)
1085
1245
class DiskTreeTransform(TreeTransformBase):
1086
1246
"""Tree transform storing its contents on disk."""
1088
def __init__(self, tree, limbodir, pb=None,
1089
case_sensitive=True):
1248
def __init__(self, tree, limbodir, pb=None, case_sensitive=True):
1090
1249
"""Constructor.
1091
1250
:param tree: The tree that will be transformed, but not necessarily
1092
1251
the output tree.
1195
1370
descendants.update(self._limbo_descendants(descendant))
1196
1371
return descendants
1198
def create_file(self, contents, trans_id, mode_id=None):
1373
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1199
1374
"""Schedule creation of a new file.
1203
Contents is an iterator of strings, all of which will be written
1204
to the target destination.
1206
New file takes the permissions of any existing file with that id,
1207
unless mode_id is specified.
1378
:param contents: an iterator of strings, all of which will be written
1379
to the target destination.
1380
:param trans_id: TreeTransform handle
1381
:param mode_id: If not None, force the mode of the target file to match
1382
the mode of the object referenced by mode_id.
1383
Otherwise, we will try to preserve mode bits of an existing file.
1384
:param sha1: If the sha1 of this content is already known, pass it in.
1385
We can use it to prevent future sha1 computations.
1209
1387
name = self._limbo_name(trans_id)
1210
f = open(name, 'wb')
1213
unique_add(self._new_contents, trans_id, 'file')
1215
# Clean up the file, it never got registered so
1216
# TreeTransform.finalize() won't clean it up.
1388
with open(name, 'wb') as f:
1389
unique_add(self._new_contents, trans_id, 'file')
1221
1390
f.writelines(contents)
1224
1391
self._set_mtime(name)
1225
1392
self._set_mode(trans_id, mode_id, S_ISREG)
1227
def _read_file_chunks(self, trans_id):
1228
cur_file = open(self._limbo_name(trans_id), 'rb')
1230
return cur_file.readlines()
1393
# It is unfortunate we have to use lstat instead of fstat, but we just
1394
# used utime and chmod on the file, so we need the accurate final
1396
if sha1 is not None:
1397
self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
1234
1399
def _read_symlink_target(self, trans_id):
1235
1400
return os.readlink(self._limbo_name(trans_id))
1296
1467
del self._limbo_children_names[trans_id]
1297
1468
delete_any(self._limbo_name(trans_id))
1470
def new_orphan(self, trans_id, parent_id):
1471
conf = self._tree.get_config_stack()
1472
handle_orphan = conf.get('transform.orphan_policy')
1473
handle_orphan(self, trans_id, parent_id)
1476
class OrphaningError(errors.BzrError):
1478
# Only bugs could lead to such exception being seen by the user
1479
internal_error = True
1480
_fmt = "Error while orphaning %s in %s directory"
1482
def __init__(self, orphan, parent):
1483
errors.BzrError.__init__(self)
1484
self.orphan = orphan
1485
self.parent = parent
1488
class OrphaningForbidden(OrphaningError):
1490
_fmt = "Policy: %s doesn't allow creating orphans."
1492
def __init__(self, policy):
1493
errors.BzrError.__init__(self)
1494
self.policy = policy
1497
def move_orphan(tt, orphan_id, parent_id):
1498
"""See TreeTransformBase.new_orphan.
1500
This creates a new orphan in the `brz-orphans` dir at the root of the
1503
:param tt: The TreeTransform orphaning `trans_id`.
1505
:param orphan_id: The trans id that should be orphaned.
1507
:param parent_id: The orphan parent trans id.
1509
# Add the orphan dir if it doesn't exist
1510
orphan_dir_basename = 'brz-orphans'
1511
od_id = tt.trans_id_tree_path(orphan_dir_basename)
1512
if tt.final_kind(od_id) is None:
1513
tt.create_directory(od_id)
1514
parent_path = tt._tree_id_paths[parent_id]
1515
# Find a name that doesn't exist yet in the orphan dir
1516
actual_name = tt.final_name(orphan_id)
1517
new_name = tt._available_backup_name(actual_name, od_id)
1518
tt.adjust_path(new_name, od_id, orphan_id)
1519
trace.warning('%s has been orphaned in %s'
1520
% (joinpath(parent_path, actual_name), orphan_dir_basename))
1523
def refuse_orphan(tt, orphan_id, parent_id):
1524
"""See TreeTransformBase.new_orphan.
1526
This refuses to create orphan, letting the caller handle the conflict.
1528
raise OrphaningForbidden('never')
1531
orphaning_registry = registry.Registry()
1532
orphaning_registry.register(
1533
u'conflict', refuse_orphan,
1534
'Leave orphans in place and create a conflict on the directory.')
1535
orphaning_registry.register(
1536
u'move', move_orphan,
1537
'Move orphans into the brz-orphans directory.')
1538
orphaning_registry._set_default_key(u'conflict')
1541
opt_transform_orphan = _mod_config.RegistryOption(
1542
'transform.orphan_policy', orphaning_registry,
1543
help='Policy for orphaned files during transform operations.',
1300
1547
class TreeTransform(DiskTreeTransform):
1301
1548
"""Represent a tree transformation.
1678
1912
new_paths = self.new_paths(filesystem_only=True)
1679
1913
modified_paths = []
1680
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1682
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1914
with ui.ui_factory.nested_progress_bar() as child_pb:
1684
1915
for num, (path, trans_id) in enumerate(new_paths):
1685
1916
if (num % 10) == 0:
1686
child_pb.update('adding file', num, len(new_paths))
1917
child_pb.update(gettext('adding file'),
1918
num, len(new_paths))
1687
1919
full_path = self._tree.abspath(path)
1688
1920
if trans_id in self._needs_rename:
1690
1922
mover.rename(self._limbo_name(trans_id), full_path)
1923
except errors.TransformRenameFailed as e:
1692
1924
# We may be renaming a dangling inventory id
1693
1925
if e.errno != errno.ENOENT:
1696
1928
self.rename_count += 1
1697
if (trans_id in self._new_contents or
1698
self.path_changed(trans_id)):
1929
# TODO: if trans_id in self._observed_sha1s, we should
1930
# re-stat the final target, since ctime will be
1931
# updated by the change.
1932
if (trans_id in self._new_contents
1933
or self.path_changed(trans_id)):
1699
1934
if trans_id in self._new_contents:
1700
1935
modified_paths.append(full_path)
1701
1936
if trans_id in self._new_executability:
1702
1937
self._set_executability(path, trans_id)
1938
if trans_id in self._observed_sha1s:
1939
o_sha1, o_st_val = self._observed_sha1s[trans_id]
1940
st = osutils.lstat(full_path)
1941
self._observed_sha1s[trans_id] = (o_sha1, st)
1942
for path, trans_id in new_paths:
1943
# new_paths includes stuff like workingtree conflicts. Only the
1944
# stuff in new_contents actually comes from limbo.
1945
if trans_id in self._limbo_files:
1946
del self._limbo_files[trans_id]
1705
1947
self._new_contents.clear()
1706
1948
return modified_paths
1950
def _apply_observed_sha1s(self):
1951
"""After we have finished renaming everything, update observed sha1s
1953
This has to be done after self._tree.apply_inventory_delta, otherwise
1954
it doesn't know anything about the files we are updating. Also, we want
1955
to do this as late as possible, so that most entries end up cached.
1957
# TODO: this doesn't update the stat information for directories. So
1958
# the first 'bzr status' will still need to rewrite
1959
# .bzr/checkout/dirstate. However, we at least don't need to
1960
# re-read all of the files.
1961
# TODO: If the operation took a while, we could do a time.sleep(3) here
1962
# to allow the clock to tick over and ensure we won't have any
1963
# problems. (we could observe start time, and finish time, and if
1964
# it is less than eg 10% overhead, add a sleep call.)
1965
paths = FinalPaths(self)
1966
for trans_id, observed in viewitems(self._observed_sha1s):
1967
path = paths.get_path(trans_id)
1968
self._tree._observed_sha1(path, observed)
1709
1971
class TransformPreview(DiskTreeTransform):
1710
1972
"""A TreeTransform for generating preview trees.
1826
2092
def lock_read(self):
1827
2093
# Perhaps in theory, this should lock the TreeTransform?
2094
return lock.LogicalLockResult(self.unlock)
1830
2096
def unlock(self):
1834
def inventory(self):
2100
def root_inventory(self):
1835
2101
"""This Tree does not use inventory as its backing data."""
1836
raise NotImplementedError(_PreviewTree.inventory)
1838
def get_root_id(self):
1839
return self._transform.final_file_id(self._transform.root)
2102
raise NotImplementedError(_PreviewTree.root_inventory)
1841
2104
def all_file_ids(self):
1842
2105
tree_ids = set(self._transform._tree.all_file_ids())
1843
2106
tree_ids.difference_update(self._transform.tree_file_id(t)
1844
2107
for t in self._transform._removed_id)
1845
tree_ids.update(self._transform._new_id.values())
2108
tree_ids.update(viewvalues(self._transform._new_id))
1846
2109
return tree_ids
1849
return iter(self.all_file_ids())
1851
def _has_id(self, file_id, fallback_check):
1852
if file_id in self._transform._r_new_id:
1854
elif file_id in set([self._transform.tree_file_id(trans_id) for
1855
trans_id in self._transform._removed_id]):
1858
return fallback_check(file_id)
1860
def has_id(self, file_id):
1861
return self._has_id(file_id, self._transform._tree.has_id)
1863
def has_or_had_id(self, file_id):
1864
return self._has_id(file_id, self._transform._tree.has_or_had_id)
2111
def all_versioned_paths(self):
2112
tree_paths = set(self._transform._tree.all_versioned_paths())
2114
tree_paths.difference_update(
2115
self._transform.trans_id_tree_path(t)
2116
for t in self._transform._removed_id)
2119
self._final_paths._determine_path(t)
2120
for t in self._transform._new_id)
1866
2124
def _path2trans_id(self, path):
1867
2125
# We must not use None here, because that is a valid value to store.
1990
2260
if not path.startswith(prefix):
1992
2262
path = path[len(prefix):]
1993
yield path, 'V', entry.kind, entry.file_id, entry
2263
yield path, 'V', entry.kind, entry
1995
2265
if from_dir is None and include_root is True:
1996
root_entry = inventory.make_entry('directory', '',
1997
ROOT_PARENT, self.get_root_id())
1998
yield '', 'V', 'directory', root_entry.file_id, root_entry
2266
root_entry = inventory.make_entry(
2267
'directory', '', ROOT_PARENT, self.path2id(''))
2268
yield '', 'V', 'directory', root_entry
1999
2269
entries = self._iter_entries_for_dir(from_dir or '')
2000
2270
for path, entry in entries:
2001
yield path, 'V', entry.kind, entry.file_id, entry
2271
yield path, 'V', entry.kind, entry
2003
def kind(self, file_id):
2004
trans_id = self._transform.trans_id_file_id(file_id)
2273
def kind(self, path):
2274
trans_id = self._path2trans_id(path)
2275
if trans_id is None:
2276
raise errors.NoSuchFile(path)
2005
2277
return self._transform.final_kind(trans_id)
2007
def stored_kind(self, file_id):
2008
trans_id = self._transform.trans_id_file_id(file_id)
2279
def stored_kind(self, path):
2280
trans_id = self._path2trans_id(path)
2281
if trans_id is None:
2282
raise errors.NoSuchFile(path)
2010
2284
return self._transform._new_contents[trans_id]
2011
2285
except KeyError:
2012
return self._transform._tree.stored_kind(file_id)
2286
return self._transform._tree.stored_kind(path)
2014
def get_file_mtime(self, file_id, path=None):
2288
def get_file_mtime(self, path):
2015
2289
"""See Tree.get_file_mtime"""
2290
file_id = self.path2id(path)
2292
raise errors.NoSuchFile(path)
2016
2293
if not self._content_change(file_id):
2017
return self._transform._tree.get_file_mtime(file_id)
2018
return self._stat_limbo_file(file_id).st_mtime
2020
def _file_size(self, entry, stat_value):
2021
return self.get_file_size(entry.file_id)
2023
def get_file_size(self, file_id):
2294
return self._transform._tree.get_file_mtime(
2295
self._transform._tree.id2path(file_id))
2296
trans_id = self._path2trans_id(path)
2297
return self._stat_limbo_file(trans_id).st_mtime
2299
def get_file_size(self, path):
2024
2300
"""See Tree.get_file_size"""
2025
if self.kind(file_id) == 'file':
2026
return self._transform._tree.get_file_size(file_id)
2301
trans_id = self._path2trans_id(path)
2302
if trans_id is None:
2303
raise errors.NoSuchFile(path)
2304
kind = self._transform.final_kind(trans_id)
2307
if trans_id in self._transform._new_contents:
2308
return self._stat_limbo_file(trans_id).st_size
2309
if self.kind(path) == 'file':
2310
return self._transform._tree.get_file_size(path)
2030
def get_file_sha1(self, file_id, path=None, stat_value=None):
2031
trans_id = self._transform.trans_id_file_id(file_id)
2032
kind = self._transform._new_contents.get(trans_id)
2034
return self._transform._tree.get_file_sha1(file_id)
2036
fileobj = self.get_file(file_id)
2314
def get_file_verifier(self, path, stat_value=None):
2315
trans_id = self._path2trans_id(path)
2316
if trans_id is None:
2317
raise errors.NoSuchFile(path)
2318
kind = self._transform._new_contents.get(trans_id)
2320
return self._transform._tree.get_file_verifier(path)
2322
with self.get_file(path) as fileobj:
2323
return ("SHA1", sha_file(fileobj))
2325
def get_file_sha1(self, path, stat_value=None):
2326
trans_id = self._path2trans_id(path)
2327
if trans_id is None:
2328
raise errors.NoSuchFile(path)
2329
kind = self._transform._new_contents.get(trans_id)
2331
return self._transform._tree.get_file_sha1(path)
2333
with self.get_file(path) as fileobj:
2038
2334
return sha_file(fileobj)
2042
def is_executable(self, file_id, path=None):
2336
def is_executable(self, path):
2337
trans_id = self._path2trans_id(path)
2338
if trans_id is None:
2045
trans_id = self._transform.trans_id_file_id(file_id)
2047
2341
return self._transform._new_executability[trans_id]
2048
2342
except KeyError:
2050
return self._transform._tree.is_executable(file_id, path)
2344
return self._transform._tree.is_executable(path)
2345
except OSError as e:
2052
2346
if e.errno == errno.ENOENT:
2055
except errors.NoSuchId:
2349
except errors.NoSuchFile:
2352
def has_filename(self, path):
2353
trans_id = self._path2trans_id(path)
2354
if trans_id in self._transform._new_contents:
2356
elif trans_id in self._transform._removed_contents:
2359
return self._transform._tree.has_filename(path)
2058
2361
def path_content_summary(self, path):
2059
2362
trans_id = self._path2trans_id(path)
2060
2363
tt = self._transform
2106
2411
raise ValueError('want_unversioned is not supported')
2107
2412
return self._transform.iter_changes()
2109
def get_file(self, file_id, path=None):
2414
def get_file(self, path):
2110
2415
"""See Tree.get_file"""
2416
file_id = self.path2id(path)
2111
2417
if not self._content_change(file_id):
2112
return self._transform._tree.get_file(file_id, path)
2113
trans_id = self._transform.trans_id_file_id(file_id)
2418
return self._transform._tree.get_file(path)
2419
trans_id = self._path2trans_id(path)
2114
2420
name = self._transform._limbo_name(trans_id)
2115
2421
return open(name, 'rb')
2117
def get_file_with_stat(self, file_id, path=None):
2118
return self.get_file(file_id, path), None
2423
def get_file_with_stat(self, path):
2424
return self.get_file(path), None
2120
def annotate_iter(self, file_id,
2426
def annotate_iter(self, path,
2121
2427
default_revision=_mod_revision.CURRENT_REVISION):
2428
file_id = self.path2id(path)
2122
2429
changes = self._iter_changes_cache.get(file_id)
2123
2430
if changes is None:
2126
changed_content, versioned, kind = (changes[2], changes[3],
2433
changed_content, versioned, kind = (
2434
changes.changed_content, changes.versioned, changes.kind)
2128
2435
if kind[1] is None:
2130
2437
get_old = (kind[0] == 'file' and versioned[0])
2132
old_annotation = self._transform._tree.annotate_iter(file_id,
2133
default_revision=default_revision)
2439
old_annotation = self._transform._tree.annotate_iter(
2440
path, default_revision=default_revision)
2135
2442
old_annotation = []
2136
2443
if changes is None:
2268
2570
:param delta_from_tree: If true, build_tree may use the input Tree to
2269
2571
generate the inventory delta.
2271
wt.lock_tree_write()
2275
if accelerator_tree is not None:
2276
accelerator_tree.lock_read()
2278
return _build_tree(tree, wt, accelerator_tree, hardlink,
2281
if accelerator_tree is not None:
2282
accelerator_tree.unlock()
2573
with cleanup.ExitStack() as exit_stack:
2574
exit_stack.enter_context(wt.lock_tree_write())
2575
exit_stack.enter_context(tree.lock_read())
2576
if accelerator_tree is not None:
2577
exit_stack.enter_context(accelerator_tree.lock_read())
2578
return _build_tree(tree, wt, accelerator_tree, hardlink,
2289
2582
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
2290
2583
"""See build_tree."""
2291
for num, _unused in enumerate(wt.all_file_ids()):
2584
for num, _unused in enumerate(wt.all_versioned_paths()):
2292
2585
if num > 0: # more than just a root
2293
2586
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
2294
existing_files = set()
2295
for dir, files in wt.walkdirs():
2296
existing_files.update(f[0] for f in files)
2297
2587
file_trans_id = {}
2298
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2588
top_pb = ui.ui_factory.nested_progress_bar()
2299
2589
pp = ProgressPhase("Build phase", 2, top_pb)
2300
if tree.inventory.root is not None:
2590
if tree.path2id('') is not None:
2301
2591
# This is kind of a hack: we should be altering the root
2302
2592
# as part of the regular tree shape diff logic.
2303
2593
# The conditional test here is to avoid doing an
2305
2595
# is set within the tree, nor setting the root and thus
2306
2596
# marking the tree as dirty, because we use two different
2307
2597
# idioms here: tree interfaces and inventory interfaces.
2308
if wt.get_root_id() != tree.get_root_id():
2309
wt.set_root_id(tree.get_root_id())
2598
if wt.path2id('') != tree.path2id(''):
2599
wt.set_root_id(tree.path2id(''))
2311
tt = TreeTransform(wt)
2601
tt = wt.get_transform()
2314
2604
pp.next_phase()
2315
file_trans_id[wt.get_root_id()] = \
2316
tt.trans_id_tree_file_id(wt.get_root_id())
2317
pb = bzrlib.ui.ui_factory.nested_progress_bar()
2605
file_trans_id[wt.path2id('')] = tt.trans_id_tree_path('')
2606
with ui.ui_factory.nested_progress_bar() as pb:
2319
2607
deferred_contents = []
2321
total = len(tree.inventory)
2609
total = len(tree.all_versioned_paths())
2322
2610
if delta_from_tree:
2323
2611
precomputed_delta = []
2325
2613
precomputed_delta = None
2614
# Check if tree inventory has content. If so, we populate
2615
# existing_files with the directory content. If there are no
2616
# entries we skip populating existing_files as its not used.
2617
# This improves performance and unncessary work on large
2618
# directory trees. (#501307)
2620
existing_files = set()
2621
for dir, files in wt.walkdirs():
2622
existing_files.update(f[0] for f in files)
2326
2623
for num, (tree_path, entry) in \
2327
enumerate(tree.inventory.iter_entries_by_dir()):
2328
pb.update("Building tree", num - len(deferred_contents), total)
2624
enumerate(tree.iter_entries_by_dir()):
2625
pb.update(gettext("Building tree"), num
2626
- len(deferred_contents), total)
2329
2627
if entry.parent_id is None:
2331
2629
reparent = False
2401
2700
new_desired_files = desired_files
2403
2702
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2404
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2405
in iter if not (c or e[0] != e[1])]
2704
change.path for change in iter
2705
if not (change.changed_content or change.executable[0] != change.executable[1])]
2406
2706
if accelerator_tree.supports_content_filtering():
2407
unchanged = [(f, p) for (f, p) in unchanged
2408
if not accelerator_tree.iter_search_rules([p]).next()]
2707
unchanged = [(tp, ap) for (tp, ap) in unchanged
2708
if not next(accelerator_tree.iter_search_rules([ap]))]
2409
2709
unchanged = dict(unchanged)
2410
2710
new_desired_files = []
2412
for file_id, (trans_id, tree_path) in desired_files:
2413
accelerator_path = unchanged.get(file_id)
2712
for unused_tree_path, (trans_id, file_id, tree_path, text_sha1) in desired_files:
2713
accelerator_path = unchanged.get(tree_path)
2414
2714
if accelerator_path is None:
2415
new_desired_files.append((file_id, (trans_id, tree_path)))
2715
new_desired_files.append((tree_path,
2716
(trans_id, file_id, tree_path, text_sha1)))
2417
pb.update('Adding file contents', count + offset, total)
2718
pb.update(gettext('Adding file contents'), count + offset, total)
2419
2720
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2422
contents = accelerator_tree.get_file(file_id, accelerator_path)
2423
if wt.supports_content_filtering():
2424
filters = wt._content_filter_stack(tree_path)
2425
contents = filtered_output_bytes(contents, filters,
2426
ContentFilterContext(tree_path, tree))
2428
tt.create_file(contents, trans_id)
2432
except AttributeError:
2433
# after filtering, contents may no longer be file-like
2723
with accelerator_tree.get_file(accelerator_path) as f:
2724
chunks = osutils.file_iterator(f)
2725
if wt.supports_content_filtering():
2726
filters = wt._content_filter_stack(tree_path)
2727
chunks = filtered_output_bytes(chunks, filters,
2728
ContentFilterContext(tree_path, tree))
2729
tt.create_file(chunks, trans_id, sha1=text_sha1)
2436
2731
offset += count
2437
for count, ((trans_id, tree_path), contents) in enumerate(
2732
for count, ((trans_id, file_id, tree_path, text_sha1), contents) in enumerate(
2438
2733
tree.iter_files_bytes(new_desired_files)):
2439
2734
if wt.supports_content_filtering():
2440
2735
filters = wt._content_filter_stack(tree_path)
2441
2736
contents = filtered_output_bytes(contents, filters,
2442
ContentFilterContext(tree_path, tree))
2443
tt.create_file(contents, trans_id)
2444
pb.update('Adding file contents', count + offset, total)
2737
ContentFilterContext(tree_path, tree))
2738
tt.create_file(contents, trans_id, sha1=text_sha1)
2739
pb.update(gettext('Adding file contents'), count + offset, total)
2447
2742
def _reparent_children(tt, old_parent, new_parent):
2448
2743
for child in tt.iter_tree_children(old_parent):
2449
2744
tt.adjust_path(tt.final_name(child), new_parent, child)
2451
2747
def _reparent_transform_children(tt, old_parent, new_parent):
2452
2748
by_parent = tt.by_parent()
2453
2749
for child in by_parent[old_parent]:
2454
2750
tt.adjust_path(tt.final_name(child), new_parent, child)
2455
2751
return by_parent[old_parent]
2457
def _content_match(tree, entry, file_id, kind, target_path):
2754
def _content_match(tree, entry, tree_path, kind, target_path):
2458
2755
if entry.kind != kind:
2460
2757
if entry.kind == "directory":
2462
2759
if entry.kind == "file":
2463
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
2760
with open(target_path, 'rb') as f1, \
2761
tree.get_file(tree_path) as f2:
2762
if osutils.compare_files(f1, f2):
2465
2764
elif entry.kind == "symlink":
2466
if tree.get_symlink_target(file_id) == os.readlink(target_path):
2765
if tree.get_symlink_target(tree_path) == os.readlink(target_path):
2487
2786
final_parent = tt.final_parent(old_file)
2488
2787
if new_file in divert:
2489
new_name = tt.final_name(old_file)+'.diverted'
2788
new_name = tt.final_name(old_file) + '.diverted'
2490
2789
tt.adjust_path(new_name, final_parent, new_file)
2491
2790
new_conflicts.add((c_type, 'Diverted to',
2492
2791
new_file, old_file))
2494
new_name = tt.final_name(old_file)+'.moved'
2793
new_name = tt.final_name(old_file) + '.moved'
2495
2794
tt.adjust_path(new_name, final_parent, old_file)
2496
2795
new_conflicts.add((c_type, 'Moved existing file to',
2497
2796
old_file, new_file))
2498
2797
return new_conflicts
2501
def new_by_entry(tt, entry, parent_id, tree):
2800
def new_by_entry(path, tt, entry, parent_id, tree):
2502
2801
"""Create a new file according to its inventory entry"""
2503
2802
name = entry.name
2504
2803
kind = entry.kind
2505
2804
if kind == 'file':
2506
contents = tree.get_file(entry.file_id).readlines()
2507
executable = tree.is_executable(entry.file_id)
2508
return tt.new_file(name, parent_id, contents, entry.file_id,
2805
with tree.get_file(path) as f:
2806
executable = tree.is_executable(path)
2808
name, parent_id, osutils.file_iterator(f), entry.file_id,
2510
2810
elif kind in ('directory', 'tree-reference'):
2511
2811
trans_id = tt.new_directory(name, parent_id, entry.file_id)
2512
2812
if kind == 'tree-reference':
2513
2813
tt.set_tree_reference(entry.reference_revision, trans_id)
2514
2814
return trans_id
2515
2815
elif kind == 'symlink':
2516
target = tree.get_symlink_target(entry.file_id)
2816
target = tree.get_symlink_target(path)
2517
2817
return tt.new_symlink(name, parent_id, target, entry.file_id)
2519
2819
raise errors.BadFileKindError(name, kind)
2522
@deprecated_function(deprecated_in((1, 9, 0)))
2523
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
2524
"""Create new file contents according to an inventory entry.
2526
DEPRECATED. Use create_from_tree instead.
2528
if entry.kind == "file":
2530
lines = tree.get_file(entry.file_id).readlines()
2531
tt.create_file(lines, trans_id, mode_id=mode_id)
2532
elif entry.kind == "symlink":
2533
tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
2534
elif entry.kind == "directory":
2535
tt.create_directory(trans_id)
2538
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2539
filter_tree_path=None):
2822
def create_from_tree(tt, trans_id, tree, path, chunks=None,
2823
filter_tree_path=None):
2540
2824
"""Create new file contents according to tree contents.
2542
2826
:param filter_tree_path: the tree path to use to lookup
2543
2827
content filters to apply to the bytes output in the working tree.
2544
2828
This only applies if the working tree supports content filtering.
2546
kind = tree.kind(file_id)
2830
kind = tree.kind(path)
2547
2831
if kind == 'directory':
2548
2832
tt.create_directory(trans_id)
2549
2833
elif kind == "file":
2551
tree_file = tree.get_file(file_id)
2553
bytes = tree_file.readlines()
2557
if wt.supports_content_filtering() and filter_tree_path is not None:
2558
filters = wt._content_filter_stack(filter_tree_path)
2559
bytes = filtered_output_bytes(bytes, filters,
2560
ContentFilterContext(filter_tree_path, tree))
2561
tt.create_file(bytes, trans_id)
2835
f = tree.get_file(path)
2836
chunks = osutils.file_iterator(f)
2841
if wt.supports_content_filtering() and filter_tree_path is not None:
2842
filters = wt._content_filter_stack(filter_tree_path)
2843
chunks = filtered_output_bytes(
2845
ContentFilterContext(filter_tree_path, tree))
2846
tt.create_file(chunks, trans_id)
2562
2850
elif kind == "symlink":
2563
tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2851
tt.create_symlink(tree.get_symlink_target(path), trans_id)
2565
2853
raise AssertionError('Unknown kind %r' % kind)
2571
2859
tt.set_executability(entry.executable, trans_id)
2574
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2575
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2578
def _get_backup_name(name, by_parent, parent_trans_id, tt):
2579
"""Produce a backup-style name that appears to be available"""
2583
yield "%s.~%d~" % (name, counter)
2585
for new_name in name_gen():
2586
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
2590
def _entry_changes(file_id, entry, working_tree):
2591
"""Determine in which ways the inventory entry has changed.
2593
Returns booleans: has_contents, content_mod, meta_mod
2594
has_contents means there are currently contents, but they differ
2595
contents_mod means contents need to be modified
2596
meta_mod means the metadata needs to be modified
2598
cur_entry = working_tree.inventory[file_id]
2600
working_kind = working_tree.kind(file_id)
2603
has_contents = False
2606
if has_contents is True:
2607
if entry.kind != working_kind:
2608
contents_mod, meta_mod = True, False
2610
cur_entry._read_tree_state(working_tree.id2path(file_id),
2612
contents_mod, meta_mod = entry.detect_changes(cur_entry)
2613
cur_entry._forget_tree_state()
2614
return has_contents, contents_mod, meta_mod
2617
2862
def revert(working_tree, target_tree, filenames, backups=False,
2618
2863
pb=None, change_reporter=None):
2619
2864
"""Revert a working tree's contents to those of a target tree."""
2620
target_tree.lock_read()
2621
2865
pb = ui.ui_factory.nested_progress_bar()
2622
tt = TreeTransform(working_tree, pb)
2624
pp = ProgressPhase("Revert phase", 3, pb)
2625
conflicts, merge_modified = _prepare_revert_transform(
2626
working_tree, target_tree, tt, filenames, backups, pp)
2628
change_reporter = delta._ChangeReporter(
2629
unversioned_filter=working_tree.is_ignored)
2630
delta.report_changes(tt.iter_changes(), change_reporter)
2631
for conflict in conflicts:
2635
working_tree.set_merge_modified(merge_modified)
2867
with target_tree.lock_read(), working_tree.get_transform(pb) as tt:
2868
pp = ProgressPhase("Revert phase", 3, pb)
2869
conflicts, merge_modified = _prepare_revert_transform(
2870
working_tree, target_tree, tt, filenames, backups, pp)
2872
change_reporter = delta._ChangeReporter(
2873
unversioned_filter=working_tree.is_ignored)
2874
delta.report_changes(tt.iter_changes(), change_reporter)
2875
for conflict in conflicts:
2876
trace.warning(text_type(conflict))
2879
if working_tree.supports_merge_modified():
2880
working_tree.set_merge_modified(merge_modified)
2637
target_tree.unlock()
2640
2883
return conflicts
2666
2903
backups, merge_modified, basis_tree=None):
2667
2904
if basis_tree is not None:
2668
2905
basis_tree.lock_read()
2669
change_list = target_tree.iter_changes(working_tree,
2670
specific_files=specific_files, pb=pb)
2671
if target_tree.get_root_id() is None:
2906
# We ask the working_tree for its changes relative to the target, rather
2907
# than the target changes relative to the working tree. Because WT4 has an
2908
# optimizer to compare itself to a target, but no optimizer for the
2910
change_list = working_tree.iter_changes(
2911
target_tree, specific_files=specific_files, pb=pb)
2912
if not target_tree.is_versioned(u''):
2672
2913
skip_root = True
2674
2915
skip_root = False
2676
2917
deferred_files = []
2677
for id_num, (file_id, path, changed_content, versioned, parent, name,
2678
kind, executable) in enumerate(change_list):
2679
if skip_root and file_id[0] is not None and parent[0] is None:
2918
for id_num, change in enumerate(change_list):
2919
file_id = change.file_id
2920
target_path, wt_path = change.path
2921
target_versioned, wt_versioned = change.versioned
2922
target_parent, wt_parent = change.parent_id
2923
target_name, wt_name = change.name
2924
target_kind, wt_kind = change.kind
2925
target_executable, wt_executable = change.executable
2926
if skip_root and wt_parent is None:
2681
2928
trans_id = tt.trans_id_file_id(file_id)
2930
if change.changed_content:
2684
2931
keep_content = False
2685
if kind[0] == 'file' and (backups or kind[1] is None):
2686
wt_sha1 = working_tree.get_file_sha1(file_id)
2687
if merge_modified.get(file_id) != wt_sha1:
2932
if wt_kind == 'file' and (backups or target_kind is None):
2933
wt_sha1 = working_tree.get_file_sha1(wt_path)
2934
if merge_modified.get(wt_path) != wt_sha1:
2688
2935
# acquire the basis tree lazily to prevent the
2689
2936
# expense of accessing it when it's not needed ?
2690
2937
# (Guessing, RBC, 200702)
2691
2938
if basis_tree is None:
2692
2939
basis_tree = working_tree.basis_tree()
2693
2940
basis_tree.lock_read()
2694
if file_id in basis_tree:
2695
if wt_sha1 != basis_tree.get_file_sha1(file_id):
2697
elif kind[1] is None and not versioned[1]:
2699
if kind[0] is not None:
2941
basis_path = find_previous_path(
2942
working_tree, basis_tree, wt_path)
2943
if basis_path is None:
2944
if target_kind is None and not target_versioned:
2947
if wt_sha1 != basis_tree.get_file_sha1(basis_path):
2949
if wt_kind is not None:
2700
2950
if not keep_content:
2701
2951
tt.delete_contents(trans_id)
2702
elif kind[1] is not None:
2703
parent_trans_id = tt.trans_id_file_id(parent[0])
2704
by_parent = tt.by_parent()
2705
backup_name = _get_backup_name(name[0], by_parent,
2706
parent_trans_id, tt)
2952
elif target_kind is not None:
2953
parent_trans_id = tt.trans_id_file_id(wt_parent)
2954
backup_name = tt._available_backup_name(
2955
wt_name, parent_trans_id)
2707
2956
tt.adjust_path(backup_name, parent_trans_id, trans_id)
2708
new_trans_id = tt.create_path(name[0], parent_trans_id)
2709
if versioned == (True, True):
2957
new_trans_id = tt.create_path(wt_name, parent_trans_id)
2958
if wt_versioned and target_versioned:
2710
2959
tt.unversion_file(trans_id)
2711
2960
tt.version_file(file_id, new_trans_id)
2712
2961
# New contents should have the same unix perms as old
2714
2963
mode_id = trans_id
2715
2964
trans_id = new_trans_id
2716
if kind[1] in ('directory', 'tree-reference'):
2965
if target_kind in ('directory', 'tree-reference'):
2717
2966
tt.create_directory(trans_id)
2718
if kind[1] == 'tree-reference':
2719
revision = target_tree.get_reference_revision(file_id,
2967
if target_kind == 'tree-reference':
2968
revision = target_tree.get_reference_revision(
2721
2970
tt.set_tree_reference(revision, trans_id)
2722
elif kind[1] == 'symlink':
2723
tt.create_symlink(target_tree.get_symlink_target(file_id),
2725
elif kind[1] == 'file':
2726
deferred_files.append((file_id, (trans_id, mode_id)))
2971
elif target_kind == 'symlink':
2972
tt.create_symlink(target_tree.get_symlink_target(
2973
target_path), trans_id)
2974
elif target_kind == 'file':
2975
deferred_files.append(
2976
(target_path, (trans_id, mode_id, file_id)))
2727
2977
if basis_tree is None:
2728
2978
basis_tree = working_tree.basis_tree()
2729
2979
basis_tree.lock_read()
2730
new_sha1 = target_tree.get_file_sha1(file_id)
2731
if (file_id in basis_tree and new_sha1 ==
2732
basis_tree.get_file_sha1(file_id)):
2733
if file_id in merge_modified:
2734
del merge_modified[file_id]
2980
new_sha1 = target_tree.get_file_sha1(target_path)
2981
basis_path = find_previous_path(target_tree, basis_tree, target_path)
2982
if (basis_path is not None and
2983
new_sha1 == basis_tree.get_file_sha1(basis_path)):
2984
# If the new contents of the file match what is in basis,
2985
# then there is no need to store in merge_modified.
2986
if basis_path in merge_modified:
2987
del merge_modified[basis_path]
2736
merge_modified[file_id] = new_sha1
2989
merge_modified[target_path] = new_sha1
2738
2991
# preserve the execute bit when backing up
2739
if keep_content and executable[0] == executable[1]:
2740
tt.set_executability(executable[1], trans_id)
2741
elif kind[1] is not None:
2742
raise AssertionError(kind[1])
2743
if versioned == (False, True):
2992
if keep_content and wt_executable == target_executable:
2993
tt.set_executability(target_executable, trans_id)
2994
elif target_kind is not None:
2995
raise AssertionError(target_kind)
2996
if not wt_versioned and target_versioned:
2744
2997
tt.version_file(file_id, trans_id)
2745
if versioned == (True, False):
2998
if wt_versioned and not target_versioned:
2746
2999
tt.unversion_file(trans_id)
2747
if (name[1] is not None and
2748
(name[0] != name[1] or parent[0] != parent[1])):
2749
if name[1] == '' and parent[1] is None:
3000
if (target_name is not None
3001
and (wt_name != target_name or wt_parent != target_parent)):
3002
if target_name == '' and target_parent is None:
2750
3003
parent_trans = ROOT_PARENT
2752
parent_trans = tt.trans_id_file_id(parent[1])
2753
if parent[0] is None and versioned[0]:
2754
tt.adjust_root_path(name[1], parent_trans)
3005
parent_trans = tt.trans_id_file_id(target_parent)
3006
if wt_parent is None and wt_versioned:
3007
tt.adjust_root_path(target_name, parent_trans)
2756
tt.adjust_path(name[1], parent_trans, trans_id)
2757
if executable[0] != executable[1] and kind[1] == "file":
2758
tt.set_executability(executable[1], trans_id)
3009
tt.adjust_path(target_name, parent_trans, trans_id)
3010
if wt_executable != target_executable and target_kind == "file":
3011
tt.set_executability(target_executable, trans_id)
2759
3012
if working_tree.supports_content_filtering():
2760
for index, ((trans_id, mode_id), bytes) in enumerate(
2761
target_tree.iter_files_bytes(deferred_files)):
2762
file_id = deferred_files[index][0]
3013
for (trans_id, mode_id, file_id), bytes in (
3014
target_tree.iter_files_bytes(deferred_files)):
2763
3015
# We're reverting a tree to the target tree so using the
2764
3016
# target tree to find the file path seems the best choice
2765
3017
# here IMO - Ian C 27/Oct/2009
2766
3018
filter_tree_path = target_tree.id2path(file_id)
2767
3019
filters = working_tree._content_filter_stack(filter_tree_path)
2768
bytes = filtered_output_bytes(bytes, filters,
3020
bytes = filtered_output_bytes(
2769
3022
ContentFilterContext(filter_tree_path, working_tree))
2770
3023
tt.create_file(bytes, trans_id, mode_id)
2772
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
3025
for (trans_id, mode_id, file_id), bytes in target_tree.iter_files_bytes(
2774
3027
tt.create_file(bytes, trans_id, mode_id)
2775
3028
tt.fixup_new_roots()