1050
969
def get_preview_tree(self):
1051
970
"""Return a tree representing the result of the transform.
1053
This tree only supports the subset of Tree functionality required
1054
by show_diff_trees. It must only be compared to tt._tree.
972
The tree is a snapshot, and altering the TreeTransform will invalidate
1056
975
return _PreviewTree(self)
1059
class TreeTransform(TreeTransformBase):
977
def commit(self, branch, message, merge_parents=None, strict=False,
978
timestamp=None, timezone=None, committer=None, authors=None,
979
revprops=None, revision_id=None):
980
"""Commit the result of this TreeTransform to a branch.
982
:param branch: The branch to commit to.
983
:param message: The message to attach to the commit.
984
:param merge_parents: Additional parent revision-ids specified by
986
:param strict: If True, abort the commit if there are unversioned
988
:param timestamp: if not None, seconds-since-epoch for the time and
989
date. (May be a float.)
990
:param timezone: Optional timezone for timestamp, as an offset in
992
:param committer: Optional committer in email-id format.
993
(e.g. "J Random Hacker <jrandom@example.com>")
994
:param authors: Optional list of authors in email-id format.
995
:param revprops: Optional dictionary of revision properties.
996
:param revision_id: Optional revision id. (Specifying a revision-id
997
may reduce performance for some non-native formats.)
998
:return: The revision_id of the revision committed.
1000
self._check_malformed()
1002
unversioned = set(self._new_contents).difference(set(self._new_id))
1003
for trans_id in unversioned:
1004
if self.final_file_id(trans_id) is None:
1005
raise errors.StrictCommitFailed()
1007
revno, last_rev_id = branch.last_revision_info()
1008
if last_rev_id == _mod_revision.NULL_REVISION:
1009
if merge_parents is not None:
1010
raise ValueError('Cannot supply merge parents for first'
1014
parent_ids = [last_rev_id]
1015
if merge_parents is not None:
1016
parent_ids.extend(merge_parents)
1017
if self._tree.get_revision_id() != last_rev_id:
1018
raise ValueError('TreeTransform not based on branch basis: %s' %
1019
self._tree.get_revision_id())
1020
revprops = commit.Commit.update_revprops(revprops, branch, authors)
1021
builder = branch.get_commit_builder(parent_ids,
1022
timestamp=timestamp,
1024
committer=committer,
1026
revision_id=revision_id)
1027
preview = self.get_preview_tree()
1028
list(builder.record_iter_changes(preview, last_rev_id,
1029
self.iter_changes()))
1030
builder.finish_inventory()
1031
revision_id = builder.commit(message)
1032
branch.set_last_revision_info(revno + 1, revision_id)
1035
def _text_parent(self, trans_id):
1036
file_id = self.tree_file_id(trans_id)
1038
if file_id is None or self._tree.kind(file_id) != 'file':
1040
except errors.NoSuchFile:
1044
def _get_parents_texts(self, trans_id):
1045
"""Get texts for compression parents of this file."""
1046
file_id = self._text_parent(trans_id)
1049
return (self._tree.get_file_text(file_id),)
1051
def _get_parents_lines(self, trans_id):
1052
"""Get lines for compression parents of this file."""
1053
file_id = self._text_parent(trans_id)
1056
return (self._tree.get_file_lines(file_id),)
1058
def serialize(self, serializer):
1059
"""Serialize this TreeTransform.
1061
:param serializer: A Serialiser like pack.ContainerSerializer.
1063
new_name = dict((k, v.encode('utf-8')) for k, v in
1064
self._new_name.items())
1065
new_executability = dict((k, int(v)) for k, v in
1066
self._new_executability.items())
1067
tree_path_ids = dict((k.encode('utf-8'), v)
1068
for k, v in self._tree_path_ids.items())
1070
'_id_number': self._id_number,
1071
'_new_name': new_name,
1072
'_new_parent': self._new_parent,
1073
'_new_executability': new_executability,
1074
'_new_id': self._new_id,
1075
'_tree_path_ids': tree_path_ids,
1076
'_removed_id': list(self._removed_id),
1077
'_removed_contents': list(self._removed_contents),
1078
'_non_present_ids': self._non_present_ids,
1080
yield serializer.bytes_record(bencode.bencode(attribs),
1082
for trans_id, kind in self._new_contents.items():
1084
lines = osutils.chunks_to_lines(
1085
self._read_file_chunks(trans_id))
1086
parents = self._get_parents_lines(trans_id)
1087
mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1088
content = ''.join(mpdiff.to_patch())
1089
if kind == 'directory':
1091
if kind == 'symlink':
1092
content = self._read_symlink_target(trans_id)
1093
yield serializer.bytes_record(content, ((trans_id, kind),))
1095
def deserialize(self, records):
1096
"""Deserialize a stored TreeTransform.
1098
:param records: An iterable of (names, content) tuples, as per
1099
pack.ContainerPushParser.
1101
names, content = records.next()
1102
attribs = bencode.bdecode(content)
1103
self._id_number = attribs['_id_number']
1104
self._new_name = dict((k, v.decode('utf-8'))
1105
for k, v in attribs['_new_name'].items())
1106
self._new_parent = attribs['_new_parent']
1107
self._new_executability = dict((k, bool(v)) for k, v in
1108
attribs['_new_executability'].items())
1109
self._new_id = attribs['_new_id']
1110
self._r_new_id = dict((v, k) for k, v in self._new_id.items())
1111
self._tree_path_ids = {}
1112
self._tree_id_paths = {}
1113
for bytepath, trans_id in attribs['_tree_path_ids'].items():
1114
path = bytepath.decode('utf-8')
1115
self._tree_path_ids[path] = trans_id
1116
self._tree_id_paths[trans_id] = path
1117
self._removed_id = set(attribs['_removed_id'])
1118
self._removed_contents = set(attribs['_removed_contents'])
1119
self._non_present_ids = attribs['_non_present_ids']
1120
for ((trans_id, kind),), content in records:
1122
mpdiff = multiparent.MultiParent.from_patch(content)
1123
lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1124
self.create_file(lines, trans_id)
1125
if kind == 'directory':
1126
self.create_directory(trans_id)
1127
if kind == 'symlink':
1128
self.create_symlink(content.decode('utf-8'), trans_id)
1131
class DiskTreeTransform(TreeTransformBase):
1132
"""Tree transform storing its contents on disk."""
1134
def __init__(self, tree, limbodir, pb=None,
1135
case_sensitive=True):
1137
:param tree: The tree that will be transformed, but not necessarily
1139
:param limbodir: A directory where new files can be stored until
1140
they are installed in their proper places
1142
:param case_sensitive: If True, the target of the transform is
1143
case sensitive, not just case preserving.
1145
TreeTransformBase.__init__(self, tree, pb, case_sensitive)
1146
self._limbodir = limbodir
1147
self._deletiondir = None
1148
# A mapping of transform ids to their limbo filename
1149
self._limbo_files = {}
1150
# A mapping of transform ids to a set of the transform ids of children
1151
# that their limbo directory has
1152
self._limbo_children = {}
1153
# Map transform ids to maps of child filename to child transform id
1154
self._limbo_children_names = {}
1155
# List of transform ids that need to be renamed from limbo into place
1156
self._needs_rename = set()
1157
self._creation_mtime = None
1160
"""Release the working tree lock, if held, clean up limbo dir.
1162
This is required if apply has not been invoked, but can be invoked
1165
if self._tree is None:
1168
entries = [(self._limbo_name(t), t, k) for t, k in
1169
self._new_contents.iteritems()]
1170
entries.sort(reverse=True)
1171
for path, trans_id, kind in entries:
1174
delete_any(self._limbodir)
1176
# We don't especially care *why* the dir is immortal.
1177
raise ImmortalLimbo(self._limbodir)
1179
if self._deletiondir is not None:
1180
delete_any(self._deletiondir)
1182
raise errors.ImmortalPendingDeletion(self._deletiondir)
1184
TreeTransformBase.finalize(self)
1186
def _limbo_name(self, trans_id):
1187
"""Generate the limbo name of a file"""
1188
limbo_name = self._limbo_files.get(trans_id)
1189
if limbo_name is None:
1190
limbo_name = self._generate_limbo_path(trans_id)
1191
self._limbo_files[trans_id] = limbo_name
1194
def _generate_limbo_path(self, trans_id):
1195
"""Generate a limbo path using the trans_id as the relative path.
1197
This is suitable as a fallback, and when the transform should not be
1198
sensitive to the path encoding of the limbo directory.
1200
self._needs_rename.add(trans_id)
1201
return pathjoin(self._limbodir, trans_id)
1203
def adjust_path(self, name, parent, trans_id):
1204
previous_parent = self._new_parent.get(trans_id)
1205
previous_name = self._new_name.get(trans_id)
1206
TreeTransformBase.adjust_path(self, name, parent, trans_id)
1207
if (trans_id in self._limbo_files and
1208
trans_id not in self._needs_rename):
1209
self._rename_in_limbo([trans_id])
1210
if previous_parent != parent:
1211
self._limbo_children[previous_parent].remove(trans_id)
1212
if previous_parent != parent or previous_name != name:
1213
del self._limbo_children_names[previous_parent][previous_name]
1215
def _rename_in_limbo(self, trans_ids):
1216
"""Fix limbo names so that the right final path is produced.
1218
This means we outsmarted ourselves-- we tried to avoid renaming
1219
these files later by creating them with their final names in their
1220
final parents. But now the previous name or parent is no longer
1221
suitable, so we have to rename them.
1223
Even for trans_ids that have no new contents, we must remove their
1224
entries from _limbo_files, because they are now stale.
1226
for trans_id in trans_ids:
1227
old_path = self._limbo_files.pop(trans_id)
1228
if trans_id not in self._new_contents:
1230
new_path = self._limbo_name(trans_id)
1231
os.rename(old_path, new_path)
1232
for descendant in self._limbo_descendants(trans_id):
1233
desc_path = self._limbo_files[descendant]
1234
desc_path = new_path + desc_path[len(old_path):]
1235
self._limbo_files[descendant] = desc_path
1237
def _limbo_descendants(self, trans_id):
1238
"""Return the set of trans_ids whose limbo paths descend from this."""
1239
descendants = set(self._limbo_children.get(trans_id, []))
1240
for descendant in list(descendants):
1241
descendants.update(self._limbo_descendants(descendant))
1244
def create_file(self, contents, trans_id, mode_id=None):
1245
"""Schedule creation of a new file.
1249
Contents is an iterator of strings, all of which will be written
1250
to the target destination.
1252
New file takes the permissions of any existing file with that id,
1253
unless mode_id is specified.
1255
name = self._limbo_name(trans_id)
1256
f = open(name, 'wb')
1259
unique_add(self._new_contents, trans_id, 'file')
1261
# Clean up the file, it never got registered so
1262
# TreeTransform.finalize() won't clean it up.
1267
f.writelines(contents)
1270
self._set_mtime(name)
1271
self._set_mode(trans_id, mode_id, S_ISREG)
1273
def _read_file_chunks(self, trans_id):
1274
cur_file = open(self._limbo_name(trans_id), 'rb')
1276
return cur_file.readlines()
1280
def _read_symlink_target(self, trans_id):
1281
return os.readlink(self._limbo_name(trans_id))
1283
def _set_mtime(self, path):
1284
"""All files that are created get the same mtime.
1286
This time is set by the first object to be created.
1288
if self._creation_mtime is None:
1289
self._creation_mtime = time.time()
1290
os.utime(path, (self._creation_mtime, self._creation_mtime))
1292
def create_hardlink(self, path, trans_id):
1293
"""Schedule creation of a hard link"""
1294
name = self._limbo_name(trans_id)
1298
if e.errno != errno.EPERM:
1300
raise errors.HardLinkNotSupported(path)
1302
unique_add(self._new_contents, trans_id, 'file')
1304
# Clean up the file, it never got registered so
1305
# TreeTransform.finalize() won't clean it up.
1309
def create_directory(self, trans_id):
1310
"""Schedule creation of a new directory.
1312
See also new_directory.
1314
os.mkdir(self._limbo_name(trans_id))
1315
unique_add(self._new_contents, trans_id, 'directory')
1317
def create_symlink(self, target, trans_id):
1318
"""Schedule creation of a new symbolic link.
1320
target is a bytestring.
1321
See also new_symlink.
1324
os.symlink(target, self._limbo_name(trans_id))
1325
unique_add(self._new_contents, trans_id, 'symlink')
1328
path = FinalPaths(self).get_path(trans_id)
1331
raise UnableCreateSymlink(path=path)
1333
def cancel_creation(self, trans_id):
1334
"""Cancel the creation of new file contents."""
1335
del self._new_contents[trans_id]
1336
children = self._limbo_children.get(trans_id)
1337
# if this is a limbo directory with children, move them before removing
1339
if children is not None:
1340
self._rename_in_limbo(children)
1341
del self._limbo_children[trans_id]
1342
del self._limbo_children_names[trans_id]
1343
delete_any(self._limbo_name(trans_id))
1345
def new_orphan(self, trans_id, parent_id):
1346
# FIXME: There is no tree config, so we use the branch one (it's weird
1347
# to define it this way as orphaning can only occur in a working tree,
1348
# but that's all we have (for now). It will find the option in
1349
# locations.conf or bazaar.conf though) -- vila 20100916
1350
conf = self._tree.branch.get_config()
1351
conf_var_name = 'bzr.transform.orphan_policy'
1352
orphan_policy = conf.get_user_option(conf_var_name)
1353
default_policy = orphaning_registry.default_key
1354
if orphan_policy is None:
1355
orphan_policy = default_policy
1356
if orphan_policy not in orphaning_registry:
1357
trace.warning('%s (from %s) is not a known policy, defaulting to %s'
1358
% (orphan_policy, conf_var_name, default_policy))
1359
orphan_policy = default_policy
1360
handle_orphan = orphaning_registry.get(orphan_policy)
1361
handle_orphan(self, trans_id, parent_id)
1364
class OrphaningError(errors.BzrError):
1366
# Only bugs could lead to such exception being seen by the user
1367
internal_error = True
1368
_fmt = "Error while orphaning %s in %s directory"
1370
def __init__(self, orphan, parent):
1371
errors.BzrError.__init__(self)
1372
self.orphan = orphan
1373
self.parent = parent
1376
class OrphaningForbidden(OrphaningError):
1378
_fmt = "Policy: %s doesn't allow creating orphans."
1380
def __init__(self, policy):
1381
errors.BzrError.__init__(self)
1382
self.policy = policy
1385
def move_orphan(tt, orphan_id, parent_id):
1386
"""See TreeTransformBase.new_orphan.
1388
This creates a new orphan in the `bzr-orphans` dir at the root of the
1391
:param tt: The TreeTransform orphaning `trans_id`.
1393
:param orphan_id: The trans id that should be orphaned.
1395
:param parent_id: The orphan parent trans id.
1397
# Add the orphan dir if it doesn't exist
1398
orphan_dir_basename = 'bzr-orphans'
1399
od_id = tt.trans_id_tree_path(orphan_dir_basename)
1400
if tt.final_kind(od_id) is None:
1401
tt.create_directory(od_id)
1402
parent_path = tt._tree_id_paths[parent_id]
1403
# Find a name that doesn't exist yet in the orphan dir
1404
actual_name = tt.final_name(orphan_id)
1405
new_name = tt._available_backup_name(actual_name, od_id)
1406
tt.adjust_path(new_name, od_id, orphan_id)
1407
trace.warning('%s has been orphaned in %s'
1408
% (joinpath(parent_path, actual_name), orphan_dir_basename))
1411
def refuse_orphan(tt, orphan_id, parent_id):
1412
"""See TreeTransformBase.new_orphan.
1414
This refuses to create orphan, letting the caller handle the conflict.
1416
raise OrphaningForbidden('never')
1419
orphaning_registry = registry.Registry()
1420
orphaning_registry.register(
1421
'conflict', refuse_orphan,
1422
'Leave orphans in place and create a conflict on the directory.')
1423
orphaning_registry.register(
1424
'move', move_orphan,
1425
'Move orphans into the bzr-orphans directory.')
1426
orphaning_registry._set_default_key('conflict')
1429
class TreeTransform(DiskTreeTransform):
1060
1430
"""Represent a tree transformation.
1062
1432
This object is designed to support incremental generation of the transform,
1152
TreeTransformBase.__init__(self, tree, limbodir, pb,
1521
# Cache of realpath results, to speed up canonical_path
1522
self._realpaths = {}
1523
# Cache of relpath results, to speed up canonical_path
1525
DiskTreeTransform.__init__(self, tree, limbodir, pb,
1153
1526
tree.case_sensitive)
1154
1527
self._deletiondir = deletiondir
1156
def apply(self, no_conflicts=False, _mover=None):
1529
def canonical_path(self, path):
1530
"""Get the canonical tree-relative path"""
1531
# don't follow final symlinks
1532
abs = self._tree.abspath(path)
1533
if abs in self._relpaths:
1534
return self._relpaths[abs]
1535
dirname, basename = os.path.split(abs)
1536
if dirname not in self._realpaths:
1537
self._realpaths[dirname] = os.path.realpath(dirname)
1538
dirname = self._realpaths[dirname]
1539
abs = pathjoin(dirname, basename)
1540
if dirname in self._relpaths:
1541
relpath = pathjoin(self._relpaths[dirname], basename)
1542
relpath = relpath.rstrip('/\\')
1544
relpath = self._tree.relpath(abs)
1545
self._relpaths[abs] = relpath
1548
def tree_kind(self, trans_id):
1549
"""Determine the file kind in the working tree.
1551
:returns: The file kind or None if the file does not exist
1553
path = self._tree_id_paths.get(trans_id)
1557
return file_kind(self._tree.abspath(path))
1558
except errors.NoSuchFile:
1561
def _set_mode(self, trans_id, mode_id, typefunc):
1562
"""Set the mode of new file contents.
1563
The mode_id is the existing file to get the mode from (often the same
1564
as trans_id). The operation is only performed if there's a mode match
1565
according to typefunc.
1570
old_path = self._tree_id_paths[mode_id]
1574
mode = os.stat(self._tree.abspath(old_path)).st_mode
1576
if e.errno in (errno.ENOENT, errno.ENOTDIR):
1577
# Either old_path doesn't exist, or the parent of the
1578
# target is not a directory (but will be one eventually)
1579
# Either way, we know it doesn't exist *right now*
1580
# See also bug #248448
1585
os.chmod(self._limbo_name(trans_id), mode)
1587
def iter_tree_children(self, parent_id):
1588
"""Iterate through the entry's tree children, if any"""
1590
path = self._tree_id_paths[parent_id]
1594
children = os.listdir(self._tree.abspath(path))
1596
if not (osutils._is_error_enotdir(e)
1597
or e.errno in (errno.ENOENT, errno.ESRCH)):
1601
for child in children:
1602
childpath = joinpath(path, child)
1603
if self._tree.is_control_filename(childpath):
1605
yield self.trans_id_tree_path(childpath)
1607
def _generate_limbo_path(self, trans_id):
1608
"""Generate a limbo path using the final path if possible.
1610
This optimizes the performance of applying the tree transform by
1611
avoiding renames. These renames can be avoided only when the parent
1612
directory is already scheduled for creation.
1614
If the final path cannot be used, falls back to using the trans_id as
1617
parent = self._new_parent.get(trans_id)
1618
# if the parent directory is already in limbo (e.g. when building a
1619
# tree), choose a limbo name inside the parent, to reduce further
1621
use_direct_path = False
1622
if self._new_contents.get(parent) == 'directory':
1623
filename = self._new_name.get(trans_id)
1624
if filename is not None:
1625
if parent not in self._limbo_children:
1626
self._limbo_children[parent] = set()
1627
self._limbo_children_names[parent] = {}
1628
use_direct_path = True
1629
# the direct path can only be used if no other file has
1630
# already taken this pathname, i.e. if the name is unused, or
1631
# if it is already associated with this trans_id.
1632
elif self._case_sensitive_target:
1633
if (self._limbo_children_names[parent].get(filename)
1634
in (trans_id, None)):
1635
use_direct_path = True
1637
for l_filename, l_trans_id in\
1638
self._limbo_children_names[parent].iteritems():
1639
if l_trans_id == trans_id:
1641
if l_filename.lower() == filename.lower():
1644
use_direct_path = True
1646
if not use_direct_path:
1647
return DiskTreeTransform._generate_limbo_path(self, trans_id)
1649
limbo_name = pathjoin(self._limbo_files[parent], filename)
1650
self._limbo_children[parent].add(trans_id)
1651
self._limbo_children_names[parent][filename] = trans_id
1655
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1157
1656
"""Apply all changes to the inventory and filesystem.
1159
1658
If filesystem or inventory conflicts are present, MalformedTransform
1211
1776
child_pb.update('removing file', num, len(tree_paths))
1212
1777
full_path = self._tree.abspath(path)
1213
1778
if trans_id in self._removed_contents:
1214
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1216
elif trans_id in self._new_name or trans_id in \
1779
delete_path = os.path.join(self._deletiondir, trans_id)
1780
mover.pre_delete(full_path, delete_path)
1781
elif (trans_id in self._new_name
1782
or trans_id in self._new_parent):
1219
1784
mover.rename(full_path, self._limbo_name(trans_id))
1785
except errors.TransformRenameFailed, e:
1221
1786
if e.errno != errno.ENOENT:
1224
1789
self.rename_count += 1
1225
if trans_id in self._removed_id:
1226
if trans_id == self._new_root:
1227
file_id = self._tree.get_root_id()
1229
file_id = self.tree_file_id(trans_id)
1230
if file_id is not None:
1231
inventory_delta.append((path, None, file_id, None))
1233
1791
child_pb.finished()
1235
def _apply_insertions(self, inv, inventory_delta, mover):
1793
def _apply_insertions(self, mover):
1236
1794
"""Perform tree operations that insert directory/inventory names.
1238
1796
That is, create any files that need to be created, and restore from
1239
1797
limbo any files that needed renaming. This must be done in strict
1240
1798
parent-to-child order.
1800
If inventory_delta is None, no inventory delta is calculated, and
1801
no list of modified paths is returned.
1242
new_paths = self.new_paths()
1803
new_paths = self.new_paths(filesystem_only=True)
1243
1804
modified_paths = []
1805
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1244
1807
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1247
1809
for num, (path, trans_id) in enumerate(new_paths):
1249
child_pb.update('adding file', num, len(new_paths))
1251
kind = self._new_contents[trans_id]
1253
kind = contents = None
1254
if trans_id in self._new_contents or \
1255
self.path_changed(trans_id):
1256
full_path = self._tree.abspath(path)
1257
if trans_id in self._needs_rename:
1259
mover.rename(self._limbo_name(trans_id), full_path)
1261
# We may be renaming a dangling inventory id
1262
if e.errno != errno.ENOENT:
1265
self.rename_count += 1
1811
child_pb.update('adding file', num, len(new_paths))
1812
full_path = self._tree.abspath(path)
1813
if trans_id in self._needs_rename:
1815
mover.rename(self._limbo_name(trans_id), full_path)
1816
except errors.TransformRenameFailed, e:
1817
# We may be renaming a dangling inventory id
1818
if e.errno != errno.ENOENT:
1821
self.rename_count += 1
1822
if (trans_id in self._new_contents or
1823
self.path_changed(trans_id)):
1266
1824
if trans_id in self._new_contents:
1267
1825
modified_paths.append(full_path)
1268
completed_new.append(trans_id)
1270
if trans_id in self._new_id:
1272
kind = file_kind(self._tree.abspath(path))
1273
if trans_id in self._new_reference_revision:
1274
new_entry = inventory.TreeReference(
1275
self._new_id[trans_id],
1276
self._new_name[trans_id],
1277
self.final_file_id(self._new_parent[trans_id]),
1278
None, self._new_reference_revision[trans_id])
1280
new_entry = inventory.make_entry(kind,
1281
self.final_name(trans_id),
1282
self.final_file_id(self.final_parent(trans_id)),
1283
self._new_id[trans_id])
1285
if trans_id in self._new_name or trans_id in\
1286
self._new_parent or\
1287
trans_id in self._new_executability:
1288
file_id = self.final_file_id(trans_id)
1289
if file_id is not None:
1290
entry = inv[file_id]
1291
new_entry = entry.copy()
1293
if trans_id in self._new_name or trans_id in\
1295
if new_entry is not None:
1296
new_entry.name = self.final_name(trans_id)
1297
parent = self.final_parent(trans_id)
1298
parent_id = self.final_file_id(parent)
1299
new_entry.parent_id = parent_id
1301
1826
if trans_id in self._new_executability:
1302
self._set_executability(path, new_entry, trans_id)
1303
if new_entry is not None:
1304
if new_entry.file_id in inv:
1305
old_path = inv.id2path(new_entry.file_id)
1308
inventory_delta.append((old_path, path,
1827
self._set_executability(path, trans_id)
1312
1829
child_pb.finished()
1313
for trans_id in completed_new:
1314
del self._new_contents[trans_id]
1830
self._new_contents.clear()
1315
1831
return modified_paths
1318
class TransformPreview(TreeTransformBase):
1834
class TransformPreview(DiskTreeTransform):
1319
1835
"""A TreeTransform for generating preview trees.
1321
1837
Unlike TreeTransform, this version works when the input tree is a
1354
1873
except KeyError:
1356
1875
file_id = self.tree_file_id(parent_id)
1357
for child in self._tree.inventory[file_id].children.iterkeys():
1878
entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
1879
children = getattr(entry, 'children', {})
1880
for child in children:
1358
1881
childpath = joinpath(path, child)
1359
1882
yield self.trans_id_tree_path(childpath)
1362
class _PreviewTree(object):
1884
def new_orphan(self, trans_id, parent_id):
1885
raise NotImplementedError(self.new_orphan)
1888
class _PreviewTree(tree.Tree):
1363
1889
"""Partial implementation of Tree to support show_diff_trees"""
1365
1891
def __init__(self, transform):
1366
1892
self._transform = transform
1893
self._final_paths = FinalPaths(transform)
1894
self.__by_parent = None
1895
self._parent_ids = []
1896
self._all_children_cache = {}
1897
self._path2trans_id_cache = {}
1898
self._final_name_cache = {}
1899
self._iter_changes_cache = dict((c[0], c) for c in
1900
self._transform.iter_changes())
1902
def _content_change(self, file_id):
1903
"""Return True if the content of this file changed"""
1904
changes = self._iter_changes_cache.get(file_id)
1905
# changes[2] is true if the file content changed. See
1906
# InterTree.iter_changes.
1907
return (changes is not None and changes[2])
1909
def _get_repository(self):
1910
repo = getattr(self._transform._tree, '_repository', None)
1912
repo = self._transform._tree.branch.repository
1915
def _iter_parent_trees(self):
1916
for revision_id in self.get_parent_ids():
1918
yield self.revision_tree(revision_id)
1919
except errors.NoSuchRevisionInTree:
1920
yield self._get_repository().revision_tree(revision_id)
1922
def _get_file_revision(self, file_id, vf, tree_revision):
1923
parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1924
self._iter_parent_trees()]
1925
vf.add_lines((file_id, tree_revision), parent_keys,
1926
self.get_file_lines(file_id))
1927
repo = self._get_repository()
1928
base_vf = repo.texts
1929
if base_vf not in vf.fallback_versionedfiles:
1930
vf.fallback_versionedfiles.append(base_vf)
1931
return tree_revision
1933
def _stat_limbo_file(self, file_id):
1934
trans_id = self._transform.trans_id_file_id(file_id)
1935
name = self._transform._limbo_name(trans_id)
1936
return os.lstat(name)
1939
def _by_parent(self):
1940
if self.__by_parent is None:
1941
self.__by_parent = self._transform.by_parent()
1942
return self.__by_parent
1944
def _comparison_data(self, entry, path):
1945
kind, size, executable, link_or_sha1 = self.path_content_summary(path)
1946
if kind == 'missing':
1950
file_id = self._transform.final_file_id(self._path2trans_id(path))
1951
executable = self.is_executable(file_id, path)
1952
return kind, executable, None
1954
def is_locked(self):
1368
1957
def lock_read(self):
1369
1958
# Perhaps in theory, this should lock the TreeTransform?
1372
1961
def unlock(self):
1375
def _iter_changes(self, from_tree, include_unchanged=False,
1965
def inventory(self):
1966
"""This Tree does not use inventory as its backing data."""
1967
raise NotImplementedError(_PreviewTree.inventory)
1969
def get_root_id(self):
1970
return self._transform.final_file_id(self._transform.root)
1972
def all_file_ids(self):
1973
tree_ids = set(self._transform._tree.all_file_ids())
1974
tree_ids.difference_update(self._transform.tree_file_id(t)
1975
for t in self._transform._removed_id)
1976
tree_ids.update(self._transform._new_id.values())
1980
return iter(self.all_file_ids())
1982
def _has_id(self, file_id, fallback_check):
1983
if file_id in self._transform._r_new_id:
1985
elif file_id in set([self._transform.tree_file_id(trans_id) for
1986
trans_id in self._transform._removed_id]):
1989
return fallback_check(file_id)
1991
def has_id(self, file_id):
1992
return self._has_id(file_id, self._transform._tree.has_id)
1994
def has_or_had_id(self, file_id):
1995
return self._has_id(file_id, self._transform._tree.has_or_had_id)
1997
def _path2trans_id(self, path):
1998
# We must not use None here, because that is a valid value to store.
1999
trans_id = self._path2trans_id_cache.get(path, object)
2000
if trans_id is not object:
2002
segments = splitpath(path)
2003
cur_parent = self._transform.root
2004
for cur_segment in segments:
2005
for child in self._all_children(cur_parent):
2006
final_name = self._final_name_cache.get(child)
2007
if final_name is None:
2008
final_name = self._transform.final_name(child)
2009
self._final_name_cache[child] = final_name
2010
if final_name == cur_segment:
2014
self._path2trans_id_cache[path] = None
2016
self._path2trans_id_cache[path] = cur_parent
2019
def path2id(self, path):
2020
return self._transform.final_file_id(self._path2trans_id(path))
2022
def id2path(self, file_id):
2023
trans_id = self._transform.trans_id_file_id(file_id)
2025
return self._final_paths._determine_path(trans_id)
2027
raise errors.NoSuchId(self, file_id)
2029
def _all_children(self, trans_id):
2030
children = self._all_children_cache.get(trans_id)
2031
if children is not None:
2033
children = set(self._transform.iter_tree_children(trans_id))
2034
# children in the _new_parent set are provided by _by_parent.
2035
children.difference_update(self._transform._new_parent.keys())
2036
children.update(self._by_parent.get(trans_id, []))
2037
self._all_children_cache[trans_id] = children
2040
def iter_children(self, file_id):
2041
trans_id = self._transform.trans_id_file_id(file_id)
2042
for child_trans_id in self._all_children(trans_id):
2043
yield self._transform.final_file_id(child_trans_id)
2046
possible_extras = set(self._transform.trans_id_tree_path(p) for p
2047
in self._transform._tree.extras())
2048
possible_extras.update(self._transform._new_contents)
2049
possible_extras.update(self._transform._removed_id)
2050
for trans_id in possible_extras:
2051
if self._transform.final_file_id(trans_id) is None:
2052
yield self._final_paths._determine_path(trans_id)
2054
def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
2055
yield_parents=False):
2056
for trans_id, parent_file_id in ordered_entries:
2057
file_id = self._transform.final_file_id(trans_id)
2060
if (specific_file_ids is not None
2061
and file_id not in specific_file_ids):
2063
kind = self._transform.final_kind(trans_id)
2065
kind = self._transform._tree.stored_kind(file_id)
2066
new_entry = inventory.make_entry(
2068
self._transform.final_name(trans_id),
2069
parent_file_id, file_id)
2070
yield new_entry, trans_id
2072
def _list_files_by_dir(self):
2073
todo = [ROOT_PARENT]
2075
while len(todo) > 0:
2077
parent_file_id = self._transform.final_file_id(parent)
2078
children = list(self._all_children(parent))
2079
paths = dict(zip(children, self._final_paths.get_paths(children)))
2080
children.sort(key=paths.get)
2081
todo.extend(reversed(children))
2082
for trans_id in children:
2083
ordered_ids.append((trans_id, parent_file_id))
2086
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
2087
# This may not be a maximally efficient implementation, but it is
2088
# reasonably straightforward. An implementation that grafts the
2089
# TreeTransform changes onto the tree's iter_entries_by_dir results
2090
# might be more efficient, but requires tricky inferences about stack
2092
ordered_ids = self._list_files_by_dir()
2093
for entry, trans_id in self._make_inv_entries(ordered_ids,
2094
specific_file_ids, yield_parents=yield_parents):
2095
yield unicode(self._final_paths.get_path(trans_id)), entry
2097
def _iter_entries_for_dir(self, dir_path):
2098
"""Return path, entry for items in a directory without recursing down."""
2099
dir_file_id = self.path2id(dir_path)
2101
for file_id in self.iter_children(dir_file_id):
2102
trans_id = self._transform.trans_id_file_id(file_id)
2103
ordered_ids.append((trans_id, file_id))
2104
for entry, trans_id in self._make_inv_entries(ordered_ids):
2105
yield unicode(self._final_paths.get_path(trans_id)), entry
2107
def list_files(self, include_root=False, from_dir=None, recursive=True):
2108
"""See WorkingTree.list_files."""
2109
# XXX This should behave like WorkingTree.list_files, but is really
2110
# more like RevisionTree.list_files.
2114
prefix = from_dir + '/'
2115
entries = self.iter_entries_by_dir()
2116
for path, entry in entries:
2117
if entry.name == '' and not include_root:
2120
if not path.startswith(prefix):
2122
path = path[len(prefix):]
2123
yield path, 'V', entry.kind, entry.file_id, entry
2125
if from_dir is None and include_root is True:
2126
root_entry = inventory.make_entry('directory', '',
2127
ROOT_PARENT, self.get_root_id())
2128
yield '', 'V', 'directory', root_entry.file_id, root_entry
2129
entries = self._iter_entries_for_dir(from_dir or '')
2130
for path, entry in entries:
2131
yield path, 'V', entry.kind, entry.file_id, entry
2133
def kind(self, file_id):
2134
trans_id = self._transform.trans_id_file_id(file_id)
2135
return self._transform.final_kind(trans_id)
2137
def stored_kind(self, file_id):
2138
trans_id = self._transform.trans_id_file_id(file_id)
2140
return self._transform._new_contents[trans_id]
2142
return self._transform._tree.stored_kind(file_id)
2144
def get_file_mtime(self, file_id, path=None):
2145
"""See Tree.get_file_mtime"""
2146
if not self._content_change(file_id):
2147
return self._transform._tree.get_file_mtime(file_id)
2148
return self._stat_limbo_file(file_id).st_mtime
2150
def _file_size(self, entry, stat_value):
2151
return self.get_file_size(entry.file_id)
2153
def get_file_size(self, file_id):
2154
"""See Tree.get_file_size"""
2155
if self.kind(file_id) == 'file':
2156
return self._transform._tree.get_file_size(file_id)
2160
def get_file_sha1(self, file_id, path=None, stat_value=None):
2161
trans_id = self._transform.trans_id_file_id(file_id)
2162
kind = self._transform._new_contents.get(trans_id)
2164
return self._transform._tree.get_file_sha1(file_id)
2166
fileobj = self.get_file(file_id)
2168
return sha_file(fileobj)
2172
def is_executable(self, file_id, path=None):
2175
trans_id = self._transform.trans_id_file_id(file_id)
2177
return self._transform._new_executability[trans_id]
2180
return self._transform._tree.is_executable(file_id, path)
2182
if e.errno == errno.ENOENT:
2185
except errors.NoSuchId:
2188
def path_content_summary(self, path):
2189
trans_id = self._path2trans_id(path)
2190
tt = self._transform
2191
tree_path = tt._tree_id_paths.get(trans_id)
2192
kind = tt._new_contents.get(trans_id)
2194
if tree_path is None or trans_id in tt._removed_contents:
2195
return 'missing', None, None, None
2196
summary = tt._tree.path_content_summary(tree_path)
2197
kind, size, executable, link_or_sha1 = summary
2200
limbo_name = tt._limbo_name(trans_id)
2201
if trans_id in tt._new_reference_revision:
2202
kind = 'tree-reference'
2204
statval = os.lstat(limbo_name)
2205
size = statval.st_size
2206
if not supports_executable():
2209
executable = statval.st_mode & S_IEXEC
2213
if kind == 'symlink':
2214
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
2215
executable = tt._new_executability.get(trans_id, executable)
2216
return kind, size, executable, link_or_sha1
2218
def iter_changes(self, from_tree, include_unchanged=False,
1376
2219
specific_files=None, pb=None, extra_trees=None,
1377
2220
require_versioned=True, want_unversioned=False):
1378
"""See InterTree._iter_changes.
2221
"""See InterTree.iter_changes.
1380
This implementation does not support include_unchanged, specific_files,
1381
or want_unversioned. extra_trees, require_versioned, and pb are
2223
This has a fast path that is only used when the from_tree matches
2224
the transform tree, and no fancy options are supplied.
1384
if from_tree is not self._transform._tree:
1385
raise ValueError('from_tree must be transform source tree.')
1386
if include_unchanged:
1387
raise ValueError('include_unchanged is not supported')
1388
if specific_files is not None:
1389
raise ValueError('specific_files is not supported')
2226
if (from_tree is not self._transform._tree or include_unchanged or
2227
specific_files or want_unversioned):
2228
return tree.InterTree(from_tree, self).iter_changes(
2229
include_unchanged=include_unchanged,
2230
specific_files=specific_files,
2232
extra_trees=extra_trees,
2233
require_versioned=require_versioned,
2234
want_unversioned=want_unversioned)
1390
2235
if want_unversioned:
1391
2236
raise ValueError('want_unversioned is not supported')
1392
return self._transform._iter_changes()
1394
def kind(self, file_id):
1395
trans_id = self._transform.trans_id_file_id(file_id)
1396
return self._transform.final_kind(trans_id)
1398
def get_file_mtime(self, file_id, path=None):
1399
"""See Tree.get_file_mtime"""
1400
trans_id = self._transform.trans_id_file_id(file_id)
1401
name = self._transform._limbo_name(trans_id)
1402
return os.stat(name).st_mtime
1404
def get_file(self, file_id):
2237
return self._transform.iter_changes()
2239
def get_file(self, file_id, path=None):
1405
2240
"""See Tree.get_file"""
2241
if not self._content_change(file_id):
2242
return self._transform._tree.get_file(file_id, path)
1406
2243
trans_id = self._transform.trans_id_file_id(file_id)
1407
2244
name = self._transform._limbo_name(trans_id)
1408
2245
return open(name, 'rb')
1410
def paths2ids(self, specific_files, trees=None, require_versioned=False):
1411
"""See Tree.paths2ids"""
2247
def get_file_with_stat(self, file_id, path=None):
2248
return self.get_file(file_id, path), None
2250
def annotate_iter(self, file_id,
2251
default_revision=_mod_revision.CURRENT_REVISION):
2252
changes = self._iter_changes_cache.get(file_id)
2256
changed_content, versioned, kind = (changes[2], changes[3],
2260
get_old = (kind[0] == 'file' and versioned[0])
2262
old_annotation = self._transform._tree.annotate_iter(file_id,
2263
default_revision=default_revision)
2267
return old_annotation
2268
if not changed_content:
2269
return old_annotation
2270
# TODO: This is doing something similar to what WT.annotate_iter is
2271
# doing, however it fails slightly because it doesn't know what
2272
# the *other* revision_id is, so it doesn't know how to give the
2273
# other as the origin for some lines, they all get
2274
# 'default_revision'
2275
# It would be nice to be able to use the new Annotator based
2276
# approach, as well.
2277
return annotate.reannotate([old_annotation],
2278
self.get_file(file_id).readlines(),
2281
def get_symlink_target(self, file_id):
2282
"""See Tree.get_symlink_target"""
2283
if not self._content_change(file_id):
2284
return self._transform._tree.get_symlink_target(file_id)
2285
trans_id = self._transform.trans_id_file_id(file_id)
2286
name = self._transform._limbo_name(trans_id)
2287
return osutils.readlink(name)
2289
def walkdirs(self, prefix=''):
2290
pending = [self._transform.root]
2291
while len(pending) > 0:
2292
parent_id = pending.pop()
2295
prefix = prefix.rstrip('/')
2296
parent_path = self._final_paths.get_path(parent_id)
2297
parent_file_id = self._transform.final_file_id(parent_id)
2298
for child_id in self._all_children(parent_id):
2299
path_from_root = self._final_paths.get_path(child_id)
2300
basename = self._transform.final_name(child_id)
2301
file_id = self._transform.final_file_id(child_id)
2302
kind = self._transform.final_kind(child_id)
2303
if kind is not None:
2304
versioned_kind = kind
2307
versioned_kind = self._transform._tree.stored_kind(file_id)
2308
if versioned_kind == 'directory':
2309
subdirs.append(child_id)
2310
children.append((path_from_root, basename, kind, None,
2311
file_id, versioned_kind))
2313
if parent_path.startswith(prefix):
2314
yield (parent_path, parent_file_id), children
2315
pending.extend(sorted(subdirs, key=self._final_paths.get_path,
2318
def get_parent_ids(self):
2319
return self._parent_ids
2321
def set_parent_ids(self, parent_ids):
2322
self._parent_ids = parent_ids
2324
def get_revision_tree(self, revision_id):
2325
return self._transform._tree.get_revision_tree(revision_id)
1415
2328
def joinpath(parent, child):
1588
2521
wt.add_conflicts(conflicts)
1589
2522
except errors.UnsupportedOperation:
1591
result = tt.apply(no_conflicts=True)
2524
result = tt.apply(no_conflicts=True,
2525
precomputed_delta=precomputed_delta)
1594
2528
top_pb.finished()
1598
def _iter_files_bytes_accelerated(tree, accelerator_tree, desired_files):
2532
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
2534
total = len(desired_files) + offset
1599
2536
if accelerator_tree is None:
1600
2537
new_desired_files = desired_files
1602
iter = accelerator_tree._iter_changes(tree, include_unchanged=True)
1603
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2539
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2540
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2541
in iter if not (c or e[0] != e[1])]
2542
if accelerator_tree.supports_content_filtering():
2543
unchanged = [(f, p) for (f, p) in unchanged
2544
if not accelerator_tree.iter_search_rules([p]).next()]
2545
unchanged = dict(unchanged)
1605
2546
new_desired_files = []
1606
for file_id, identifier in desired_files:
2548
for file_id, (trans_id, tree_path) in desired_files:
1607
2549
accelerator_path = unchanged.get(file_id)
1608
2550
if accelerator_path is None:
1609
new_desired_files.append((file_id, identifier))
2551
new_desired_files.append((file_id, (trans_id, tree_path)))
1611
contents = accelerator_tree.get_file(file_id, accelerator_path)
1614
contents_bytes = (contents.read(),)
1617
yield identifier, contents_bytes
1618
for result in tree.iter_files_bytes(new_desired_files):
2553
pb.update('Adding file contents', count + offset, total)
2555
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2558
contents = accelerator_tree.get_file(file_id, accelerator_path)
2559
if wt.supports_content_filtering():
2560
filters = wt._content_filter_stack(tree_path)
2561
contents = filtered_output_bytes(contents, filters,
2562
ContentFilterContext(tree_path, tree))
2564
tt.create_file(contents, trans_id)
2568
except AttributeError:
2569
# after filtering, contents may no longer be file-like
2573
for count, ((trans_id, tree_path), contents) in enumerate(
2574
tree.iter_files_bytes(new_desired_files)):
2575
if wt.supports_content_filtering():
2576
filters = wt._content_filter_stack(tree_path)
2577
contents = filtered_output_bytes(contents, filters,
2578
ContentFilterContext(tree_path, tree))
2579
tt.create_file(contents, trans_id)
2580
pb.update('Adding file contents', count + offset, total)
1622
2583
def _reparent_children(tt, old_parent, new_parent):
1623
2584
for child in tt.iter_tree_children(old_parent):
1624
2585
tt.adjust_path(tt.final_name(child), new_parent, child)
1626
2588
def _reparent_transform_children(tt, old_parent, new_parent):
1627
2589
by_parent = tt.by_parent()
1628
2590
for child in by_parent[old_parent]:
1629
2591
tt.adjust_path(tt.final_name(child), new_parent, child)
2592
return by_parent[old_parent]
1631
2595
def _content_match(tree, entry, file_id, kind, target_path):
1632
2596
if entry.kind != kind: