14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
19
21
from stat import S_ISREG, S_IEXEC
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
31
lazy_import.lazy_import(globals(), """
23
32
from bzrlib import (
32
42
revision as _mod_revision,
46
from bzrlib.i18n import gettext
35
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
36
ReusingTransform, NotVersionedError, CantMoveRoot,
48
from bzrlib.errors import (DuplicateKey, MalformedTransform,
49
ReusingTransform, CantMoveRoot,
37
50
ExistingLimbo, ImmortalLimbo, NoFinalPath,
38
51
UnableCreateSymlink)
39
52
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
40
from bzrlib.inventory import InventoryEntry
41
53
from bzrlib.osutils import (
49
60
supports_executable,
51
from bzrlib.progress import DummyProgress, ProgressPhase
62
from bzrlib.progress import ProgressPhase
52
63
from bzrlib.symbol_versioning import (
56
from bzrlib.trace import mutter, warning
57
from bzrlib import tree
59
import bzrlib.urlutils as urlutils
62
70
ROOT_PARENT = "root-parent"
65
72
def unique_add(map, key, value):
67
74
raise DuplicateKey(key=key)
71
79
class _TransformResults(object):
72
80
def __init__(self, modified_paths, rename_count):
73
81
object.__init__(self)
202
218
self.version_file(old_root_file_id, old_root)
203
219
self.unversion_file(self._new_root)
221
def fixup_new_roots(self):
222
"""Reinterpret requests to change the root directory
224
Instead of creating a root directory, or moving an existing directory,
225
all the attributes and children of the new root are applied to the
226
existing root directory.
228
This means that the old root trans-id becomes obsolete, so it is
229
recommended only to invoke this after the root trans-id has become
233
new_roots = [k for k, v in self._new_parent.iteritems() if v is
235
if len(new_roots) < 1:
237
if len(new_roots) != 1:
238
raise ValueError('A tree cannot have two roots!')
239
if self._new_root is None:
240
self._new_root = new_roots[0]
242
old_new_root = new_roots[0]
243
# unversion the new root's directory.
244
if self.final_kind(self._new_root) is None:
245
file_id = self.final_file_id(old_new_root)
247
file_id = self.final_file_id(self._new_root)
248
if old_new_root in self._new_id:
249
self.cancel_versioning(old_new_root)
251
self.unversion_file(old_new_root)
252
# if, at this stage, root still has an old file_id, zap it so we can
253
# stick a new one in.
254
if (self.tree_file_id(self._new_root) is not None and
255
self._new_root not in self._removed_id):
256
self.unversion_file(self._new_root)
257
if file_id is not None:
258
self.version_file(file_id, self._new_root)
260
# Now move children of new root into old root directory.
261
# Ensure all children are registered with the transaction, but don't
262
# use directly-- some tree children have new parents
263
list(self.iter_tree_children(old_new_root))
264
# Move all children of new root into old root directory.
265
for child in self.by_parent().get(old_new_root, []):
266
self.adjust_path(self.final_name(child), self._new_root, child)
268
# Ensure old_new_root has no directory.
269
if old_new_root in self._new_contents:
270
self.cancel_creation(old_new_root)
272
self.delete_contents(old_new_root)
274
# prevent deletion of root directory.
275
if self._new_root in self._removed_contents:
276
self.cancel_deletion(self._new_root)
278
# destroy path info for old_new_root.
279
del self._new_parent[old_new_root]
280
del self._new_name[old_new_root]
205
282
def trans_id_tree_file_id(self, inventory_id):
206
283
"""Determine the transaction id of a working tree file.
317
395
return sorted(FinalPaths(self).get_paths(new_ids))
319
397
def _inventory_altered(self):
320
"""Get the trans_ids and paths of files needing new inv entries."""
322
for id_set in [self._new_name, self._new_parent, self._new_id,
398
"""Determine which trans_ids need new Inventory entries.
400
An new entry is needed when anything that would be reflected by an
401
inventory entry changes, including file name, file_id, parent file_id,
402
file kind, and the execute bit.
404
Some care is taken to return entries with real changes, not cases
405
where the value is deleted and then restored to its original value,
406
but some actually unchanged values may be returned.
408
:returns: A list of (path, trans_id) for all items requiring an
409
inventory change. Ordered by path.
412
# Find entries whose file_ids are new (or changed).
413
new_file_id = set(t for t in self._new_id
414
if self._new_id[t] != self.tree_file_id(t))
415
for id_set in [self._new_name, self._new_parent, new_file_id,
323
416
self._new_executability]:
324
new_ids.update(id_set)
417
changed_ids.update(id_set)
418
# removing implies a kind change
325
419
changed_kind = set(self._removed_contents)
326
421
changed_kind.intersection_update(self._new_contents)
327
changed_kind.difference_update(new_ids)
328
changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
330
new_ids.update(changed_kind)
331
return sorted(FinalPaths(self).get_paths(new_ids))
422
# Ignore entries that are already known to have changed.
423
changed_kind.difference_update(changed_ids)
424
# to keep only the truly changed ones
425
changed_kind = (t for t in changed_kind
426
if self.tree_kind(t) != self.final_kind(t))
427
# all kind changes will alter the inventory
428
changed_ids.update(changed_kind)
429
# To find entries with changed parent_ids, find parents which existed,
430
# but changed file_id.
431
changed_file_id = set(t for t in new_file_id if t in self._removed_id)
432
# Now add all their children to the set.
433
for parent_trans_id in new_file_id:
434
changed_ids.update(self.iter_tree_children(parent_trans_id))
435
return sorted(FinalPaths(self).get_paths(changed_ids))
333
437
def final_kind(self, trans_id):
334
438
"""Determine the final file kind, after any changes applied.
336
Raises NoSuchFile if the file does not exist/has no contents.
337
(It is conceivable that a path would be created without the
338
corresponding contents insertion command)
440
:return: None if the file does not exist/has no contents. (It is
441
conceivable that a path would be created without the corresponding
442
contents insertion command)
340
444
if trans_id in self._new_contents:
341
445
return self._new_contents[trans_id]
342
446
elif trans_id in self._removed_contents:
343
raise NoSuchFile(None)
345
449
return self.tree_kind(trans_id)
463
572
# ensure that all children are registered with the transaction
464
573
list(self.iter_tree_children(parent_id))
575
@deprecated_method(deprecated_in((2, 3, 0)))
466
576
def has_named_child(self, by_parent, parent_id, name):
468
children = by_parent[parent_id]
471
for child in children:
577
return self._has_named_child(
578
name, parent_id, known_children=by_parent.get(parent_id, []))
580
def _has_named_child(self, name, parent_id, known_children):
581
"""Does a parent already have a name child.
583
:param name: The searched for name.
585
:param parent_id: The parent for which the check is made.
587
:param known_children: The already known children. This should have
588
been recently obtained from `self.by_parent.get(parent_id)`
589
(or will be if None is passed).
591
if known_children is None:
592
known_children = self.by_parent().get(parent_id, [])
593
for child in known_children:
472
594
if self.final_name(child) == name:
475
path = self._tree_id_paths[parent_id]
596
parent_path = self._tree_id_paths.get(parent_id, None)
597
if parent_path is None:
598
# No parent... no children
478
childpath = joinpath(path, name)
479
child_id = self._tree_path_ids.get(childpath)
600
child_path = joinpath(parent_path, name)
601
child_id = self._tree_path_ids.get(child_path, None)
480
602
if child_id is None:
481
return lexists(self._tree.abspath(childpath))
603
# Not known by the tree transform yet, check the filesystem
604
return osutils.lexists(self._tree.abspath(child_path))
483
if self.final_parent(child_id) != parent_id:
485
if child_id in self._removed_contents:
486
# XXX What about dangling file-ids?
606
raise AssertionError('child_id is missing: %s, %s, %s'
607
% (name, parent_id, child_id))
609
def _available_backup_name(self, name, target_id):
610
"""Find an available backup name.
612
:param name: The basename of the file.
614
:param target_id: The directory trans_id where the backup should
617
known_children = self.by_parent().get(target_id, [])
618
return osutils.available_backup_name(
620
lambda base: self._has_named_child(
621
base, target_id, known_children))
491
623
def _parent_loops(self):
492
624
"""No entry should be its own ancestor"""
613
740
def _parent_type_conflicts(self, by_parent):
614
"""parents must have directory 'contents'."""
741
"""Children must have a directory parent"""
616
743
for parent_id, children in by_parent.iteritems():
617
744
if parent_id is ROOT_PARENT:
619
if not self._any_contents(children):
747
for child_id in children:
748
if self.final_kind(child_id) is not None:
621
for child in children:
623
self.final_kind(child)
627
kind = self.final_kind(parent_id)
753
# There is at least a child, so we need an existing directory to
755
kind = self.final_kind(parent_id)
757
# The directory will be deleted
631
758
conflicts.append(('missing parent', parent_id))
632
759
elif kind != "directory":
760
# Meh, we need a *directory* to put something in it
633
761
conflicts.append(('non-directory parent', parent_id))
636
def _any_contents(self, trans_ids):
637
"""Return true if any of the trans_ids, will have contents."""
638
for trans_id in trans_ids:
640
kind = self.final_kind(trans_id)
646
764
def _set_executability(self, path, trans_id):
647
765
"""Set the executability of versioned files """
648
766
if supports_executable():
712
830
self.create_symlink(target, trans_id)
833
def new_orphan(self, trans_id, parent_id):
834
"""Schedule an item to be orphaned.
836
When a directory is about to be removed, its children, if they are not
837
versioned are moved out of the way: they don't have a parent anymore.
839
:param trans_id: The trans_id of the existing item.
840
:param parent_id: The parent trans_id of the item.
842
raise NotImplementedError(self.new_orphan)
844
def _get_potential_orphans(self, dir_id):
845
"""Find the potential orphans in a directory.
847
A directory can't be safely deleted if there are versioned files in it.
848
If all the contained files are unversioned then they can be orphaned.
850
The 'None' return value means that the directory contains at least one
851
versioned file and should not be deleted.
853
:param dir_id: The directory trans id.
855
:return: A list of the orphan trans ids or None if at least one
856
versioned file is present.
859
# Find the potential orphans, stop if one item should be kept
860
for child_tid in self.by_parent()[dir_id]:
861
if child_tid in self._removed_contents:
862
# The child is removed as part of the transform. Since it was
863
# versioned before, it's not an orphan
865
elif self.final_file_id(child_tid) is None:
866
# The child is not versioned
867
orphans.append(child_tid)
869
# We have a versioned file here, searching for orphans is
715
875
def _affected_ids(self):
716
876
"""Return the set of transform ids affected by the transform"""
717
877
trans_ids = set(self._removed_id)
854
1011
def get_preview_tree(self):
855
1012
"""Return a tree representing the result of the transform.
857
This tree only supports the subset of Tree functionality required
858
by show_diff_trees. It must only be compared to tt._tree.
1014
The tree is a snapshot, and altering the TreeTransform will invalidate
860
1017
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)
862
1077
def _text_parent(self, trans_id):
863
1078
file_id = self.tree_file_id(trans_id)
1015
1236
def _limbo_name(self, trans_id):
1016
1237
"""Generate the limbo name of a file"""
1017
1238
limbo_name = self._limbo_files.get(trans_id)
1018
if limbo_name is not None:
1020
parent = self._new_parent.get(trans_id)
1021
# if the parent directory is already in limbo (e.g. when building a
1022
# tree), choose a limbo name inside the parent, to reduce further
1024
use_direct_path = False
1025
if self._new_contents.get(parent) == 'directory':
1026
filename = self._new_name.get(trans_id)
1027
if filename is not None:
1028
if parent not in self._limbo_children:
1029
self._limbo_children[parent] = set()
1030
self._limbo_children_names[parent] = {}
1031
use_direct_path = True
1032
# the direct path can only be used if no other file has
1033
# already taken this pathname, i.e. if the name is unused, or
1034
# if it is already associated with this trans_id.
1035
elif self._case_sensitive_target:
1036
if (self._limbo_children_names[parent].get(filename)
1037
in (trans_id, None)):
1038
use_direct_path = True
1040
for l_filename, l_trans_id in\
1041
self._limbo_children_names[parent].iteritems():
1042
if l_trans_id == trans_id:
1044
if l_filename.lower() == filename.lower():
1047
use_direct_path = True
1050
limbo_name = pathjoin(self._limbo_files[parent], filename)
1051
self._limbo_children[parent].add(trans_id)
1052
self._limbo_children_names[parent][filename] = trans_id
1054
limbo_name = pathjoin(self._limbodir, trans_id)
1055
self._needs_rename.add(trans_id)
1056
self._limbo_files[trans_id] = limbo_name
1239
if limbo_name is None:
1240
limbo_name = self._generate_limbo_path(trans_id)
1241
self._limbo_files[trans_id] = limbo_name
1057
1242
return 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)
1059
1253
def adjust_path(self, name, parent, trans_id):
1060
1254
previous_parent = self._new_parent.get(trans_id)
1061
1255
previous_name = self._new_name.get(trans_id)
1078
1274
entries from _limbo_files, because they are now stale.
1080
1276
for trans_id in trans_ids:
1081
old_path = self._limbo_files.pop(trans_id)
1277
old_path = self._limbo_files[trans_id]
1278
self._possibly_stale_limbo_files.add(old_path)
1279
del self._limbo_files[trans_id]
1082
1280
if trans_id not in self._new_contents:
1084
1282
new_path = self._limbo_name(trans_id)
1085
1283
os.rename(old_path, new_path)
1087
def create_file(self, contents, trans_id, mode_id=None):
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):
1088
1298
"""Schedule creation of a new file.
1092
Contents is an iterator of strings, all of which will be written
1093
to the target destination.
1095
New file takes the permissions of any existing file with that id,
1096
unless mode_id is specified.
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.
1098
1311
name = self._limbo_name(trans_id)
1099
1312
f = open(name, 'wb')
1102
unique_add(self._new_contents, trans_id, 'file')
1104
# Clean up the file, it never got registered so
1105
# TreeTransform.finalize() won't clean it up.
1314
unique_add(self._new_contents, trans_id, 'file')
1110
1315
f.writelines(contents)
1318
self._set_mtime(name)
1113
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))
1115
1326
def _read_file_chunks(self, trans_id):
1116
1327
cur_file = open(self._limbo_name(trans_id), 'rb')
1175
1397
del self._limbo_children_names[trans_id]
1176
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')
1179
1484
class TreeTransform(DiskTreeTransform):
1180
1485
"""Represent a tree transformation.
1358
1656
yield self.trans_id_tree_path(childpath)
1658
def _generate_limbo_path(self, trans_id):
1659
"""Generate a limbo path using the final path if possible.
1661
This optimizes the performance of applying the tree transform by
1662
avoiding renames. These renames can be avoided only when the parent
1663
directory is already scheduled for creation.
1665
If the final path cannot be used, falls back to using the trans_id as
1668
parent = self._new_parent.get(trans_id)
1669
# if the parent directory is already in limbo (e.g. when building a
1670
# tree), choose a limbo name inside the parent, to reduce further
1672
use_direct_path = False
1673
if self._new_contents.get(parent) == 'directory':
1674
filename = self._new_name.get(trans_id)
1675
if filename is not None:
1676
if parent not in self._limbo_children:
1677
self._limbo_children[parent] = set()
1678
self._limbo_children_names[parent] = {}
1679
use_direct_path = True
1680
# the direct path can only be used if no other file has
1681
# already taken this pathname, i.e. if the name is unused, or
1682
# if it is already associated with this trans_id.
1683
elif self._case_sensitive_target:
1684
if (self._limbo_children_names[parent].get(filename)
1685
in (trans_id, None)):
1686
use_direct_path = True
1688
for l_filename, l_trans_id in\
1689
self._limbo_children_names[parent].iteritems():
1690
if l_trans_id == trans_id:
1692
if l_filename.lower() == filename.lower():
1695
use_direct_path = True
1697
if not use_direct_path:
1698
return DiskTreeTransform._generate_limbo_path(self, trans_id)
1700
limbo_name = pathjoin(self._limbo_files[parent], filename)
1701
self._limbo_children[parent].add(trans_id)
1702
self._limbo_children_names[parent][filename] = trans_id
1361
1706
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1362
1707
"""Apply all changes to the inventory and filesystem.
1479
1824
tree_paths = list(self._tree_path_ids.iteritems())
1480
1825
tree_paths.sort(reverse=True)
1481
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1826
child_pb = ui.ui_factory.nested_progress_bar()
1483
for num, data in enumerate(tree_paths):
1484
path, trans_id = data
1485
child_pb.update('removing file', num, len(tree_paths))
1828
for num, (path, trans_id) in enumerate(tree_paths):
1829
# do not attempt to move root into a subdirectory of itself.
1832
child_pb.update(gettext('removing file'), num, len(tree_paths))
1486
1833
full_path = self._tree.abspath(path)
1487
1834
if trans_id in self._removed_contents:
1488
mover.pre_delete(full_path, os.path.join(self._deletiondir,
1490
elif trans_id in self._new_name or trans_id in \
1835
delete_path = os.path.join(self._deletiondir, trans_id)
1836
mover.pre_delete(full_path, delete_path)
1837
elif (trans_id in self._new_name
1838
or trans_id in self._new_parent):
1493
1840
mover.rename(full_path, self._limbo_name(trans_id))
1841
except errors.TransformRenameFailed, e:
1495
1842
if e.errno != errno.ENOENT:
1513
1860
modified_paths = []
1514
1861
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1516
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1863
child_pb = ui.ui_factory.nested_progress_bar()
1518
1865
for num, (path, trans_id) in enumerate(new_paths):
1519
1866
if (num % 10) == 0:
1520
child_pb.update('adding file', num, len(new_paths))
1867
child_pb.update(gettext('adding file'), num, len(new_paths))
1521
1868
full_path = self._tree.abspath(path)
1522
1869
if trans_id in self._needs_rename:
1524
1871
mover.rename(self._limbo_name(trans_id), full_path)
1872
except errors.TransformRenameFailed, e:
1526
1873
# We may be renaming a dangling inventory id
1527
1874
if e.errno != errno.ENOENT:
1530
1877
self.rename_count += 1
1878
# TODO: if trans_id in self._observed_sha1s, we should
1879
# re-stat the final target, since ctime will be
1880
# updated by the change.
1531
1881
if (trans_id in self._new_contents or
1532
1882
self.path_changed(trans_id)):
1533
1883
if trans_id in self._new_contents:
1534
1884
modified_paths.append(full_path)
1535
1885
if trans_id in self._new_executability:
1536
1886
self._set_executability(path, trans_id)
1887
if trans_id in self._observed_sha1s:
1888
o_sha1, o_st_val = self._observed_sha1s[trans_id]
1889
st = osutils.lstat(full_path)
1890
self._observed_sha1s[trans_id] = (o_sha1, st)
1538
1892
child_pb.finished()
1893
for path, trans_id in new_paths:
1894
# new_paths includes stuff like workingtree conflicts. Only the
1895
# stuff in new_contents actually comes from limbo.
1896
if trans_id in self._limbo_files:
1897
del self._limbo_files[trans_id]
1539
1898
self._new_contents.clear()
1540
1899
return modified_paths
1901
def _apply_observed_sha1s(self):
1902
"""After we have finished renaming everything, update observed sha1s
1904
This has to be done after self._tree.apply_inventory_delta, otherwise
1905
it doesn't know anything about the files we are updating. Also, we want
1906
to do this as late as possible, so that most entries end up cached.
1908
# TODO: this doesn't update the stat information for directories. So
1909
# the first 'bzr status' will still need to rewrite
1910
# .bzr/checkout/dirstate. However, we at least don't need to
1911
# re-read all of the files.
1912
# TODO: If the operation took a while, we could do a time.sleep(3) here
1913
# to allow the clock to tick over and ensure we won't have any
1914
# problems. (we could observe start time, and finish time, and if
1915
# it is less than eg 10% overhead, add a sleep call.)
1916
paths = FinalPaths(self)
1917
for trans_id, observed in self._observed_sha1s.iteritems():
1918
path = paths.get_path(trans_id)
1919
# We could get the file_id, but dirstate prefers to use the path
1920
# anyway, and it is 'cheaper' to determine.
1921
# file_id = self._new_id[trans_id]
1922
self._tree._observed_sha1(None, path, observed)
1543
1925
class TransformPreview(DiskTreeTransform):
1544
1926
"""A TreeTransform for generating preview trees.
2135
2559
pp.next_phase()
2136
2560
file_trans_id[wt.get_root_id()] = \
2137
2561
tt.trans_id_tree_file_id(wt.get_root_id())
2138
pb = bzrlib.ui.ui_factory.nested_progress_bar()
2562
pb = ui.ui_factory.nested_progress_bar()
2140
2564
deferred_contents = []
2142
total = len(tree.inventory)
2566
total = len(tree.all_file_ids())
2143
2567
if delta_from_tree:
2144
2568
precomputed_delta = []
2146
2570
precomputed_delta = None
2571
# Check if tree inventory has content. If so, we populate
2572
# existing_files with the directory content. If there are no
2573
# entries we skip populating existing_files as its not used.
2574
# This improves performance and unncessary work on large
2575
# directory trees. (#501307)
2577
existing_files = set()
2578
for dir, files in wt.walkdirs():
2579
existing_files.update(f[0] for f in files)
2147
2580
for num, (tree_path, entry) in \
2148
enumerate(tree.inventory.iter_entries_by_dir()):
2149
pb.update("Building tree", num - len(deferred_contents), total)
2581
enumerate(tree.iter_entries_by_dir()):
2582
pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2150
2583
if entry.parent_id is None:
2152
2585
reparent = False
2222
2655
new_desired_files = desired_files
2224
2657
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2225
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2226
in iter if not (c or e[0] != e[1]))
2658
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2659
in iter if not (c or e[0] != e[1])]
2660
if accelerator_tree.supports_content_filtering():
2661
unchanged = [(f, p) for (f, p) in unchanged
2662
if not accelerator_tree.iter_search_rules([p]).next()]
2663
unchanged = dict(unchanged)
2227
2664
new_desired_files = []
2229
for file_id, (trans_id, tree_path) in desired_files:
2666
for file_id, (trans_id, tree_path, text_sha1) in desired_files:
2230
2667
accelerator_path = unchanged.get(file_id)
2231
2668
if accelerator_path is None:
2232
new_desired_files.append((file_id, (trans_id, tree_path)))
2669
new_desired_files.append((file_id,
2670
(trans_id, tree_path, text_sha1)))
2234
pb.update('Adding file contents', count + offset, total)
2672
pb.update(gettext('Adding file contents'), count + offset, total)
2236
2674
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2253
2691
offset += count
2254
for count, ((trans_id, tree_path), contents) in enumerate(
2692
for count, ((trans_id, tree_path, text_sha1), contents) in enumerate(
2255
2693
tree.iter_files_bytes(new_desired_files)):
2256
2694
if wt.supports_content_filtering():
2257
2695
filters = wt._content_filter_stack(tree_path)
2258
2696
contents = filtered_output_bytes(contents, filters,
2259
2697
ContentFilterContext(tree_path, tree))
2260
tt.create_file(contents, trans_id)
2261
pb.update('Adding file contents', count + offset, total)
2698
tt.create_file(contents, trans_id, sha1=text_sha1)
2699
pb.update(gettext('Adding file contents'), count + offset, total)
2264
2702
def _reparent_children(tt, old_parent, new_parent):
2265
2703
for child in tt.iter_tree_children(old_parent):
2266
2704
tt.adjust_path(tt.final_name(child), new_parent, child)
2268
2707
def _reparent_transform_children(tt, old_parent, new_parent):
2269
2708
by_parent = tt.by_parent()
2270
2709
for child in by_parent[old_parent]:
2271
2710
tt.adjust_path(tt.final_name(child), new_parent, child)
2272
2711
return by_parent[old_parent]
2274
2714
def _content_match(tree, entry, file_id, kind, target_path):
2275
2715
if entry.kind != kind:
2277
2717
if entry.kind == "directory":
2279
2719
if entry.kind == "file":
2280
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
2720
f = file(target_path, 'rb')
2722
if tree.get_file_text(file_id) == f.read():
2282
2726
elif entry.kind == "symlink":
2283
2727
if tree.get_symlink_target(file_id) == os.readlink(target_path):
2336
2780
raise errors.BadFileKindError(name, kind)
2339
@deprecated_function(deprecated_in((1, 9, 0)))
2340
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
2341
"""Create new file contents according to an inventory entry.
2343
DEPRECATED. Use create_from_tree instead.
2783
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
2784
filter_tree_path=None):
2785
"""Create new file contents according to tree contents.
2787
:param filter_tree_path: the tree path to use to lookup
2788
content filters to apply to the bytes output in the working tree.
2789
This only applies if the working tree supports content filtering.
2345
if entry.kind == "file":
2347
lines = tree.get_file(entry.file_id).readlines()
2348
tt.create_file(lines, trans_id, mode_id=mode_id)
2349
elif entry.kind == "symlink":
2350
tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
2351
elif entry.kind == "directory":
2352
tt.create_directory(trans_id)
2355
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2356
"""Create new file contents according to tree contents."""
2357
2791
kind = tree.kind(file_id)
2358
2792
if kind == 'directory':
2359
2793
tt.create_directory(trans_id)
2393
2834
return new_name
2396
def _entry_changes(file_id, entry, working_tree):
2397
"""Determine in which ways the inventory entry has changed.
2399
Returns booleans: has_contents, content_mod, meta_mod
2400
has_contents means there are currently contents, but they differ
2401
contents_mod means contents need to be modified
2402
meta_mod means the metadata needs to be modified
2404
cur_entry = working_tree.inventory[file_id]
2406
working_kind = working_tree.kind(file_id)
2409
has_contents = False
2412
if has_contents is True:
2413
if entry.kind != working_kind:
2414
contents_mod, meta_mod = True, False
2416
cur_entry._read_tree_state(working_tree.id2path(file_id),
2418
contents_mod, meta_mod = entry.detect_changes(cur_entry)
2419
cur_entry._forget_tree_state()
2420
return has_contents, contents_mod, meta_mod
2423
2837
def revert(working_tree, target_tree, filenames, backups=False,
2424
pb=DummyProgress(), change_reporter=None):
2838
pb=None, change_reporter=None):
2425
2839
"""Revert a working tree's contents to those of a target tree."""
2426
2840
target_tree.lock_read()
2841
pb = ui.ui_factory.nested_progress_bar()
2427
2842
tt = TreeTransform(working_tree, pb)
2429
2844
pp = ProgressPhase("Revert phase", 3, pb)
2498
2921
if basis_tree is None:
2499
2922
basis_tree = working_tree.basis_tree()
2500
2923
basis_tree.lock_read()
2501
if file_id in basis_tree:
2924
if basis_tree.has_id(file_id):
2502
2925
if wt_sha1 != basis_tree.get_file_sha1(file_id):
2503
2926
keep_content = True
2504
elif kind[1] is None and not versioned[1]:
2927
elif target_kind is None and not target_versioned:
2505
2928
keep_content = True
2506
if kind[0] is not None:
2929
if wt_kind is not None:
2507
2930
if not keep_content:
2508
2931
tt.delete_contents(trans_id)
2509
elif kind[1] is not None:
2510
parent_trans_id = tt.trans_id_file_id(parent[0])
2511
by_parent = tt.by_parent()
2512
backup_name = _get_backup_name(name[0], by_parent,
2513
parent_trans_id, tt)
2932
elif target_kind is not None:
2933
parent_trans_id = tt.trans_id_file_id(wt_parent)
2934
backup_name = tt._available_backup_name(
2935
wt_name, parent_trans_id)
2514
2936
tt.adjust_path(backup_name, parent_trans_id, trans_id)
2515
new_trans_id = tt.create_path(name[0], parent_trans_id)
2516
if versioned == (True, True):
2937
new_trans_id = tt.create_path(wt_name, parent_trans_id)
2938
if wt_versioned and target_versioned:
2517
2939
tt.unversion_file(trans_id)
2518
2940
tt.version_file(file_id, new_trans_id)
2519
2941
# New contents should have the same unix perms as old
2521
2943
mode_id = trans_id
2522
2944
trans_id = new_trans_id
2523
if kind[1] in ('directory', 'tree-reference'):
2945
if target_kind in ('directory', 'tree-reference'):
2524
2946
tt.create_directory(trans_id)
2525
if kind[1] == 'tree-reference':
2947
if target_kind == 'tree-reference':
2526
2948
revision = target_tree.get_reference_revision(file_id,
2528
2950
tt.set_tree_reference(revision, trans_id)
2529
elif kind[1] == 'symlink':
2951
elif target_kind == 'symlink':
2530
2952
tt.create_symlink(target_tree.get_symlink_target(file_id),
2532
elif kind[1] == 'file':
2954
elif target_kind == 'file':
2533
2955
deferred_files.append((file_id, (trans_id, mode_id)))
2534
2956
if basis_tree is None:
2535
2957
basis_tree = working_tree.basis_tree()
2536
2958
basis_tree.lock_read()
2537
2959
new_sha1 = target_tree.get_file_sha1(file_id)
2538
if (file_id in basis_tree and new_sha1 ==
2539
basis_tree.get_file_sha1(file_id)):
2960
if (basis_tree.has_id(file_id) and
2961
new_sha1 == basis_tree.get_file_sha1(file_id)):
2540
2962
if file_id in merge_modified:
2541
2963
del merge_modified[file_id]
2543
2965
merge_modified[file_id] = new_sha1
2545
2967
# preserve the execute bit when backing up
2546
if keep_content and executable[0] == executable[1]:
2547
tt.set_executability(executable[1], trans_id)
2548
elif kind[1] is not None:
2549
raise AssertionError(kind[1])
2550
if versioned == (False, True):
2968
if keep_content and wt_executable == target_executable:
2969
tt.set_executability(target_executable, trans_id)
2970
elif target_kind is not None:
2971
raise AssertionError(target_kind)
2972
if not wt_versioned and target_versioned:
2551
2973
tt.version_file(file_id, trans_id)
2552
if versioned == (True, False):
2974
if wt_versioned and not target_versioned:
2553
2975
tt.unversion_file(trans_id)
2554
if (name[1] is not None and
2555
(name[0] != name[1] or parent[0] != parent[1])):
2556
if name[1] == '' and parent[1] is None:
2976
if (target_name is not None and
2977
(wt_name != target_name or wt_parent != target_parent)):
2978
if target_name == '' and target_parent is None:
2557
2979
parent_trans = ROOT_PARENT
2559
parent_trans = tt.trans_id_file_id(parent[1])
2560
tt.adjust_path(name[1], parent_trans, trans_id)
2561
if executable[0] != executable[1] and kind[1] == "file":
2562
tt.set_executability(executable[1], trans_id)
2563
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2565
tt.create_file(bytes, trans_id, mode_id)
2981
parent_trans = tt.trans_id_file_id(target_parent)
2982
if wt_parent is None and wt_versioned:
2983
tt.adjust_root_path(target_name, parent_trans)
2985
tt.adjust_path(target_name, parent_trans, trans_id)
2986
if wt_executable != target_executable and target_kind == "file":
2987
tt.set_executability(target_executable, trans_id)
2988
if working_tree.supports_content_filtering():
2989
for index, ((trans_id, mode_id), bytes) in enumerate(
2990
target_tree.iter_files_bytes(deferred_files)):
2991
file_id = deferred_files[index][0]
2992
# We're reverting a tree to the target tree so using the
2993
# target tree to find the file path seems the best choice
2994
# here IMO - Ian C 27/Oct/2009
2995
filter_tree_path = target_tree.id2path(file_id)
2996
filters = working_tree._content_filter_stack(filter_tree_path)
2997
bytes = filtered_output_bytes(bytes, filters,
2998
ContentFilterContext(filter_tree_path, working_tree))
2999
tt.create_file(bytes, trans_id, mode_id)
3001
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
3003
tt.create_file(bytes, trans_id, mode_id)
3004
tt.fixup_new_roots()
2567
3006
if basis_tree is not None:
2568
3007
basis_tree.unlock()
2569
3008
return merge_modified
2572
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
3011
def resolve_conflicts(tt, pb=None, pass_func=None):
2573
3012
"""Make many conflict-resolution attempts, but die if they fail"""
2574
3013
if pass_func is None:
2575
3014
pass_func = conflict_pass
2576
3015
new_conflicts = set()
3016
pb = ui.ui_factory.nested_progress_bar()
2578
3018
for n in range(10):
2579
pb.update('Resolution pass', n+1, 10)
3019
pb.update(gettext('Resolution pass'), n+1, 10)
2580
3020
conflicts = tt.find_conflicts()
2581
3021
if len(conflicts) == 0:
2582
3022
return new_conflicts
2583
3023
new_conflicts.update(pass_func(tt, conflicts))
2584
3024
raise MalformedTransform(conflicts=conflicts)
2589
3029
def conflict_pass(tt, conflicts, path_tree=None):