303
251
return ROOT_PARENT
304
252
return self.trans_id_tree_path(os.path.dirname(path))
306
def create_file(self, contents, trans_id, mode_id=None):
307
"""Schedule creation of a new file.
311
Contents is an iterator of strings, all of which will be written
312
to the target destination.
314
New file takes the permissions of any existing file with that id,
315
unless mode_id is specified.
317
name = self._limbo_name(trans_id)
321
unique_add(self._new_contents, trans_id, 'file')
323
# Clean up the file, it never got registered so
324
# TreeTransform.finalize() won't clean it up.
329
f.writelines(contents)
332
self._set_mode(trans_id, mode_id, S_ISREG)
334
def _set_mode(self, trans_id, mode_id, typefunc):
335
"""Set the mode of new file contents.
336
The mode_id is the existing file to get the mode from (often the same
337
as trans_id). The operation is only performed if there's a mode match
338
according to typefunc.
343
old_path = self._tree_id_paths[mode_id]
347
mode = os.stat(self._tree.abspath(old_path)).st_mode
349
if e.errno == errno.ENOENT:
354
os.chmod(self._limbo_name(trans_id), mode)
356
def create_hardlink(self, path, trans_id):
357
"""Schedule creation of a hard link"""
358
name = self._limbo_name(trans_id)
362
if e.errno != errno.EPERM:
364
raise errors.HardLinkNotSupported(path)
366
unique_add(self._new_contents, trans_id, 'file')
368
# Clean up the file, it never got registered so
369
# TreeTransform.finalize() won't clean it up.
373
def create_directory(self, trans_id):
374
"""Schedule creation of a new directory.
376
See also new_directory.
378
os.mkdir(self._limbo_name(trans_id))
379
unique_add(self._new_contents, trans_id, 'directory')
381
def create_symlink(self, target, trans_id):
382
"""Schedule creation of a new symbolic link.
384
target is a bytestring.
385
See also new_symlink.
388
os.symlink(target, self._limbo_name(trans_id))
389
unique_add(self._new_contents, trans_id, 'symlink')
392
path = FinalPaths(self).get_path(trans_id)
395
raise UnableCreateSymlink(path=path)
397
def cancel_creation(self, trans_id):
398
"""Cancel the creation of new file contents."""
399
del self._new_contents[trans_id]
400
children = self._limbo_children.get(trans_id)
401
# if this is a limbo directory with children, move them before removing
403
if children is not None:
404
self._rename_in_limbo(children)
405
del self._limbo_children[trans_id]
406
del self._limbo_children_names[trans_id]
407
delete_any(self._limbo_name(trans_id))
409
254
def delete_contents(self, trans_id):
410
255
"""Schedule the contents of a path entry for deletion"""
411
256
self.tree_kind(trans_id)
450
295
del self._new_id[trans_id]
451
296
del self._r_new_id[file_id]
454
"""Determine the paths of all new and changed files"""
456
fp = FinalPaths(self)
457
for id_set in (self._new_name, self._new_parent, self._new_contents,
458
self._new_id, self._new_executability):
459
new_ids.update(id_set)
460
new_paths = [(fp.get_path(t), t) for t in new_ids]
464
def tree_kind(self, trans_id):
465
"""Determine the file kind in the working tree.
467
Raises NoSuchFile if the file does not exist
298
def new_paths(self, filesystem_only=False):
299
"""Determine the paths of all new and changed files.
301
:param filesystem_only: if True, only calculate values for files
302
that require renames or execute bit changes.
469
path = self._tree_id_paths.get(trans_id)
471
raise NoSuchFile(None)
473
return file_kind(self._tree.abspath(path))
475
if e.errno != errno.ENOENT:
478
raise NoSuchFile(path)
306
stale_ids = self._needs_rename.difference(self._new_name)
307
stale_ids.difference_update(self._new_parent)
308
stale_ids.difference_update(self._new_contents)
309
stale_ids.difference_update(self._new_id)
310
needs_rename = self._needs_rename.difference(stale_ids)
311
id_sets = (needs_rename, self._new_executability)
313
id_sets = (self._new_name, self._new_parent, self._new_contents,
314
self._new_id, self._new_executability)
315
for id_set in id_sets:
316
new_ids.update(id_set)
317
return sorted(FinalPaths(self).get_paths(new_ids))
319
def _inventory_altered(self):
320
"""Get the trans_ids and paths of files needing new inv entries."""
322
for id_set in [self._new_name, self._new_parent, self._new_id,
323
self._new_executability]:
324
new_ids.update(id_set)
325
changed_kind = set(self._removed_contents)
326
changed_kind.intersection_update(self._new_contents)
327
changed_kind.difference_update(new_ids)
328
changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
330
new_ids.update(changed_kind)
331
return sorted(FinalPaths(self).get_paths(new_ids))
480
333
def final_kind(self, trans_id):
481
334
"""Determine the final file kind, after any changes applied.
483
336
Raises NoSuchFile if the file does not exist/has no contents.
484
337
(It is conceivable that a path would be created without the
485
338
corresponding contents insertion command)
1071
860
return _PreviewTree(self)
1074
class TreeTransform(TreeTransformBase):
862
def _text_parent(self, trans_id):
863
file_id = self.tree_file_id(trans_id)
865
if file_id is None or self._tree.kind(file_id) != 'file':
867
except errors.NoSuchFile:
871
def _get_parents_texts(self, trans_id):
872
"""Get texts for compression parents of this file."""
873
file_id = self._text_parent(trans_id)
876
return (self._tree.get_file_text(file_id),)
878
def _get_parents_lines(self, trans_id):
879
"""Get lines for compression parents of this file."""
880
file_id = self._text_parent(trans_id)
883
return (self._tree.get_file_lines(file_id),)
885
def serialize(self, serializer):
886
"""Serialize this TreeTransform.
888
:param serializer: A Serialiser like pack.ContainerSerializer.
890
new_name = dict((k, v.encode('utf-8')) for k, v in
891
self._new_name.items())
892
new_executability = dict((k, int(v)) for k, v in
893
self._new_executability.items())
894
tree_path_ids = dict((k.encode('utf-8'), v)
895
for k, v in self._tree_path_ids.items())
897
'_id_number': self._id_number,
898
'_new_name': new_name,
899
'_new_parent': self._new_parent,
900
'_new_executability': new_executability,
901
'_new_id': self._new_id,
902
'_tree_path_ids': tree_path_ids,
903
'_removed_id': list(self._removed_id),
904
'_removed_contents': list(self._removed_contents),
905
'_non_present_ids': self._non_present_ids,
907
yield serializer.bytes_record(bencode.bencode(attribs),
909
for trans_id, kind in self._new_contents.items():
911
lines = osutils.chunks_to_lines(
912
self._read_file_chunks(trans_id))
913
parents = self._get_parents_lines(trans_id)
914
mpdiff = multiparent.MultiParent.from_lines(lines, parents)
915
content = ''.join(mpdiff.to_patch())
916
if kind == 'directory':
918
if kind == 'symlink':
919
content = self._read_symlink_target(trans_id)
920
yield serializer.bytes_record(content, ((trans_id, kind),))
922
def deserialize(self, records):
923
"""Deserialize a stored TreeTransform.
925
:param records: An iterable of (names, content) tuples, as per
926
pack.ContainerPushParser.
928
names, content = records.next()
929
attribs = bencode.bdecode(content)
930
self._id_number = attribs['_id_number']
931
self._new_name = dict((k, v.decode('utf-8'))
932
for k, v in attribs['_new_name'].items())
933
self._new_parent = attribs['_new_parent']
934
self._new_executability = dict((k, bool(v)) for k, v in
935
attribs['_new_executability'].items())
936
self._new_id = attribs['_new_id']
937
self._r_new_id = dict((v, k) for k, v in self._new_id.items())
938
self._tree_path_ids = {}
939
self._tree_id_paths = {}
940
for bytepath, trans_id in attribs['_tree_path_ids'].items():
941
path = bytepath.decode('utf-8')
942
self._tree_path_ids[path] = trans_id
943
self._tree_id_paths[trans_id] = path
944
self._removed_id = set(attribs['_removed_id'])
945
self._removed_contents = set(attribs['_removed_contents'])
946
self._non_present_ids = attribs['_non_present_ids']
947
for ((trans_id, kind),), content in records:
949
mpdiff = multiparent.MultiParent.from_patch(content)
950
lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
951
self.create_file(lines, trans_id)
952
if kind == 'directory':
953
self.create_directory(trans_id)
954
if kind == 'symlink':
955
self.create_symlink(content.decode('utf-8'), trans_id)
958
class DiskTreeTransform(TreeTransformBase):
959
"""Tree transform storing its contents on disk."""
961
def __init__(self, tree, limbodir, pb=DummyProgress(),
962
case_sensitive=True):
964
:param tree: The tree that will be transformed, but not necessarily
966
:param limbodir: A directory where new files can be stored until
967
they are installed in their proper places
968
:param pb: A ProgressBar indicating how much progress is being made
969
:param case_sensitive: If True, the target of the transform is
970
case sensitive, not just case preserving.
972
TreeTransformBase.__init__(self, tree, pb, case_sensitive)
973
self._limbodir = limbodir
974
self._deletiondir = None
975
# A mapping of transform ids to their limbo filename
976
self._limbo_files = {}
977
# A mapping of transform ids to a set of the transform ids of children
978
# that their limbo directory has
979
self._limbo_children = {}
980
# Map transform ids to maps of child filename to child transform id
981
self._limbo_children_names = {}
982
# List of transform ids that need to be renamed from limbo into place
983
self._needs_rename = set()
986
"""Release the working tree lock, if held, clean up limbo dir.
988
This is required if apply has not been invoked, but can be invoked
991
if self._tree is None:
994
entries = [(self._limbo_name(t), t, k) for t, k in
995
self._new_contents.iteritems()]
996
entries.sort(reverse=True)
997
for path, trans_id, kind in entries:
998
if kind == "directory":
1003
os.rmdir(self._limbodir)
1005
# We don't especially care *why* the dir is immortal.
1006
raise ImmortalLimbo(self._limbodir)
1008
if self._deletiondir is not None:
1009
os.rmdir(self._deletiondir)
1011
raise errors.ImmortalPendingDeletion(self._deletiondir)
1013
TreeTransformBase.finalize(self)
1015
def _limbo_name(self, trans_id):
1016
"""Generate the limbo name of a file"""
1017
limbo_name = self._limbo_files.get(trans_id)
1018
if limbo_name is not None:
1020
parent = self._new_parent.get(trans_id)
1021
# if the parent directory is already in limbo (e.g. when building a
1022
# tree), choose a limbo name inside the parent, to reduce further
1024
use_direct_path = False
1025
if self._new_contents.get(parent) == 'directory':
1026
filename = self._new_name.get(trans_id)
1027
if filename is not None:
1028
if parent not in self._limbo_children:
1029
self._limbo_children[parent] = set()
1030
self._limbo_children_names[parent] = {}
1031
use_direct_path = True
1032
# the direct path can only be used if no other file has
1033
# already taken this pathname, i.e. if the name is unused, or
1034
# if it is already associated with this trans_id.
1035
elif self._case_sensitive_target:
1036
if (self._limbo_children_names[parent].get(filename)
1037
in (trans_id, None)):
1038
use_direct_path = True
1040
for l_filename, l_trans_id in\
1041
self._limbo_children_names[parent].iteritems():
1042
if l_trans_id == trans_id:
1044
if l_filename.lower() == filename.lower():
1047
use_direct_path = True
1050
limbo_name = pathjoin(self._limbo_files[parent], filename)
1051
self._limbo_children[parent].add(trans_id)
1052
self._limbo_children_names[parent][filename] = trans_id
1054
limbo_name = pathjoin(self._limbodir, trans_id)
1055
self._needs_rename.add(trans_id)
1056
self._limbo_files[trans_id] = limbo_name
1059
def adjust_path(self, name, parent, trans_id):
1060
previous_parent = self._new_parent.get(trans_id)
1061
previous_name = self._new_name.get(trans_id)
1062
TreeTransformBase.adjust_path(self, name, parent, trans_id)
1063
if (trans_id in self._limbo_files and
1064
trans_id not in self._needs_rename):
1065
self._rename_in_limbo([trans_id])
1066
self._limbo_children[previous_parent].remove(trans_id)
1067
del self._limbo_children_names[previous_parent][previous_name]
1069
def _rename_in_limbo(self, trans_ids):
1070
"""Fix limbo names so that the right final path is produced.
1072
This means we outsmarted ourselves-- we tried to avoid renaming
1073
these files later by creating them with their final names in their
1074
final parents. But now the previous name or parent is no longer
1075
suitable, so we have to rename them.
1077
Even for trans_ids that have no new contents, we must remove their
1078
entries from _limbo_files, because they are now stale.
1080
for trans_id in trans_ids:
1081
old_path = self._limbo_files.pop(trans_id)
1082
if trans_id not in self._new_contents:
1084
new_path = self._limbo_name(trans_id)
1085
os.rename(old_path, new_path)
1087
def create_file(self, contents, trans_id, mode_id=None):
1088
"""Schedule creation of a new file.
1092
Contents is an iterator of strings, all of which will be written
1093
to the target destination.
1095
New file takes the permissions of any existing file with that id,
1096
unless mode_id is specified.
1098
name = self._limbo_name(trans_id)
1099
f = open(name, 'wb')
1102
unique_add(self._new_contents, trans_id, 'file')
1104
# Clean up the file, it never got registered so
1105
# TreeTransform.finalize() won't clean it up.
1110
f.writelines(contents)
1113
self._set_mode(trans_id, mode_id, S_ISREG)
1115
def _read_file_chunks(self, trans_id):
1116
cur_file = open(self._limbo_name(trans_id), 'rb')
1118
return cur_file.readlines()
1122
def _read_symlink_target(self, trans_id):
1123
return os.readlink(self._limbo_name(trans_id))
1125
def create_hardlink(self, path, trans_id):
1126
"""Schedule creation of a hard link"""
1127
name = self._limbo_name(trans_id)
1131
if e.errno != errno.EPERM:
1133
raise errors.HardLinkNotSupported(path)
1135
unique_add(self._new_contents, trans_id, 'file')
1137
# Clean up the file, it never got registered so
1138
# TreeTransform.finalize() won't clean it up.
1142
def create_directory(self, trans_id):
1143
"""Schedule creation of a new directory.
1145
See also new_directory.
1147
os.mkdir(self._limbo_name(trans_id))
1148
unique_add(self._new_contents, trans_id, 'directory')
1150
def create_symlink(self, target, trans_id):
1151
"""Schedule creation of a new symbolic link.
1153
target is a bytestring.
1154
See also new_symlink.
1157
os.symlink(target, self._limbo_name(trans_id))
1158
unique_add(self._new_contents, trans_id, 'symlink')
1161
path = FinalPaths(self).get_path(trans_id)
1164
raise UnableCreateSymlink(path=path)
1166
def cancel_creation(self, trans_id):
1167
"""Cancel the creation of new file contents."""
1168
del self._new_contents[trans_id]
1169
children = self._limbo_children.get(trans_id)
1170
# if this is a limbo directory with children, move them before removing
1172
if children is not None:
1173
self._rename_in_limbo(children)
1174
del self._limbo_children[trans_id]
1175
del self._limbo_children_names[trans_id]
1176
delete_any(self._limbo_name(trans_id))
1179
class TreeTransform(DiskTreeTransform):
1075
1180
"""Represent a tree transformation.
1077
1182
This object is designed to support incremental generation of the transform,
1167
TreeTransformBase.__init__(self, tree, limbodir, pb,
1271
# Cache of realpath results, to speed up canonical_path
1272
self._realpaths = {}
1273
# Cache of relpath results, to speed up canonical_path
1275
DiskTreeTransform.__init__(self, tree, limbodir, pb,
1168
1276
tree.case_sensitive)
1169
1277
self._deletiondir = deletiondir
1171
def apply(self, no_conflicts=False, _mover=None):
1279
def canonical_path(self, path):
1280
"""Get the canonical tree-relative path"""
1281
# don't follow final symlinks
1282
abs = self._tree.abspath(path)
1283
if abs in self._relpaths:
1284
return self._relpaths[abs]
1285
dirname, basename = os.path.split(abs)
1286
if dirname not in self._realpaths:
1287
self._realpaths[dirname] = os.path.realpath(dirname)
1288
dirname = self._realpaths[dirname]
1289
abs = pathjoin(dirname, basename)
1290
if dirname in self._relpaths:
1291
relpath = pathjoin(self._relpaths[dirname], basename)
1292
relpath = relpath.rstrip('/\\')
1294
relpath = self._tree.relpath(abs)
1295
self._relpaths[abs] = relpath
1298
def tree_kind(self, trans_id):
1299
"""Determine the file kind in the working tree.
1301
Raises NoSuchFile if the file does not exist
1303
path = self._tree_id_paths.get(trans_id)
1305
raise NoSuchFile(None)
1307
return file_kind(self._tree.abspath(path))
1309
if e.errno != errno.ENOENT:
1312
raise NoSuchFile(path)
1314
def _set_mode(self, trans_id, mode_id, typefunc):
1315
"""Set the mode of new file contents.
1316
The mode_id is the existing file to get the mode from (often the same
1317
as trans_id). The operation is only performed if there's a mode match
1318
according to typefunc.
1323
old_path = self._tree_id_paths[mode_id]
1327
mode = os.stat(self._tree.abspath(old_path)).st_mode
1329
if e.errno in (errno.ENOENT, errno.ENOTDIR):
1330
# Either old_path doesn't exist, or the parent of the
1331
# target is not a directory (but will be one eventually)
1332
# Either way, we know it doesn't exist *right now*
1333
# See also bug #248448
1338
os.chmod(self._limbo_name(trans_id), mode)
1340
def iter_tree_children(self, parent_id):
1341
"""Iterate through the entry's tree children, if any"""
1343
path = self._tree_id_paths[parent_id]
1347
children = os.listdir(self._tree.abspath(path))
1349
if not (osutils._is_error_enotdir(e)
1350
or e.errno in (errno.ENOENT, errno.ESRCH)):
1354
for child in children:
1355
childpath = joinpath(path, child)
1356
if self._tree.is_control_filename(childpath):
1358
yield self.trans_id_tree_path(childpath)
1361
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1172
1362
"""Apply all changes to the inventory and filesystem.
1174
1364
If filesystem or inventory conflicts are present, MalformedTransform
1208
1406
self.finalize()
1209
1407
return _TransformResults(modified_paths, self.rename_count)
1211
def _apply_removals(self, inventory_delta, mover):
1409
def _generate_inventory_delta(self):
1410
"""Generate an inventory delta for the current transform."""
1411
inventory_delta = []
1412
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1413
new_paths = self._inventory_altered()
1414
total_entries = len(new_paths) + len(self._removed_id)
1416
for num, trans_id in enumerate(self._removed_id):
1418
child_pb.update('removing file', num, total_entries)
1419
if trans_id == self._new_root:
1420
file_id = self._tree.get_root_id()
1422
file_id = self.tree_file_id(trans_id)
1423
# File-id isn't really being deleted, just moved
1424
if file_id in self._r_new_id:
1426
path = self._tree_id_paths[trans_id]
1427
inventory_delta.append((path, None, file_id, None))
1428
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1430
entries = self._tree.iter_entries_by_dir(
1431
new_path_file_ids.values())
1432
old_paths = dict((e.file_id, p) for p, e in entries)
1434
for num, (path, trans_id) in enumerate(new_paths):
1436
child_pb.update('adding file',
1437
num + len(self._removed_id), total_entries)
1438
file_id = new_path_file_ids[trans_id]
1443
kind = self.final_kind(trans_id)
1445
kind = self._tree.stored_kind(file_id)
1446
parent_trans_id = self.final_parent(trans_id)
1447
parent_file_id = new_path_file_ids.get(parent_trans_id)
1448
if parent_file_id is None:
1449
parent_file_id = self.final_file_id(parent_trans_id)
1450
if trans_id in self._new_reference_revision:
1451
new_entry = inventory.TreeReference(
1453
self._new_name[trans_id],
1454
self.final_file_id(self._new_parent[trans_id]),
1455
None, self._new_reference_revision[trans_id])
1457
new_entry = inventory.make_entry(kind,
1458
self.final_name(trans_id),
1459
parent_file_id, file_id)
1460
old_path = old_paths.get(new_entry.file_id)
1461
new_executability = self._new_executability.get(trans_id)
1462
if new_executability is not None:
1463
new_entry.executable = new_executability
1464
inventory_delta.append(
1465
(old_path, path, new_entry.file_id, new_entry))
1468
return inventory_delta
1470
def _apply_removals(self, mover):
1212
1471
"""Perform tree operations that remove directory/inventory names.
1214
1473
That is, delete files that are to be deleted, and put any files that
1215
1474
need renaming into limbo. This must be done in strict child-to-parent
1477
If inventory_delta is None, no inventory delta generation is performed.
1218
1479
tree_paths = list(self._tree_path_ids.iteritems())
1219
1480
tree_paths.sort(reverse=True)
1237
1498
self.rename_count += 1
1238
if trans_id in self._removed_id:
1239
if trans_id == self._new_root:
1240
file_id = self._tree.get_root_id()
1242
file_id = self.tree_file_id(trans_id)
1243
# File-id isn't really being deleted, just moved
1244
if file_id in self._r_new_id:
1246
inventory_delta.append((path, None, file_id, None))
1248
1500
child_pb.finished()
1250
def _apply_insertions(self, inventory_delta, mover):
1502
def _apply_insertions(self, mover):
1251
1503
"""Perform tree operations that insert directory/inventory names.
1253
1505
That is, create any files that need to be created, and restore from
1254
1506
limbo any files that needed renaming. This must be done in strict
1255
1507
parent-to-child order.
1509
If inventory_delta is None, no inventory delta is calculated, and
1510
no list of modified paths is returned.
1257
new_paths = self.new_paths()
1512
new_paths = self.new_paths(filesystem_only=True)
1258
1513
modified_paths = []
1514
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1259
1516
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1262
1518
for num, (path, trans_id) in enumerate(new_paths):
1264
child_pb.update('adding file', num, len(new_paths))
1265
if trans_id in self._new_contents or \
1266
self.path_changed(trans_id):
1267
full_path = self._tree.abspath(path)
1268
if trans_id in self._needs_rename:
1270
mover.rename(self._limbo_name(trans_id), full_path)
1272
# We may be renaming a dangling inventory id
1273
if e.errno != errno.ENOENT:
1276
self.rename_count += 1
1520
child_pb.update('adding file', num, len(new_paths))
1521
full_path = self._tree.abspath(path)
1522
if trans_id in self._needs_rename:
1524
mover.rename(self._limbo_name(trans_id), full_path)
1526
# We may be renaming a dangling inventory id
1527
if e.errno != errno.ENOENT:
1530
self.rename_count += 1
1531
if (trans_id in self._new_contents or
1532
self.path_changed(trans_id)):
1277
1533
if trans_id in self._new_contents:
1278
1534
modified_paths.append(full_path)
1279
completed_new.append(trans_id)
1280
file_id = self.final_file_id(trans_id)
1281
if file_id is not None and (trans_id in self._new_id or
1282
trans_id in self._new_name or trans_id in self._new_parent
1283
or trans_id in self._new_executability):
1285
kind = self.final_kind(trans_id)
1287
kind = self._tree.stored_kind(file_id)
1288
if trans_id in self._new_reference_revision:
1289
new_entry = inventory.TreeReference(
1290
self.final_file_id(trans_id),
1291
self._new_name[trans_id],
1292
self.final_file_id(self._new_parent[trans_id]),
1293
None, self._new_reference_revision[trans_id])
1295
new_entry = inventory.make_entry(kind,
1296
self.final_name(trans_id),
1297
self.final_file_id(self.final_parent(trans_id)),
1298
self.final_file_id(trans_id))
1300
old_path = self._tree.id2path(new_entry.file_id)
1301
except errors.NoSuchId:
1303
inventory_delta.append((old_path, path, new_entry.file_id,
1306
1535
if trans_id in self._new_executability:
1307
self._set_executability(path, new_entry, trans_id)
1536
self._set_executability(path, trans_id)
1309
1538
child_pb.finished()
1310
for trans_id in completed_new:
1311
del self._new_contents[trans_id]
1539
self._new_contents.clear()
1312
1540
return modified_paths
1315
class TransformPreview(TreeTransformBase):
1543
class TransformPreview(DiskTreeTransform):
1316
1544
"""A TreeTransform for generating preview trees.
1318
1546
Unlike TreeTransform, this version works when the input tree is a
1428
1723
except NoFinalPath:
1429
1724
raise errors.NoSuchId(self, file_id)
1726
def _all_children(self, trans_id):
1727
children = self._all_children_cache.get(trans_id)
1728
if children is not None:
1730
children = set(self._transform.iter_tree_children(trans_id))
1731
# children in the _new_parent set are provided by _by_parent.
1732
children.difference_update(self._transform._new_parent.keys())
1733
children.update(self._by_parent.get(trans_id, []))
1734
self._all_children_cache[trans_id] = children
1737
def iter_children(self, file_id):
1738
trans_id = self._transform.trans_id_file_id(file_id)
1739
for child_trans_id in self._all_children(trans_id):
1740
yield self._transform.final_file_id(child_trans_id)
1743
possible_extras = set(self._transform.trans_id_tree_path(p) for p
1744
in self._transform._tree.extras())
1745
possible_extras.update(self._transform._new_contents)
1746
possible_extras.update(self._transform._removed_id)
1747
for trans_id in possible_extras:
1748
if self._transform.final_file_id(trans_id) is None:
1749
yield self._final_paths._determine_path(trans_id)
1751
def _make_inv_entries(self, ordered_entries, specific_file_ids=None):
1752
for trans_id, parent_file_id in ordered_entries:
1753
file_id = self._transform.final_file_id(trans_id)
1756
if (specific_file_ids is not None
1757
and file_id not in specific_file_ids):
1760
kind = self._transform.final_kind(trans_id)
1762
kind = self._transform._tree.stored_kind(file_id)
1763
new_entry = inventory.make_entry(
1765
self._transform.final_name(trans_id),
1766
parent_file_id, file_id)
1767
yield new_entry, trans_id
1769
def _list_files_by_dir(self):
1770
todo = [ROOT_PARENT]
1772
while len(todo) > 0:
1774
parent_file_id = self._transform.final_file_id(parent)
1775
children = list(self._all_children(parent))
1776
paths = dict(zip(children, self._final_paths.get_paths(children)))
1777
children.sort(key=paths.get)
1778
todo.extend(reversed(children))
1779
for trans_id in children:
1780
ordered_ids.append((trans_id, parent_file_id))
1431
1783
def iter_entries_by_dir(self, specific_file_ids=None):
1432
return self._transform._tree.iter_entries_by_dir(specific_file_ids)
1784
# This may not be a maximally efficient implementation, but it is
1785
# reasonably straightforward. An implementation that grafts the
1786
# TreeTransform changes onto the tree's iter_entries_by_dir results
1787
# might be more efficient, but requires tricky inferences about stack
1789
ordered_ids = self._list_files_by_dir()
1790
for entry, trans_id in self._make_inv_entries(ordered_ids,
1792
yield unicode(self._final_paths.get_path(trans_id)), entry
1794
def _iter_entries_for_dir(self, dir_path):
1795
"""Return path, entry for items in a directory without recursing down."""
1796
dir_file_id = self.path2id(dir_path)
1798
for file_id in self.iter_children(dir_file_id):
1799
trans_id = self._transform.trans_id_file_id(file_id)
1800
ordered_ids.append((trans_id, file_id))
1801
for entry, trans_id in self._make_inv_entries(ordered_ids):
1802
yield unicode(self._final_paths.get_path(trans_id)), entry
1804
def list_files(self, include_root=False, from_dir=None, recursive=True):
1805
"""See WorkingTree.list_files."""
1806
# XXX This should behave like WorkingTree.list_files, but is really
1807
# more like RevisionTree.list_files.
1811
prefix = from_dir + '/'
1812
entries = self.iter_entries_by_dir()
1813
for path, entry in entries:
1814
if entry.name == '' and not include_root:
1817
if not path.startswith(prefix):
1819
path = path[len(prefix):]
1820
yield path, 'V', entry.kind, entry.file_id, entry
1822
if from_dir is None and include_root is True:
1823
root_entry = inventory.make_entry('directory', '',
1824
ROOT_PARENT, self.get_root_id())
1825
yield '', 'V', 'directory', root_entry.file_id, root_entry
1826
entries = self._iter_entries_for_dir(from_dir or '')
1827
for path, entry in entries:
1828
yield path, 'V', entry.kind, entry.file_id, entry
1434
1830
def kind(self, file_id):
1435
1831
trans_id = self._transform.trans_id_file_id(file_id)
1436
1832
return self._transform.final_kind(trans_id)
1438
1834
def stored_kind(self, file_id):
1439
return self._transform._tree.stored_kind(file_id)
1835
trans_id = self._transform.trans_id_file_id(file_id)
1837
return self._transform._new_contents[trans_id]
1839
return self._transform._tree.stored_kind(file_id)
1441
1841
def get_file_mtime(self, file_id, path=None):
1442
1842
"""See Tree.get_file_mtime"""
1454
1857
def get_file_sha1(self, file_id, path=None, stat_value=None):
1455
return self._transform._tree.get_file_sha1(file_id)
1858
trans_id = self._transform.trans_id_file_id(file_id)
1859
kind = self._transform._new_contents.get(trans_id)
1861
return self._transform._tree.get_file_sha1(file_id)
1863
fileobj = self.get_file(file_id)
1865
return sha_file(fileobj)
1457
1869
def is_executable(self, file_id, path=None):
1458
return self._transform._tree.is_executable(file_id, path)
1872
trans_id = self._transform.trans_id_file_id(file_id)
1874
return self._transform._new_executability[trans_id]
1877
return self._transform._tree.is_executable(file_id, path)
1879
if e.errno == errno.ENOENT:
1882
except errors.NoSuchId:
1460
1885
def path_content_summary(self, path):
1461
return self._transform._tree.path_content_summary(path)
1886
trans_id = self._path2trans_id(path)
1887
tt = self._transform
1888
tree_path = tt._tree_id_paths.get(trans_id)
1889
kind = tt._new_contents.get(trans_id)
1891
if tree_path is None or trans_id in tt._removed_contents:
1892
return 'missing', None, None, None
1893
summary = tt._tree.path_content_summary(tree_path)
1894
kind, size, executable, link_or_sha1 = summary
1897
limbo_name = tt._limbo_name(trans_id)
1898
if trans_id in tt._new_reference_revision:
1899
kind = 'tree-reference'
1901
statval = os.lstat(limbo_name)
1902
size = statval.st_size
1903
if not supports_executable():
1906
executable = statval.st_mode & S_IEXEC
1910
if kind == 'symlink':
1911
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
1912
if supports_executable():
1913
executable = tt._new_executability.get(trans_id, executable)
1914
return kind, size, executable, link_or_sha1
1463
1916
def iter_changes(self, from_tree, include_unchanged=False,
1464
1917
specific_files=None, pb=None, extra_trees=None,
1465
1918
require_versioned=True, want_unversioned=False):
1466
1919
"""See InterTree.iter_changes.
1468
This implementation does not support include_unchanged, specific_files,
1469
or want_unversioned. extra_trees, require_versioned, and pb are
1921
This has a fast path that is only used when the from_tree matches
1922
the transform tree, and no fancy options are supplied.
1472
if from_tree is not self._transform._tree:
1473
raise ValueError('from_tree must be transform source tree.')
1474
if include_unchanged:
1475
raise ValueError('include_unchanged is not supported')
1476
if specific_files is not None:
1477
raise ValueError('specific_files is not supported')
1924
if (from_tree is not self._transform._tree or include_unchanged or
1925
specific_files or want_unversioned):
1926
return tree.InterTree(from_tree, self).iter_changes(
1927
include_unchanged=include_unchanged,
1928
specific_files=specific_files,
1930
extra_trees=extra_trees,
1931
require_versioned=require_versioned,
1932
want_unversioned=want_unversioned)
1478
1933
if want_unversioned:
1479
1934
raise ValueError('want_unversioned is not supported')
1480
1935
return self._transform.iter_changes()