13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
19
21
from stat import S_ISREG, S_IEXEC
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
25
config as _mod_config,
32
lazy_import.lazy_import(globals(), """
23
33
from bzrlib import (
31
43
revision as _mod_revision,
33
from bzrlib.util import bencode
47
from bzrlib.i18n import gettext
35
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
36
ReusingTransform, NotVersionedError, CantMoveRoot,
37
ExistingLimbo, ImmortalLimbo, NoFinalPath,
49
from bzrlib.errors import (DuplicateKey, MalformedTransform,
50
ReusingTransform, CantMoveRoot,
51
ImmortalLimbo, NoFinalPath,
38
52
UnableCreateSymlink)
39
from bzrlib.inventory import InventoryEntry
53
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
54
from bzrlib.mutabletree import MutableTree
40
55
from bzrlib.osutils import (
50
from bzrlib.progress import DummyProgress, ProgressPhase
63
from bzrlib.progress import ProgressPhase
51
64
from bzrlib.symbol_versioning import (
55
from bzrlib.trace import mutter, warning
56
from bzrlib import tree
58
import bzrlib.urlutils as urlutils
61
71
ROOT_PARENT = "root-parent"
64
73
def unique_add(map, key, value):
66
75
raise DuplicateKey(key=key)
70
80
class _TransformResults(object):
71
81
def __init__(self, modified_paths, rename_count):
72
82
object.__init__(self)
263
221
self.version_file(old_root_file_id, old_root)
264
222
self.unversion_file(self._new_root)
224
def fixup_new_roots(self):
225
"""Reinterpret requests to change the root directory
227
Instead of creating a root directory, or moving an existing directory,
228
all the attributes and children of the new root are applied to the
229
existing root directory.
231
This means that the old root trans-id becomes obsolete, so it is
232
recommended only to invoke this after the root trans-id has become
236
new_roots = [k for k, v in self._new_parent.iteritems() if v ==
238
if len(new_roots) < 1:
240
if len(new_roots) != 1:
241
raise ValueError('A tree cannot have two roots!')
242
if self._new_root is None:
243
self._new_root = new_roots[0]
245
old_new_root = new_roots[0]
246
# unversion the new root's directory.
247
if self.final_kind(self._new_root) is None:
248
file_id = self.final_file_id(old_new_root)
250
file_id = self.final_file_id(self._new_root)
251
if old_new_root in self._new_id:
252
self.cancel_versioning(old_new_root)
254
self.unversion_file(old_new_root)
255
# if, at this stage, root still has an old file_id, zap it so we can
256
# stick a new one in.
257
if (self.tree_file_id(self._new_root) is not None and
258
self._new_root not in self._removed_id):
259
self.unversion_file(self._new_root)
260
if file_id is not None:
261
self.version_file(file_id, self._new_root)
263
# Now move children of new root into old root directory.
264
# Ensure all children are registered with the transaction, but don't
265
# use directly-- some tree children have new parents
266
list(self.iter_tree_children(old_new_root))
267
# Move all children of new root into old root directory.
268
for child in self.by_parent().get(old_new_root, []):
269
self.adjust_path(self.final_name(child), self._new_root, child)
271
# Ensure old_new_root has no directory.
272
if old_new_root in self._new_contents:
273
self.cancel_creation(old_new_root)
275
self.delete_contents(old_new_root)
277
# prevent deletion of root directory.
278
if self._new_root in self._removed_contents:
279
self.cancel_deletion(self._new_root)
281
# destroy path info for old_new_root.
282
del self._new_parent[old_new_root]
283
del self._new_name[old_new_root]
266
285
def trans_id_tree_file_id(self, inventory_id):
267
286
"""Determine the transaction id of a working tree file.
269
288
This reflects only files that already exist, not ones that will be
270
289
added by transactions.
331
331
return ROOT_PARENT
332
332
return self.trans_id_tree_path(os.path.dirname(path))
334
def create_file(self, contents, trans_id, mode_id=None):
335
"""Schedule creation of a new file.
339
Contents is an iterator of strings, all of which will be written
340
to the target destination.
342
New file takes the permissions of any existing file with that id,
343
unless mode_id is specified.
345
name = self._limbo_name(trans_id)
349
unique_add(self._new_contents, trans_id, 'file')
351
# Clean up the file, it never got registered so
352
# TreeTransform.finalize() won't clean it up.
357
f.writelines(contents)
360
self._set_mode(trans_id, mode_id, S_ISREG)
362
def _set_mode(self, trans_id, mode_id, typefunc):
363
"""Set the mode of new file contents.
364
The mode_id is the existing file to get the mode from (often the same
365
as trans_id). The operation is only performed if there's a mode match
366
according to typefunc.
371
old_path = self._tree_id_paths[mode_id]
375
mode = os.stat(self._tree.abspath(old_path)).st_mode
377
if e.errno in (errno.ENOENT, errno.ENOTDIR):
378
# Either old_path doesn't exist, or the parent of the
379
# target is not a directory (but will be one eventually)
380
# Either way, we know it doesn't exist *right now*
381
# See also bug #248448
386
os.chmod(self._limbo_name(trans_id), mode)
388
def create_hardlink(self, path, trans_id):
389
"""Schedule creation of a hard link"""
390
name = self._limbo_name(trans_id)
394
if e.errno != errno.EPERM:
396
raise errors.HardLinkNotSupported(path)
398
unique_add(self._new_contents, trans_id, 'file')
400
# Clean up the file, it never got registered so
401
# TreeTransform.finalize() won't clean it up.
405
def create_directory(self, trans_id):
406
"""Schedule creation of a new directory.
408
See also new_directory.
410
os.mkdir(self._limbo_name(trans_id))
411
unique_add(self._new_contents, trans_id, 'directory')
413
def create_symlink(self, target, trans_id):
414
"""Schedule creation of a new symbolic link.
416
target is a bytestring.
417
See also new_symlink.
420
os.symlink(target, self._limbo_name(trans_id))
421
unique_add(self._new_contents, trans_id, 'symlink')
424
path = FinalPaths(self).get_path(trans_id)
427
raise UnableCreateSymlink(path=path)
429
def cancel_creation(self, trans_id):
430
"""Cancel the creation of new file contents."""
431
del self._new_contents[trans_id]
432
children = self._limbo_children.get(trans_id)
433
# if this is a limbo directory with children, move them before removing
435
if children is not None:
436
self._rename_in_limbo(children)
437
del self._limbo_children[trans_id]
438
del self._limbo_children_names[trans_id]
439
delete_any(self._limbo_name(trans_id))
441
334
def delete_contents(self, trans_id):
442
335
"""Schedule the contents of a path entry for deletion"""
443
self.tree_kind(trans_id)
444
self._removed_contents.add(trans_id)
336
kind = self.tree_kind(trans_id)
338
self._removed_contents.add(trans_id)
446
340
def cancel_deletion(self, trans_id):
447
341
"""Cancel a scheduled deletion"""
504
398
return sorted(FinalPaths(self).get_paths(new_ids))
506
400
def _inventory_altered(self):
507
"""Get the trans_ids and paths of files needing new inv entries."""
509
for id_set in [self._new_name, self._new_parent, self._new_id,
401
"""Determine which trans_ids need new Inventory entries.
403
An new entry is needed when anything that would be reflected by an
404
inventory entry changes, including file name, file_id, parent file_id,
405
file kind, and the execute bit.
407
Some care is taken to return entries with real changes, not cases
408
where the value is deleted and then restored to its original value,
409
but some actually unchanged values may be returned.
411
:returns: A list of (path, trans_id) for all items requiring an
412
inventory change. Ordered by path.
415
# Find entries whose file_ids are new (or changed).
416
new_file_id = set(t for t in self._new_id
417
if self._new_id[t] != self.tree_file_id(t))
418
for id_set in [self._new_name, self._new_parent, new_file_id,
510
419
self._new_executability]:
511
new_ids.update(id_set)
420
changed_ids.update(id_set)
421
# removing implies a kind change
512
422
changed_kind = set(self._removed_contents)
513
424
changed_kind.intersection_update(self._new_contents)
514
changed_kind.difference_update(new_ids)
515
changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
517
new_ids.update(changed_kind)
518
return sorted(FinalPaths(self).get_paths(new_ids))
520
def tree_kind(self, trans_id):
521
"""Determine the file kind in the working tree.
523
Raises NoSuchFile if the file does not exist
525
path = self._tree_id_paths.get(trans_id)
527
raise NoSuchFile(None)
529
return file_kind(self._tree.abspath(path))
531
if e.errno != errno.ENOENT:
534
raise NoSuchFile(path)
425
# Ignore entries that are already known to have changed.
426
changed_kind.difference_update(changed_ids)
427
# to keep only the truly changed ones
428
changed_kind = (t for t in changed_kind
429
if self.tree_kind(t) != self.final_kind(t))
430
# all kind changes will alter the inventory
431
changed_ids.update(changed_kind)
432
# To find entries with changed parent_ids, find parents which existed,
433
# but changed file_id.
434
changed_file_id = set(t for t in new_file_id if t in self._removed_id)
435
# Now add all their children to the set.
436
for parent_trans_id in new_file_id:
437
changed_ids.update(self.iter_tree_children(parent_trans_id))
438
return sorted(FinalPaths(self).get_paths(changed_ids))
536
440
def final_kind(self, trans_id):
537
441
"""Determine the final file kind, after any changes applied.
539
Raises NoSuchFile if the file does not exist/has no contents.
540
(It is conceivable that a path would be created without the
541
corresponding contents insertion command)
443
:return: None if the file does not exist/has no contents. (It is
444
conceivable that a path would be created without the corresponding
445
contents insertion command)
543
447
if trans_id in self._new_contents:
544
448
return self._new_contents[trans_id]
545
449
elif trans_id in self._removed_contents:
546
raise NoSuchFile(None)
548
452
return self.tree_kind(trans_id)
666
575
# ensure that all children are registered with the transaction
667
576
list(self.iter_tree_children(parent_id))
669
def iter_tree_children(self, parent_id):
670
"""Iterate through the entry's tree children, if any"""
672
path = self._tree_id_paths[parent_id]
676
children = os.listdir(self._tree.abspath(path))
678
if not (osutils._is_error_enotdir(e)
679
or e.errno in (errno.ENOENT, errno.ESRCH)):
683
for child in children:
684
childpath = joinpath(path, child)
685
if self._tree.is_control_filename(childpath):
687
yield self.trans_id_tree_path(childpath)
689
def has_named_child(self, by_parent, parent_id, name):
691
children = by_parent[parent_id]
694
for child in children:
578
def _has_named_child(self, name, parent_id, known_children):
579
"""Does a parent already have a name child.
581
:param name: The searched for name.
583
:param parent_id: The parent for which the check is made.
585
:param known_children: The already known children. This should have
586
been recently obtained from `self.by_parent.get(parent_id)`
587
(or will be if None is passed).
589
if known_children is None:
590
known_children = self.by_parent().get(parent_id, [])
591
for child in known_children:
695
592
if self.final_name(child) == name:
698
path = self._tree_id_paths[parent_id]
594
parent_path = self._tree_id_paths.get(parent_id, None)
595
if parent_path is None:
596
# No parent... no children
701
childpath = joinpath(path, name)
702
child_id = self._tree_path_ids.get(childpath)
598
child_path = joinpath(parent_path, name)
599
child_id = self._tree_path_ids.get(child_path, None)
703
600
if child_id is None:
704
return lexists(self._tree.abspath(childpath))
601
# Not known by the tree transform yet, check the filesystem
602
return osutils.lexists(self._tree.abspath(child_path))
706
if self.final_parent(child_id) != parent_id:
708
if child_id in self._removed_contents:
709
# XXX What about dangling file-ids?
604
raise AssertionError('child_id is missing: %s, %s, %s'
605
% (name, parent_id, child_id))
607
def _available_backup_name(self, name, target_id):
608
"""Find an available backup name.
610
:param name: The basename of the file.
612
:param target_id: The directory trans_id where the backup should
615
known_children = self.by_parent().get(target_id, [])
616
return osutils.available_backup_name(
618
lambda base: self._has_named_child(
619
base, target_id, known_children))
714
621
def _parent_loops(self):
715
622
"""No entry should be its own ancestor"""
836
738
def _parent_type_conflicts(self, by_parent):
837
"""parents must have directory 'contents'."""
739
"""Children must have a directory parent"""
839
741
for parent_id, children in by_parent.iteritems():
840
if parent_id is ROOT_PARENT:
842
if not self._any_contents(children):
844
for child in children:
846
self.final_kind(child)
850
kind = self.final_kind(parent_id)
742
if parent_id == ROOT_PARENT:
745
for child_id in children:
746
if self.final_kind(child_id) is not None:
751
# There is at least a child, so we need an existing directory to
753
kind = self.final_kind(parent_id)
755
# The directory will be deleted
854
756
conflicts.append(('missing parent', parent_id))
855
757
elif kind != "directory":
758
# Meh, we need a *directory* to put something in it
856
759
conflicts.append(('non-directory parent', parent_id))
859
def _any_contents(self, trans_ids):
860
"""Return true if any of the trans_ids, will have contents."""
861
for trans_id in trans_ids:
863
kind = self.final_kind(trans_id)
869
def _limbo_name(self, trans_id):
870
"""Generate the limbo name of a file"""
871
limbo_name = self._limbo_files.get(trans_id)
872
if limbo_name is not None:
874
parent = self._new_parent.get(trans_id)
875
# if the parent directory is already in limbo (e.g. when building a
876
# tree), choose a limbo name inside the parent, to reduce further
878
use_direct_path = False
879
if self._new_contents.get(parent) == 'directory':
880
filename = self._new_name.get(trans_id)
881
if filename is not None:
882
if parent not in self._limbo_children:
883
self._limbo_children[parent] = set()
884
self._limbo_children_names[parent] = {}
885
use_direct_path = True
886
# the direct path can only be used if no other file has
887
# already taken this pathname, i.e. if the name is unused, or
888
# if it is already associated with this trans_id.
889
elif self._case_sensitive_target:
890
if (self._limbo_children_names[parent].get(filename)
891
in (trans_id, None)):
892
use_direct_path = True
894
for l_filename, l_trans_id in\
895
self._limbo_children_names[parent].iteritems():
896
if l_trans_id == trans_id:
898
if l_filename.lower() == filename.lower():
901
use_direct_path = True
904
limbo_name = pathjoin(self._limbo_files[parent], filename)
905
self._limbo_children[parent].add(trans_id)
906
self._limbo_children_names[parent][filename] = trans_id
908
limbo_name = pathjoin(self._limbodir, trans_id)
909
self._needs_rename.add(trans_id)
910
self._limbo_files[trans_id] = limbo_name
913
762
def _set_executability(self, path, trans_id):
914
763
"""Set the executability of versioned files """
915
if supports_executable():
764
if self._tree._supports_executable():
916
765
new_executability = self._new_executability[trans_id]
917
766
abspath = self._tree.abspath(path)
918
767
current_mode = os.stat(abspath).st_mode
1121
1009
def get_preview_tree(self):
1122
1010
"""Return a tree representing the result of the transform.
1124
This tree only supports the subset of Tree functionality required
1125
by show_diff_trees. It must only be compared to tt._tree.
1012
The tree is a snapshot, and altering the TreeTransform will invalidate
1127
1015
return _PreviewTree(self)
1017
def commit(self, branch, message, merge_parents=None, strict=False,
1018
timestamp=None, timezone=None, committer=None, authors=None,
1019
revprops=None, revision_id=None):
1020
"""Commit the result of this TreeTransform to a branch.
1022
:param branch: The branch to commit to.
1023
:param message: The message to attach to the commit.
1024
:param merge_parents: Additional parent revision-ids specified by
1026
:param strict: If True, abort the commit if there are unversioned
1028
:param timestamp: if not None, seconds-since-epoch for the time and
1029
date. (May be a float.)
1030
:param timezone: Optional timezone for timestamp, as an offset in
1032
:param committer: Optional committer in email-id format.
1033
(e.g. "J Random Hacker <jrandom@example.com>")
1034
:param authors: Optional list of authors in email-id format.
1035
:param revprops: Optional dictionary of revision properties.
1036
:param revision_id: Optional revision id. (Specifying a revision-id
1037
may reduce performance for some non-native formats.)
1038
:return: The revision_id of the revision committed.
1040
self._check_malformed()
1042
unversioned = set(self._new_contents).difference(set(self._new_id))
1043
for trans_id in unversioned:
1044
if self.final_file_id(trans_id) is None:
1045
raise errors.StrictCommitFailed()
1047
revno, last_rev_id = branch.last_revision_info()
1048
if last_rev_id == _mod_revision.NULL_REVISION:
1049
if merge_parents is not None:
1050
raise ValueError('Cannot supply merge parents for first'
1054
parent_ids = [last_rev_id]
1055
if merge_parents is not None:
1056
parent_ids.extend(merge_parents)
1057
if self._tree.get_revision_id() != last_rev_id:
1058
raise ValueError('TreeTransform not based on branch basis: %s' %
1059
self._tree.get_revision_id())
1060
revprops = commit.Commit.update_revprops(revprops, branch, authors)
1061
builder = branch.get_commit_builder(parent_ids,
1062
timestamp=timestamp,
1064
committer=committer,
1066
revision_id=revision_id)
1067
preview = self.get_preview_tree()
1068
list(builder.record_iter_changes(preview, last_rev_id,
1069
self.iter_changes()))
1070
builder.finish_inventory()
1071
revision_id = builder.commit(message)
1072
branch.set_last_revision_info(revno + 1, revision_id)
1129
1075
def _text_parent(self, trans_id):
1130
1076
file_id = self.tree_file_id(trans_id)
1226
1168
self.create_symlink(content.decode('utf-8'), trans_id)
1229
class TreeTransform(TreeTransformBase):
1171
class DiskTreeTransform(TreeTransformBase):
1172
"""Tree transform storing its contents on disk."""
1174
def __init__(self, tree, limbodir, pb=None,
1175
case_sensitive=True):
1177
:param tree: The tree that will be transformed, but not necessarily
1179
:param limbodir: A directory where new files can be stored until
1180
they are installed in their proper places
1182
:param case_sensitive: If True, the target of the transform is
1183
case sensitive, not just case preserving.
1185
TreeTransformBase.__init__(self, tree, pb, case_sensitive)
1186
self._limbodir = limbodir
1187
self._deletiondir = None
1188
# A mapping of transform ids to their limbo filename
1189
self._limbo_files = {}
1190
self._possibly_stale_limbo_files = set()
1191
# A mapping of transform ids to a set of the transform ids of children
1192
# that their limbo directory has
1193
self._limbo_children = {}
1194
# Map transform ids to maps of child filename to child transform id
1195
self._limbo_children_names = {}
1196
# List of transform ids that need to be renamed from limbo into place
1197
self._needs_rename = set()
1198
self._creation_mtime = None
1201
"""Release the working tree lock, if held, clean up limbo dir.
1203
This is required if apply has not been invoked, but can be invoked
1206
if self._tree is None:
1209
limbo_paths = self._limbo_files.values() + list(
1210
self._possibly_stale_limbo_files)
1211
limbo_paths = sorted(limbo_paths, reverse=True)
1212
for path in limbo_paths:
1216
if e.errno != errno.ENOENT:
1218
# XXX: warn? perhaps we just got interrupted at an
1219
# inconvenient moment, but perhaps files are disappearing
1222
delete_any(self._limbodir)
1224
# We don't especially care *why* the dir is immortal.
1225
raise ImmortalLimbo(self._limbodir)
1227
if self._deletiondir is not None:
1228
delete_any(self._deletiondir)
1230
raise errors.ImmortalPendingDeletion(self._deletiondir)
1232
TreeTransformBase.finalize(self)
1234
def _limbo_supports_executable(self):
1235
"""Check if the limbo path supports the executable bit."""
1236
# FIXME: Check actual file system capabilities of limbodir
1237
return osutils.supports_executable()
1239
def _limbo_name(self, trans_id):
1240
"""Generate the limbo name of a file"""
1241
limbo_name = self._limbo_files.get(trans_id)
1242
if limbo_name is None:
1243
limbo_name = self._generate_limbo_path(trans_id)
1244
self._limbo_files[trans_id] = limbo_name
1247
def _generate_limbo_path(self, trans_id):
1248
"""Generate a limbo path using the trans_id as the relative path.
1250
This is suitable as a fallback, and when the transform should not be
1251
sensitive to the path encoding of the limbo directory.
1253
self._needs_rename.add(trans_id)
1254
return pathjoin(self._limbodir, trans_id)
1256
def adjust_path(self, name, parent, trans_id):
1257
previous_parent = self._new_parent.get(trans_id)
1258
previous_name = self._new_name.get(trans_id)
1259
TreeTransformBase.adjust_path(self, name, parent, trans_id)
1260
if (trans_id in self._limbo_files and
1261
trans_id not in self._needs_rename):
1262
self._rename_in_limbo([trans_id])
1263
if previous_parent != parent:
1264
self._limbo_children[previous_parent].remove(trans_id)
1265
if previous_parent != parent or previous_name != name:
1266
del self._limbo_children_names[previous_parent][previous_name]
1268
def _rename_in_limbo(self, trans_ids):
1269
"""Fix limbo names so that the right final path is produced.
1271
This means we outsmarted ourselves-- we tried to avoid renaming
1272
these files later by creating them with their final names in their
1273
final parents. But now the previous name or parent is no longer
1274
suitable, so we have to rename them.
1276
Even for trans_ids that have no new contents, we must remove their
1277
entries from _limbo_files, because they are now stale.
1279
for trans_id in trans_ids:
1280
old_path = self._limbo_files[trans_id]
1281
self._possibly_stale_limbo_files.add(old_path)
1282
del self._limbo_files[trans_id]
1283
if trans_id not in self._new_contents:
1285
new_path = self._limbo_name(trans_id)
1286
os.rename(old_path, new_path)
1287
self._possibly_stale_limbo_files.remove(old_path)
1288
for descendant in self._limbo_descendants(trans_id):
1289
desc_path = self._limbo_files[descendant]
1290
desc_path = new_path + desc_path[len(old_path):]
1291
self._limbo_files[descendant] = desc_path
1293
def _limbo_descendants(self, trans_id):
1294
"""Return the set of trans_ids whose limbo paths descend from this."""
1295
descendants = set(self._limbo_children.get(trans_id, []))
1296
for descendant in list(descendants):
1297
descendants.update(self._limbo_descendants(descendant))
1300
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1301
"""Schedule creation of a new file.
1305
:param contents: an iterator of strings, all of which will be written
1306
to the target destination.
1307
:param trans_id: TreeTransform handle
1308
:param mode_id: If not None, force the mode of the target file to match
1309
the mode of the object referenced by mode_id.
1310
Otherwise, we will try to preserve mode bits of an existing file.
1311
:param sha1: If the sha1 of this content is already known, pass it in.
1312
We can use it to prevent future sha1 computations.
1314
name = self._limbo_name(trans_id)
1315
f = open(name, 'wb')
1317
unique_add(self._new_contents, trans_id, 'file')
1318
f.writelines(contents)
1321
self._set_mtime(name)
1322
self._set_mode(trans_id, mode_id, S_ISREG)
1323
# It is unfortunate we have to use lstat instead of fstat, but we just
1324
# used utime and chmod on the file, so we need the accurate final
1326
if sha1 is not None:
1327
self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
1329
def _read_file_chunks(self, trans_id):
1330
cur_file = open(self._limbo_name(trans_id), 'rb')
1332
return cur_file.readlines()
1336
def _read_symlink_target(self, trans_id):
1337
return os.readlink(self._limbo_name(trans_id))
1339
def _set_mtime(self, path):
1340
"""All files that are created get the same mtime.
1342
This time is set by the first object to be created.
1344
if self._creation_mtime is None:
1345
self._creation_mtime = time.time()
1346
os.utime(path, (self._creation_mtime, self._creation_mtime))
1348
def create_hardlink(self, path, trans_id):
1349
"""Schedule creation of a hard link"""
1350
name = self._limbo_name(trans_id)
1354
if e.errno != errno.EPERM:
1356
raise errors.HardLinkNotSupported(path)
1358
unique_add(self._new_contents, trans_id, 'file')
1360
# Clean up the file, it never got registered so
1361
# TreeTransform.finalize() won't clean it up.
1365
def create_directory(self, trans_id):
1366
"""Schedule creation of a new directory.
1368
See also new_directory.
1370
os.mkdir(self._limbo_name(trans_id))
1371
unique_add(self._new_contents, trans_id, 'directory')
1373
def create_symlink(self, target, trans_id):
1374
"""Schedule creation of a new symbolic link.
1376
target is a bytestring.
1377
See also new_symlink.
1380
os.symlink(target, self._limbo_name(trans_id))
1381
unique_add(self._new_contents, trans_id, 'symlink')
1384
path = FinalPaths(self).get_path(trans_id)
1387
raise UnableCreateSymlink(path=path)
1389
def cancel_creation(self, trans_id):
1390
"""Cancel the creation of new file contents."""
1391
del self._new_contents[trans_id]
1392
if trans_id in self._observed_sha1s:
1393
del self._observed_sha1s[trans_id]
1394
children = self._limbo_children.get(trans_id)
1395
# if this is a limbo directory with children, move them before removing
1397
if children is not None:
1398
self._rename_in_limbo(children)
1399
del self._limbo_children[trans_id]
1400
del self._limbo_children_names[trans_id]
1401
delete_any(self._limbo_name(trans_id))
1403
def new_orphan(self, trans_id, parent_id):
1404
conf = self._tree.get_config_stack()
1405
handle_orphan = conf.get('bzr.transform.orphan_policy')
1406
handle_orphan(self, trans_id, parent_id)
1409
class OrphaningError(errors.BzrError):
1411
# Only bugs could lead to such exception being seen by the user
1412
internal_error = True
1413
_fmt = "Error while orphaning %s in %s directory"
1415
def __init__(self, orphan, parent):
1416
errors.BzrError.__init__(self)
1417
self.orphan = orphan
1418
self.parent = parent
1421
class OrphaningForbidden(OrphaningError):
1423
_fmt = "Policy: %s doesn't allow creating orphans."
1425
def __init__(self, policy):
1426
errors.BzrError.__init__(self)
1427
self.policy = policy
1430
def move_orphan(tt, orphan_id, parent_id):
1431
"""See TreeTransformBase.new_orphan.
1433
This creates a new orphan in the `bzr-orphans` dir at the root of the
1436
:param tt: The TreeTransform orphaning `trans_id`.
1438
:param orphan_id: The trans id that should be orphaned.
1440
:param parent_id: The orphan parent trans id.
1442
# Add the orphan dir if it doesn't exist
1443
orphan_dir_basename = 'bzr-orphans'
1444
od_id = tt.trans_id_tree_path(orphan_dir_basename)
1445
if tt.final_kind(od_id) is None:
1446
tt.create_directory(od_id)
1447
parent_path = tt._tree_id_paths[parent_id]
1448
# Find a name that doesn't exist yet in the orphan dir
1449
actual_name = tt.final_name(orphan_id)
1450
new_name = tt._available_backup_name(actual_name, od_id)
1451
tt.adjust_path(new_name, od_id, orphan_id)
1452
trace.warning('%s has been orphaned in %s'
1453
% (joinpath(parent_path, actual_name), orphan_dir_basename))
1456
def refuse_orphan(tt, orphan_id, parent_id):
1457
"""See TreeTransformBase.new_orphan.
1459
This refuses to create orphan, letting the caller handle the conflict.
1461
raise OrphaningForbidden('never')
1464
orphaning_registry = registry.Registry()
1465
orphaning_registry.register(
1466
'conflict', refuse_orphan,
1467
'Leave orphans in place and create a conflict on the directory.')
1468
orphaning_registry.register(
1469
'move', move_orphan,
1470
'Move orphans into the bzr-orphans directory.')
1471
orphaning_registry._set_default_key('conflict')
1474
opt_transform_orphan = _mod_config.RegistryOption(
1475
'bzr.transform.orphan_policy', orphaning_registry,
1476
help='Policy for orphaned files during transform operations.',
1480
class TreeTransform(DiskTreeTransform):
1230
1481
"""Represent a tree transformation.
1232
1483
This object is designed to support incremental generation of the transform,
1303
1554
limbodir = urlutils.local_path_from_url(
1304
1555
tree._transport.abspath('limbo'))
1308
if e.errno == errno.EEXIST:
1309
raise ExistingLimbo(limbodir)
1556
osutils.ensure_empty_directory_exists(
1558
errors.ExistingLimbo)
1310
1559
deletiondir = urlutils.local_path_from_url(
1311
1560
tree._transport.abspath('pending-deletion'))
1313
os.mkdir(deletiondir)
1315
if e.errno == errno.EEXIST:
1316
raise errors.ExistingPendingDeletion(deletiondir)
1561
osutils.ensure_empty_directory_exists(
1563
errors.ExistingPendingDeletion)
1321
TreeTransformBase.__init__(self, tree, limbodir, pb,
1568
# Cache of realpath results, to speed up canonical_path
1569
self._realpaths = {}
1570
# Cache of relpath results, to speed up canonical_path
1572
DiskTreeTransform.__init__(self, tree, limbodir, pb,
1322
1573
tree.case_sensitive)
1323
1574
self._deletiondir = deletiondir
1576
def canonical_path(self, path):
1577
"""Get the canonical tree-relative path"""
1578
# don't follow final symlinks
1579
abs = self._tree.abspath(path)
1580
if abs in self._relpaths:
1581
return self._relpaths[abs]
1582
dirname, basename = os.path.split(abs)
1583
if dirname not in self._realpaths:
1584
self._realpaths[dirname] = os.path.realpath(dirname)
1585
dirname = self._realpaths[dirname]
1586
abs = pathjoin(dirname, basename)
1587
if dirname in self._relpaths:
1588
relpath = pathjoin(self._relpaths[dirname], basename)
1589
relpath = relpath.rstrip('/\\')
1591
relpath = self._tree.relpath(abs)
1592
self._relpaths[abs] = relpath
1595
def tree_kind(self, trans_id):
1596
"""Determine the file kind in the working tree.
1598
:returns: The file kind or None if the file does not exist
1600
path = self._tree_id_paths.get(trans_id)
1604
return file_kind(self._tree.abspath(path))
1605
except errors.NoSuchFile:
1608
def _set_mode(self, trans_id, mode_id, typefunc):
1609
"""Set the mode of new file contents.
1610
The mode_id is the existing file to get the mode from (often the same
1611
as trans_id). The operation is only performed if there's a mode match
1612
according to typefunc.
1617
old_path = self._tree_id_paths[mode_id]
1621
mode = os.stat(self._tree.abspath(old_path)).st_mode
1623
if e.errno in (errno.ENOENT, errno.ENOTDIR):
1624
# Either old_path doesn't exist, or the parent of the
1625
# target is not a directory (but will be one eventually)
1626
# Either way, we know it doesn't exist *right now*
1627
# See also bug #248448
1632
osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
1634
def iter_tree_children(self, parent_id):
1635
"""Iterate through the entry's tree children, if any"""
1637
path = self._tree_id_paths[parent_id]
1641
children = os.listdir(self._tree.abspath(path))
1643
if not (osutils._is_error_enotdir(e)
1644
or e.errno in (errno.ENOENT, errno.ESRCH)):
1648
for child in children:
1649
childpath = joinpath(path, child)
1650
if self._tree.is_control_filename(childpath):
1652
yield self.trans_id_tree_path(childpath)
1654
def _generate_limbo_path(self, trans_id):
1655
"""Generate a limbo path using the final path if possible.
1657
This optimizes the performance of applying the tree transform by
1658
avoiding renames. These renames can be avoided only when the parent
1659
directory is already scheduled for creation.
1661
If the final path cannot be used, falls back to using the trans_id as
1664
parent = self._new_parent.get(trans_id)
1665
# if the parent directory is already in limbo (e.g. when building a
1666
# tree), choose a limbo name inside the parent, to reduce further
1668
use_direct_path = False
1669
if self._new_contents.get(parent) == 'directory':
1670
filename = self._new_name.get(trans_id)
1671
if filename is not None:
1672
if parent not in self._limbo_children:
1673
self._limbo_children[parent] = set()
1674
self._limbo_children_names[parent] = {}
1675
use_direct_path = True
1676
# the direct path can only be used if no other file has
1677
# already taken this pathname, i.e. if the name is unused, or
1678
# if it is already associated with this trans_id.
1679
elif self._case_sensitive_target:
1680
if (self._limbo_children_names[parent].get(filename)
1681
in (trans_id, None)):
1682
use_direct_path = True
1684
for l_filename, l_trans_id in\
1685
self._limbo_children_names[parent].iteritems():
1686
if l_trans_id == trans_id:
1688
if l_filename.lower() == filename.lower():
1691
use_direct_path = True
1693
if not use_direct_path:
1694
return DiskTreeTransform._generate_limbo_path(self, trans_id)
1696
limbo_name = pathjoin(self._limbo_files[parent], filename)
1697
self._limbo_children[parent].add(trans_id)
1698
self._limbo_children_names[parent][filename] = trans_id
1325
1702
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1326
1703
"""Apply all changes to the inventory and filesystem.
1477
1858
modified_paths = []
1478
1859
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1480
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1861
child_pb = ui.ui_factory.nested_progress_bar()
1482
1863
for num, (path, trans_id) in enumerate(new_paths):
1483
1864
if (num % 10) == 0:
1484
child_pb.update('adding file', num, len(new_paths))
1865
child_pb.update(gettext('adding file'), num, len(new_paths))
1485
1866
full_path = self._tree.abspath(path)
1486
1867
if trans_id in self._needs_rename:
1488
1869
mover.rename(self._limbo_name(trans_id), full_path)
1870
except errors.TransformRenameFailed, e:
1490
1871
# We may be renaming a dangling inventory id
1491
1872
if e.errno != errno.ENOENT:
1494
1875
self.rename_count += 1
1876
# TODO: if trans_id in self._observed_sha1s, we should
1877
# re-stat the final target, since ctime will be
1878
# updated by the change.
1495
1879
if (trans_id in self._new_contents or
1496
1880
self.path_changed(trans_id)):
1497
1881
if trans_id in self._new_contents:
1498
1882
modified_paths.append(full_path)
1499
1883
if trans_id in self._new_executability:
1500
1884
self._set_executability(path, trans_id)
1885
if trans_id in self._observed_sha1s:
1886
o_sha1, o_st_val = self._observed_sha1s[trans_id]
1887
st = osutils.lstat(full_path)
1888
self._observed_sha1s[trans_id] = (o_sha1, st)
1502
1890
child_pb.finished()
1891
for path, trans_id in new_paths:
1892
# new_paths includes stuff like workingtree conflicts. Only the
1893
# stuff in new_contents actually comes from limbo.
1894
if trans_id in self._limbo_files:
1895
del self._limbo_files[trans_id]
1503
1896
self._new_contents.clear()
1504
1897
return modified_paths
1507
class TransformPreview(TreeTransformBase):
1899
def _apply_observed_sha1s(self):
1900
"""After we have finished renaming everything, update observed sha1s
1902
This has to be done after self._tree.apply_inventory_delta, otherwise
1903
it doesn't know anything about the files we are updating. Also, we want
1904
to do this as late as possible, so that most entries end up cached.
1906
# TODO: this doesn't update the stat information for directories. So
1907
# the first 'bzr status' will still need to rewrite
1908
# .bzr/checkout/dirstate. However, we at least don't need to
1909
# re-read all of the files.
1910
# TODO: If the operation took a while, we could do a time.sleep(3) here
1911
# to allow the clock to tick over and ensure we won't have any
1912
# problems. (we could observe start time, and finish time, and if
1913
# it is less than eg 10% overhead, add a sleep call.)
1914
paths = FinalPaths(self)
1915
for trans_id, observed in self._observed_sha1s.iteritems():
1916
path = paths.get_path(trans_id)
1917
# We could get the file_id, but dirstate prefers to use the path
1918
# anyway, and it is 'cheaper' to determine.
1919
# file_id = self._new_id[trans_id]
1920
self._tree._observed_sha1(None, path, observed)
1923
class TransformPreview(DiskTreeTransform):
1508
1924
"""A TreeTransform for generating preview trees.
1510
1926
Unlike TreeTransform, this version works when the input tree is a
1753
2199
ordered_ids = self._list_files_by_dir()
1754
2200
for entry, trans_id in self._make_inv_entries(ordered_ids,
1756
yield unicode(self._final_paths.get_path(trans_id)), entry
1758
def list_files(self, include_root=False):
1759
"""See Tree.list_files."""
2201
specific_file_ids, yield_parents=yield_parents):
2202
yield unicode(self._final_paths.get_path(trans_id)), entry
2204
def _iter_entries_for_dir(self, dir_path):
2205
"""Return path, entry for items in a directory without recursing down."""
2206
dir_file_id = self.path2id(dir_path)
2208
for file_id in self.iter_children(dir_file_id):
2209
trans_id = self._transform.trans_id_file_id(file_id)
2210
ordered_ids.append((trans_id, file_id))
2211
for entry, trans_id in self._make_inv_entries(ordered_ids):
2212
yield unicode(self._final_paths.get_path(trans_id)), entry
2214
def list_files(self, include_root=False, from_dir=None, recursive=True):
2215
"""See WorkingTree.list_files."""
1760
2216
# XXX This should behave like WorkingTree.list_files, but is really
1761
2217
# more like RevisionTree.list_files.
1762
for path, entry in self.iter_entries_by_dir():
1763
if entry.name == '' and not include_root:
1765
yield path, 'V', entry.kind, entry.file_id, entry
2221
prefix = from_dir + '/'
2222
entries = self.iter_entries_by_dir()
2223
for path, entry in entries:
2224
if entry.name == '' and not include_root:
2227
if not path.startswith(prefix):
2229
path = path[len(prefix):]
2230
yield path, 'V', entry.kind, entry.file_id, entry
2232
if from_dir is None and include_root is True:
2233
root_entry = inventory.make_entry('directory', '',
2234
ROOT_PARENT, self.get_root_id())
2235
yield '', 'V', 'directory', root_entry.file_id, root_entry
2236
entries = self._iter_entries_for_dir(from_dir or '')
2237
for path, entry in entries:
2238
yield path, 'V', entry.kind, entry.file_id, entry
1767
2240
def kind(self, file_id):
1768
2241
trans_id = self._transform.trans_id_file_id(file_id)
2150
2666
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
2152
2668
total = len(desired_files) + offset
2153
2670
if accelerator_tree is None:
2154
2671
new_desired_files = desired_files
2156
2673
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2157
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2158
in iter if not (c or e[0] != e[1]))
2674
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2675
in iter if not (c or e[0] != e[1])]
2676
if accelerator_tree.supports_content_filtering():
2677
unchanged = [(f, p) for (f, p) in unchanged
2678
if not accelerator_tree.iter_search_rules([p]).next()]
2679
unchanged = dict(unchanged)
2159
2680
new_desired_files = []
2161
for file_id, trans_id in desired_files:
2682
for file_id, (trans_id, tree_path, text_sha1) in desired_files:
2162
2683
accelerator_path = unchanged.get(file_id)
2163
2684
if accelerator_path is None:
2164
new_desired_files.append((file_id, trans_id))
2685
new_desired_files.append((file_id,
2686
(trans_id, tree_path, text_sha1)))
2166
pb.update('Adding file contents', count + offset, total)
2688
pb.update(gettext('Adding file contents'), count + offset, total)
2168
2690
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2171
2693
contents = accelerator_tree.get_file(file_id, accelerator_path)
2694
if wt.supports_content_filtering():
2695
filters = wt._content_filter_stack(tree_path)
2696
contents = filtered_output_bytes(contents, filters,
2697
ContentFilterContext(tree_path, tree))
2173
tt.create_file(contents, trans_id)
2699
tt.create_file(contents, trans_id, sha1=text_sha1)
2703
except AttributeError:
2704
# after filtering, contents may no longer be file-like
2177
2707
offset += count
2178
for count, (trans_id, contents) in enumerate(tree.iter_files_bytes(
2179
new_desired_files)):
2180
tt.create_file(contents, trans_id)
2181
pb.update('Adding file contents', count + offset, total)
2708
for count, ((trans_id, tree_path, text_sha1), contents) in enumerate(
2709
tree.iter_files_bytes(new_desired_files)):
2710
if wt.supports_content_filtering():
2711
filters = wt._content_filter_stack(tree_path)
2712
contents = filtered_output_bytes(contents, filters,
2713
ContentFilterContext(tree_path, tree))
2714
tt.create_file(contents, trans_id, sha1=text_sha1)
2715
pb.update(gettext('Adding file contents'), count + offset, total)
2184
2718
def _reparent_children(tt, old_parent, new_parent):
2185
2719
for child in tt.iter_tree_children(old_parent):
2186
2720
tt.adjust_path(tt.final_name(child), new_parent, child)
2188
2723
def _reparent_transform_children(tt, old_parent, new_parent):
2189
2724
by_parent = tt.by_parent()
2190
2725
for child in by_parent[old_parent]:
2191
2726
tt.adjust_path(tt.final_name(child), new_parent, child)
2192
2727
return by_parent[old_parent]
2194
2730
def _content_match(tree, entry, file_id, kind, target_path):
2195
2731
if entry.kind != kind:
2197
2733
if entry.kind == "directory":
2199
2735
if entry.kind == "file":
2200
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
2736
f = file(target_path, 'rb')
2738
if tree.get_file_text(file_id) == f.read():
2202
2742
elif entry.kind == "symlink":
2203
2743
if tree.get_symlink_target(file_id) == os.readlink(target_path):
2297
2832
tt.set_executability(entry.executable, trans_id)
2300
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2301
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2304
def _get_backup_name(name, by_parent, parent_trans_id, tt):
2305
"""Produce a backup-style name that appears to be available"""
2309
yield "%s.~%d~" % (name, counter)
2311
for new_name in name_gen():
2312
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
2316
def _entry_changes(file_id, entry, working_tree):
2317
"""Determine in which ways the inventory entry has changed.
2319
Returns booleans: has_contents, content_mod, meta_mod
2320
has_contents means there are currently contents, but they differ
2321
contents_mod means contents need to be modified
2322
meta_mod means the metadata needs to be modified
2324
cur_entry = working_tree.inventory[file_id]
2326
working_kind = working_tree.kind(file_id)
2329
has_contents = False
2332
if has_contents is True:
2333
if entry.kind != working_kind:
2334
contents_mod, meta_mod = True, False
2336
cur_entry._read_tree_state(working_tree.id2path(file_id),
2338
contents_mod, meta_mod = entry.detect_changes(cur_entry)
2339
cur_entry._forget_tree_state()
2340
return has_contents, contents_mod, meta_mod
2343
2835
def revert(working_tree, target_tree, filenames, backups=False,
2344
pb=DummyProgress(), change_reporter=None):
2836
pb=None, change_reporter=None):
2345
2837
"""Revert a working tree's contents to those of a target tree."""
2346
2838
target_tree.lock_read()
2839
pb = ui.ui_factory.nested_progress_bar()
2347
2840
tt = TreeTransform(working_tree, pb)
2349
2842
pp = ProgressPhase("Revert phase", 3, pb)
2418
2919
if basis_tree is None:
2419
2920
basis_tree = working_tree.basis_tree()
2420
2921
basis_tree.lock_read()
2421
if file_id in basis_tree:
2922
if basis_tree.has_id(file_id):
2422
2923
if wt_sha1 != basis_tree.get_file_sha1(file_id):
2423
2924
keep_content = True
2424
elif kind[1] is None and not versioned[1]:
2925
elif target_kind is None and not target_versioned:
2425
2926
keep_content = True
2426
if kind[0] is not None:
2927
if wt_kind is not None:
2427
2928
if not keep_content:
2428
2929
tt.delete_contents(trans_id)
2429
elif kind[1] is not None:
2430
parent_trans_id = tt.trans_id_file_id(parent[0])
2431
by_parent = tt.by_parent()
2432
backup_name = _get_backup_name(name[0], by_parent,
2433
parent_trans_id, tt)
2930
elif target_kind is not None:
2931
parent_trans_id = tt.trans_id_file_id(wt_parent)
2932
backup_name = tt._available_backup_name(
2933
wt_name, parent_trans_id)
2434
2934
tt.adjust_path(backup_name, parent_trans_id, trans_id)
2435
new_trans_id = tt.create_path(name[0], parent_trans_id)
2436
if versioned == (True, True):
2935
new_trans_id = tt.create_path(wt_name, parent_trans_id)
2936
if wt_versioned and target_versioned:
2437
2937
tt.unversion_file(trans_id)
2438
2938
tt.version_file(file_id, new_trans_id)
2439
2939
# New contents should have the same unix perms as old
2441
2941
mode_id = trans_id
2442
2942
trans_id = new_trans_id
2443
if kind[1] in ('directory', 'tree-reference'):
2943
if target_kind in ('directory', 'tree-reference'):
2444
2944
tt.create_directory(trans_id)
2445
if kind[1] == 'tree-reference':
2945
if target_kind == 'tree-reference':
2446
2946
revision = target_tree.get_reference_revision(file_id,
2448
2948
tt.set_tree_reference(revision, trans_id)
2449
elif kind[1] == 'symlink':
2949
elif target_kind == 'symlink':
2450
2950
tt.create_symlink(target_tree.get_symlink_target(file_id),
2452
elif kind[1] == 'file':
2952
elif target_kind == 'file':
2453
2953
deferred_files.append((file_id, (trans_id, mode_id)))
2454
2954
if basis_tree is None:
2455
2955
basis_tree = working_tree.basis_tree()
2456
2956
basis_tree.lock_read()
2457
2957
new_sha1 = target_tree.get_file_sha1(file_id)
2458
if (file_id in basis_tree and new_sha1 ==
2459
basis_tree.get_file_sha1(file_id)):
2958
if (basis_tree.has_id(file_id) and
2959
new_sha1 == basis_tree.get_file_sha1(file_id)):
2460
2960
if file_id in merge_modified:
2461
2961
del merge_modified[file_id]
2463
2963
merge_modified[file_id] = new_sha1
2465
2965
# preserve the execute bit when backing up
2466
if keep_content and executable[0] == executable[1]:
2467
tt.set_executability(executable[1], trans_id)
2468
elif kind[1] is not None:
2469
raise AssertionError(kind[1])
2470
if versioned == (False, True):
2966
if keep_content and wt_executable == target_executable:
2967
tt.set_executability(target_executable, trans_id)
2968
elif target_kind is not None:
2969
raise AssertionError(target_kind)
2970
if not wt_versioned and target_versioned:
2471
2971
tt.version_file(file_id, trans_id)
2472
if versioned == (True, False):
2972
if wt_versioned and not target_versioned:
2473
2973
tt.unversion_file(trans_id)
2474
if (name[1] is not None and
2475
(name[0] != name[1] or parent[0] != parent[1])):
2476
if name[1] == '' and parent[1] is None:
2974
if (target_name is not None and
2975
(wt_name != target_name or wt_parent != target_parent)):
2976
if target_name == '' and target_parent is None:
2477
2977
parent_trans = ROOT_PARENT
2479
parent_trans = tt.trans_id_file_id(parent[1])
2480
tt.adjust_path(name[1], parent_trans, trans_id)
2481
if executable[0] != executable[1] and kind[1] == "file":
2482
tt.set_executability(executable[1], trans_id)
2483
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2485
tt.create_file(bytes, trans_id, mode_id)
2979
parent_trans = tt.trans_id_file_id(target_parent)
2980
if wt_parent is None and wt_versioned:
2981
tt.adjust_root_path(target_name, parent_trans)
2983
tt.adjust_path(target_name, parent_trans, trans_id)
2984
if wt_executable != target_executable and target_kind == "file":
2985
tt.set_executability(target_executable, trans_id)
2986
if working_tree.supports_content_filtering():
2987
for index, ((trans_id, mode_id), bytes) in enumerate(
2988
target_tree.iter_files_bytes(deferred_files)):
2989
file_id = deferred_files[index][0]
2990
# We're reverting a tree to the target tree so using the
2991
# target tree to find the file path seems the best choice
2992
# here IMO - Ian C 27/Oct/2009
2993
filter_tree_path = target_tree.id2path(file_id)
2994
filters = working_tree._content_filter_stack(filter_tree_path)
2995
bytes = filtered_output_bytes(bytes, filters,
2996
ContentFilterContext(filter_tree_path, working_tree))
2997
tt.create_file(bytes, trans_id, mode_id)
2999
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
3001
tt.create_file(bytes, trans_id, mode_id)
3002
tt.fixup_new_roots()
2487
3004
if basis_tree is not None:
2488
3005
basis_tree.unlock()
2489
3006
return merge_modified
2492
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
3009
def resolve_conflicts(tt, pb=None, pass_func=None):
2493
3010
"""Make many conflict-resolution attempts, but die if they fail"""
2494
3011
if pass_func is None:
2495
3012
pass_func = conflict_pass
2496
3013
new_conflicts = set()
3014
pb = ui.ui_factory.nested_progress_bar()
2498
3016
for n in range(10):
2499
pb.update('Resolution pass', n+1, 10)
3017
pb.update(gettext('Resolution pass'), n+1, 10)
2500
3018
conflicts = tt.find_conflicts()
2501
3019
if len(conflicts) == 0:
2502
3020
return new_conflicts
2503
3021
new_conflicts.update(pass_func(tt, conflicts))
2504
3022
raise MalformedTransform(conflicts=conflicts)
2509
3027
def conflict_pass(tt, conflicts, path_tree=None):