326
323
return ROOT_PARENT
327
324
return self.trans_id_tree_path(os.path.dirname(path))
326
def create_file(self, contents, trans_id, mode_id=None):
327
"""Schedule creation of a new file.
331
Contents is an iterator of strings, all of which will be written
332
to the target destination.
334
New file takes the permissions of any existing file with that id,
335
unless mode_id is specified.
337
name = self._limbo_name(trans_id)
341
unique_add(self._new_contents, trans_id, 'file')
343
# Clean up the file, it never got registered so
344
# TreeTransform.finalize() won't clean it up.
349
f.writelines(contents)
352
self._set_mode(trans_id, mode_id, S_ISREG)
354
def _set_mode(self, trans_id, mode_id, typefunc):
355
"""Set the mode of new file contents.
356
The mode_id is the existing file to get the mode from (often the same
357
as trans_id). The operation is only performed if there's a mode match
358
according to typefunc.
363
old_path = self._tree_id_paths[mode_id]
367
mode = os.stat(self._tree.abspath(old_path)).st_mode
369
if e.errno in (errno.ENOENT, errno.ENOTDIR):
370
# Either old_path doesn't exist, or the parent of the
371
# target is not a directory (but will be one eventually)
372
# Either way, we know it doesn't exist *right now*
373
# See also bug #248448
378
os.chmod(self._limbo_name(trans_id), mode)
380
def create_hardlink(self, path, trans_id):
381
"""Schedule creation of a hard link"""
382
name = self._limbo_name(trans_id)
386
if e.errno != errno.EPERM:
388
raise errors.HardLinkNotSupported(path)
390
unique_add(self._new_contents, trans_id, 'file')
392
# Clean up the file, it never got registered so
393
# TreeTransform.finalize() won't clean it up.
397
def create_directory(self, trans_id):
398
"""Schedule creation of a new directory.
400
See also new_directory.
402
os.mkdir(self._limbo_name(trans_id))
403
unique_add(self._new_contents, trans_id, 'directory')
405
def create_symlink(self, target, trans_id):
406
"""Schedule creation of a new symbolic link.
408
target is a bytestring.
409
See also new_symlink.
412
os.symlink(target, self._limbo_name(trans_id))
413
unique_add(self._new_contents, trans_id, 'symlink')
416
path = FinalPaths(self).get_path(trans_id)
419
raise UnableCreateSymlink(path=path)
421
def cancel_creation(self, trans_id):
422
"""Cancel the creation of new file contents."""
423
del self._new_contents[trans_id]
424
children = self._limbo_children.get(trans_id)
425
# if this is a limbo directory with children, move them before removing
427
if children is not None:
428
self._rename_in_limbo(children)
429
del self._limbo_children[trans_id]
430
del self._limbo_children_names[trans_id]
431
delete_any(self._limbo_name(trans_id))
329
433
def delete_contents(self, trans_id):
330
434
"""Schedule the contents of a path entry for deletion"""
331
kind = self.tree_kind(trans_id)
333
self._removed_contents.add(trans_id)
435
self.tree_kind(trans_id)
436
self._removed_contents.add(trans_id)
335
438
def cancel_deletion(self, trans_id):
336
439
"""Cancel a scheduled deletion"""
1011
1113
def get_preview_tree(self):
1012
1114
"""Return a tree representing the result of the transform.
1014
The tree is a snapshot, and altering the TreeTransform will invalidate
1116
This tree only supports the subset of Tree functionality required
1117
by show_diff_trees. It must only be compared to tt._tree.
1017
1119
return _PreviewTree(self)
1019
def commit(self, branch, message, merge_parents=None, strict=False,
1020
timestamp=None, timezone=None, committer=None, authors=None,
1021
revprops=None, revision_id=None):
1022
"""Commit the result of this TreeTransform to a branch.
1024
:param branch: The branch to commit to.
1025
:param message: The message to attach to the commit.
1026
:param merge_parents: Additional parent revision-ids specified by
1028
:param strict: If True, abort the commit if there are unversioned
1030
:param timestamp: if not None, seconds-since-epoch for the time and
1031
date. (May be a float.)
1032
:param timezone: Optional timezone for timestamp, as an offset in
1034
:param committer: Optional committer in email-id format.
1035
(e.g. "J Random Hacker <jrandom@example.com>")
1036
:param authors: Optional list of authors in email-id format.
1037
:param revprops: Optional dictionary of revision properties.
1038
:param revision_id: Optional revision id. (Specifying a revision-id
1039
may reduce performance for some non-native formats.)
1040
:return: The revision_id of the revision committed.
1042
self._check_malformed()
1044
unversioned = set(self._new_contents).difference(set(self._new_id))
1045
for trans_id in unversioned:
1046
if self.final_file_id(trans_id) is None:
1047
raise errors.StrictCommitFailed()
1049
revno, last_rev_id = branch.last_revision_info()
1050
if last_rev_id == _mod_revision.NULL_REVISION:
1051
if merge_parents is not None:
1052
raise ValueError('Cannot supply merge parents for first'
1056
parent_ids = [last_rev_id]
1057
if merge_parents is not None:
1058
parent_ids.extend(merge_parents)
1059
if self._tree.get_revision_id() != last_rev_id:
1060
raise ValueError('TreeTransform not based on branch basis: %s' %
1061
self._tree.get_revision_id())
1062
revprops = commit.Commit.update_revprops(revprops, branch, authors)
1063
builder = branch.get_commit_builder(parent_ids,
1064
timestamp=timestamp,
1066
committer=committer,
1068
revision_id=revision_id)
1069
preview = self.get_preview_tree()
1070
list(builder.record_iter_changes(preview, last_rev_id,
1071
self.iter_changes()))
1072
builder.finish_inventory()
1073
revision_id = builder.commit(message)
1074
branch.set_last_revision_info(revno + 1, revision_id)
1077
def _text_parent(self, trans_id):
1078
file_id = self.tree_file_id(trans_id)
1080
if file_id is None or self._tree.kind(file_id) != 'file':
1082
except errors.NoSuchFile:
1086
def _get_parents_texts(self, trans_id):
1087
"""Get texts for compression parents of this file."""
1088
file_id = self._text_parent(trans_id)
1091
return (self._tree.get_file_text(file_id),)
1093
def _get_parents_lines(self, trans_id):
1094
"""Get lines for compression parents of this file."""
1095
file_id = self._text_parent(trans_id)
1098
return (self._tree.get_file_lines(file_id),)
1100
def serialize(self, serializer):
1101
"""Serialize this TreeTransform.
1103
:param serializer: A Serialiser like pack.ContainerSerializer.
1105
new_name = dict((k, v.encode('utf-8')) for k, v in
1106
self._new_name.items())
1107
new_executability = dict((k, int(v)) for k, v in
1108
self._new_executability.items())
1109
tree_path_ids = dict((k.encode('utf-8'), v)
1110
for k, v in self._tree_path_ids.items())
1112
'_id_number': self._id_number,
1113
'_new_name': new_name,
1114
'_new_parent': self._new_parent,
1115
'_new_executability': new_executability,
1116
'_new_id': self._new_id,
1117
'_tree_path_ids': tree_path_ids,
1118
'_removed_id': list(self._removed_id),
1119
'_removed_contents': list(self._removed_contents),
1120
'_non_present_ids': self._non_present_ids,
1122
yield serializer.bytes_record(bencode.bencode(attribs),
1124
for trans_id, kind in self._new_contents.items():
1126
lines = osutils.chunks_to_lines(
1127
self._read_file_chunks(trans_id))
1128
parents = self._get_parents_lines(trans_id)
1129
mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1130
content = ''.join(mpdiff.to_patch())
1131
if kind == 'directory':
1133
if kind == 'symlink':
1134
content = self._read_symlink_target(trans_id)
1135
yield serializer.bytes_record(content, ((trans_id, kind),))
1137
def deserialize(self, records):
1138
"""Deserialize a stored TreeTransform.
1140
:param records: An iterable of (names, content) tuples, as per
1141
pack.ContainerPushParser.
1143
names, content = records.next()
1144
attribs = bencode.bdecode(content)
1145
self._id_number = attribs['_id_number']
1146
self._new_name = dict((k, v.decode('utf-8'))
1147
for k, v in attribs['_new_name'].items())
1148
self._new_parent = attribs['_new_parent']
1149
self._new_executability = dict((k, bool(v)) for k, v in
1150
attribs['_new_executability'].items())
1151
self._new_id = attribs['_new_id']
1152
self._r_new_id = dict((v, k) for k, v in self._new_id.items())
1153
self._tree_path_ids = {}
1154
self._tree_id_paths = {}
1155
for bytepath, trans_id in attribs['_tree_path_ids'].items():
1156
path = bytepath.decode('utf-8')
1157
self._tree_path_ids[path] = trans_id
1158
self._tree_id_paths[trans_id] = path
1159
self._removed_id = set(attribs['_removed_id'])
1160
self._removed_contents = set(attribs['_removed_contents'])
1161
self._non_present_ids = attribs['_non_present_ids']
1162
for ((trans_id, kind),), content in records:
1164
mpdiff = multiparent.MultiParent.from_patch(content)
1165
lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1166
self.create_file(lines, trans_id)
1167
if kind == 'directory':
1168
self.create_directory(trans_id)
1169
if kind == 'symlink':
1170
self.create_symlink(content.decode('utf-8'), trans_id)
1173
class DiskTreeTransform(TreeTransformBase):
1174
"""Tree transform storing its contents on disk."""
1176
def __init__(self, tree, limbodir, pb=None,
1177
case_sensitive=True):
1179
:param tree: The tree that will be transformed, but not necessarily
1181
:param limbodir: A directory where new files can be stored until
1182
they are installed in their proper places
1184
:param case_sensitive: If True, the target of the transform is
1185
case sensitive, not just case preserving.
1187
TreeTransformBase.__init__(self, tree, pb, case_sensitive)
1188
self._limbodir = limbodir
1189
self._deletiondir = None
1190
# A mapping of transform ids to their limbo filename
1191
self._limbo_files = {}
1192
self._possibly_stale_limbo_files = set()
1193
# A mapping of transform ids to a set of the transform ids of children
1194
# that their limbo directory has
1195
self._limbo_children = {}
1196
# Map transform ids to maps of child filename to child transform id
1197
self._limbo_children_names = {}
1198
# List of transform ids that need to be renamed from limbo into place
1199
self._needs_rename = set()
1200
self._creation_mtime = None
1203
"""Release the working tree lock, if held, clean up limbo dir.
1205
This is required if apply has not been invoked, but can be invoked
1208
if self._tree is None:
1211
limbo_paths = self._limbo_files.values() + list(
1212
self._possibly_stale_limbo_files)
1213
limbo_paths = sorted(limbo_paths, reverse=True)
1214
for path in limbo_paths:
1218
if e.errno != errno.ENOENT:
1220
# XXX: warn? perhaps we just got interrupted at an
1221
# inconvenient moment, but perhaps files are disappearing
1224
delete_any(self._limbodir)
1226
# We don't especially care *why* the dir is immortal.
1227
raise ImmortalLimbo(self._limbodir)
1229
if self._deletiondir is not None:
1230
delete_any(self._deletiondir)
1232
raise errors.ImmortalPendingDeletion(self._deletiondir)
1234
TreeTransformBase.finalize(self)
1236
def _limbo_name(self, trans_id):
1237
"""Generate the limbo name of a file"""
1238
limbo_name = self._limbo_files.get(trans_id)
1239
if limbo_name is None:
1240
limbo_name = self._generate_limbo_path(trans_id)
1241
self._limbo_files[trans_id] = limbo_name
1244
def _generate_limbo_path(self, trans_id):
1245
"""Generate a limbo path using the trans_id as the relative path.
1247
This is suitable as a fallback, and when the transform should not be
1248
sensitive to the path encoding of the limbo directory.
1250
self._needs_rename.add(trans_id)
1251
return pathjoin(self._limbodir, trans_id)
1253
def adjust_path(self, name, parent, trans_id):
1254
previous_parent = self._new_parent.get(trans_id)
1255
previous_name = self._new_name.get(trans_id)
1256
TreeTransformBase.adjust_path(self, name, parent, trans_id)
1257
if (trans_id in self._limbo_files and
1258
trans_id not in self._needs_rename):
1259
self._rename_in_limbo([trans_id])
1260
if previous_parent != parent:
1261
self._limbo_children[previous_parent].remove(trans_id)
1262
if previous_parent != parent or previous_name != name:
1263
del self._limbo_children_names[previous_parent][previous_name]
1265
def _rename_in_limbo(self, trans_ids):
1266
"""Fix limbo names so that the right final path is produced.
1268
This means we outsmarted ourselves-- we tried to avoid renaming
1269
these files later by creating them with their final names in their
1270
final parents. But now the previous name or parent is no longer
1271
suitable, so we have to rename them.
1273
Even for trans_ids that have no new contents, we must remove their
1274
entries from _limbo_files, because they are now stale.
1276
for trans_id in trans_ids:
1277
old_path = self._limbo_files[trans_id]
1278
self._possibly_stale_limbo_files.add(old_path)
1279
del self._limbo_files[trans_id]
1280
if trans_id not in self._new_contents:
1282
new_path = self._limbo_name(trans_id)
1283
os.rename(old_path, new_path)
1284
self._possibly_stale_limbo_files.remove(old_path)
1285
for descendant in self._limbo_descendants(trans_id):
1286
desc_path = self._limbo_files[descendant]
1287
desc_path = new_path + desc_path[len(old_path):]
1288
self._limbo_files[descendant] = desc_path
1290
def _limbo_descendants(self, trans_id):
1291
"""Return the set of trans_ids whose limbo paths descend from this."""
1292
descendants = set(self._limbo_children.get(trans_id, []))
1293
for descendant in list(descendants):
1294
descendants.update(self._limbo_descendants(descendant))
1297
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1298
"""Schedule creation of a new file.
1302
:param contents: an iterator of strings, all of which will be written
1303
to the target destination.
1304
:param trans_id: TreeTransform handle
1305
:param mode_id: If not None, force the mode of the target file to match
1306
the mode of the object referenced by mode_id.
1307
Otherwise, we will try to preserve mode bits of an existing file.
1308
:param sha1: If the sha1 of this content is already known, pass it in.
1309
We can use it to prevent future sha1 computations.
1311
name = self._limbo_name(trans_id)
1312
f = open(name, 'wb')
1314
unique_add(self._new_contents, trans_id, 'file')
1315
f.writelines(contents)
1318
self._set_mtime(name)
1319
self._set_mode(trans_id, mode_id, S_ISREG)
1320
# It is unfortunate we have to use lstat instead of fstat, but we just
1321
# used utime and chmod on the file, so we need the accurate final
1323
if sha1 is not None:
1324
self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
1326
def _read_file_chunks(self, trans_id):
1327
cur_file = open(self._limbo_name(trans_id), 'rb')
1329
return cur_file.readlines()
1333
def _read_symlink_target(self, trans_id):
1334
return os.readlink(self._limbo_name(trans_id))
1336
def _set_mtime(self, path):
1337
"""All files that are created get the same mtime.
1339
This time is set by the first object to be created.
1341
if self._creation_mtime is None:
1342
self._creation_mtime = time.time()
1343
os.utime(path, (self._creation_mtime, self._creation_mtime))
1345
def create_hardlink(self, path, trans_id):
1346
"""Schedule creation of a hard link"""
1347
name = self._limbo_name(trans_id)
1351
if e.errno != errno.EPERM:
1353
raise errors.HardLinkNotSupported(path)
1355
unique_add(self._new_contents, trans_id, 'file')
1357
# Clean up the file, it never got registered so
1358
# TreeTransform.finalize() won't clean it up.
1362
def create_directory(self, trans_id):
1363
"""Schedule creation of a new directory.
1365
See also new_directory.
1367
os.mkdir(self._limbo_name(trans_id))
1368
unique_add(self._new_contents, trans_id, 'directory')
1370
def create_symlink(self, target, trans_id):
1371
"""Schedule creation of a new symbolic link.
1373
target is a bytestring.
1374
See also new_symlink.
1377
os.symlink(target, self._limbo_name(trans_id))
1378
unique_add(self._new_contents, trans_id, 'symlink')
1381
path = FinalPaths(self).get_path(trans_id)
1384
raise UnableCreateSymlink(path=path)
1386
def cancel_creation(self, trans_id):
1387
"""Cancel the creation of new file contents."""
1388
del self._new_contents[trans_id]
1389
if trans_id in self._observed_sha1s:
1390
del self._observed_sha1s[trans_id]
1391
children = self._limbo_children.get(trans_id)
1392
# if this is a limbo directory with children, move them before removing
1394
if children is not None:
1395
self._rename_in_limbo(children)
1396
del self._limbo_children[trans_id]
1397
del self._limbo_children_names[trans_id]
1398
delete_any(self._limbo_name(trans_id))
1400
def new_orphan(self, trans_id, parent_id):
1401
# FIXME: There is no tree config, so we use the branch one (it's weird
1402
# to define it this way as orphaning can only occur in a working tree,
1403
# but that's all we have (for now). It will find the option in
1404
# locations.conf or bazaar.conf though) -- vila 20100916
1405
conf = self._tree.branch.get_config()
1406
conf_var_name = 'bzr.transform.orphan_policy'
1407
orphan_policy = conf.get_user_option(conf_var_name)
1408
default_policy = orphaning_registry.default_key
1409
if orphan_policy is None:
1410
orphan_policy = default_policy
1411
if orphan_policy not in orphaning_registry:
1412
trace.warning('%s (from %s) is not a known policy, defaulting '
1413
'to %s' % (orphan_policy, conf_var_name, default_policy))
1414
orphan_policy = default_policy
1415
handle_orphan = orphaning_registry.get(orphan_policy)
1416
handle_orphan(self, trans_id, parent_id)
1419
class OrphaningError(errors.BzrError):
1421
# Only bugs could lead to such exception being seen by the user
1422
internal_error = True
1423
_fmt = "Error while orphaning %s in %s directory"
1425
def __init__(self, orphan, parent):
1426
errors.BzrError.__init__(self)
1427
self.orphan = orphan
1428
self.parent = parent
1431
class OrphaningForbidden(OrphaningError):
1433
_fmt = "Policy: %s doesn't allow creating orphans."
1435
def __init__(self, policy):
1436
errors.BzrError.__init__(self)
1437
self.policy = policy
1440
def move_orphan(tt, orphan_id, parent_id):
1441
"""See TreeTransformBase.new_orphan.
1443
This creates a new orphan in the `bzr-orphans` dir at the root of the
1446
:param tt: The TreeTransform orphaning `trans_id`.
1448
:param orphan_id: The trans id that should be orphaned.
1450
:param parent_id: The orphan parent trans id.
1452
# Add the orphan dir if it doesn't exist
1453
orphan_dir_basename = 'bzr-orphans'
1454
od_id = tt.trans_id_tree_path(orphan_dir_basename)
1455
if tt.final_kind(od_id) is None:
1456
tt.create_directory(od_id)
1457
parent_path = tt._tree_id_paths[parent_id]
1458
# Find a name that doesn't exist yet in the orphan dir
1459
actual_name = tt.final_name(orphan_id)
1460
new_name = tt._available_backup_name(actual_name, od_id)
1461
tt.adjust_path(new_name, od_id, orphan_id)
1462
trace.warning('%s has been orphaned in %s'
1463
% (joinpath(parent_path, actual_name), orphan_dir_basename))
1466
def refuse_orphan(tt, orphan_id, parent_id):
1467
"""See TreeTransformBase.new_orphan.
1469
This refuses to create orphan, letting the caller handle the conflict.
1471
raise OrphaningForbidden('never')
1474
orphaning_registry = registry.Registry()
1475
orphaning_registry.register(
1476
'conflict', refuse_orphan,
1477
'Leave orphans in place and create a conflict on the directory.')
1478
orphaning_registry.register(
1479
'move', move_orphan,
1480
'Move orphans into the bzr-orphans directory.')
1481
orphaning_registry._set_default_key('conflict')
1484
class TreeTransform(DiskTreeTransform):
1122
class TreeTransform(TreeTransformBase):
1485
1123
"""Represent a tree transformation.
1487
1125
This object is designed to support incremental generation of the transform,
1576
# Cache of realpath results, to speed up canonical_path
1577
self._realpaths = {}
1578
# Cache of relpath results, to speed up canonical_path
1580
DiskTreeTransform.__init__(self, tree, limbodir, pb,
1214
TreeTransformBase.__init__(self, tree, limbodir, pb,
1581
1215
tree.case_sensitive)
1582
1216
self._deletiondir = deletiondir
1584
def canonical_path(self, path):
1585
"""Get the canonical tree-relative path"""
1586
# don't follow final symlinks
1587
abs = self._tree.abspath(path)
1588
if abs in self._relpaths:
1589
return self._relpaths[abs]
1590
dirname, basename = os.path.split(abs)
1591
if dirname not in self._realpaths:
1592
self._realpaths[dirname] = os.path.realpath(dirname)
1593
dirname = self._realpaths[dirname]
1594
abs = pathjoin(dirname, basename)
1595
if dirname in self._relpaths:
1596
relpath = pathjoin(self._relpaths[dirname], basename)
1597
relpath = relpath.rstrip('/\\')
1599
relpath = self._tree.relpath(abs)
1600
self._relpaths[abs] = relpath
1603
def tree_kind(self, trans_id):
1604
"""Determine the file kind in the working tree.
1606
:returns: The file kind or None if the file does not exist
1608
path = self._tree_id_paths.get(trans_id)
1612
return file_kind(self._tree.abspath(path))
1613
except errors.NoSuchFile:
1616
def _set_mode(self, trans_id, mode_id, typefunc):
1617
"""Set the mode of new file contents.
1618
The mode_id is the existing file to get the mode from (often the same
1619
as trans_id). The operation is only performed if there's a mode match
1620
according to typefunc.
1625
old_path = self._tree_id_paths[mode_id]
1629
mode = os.stat(self._tree.abspath(old_path)).st_mode
1631
if e.errno in (errno.ENOENT, errno.ENOTDIR):
1632
# Either old_path doesn't exist, or the parent of the
1633
# target is not a directory (but will be one eventually)
1634
# Either way, we know it doesn't exist *right now*
1635
# See also bug #248448
1640
os.chmod(self._limbo_name(trans_id), mode)
1642
def iter_tree_children(self, parent_id):
1643
"""Iterate through the entry's tree children, if any"""
1645
path = self._tree_id_paths[parent_id]
1649
children = os.listdir(self._tree.abspath(path))
1651
if not (osutils._is_error_enotdir(e)
1652
or e.errno in (errno.ENOENT, errno.ESRCH)):
1656
for child in children:
1657
childpath = joinpath(path, child)
1658
if self._tree.is_control_filename(childpath):
1660
yield self.trans_id_tree_path(childpath)
1662
def _generate_limbo_path(self, trans_id):
1663
"""Generate a limbo path using the final path if possible.
1665
This optimizes the performance of applying the tree transform by
1666
avoiding renames. These renames can be avoided only when the parent
1667
directory is already scheduled for creation.
1669
If the final path cannot be used, falls back to using the trans_id as
1672
parent = self._new_parent.get(trans_id)
1673
# if the parent directory is already in limbo (e.g. when building a
1674
# tree), choose a limbo name inside the parent, to reduce further
1676
use_direct_path = False
1677
if self._new_contents.get(parent) == 'directory':
1678
filename = self._new_name.get(trans_id)
1679
if filename is not None:
1680
if parent not in self._limbo_children:
1681
self._limbo_children[parent] = set()
1682
self._limbo_children_names[parent] = {}
1683
use_direct_path = True
1684
# the direct path can only be used if no other file has
1685
# already taken this pathname, i.e. if the name is unused, or
1686
# if it is already associated with this trans_id.
1687
elif self._case_sensitive_target:
1688
if (self._limbo_children_names[parent].get(filename)
1689
in (trans_id, None)):
1690
use_direct_path = True
1692
for l_filename, l_trans_id in\
1693
self._limbo_children_names[parent].iteritems():
1694
if l_trans_id == trans_id:
1696
if l_filename.lower() == filename.lower():
1699
use_direct_path = True
1701
if not use_direct_path:
1702
return DiskTreeTransform._generate_limbo_path(self, trans_id)
1704
limbo_name = pathjoin(self._limbo_files[parent], filename)
1705
self._limbo_children[parent].add(trans_id)
1706
self._limbo_children_names[parent][filename] = trans_id
1710
1218
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1711
1219
"""Apply all changes to the inventory and filesystem.
2952
2234
if basis_tree is None:
2953
2235
basis_tree = working_tree.basis_tree()
2954
2236
basis_tree.lock_read()
2955
if basis_tree.has_id(file_id):
2237
if file_id in basis_tree:
2956
2238
if wt_sha1 != basis_tree.get_file_sha1(file_id):
2957
2239
keep_content = True
2958
elif target_kind is None and not target_versioned:
2240
elif kind[1] is None and not versioned[1]:
2959
2241
keep_content = True
2960
if wt_kind is not None:
2242
if kind[0] is not None:
2961
2243
if not keep_content:
2962
2244
tt.delete_contents(trans_id)
2963
elif target_kind is not None:
2964
parent_trans_id = tt.trans_id_file_id(wt_parent)
2965
backup_name = tt._available_backup_name(
2966
wt_name, parent_trans_id)
2245
elif kind[1] is not None:
2246
parent_trans_id = tt.trans_id_file_id(parent[0])
2247
by_parent = tt.by_parent()
2248
backup_name = _get_backup_name(name[0], by_parent,
2249
parent_trans_id, tt)
2967
2250
tt.adjust_path(backup_name, parent_trans_id, trans_id)
2968
new_trans_id = tt.create_path(wt_name, parent_trans_id)
2969
if wt_versioned and target_versioned:
2251
new_trans_id = tt.create_path(name[0], parent_trans_id)
2252
if versioned == (True, True):
2970
2253
tt.unversion_file(trans_id)
2971
2254
tt.version_file(file_id, new_trans_id)
2972
2255
# New contents should have the same unix perms as old
2974
2257
mode_id = trans_id
2975
2258
trans_id = new_trans_id
2976
if target_kind in ('directory', 'tree-reference'):
2259
if kind[1] in ('directory', 'tree-reference'):
2977
2260
tt.create_directory(trans_id)
2978
if target_kind == 'tree-reference':
2261
if kind[1] == 'tree-reference':
2979
2262
revision = target_tree.get_reference_revision(file_id,
2981
2264
tt.set_tree_reference(revision, trans_id)
2982
elif target_kind == 'symlink':
2265
elif kind[1] == 'symlink':
2983
2266
tt.create_symlink(target_tree.get_symlink_target(file_id),
2985
elif target_kind == 'file':
2268
elif kind[1] == 'file':
2986
2269
deferred_files.append((file_id, (trans_id, mode_id)))
2987
2270
if basis_tree is None:
2988
2271
basis_tree = working_tree.basis_tree()
2989
2272
basis_tree.lock_read()
2990
2273
new_sha1 = target_tree.get_file_sha1(file_id)
2991
if (basis_tree.has_id(file_id) and
2992
new_sha1 == basis_tree.get_file_sha1(file_id)):
2274
if (file_id in basis_tree and new_sha1 ==
2275
basis_tree.get_file_sha1(file_id)):
2993
2276
if file_id in merge_modified:
2994
2277
del merge_modified[file_id]
2996
2279
merge_modified[file_id] = new_sha1
2998
2281
# preserve the execute bit when backing up
2999
if keep_content and wt_executable == target_executable:
3000
tt.set_executability(target_executable, trans_id)
3001
elif target_kind is not None:
3002
raise AssertionError(target_kind)
3003
if not wt_versioned and target_versioned:
2282
if keep_content and executable[0] == executable[1]:
2283
tt.set_executability(executable[1], trans_id)
2284
elif kind[1] is not None:
2285
raise AssertionError(kind[1])
2286
if versioned == (False, True):
3004
2287
tt.version_file(file_id, trans_id)
3005
if wt_versioned and not target_versioned:
2288
if versioned == (True, False):
3006
2289
tt.unversion_file(trans_id)
3007
if (target_name is not None and
3008
(wt_name != target_name or wt_parent != target_parent)):
3009
if target_name == '' and target_parent is None:
2290
if (name[1] is not None and
2291
(name[0] != name[1] or parent[0] != parent[1])):
2292
if name[1] == '' and parent[1] is None:
3010
2293
parent_trans = ROOT_PARENT
3012
parent_trans = tt.trans_id_file_id(target_parent)
3013
if wt_parent is None and wt_versioned:
3014
tt.adjust_root_path(target_name, parent_trans)
3016
tt.adjust_path(target_name, parent_trans, trans_id)
3017
if wt_executable != target_executable and target_kind == "file":
3018
tt.set_executability(target_executable, trans_id)
3019
if working_tree.supports_content_filtering():
3020
for index, ((trans_id, mode_id), bytes) in enumerate(
3021
target_tree.iter_files_bytes(deferred_files)):
3022
file_id = deferred_files[index][0]
3023
# We're reverting a tree to the target tree so using the
3024
# target tree to find the file path seems the best choice
3025
# here IMO - Ian C 27/Oct/2009
3026
filter_tree_path = target_tree.id2path(file_id)
3027
filters = working_tree._content_filter_stack(filter_tree_path)
3028
bytes = filtered_output_bytes(bytes, filters,
3029
ContentFilterContext(filter_tree_path, working_tree))
3030
tt.create_file(bytes, trans_id, mode_id)
3032
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
3034
tt.create_file(bytes, trans_id, mode_id)
3035
tt.fixup_new_roots()
2295
parent_trans = tt.trans_id_file_id(parent[1])
2296
tt.adjust_path(name[1], parent_trans, trans_id)
2297
if executable[0] != executable[1] and kind[1] == "file":
2298
tt.set_executability(executable[1], trans_id)
2299
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2301
tt.create_file(bytes, trans_id, mode_id)
3037
2303
if basis_tree is not None:
3038
2304
basis_tree.unlock()
3039
2305
return merge_modified
3042
def resolve_conflicts(tt, pb=None, pass_func=None):
2308
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
3043
2309
"""Make many conflict-resolution attempts, but die if they fail"""
3044
2310
if pass_func is None:
3045
2311
pass_func = conflict_pass
3046
2312
new_conflicts = set()
3047
pb = ui.ui_factory.nested_progress_bar()
3049
2314
for n in range(10):
3050
2315
pb.update('Resolution pass', n+1, 10)