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
22
24
from bzrlib import (
25
config as _mod_config,
47
from bzrlib.i18n import gettext
46
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
49
from bzrlib.errors import (DuplicateKey, MalformedTransform,
47
50
ReusingTransform, CantMoveRoot,
48
ExistingLimbo, ImmortalLimbo, NoFinalPath,
51
ImmortalLimbo, NoFinalPath,
49
52
UnableCreateSymlink)
50
53
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
54
from bzrlib.mutabletree import MutableTree
51
55
from bzrlib.osutils import (
561
566
for trans_id in self._removed_id:
562
567
file_id = self.tree_file_id(trans_id)
563
568
if file_id is not None:
564
# XXX: This seems like something that should go via a different
566
if self._tree.inventory[file_id].kind == 'directory':
569
if self._tree.stored_kind(file_id) == 'directory':
567
570
parents.append(trans_id)
568
571
elif self.tree_kind(trans_id) == 'directory':
569
572
parents.append(trans_id)
572
575
# ensure that all children are registered with the transaction
573
576
list(self.iter_tree_children(parent_id))
575
@deprecated_method(deprecated_in((2, 3, 0)))
576
def has_named_child(self, by_parent, parent_id, name):
577
return self._has_named_child(
578
name, parent_id, known_children=by_parent.get(parent_id, []))
580
578
def _has_named_child(self, name, parent_id, known_children):
581
579
"""Does a parent already have a name child.
642
640
"""If parent directories are versioned, children must be versioned."""
644
642
for parent_id, children in by_parent.iteritems():
645
if parent_id is ROOT_PARENT:
643
if parent_id == ROOT_PARENT:
647
645
if self.final_file_id(parent_id) is not None:
741
739
"""Children must have a directory parent"""
743
741
for parent_id, children in by_parent.iteritems():
744
if parent_id is ROOT_PARENT:
742
if parent_id == ROOT_PARENT:
746
744
no_children = True
747
745
for child_id in children:
764
762
def _set_executability(self, path, trans_id):
765
763
"""Set the executability of versioned files """
766
if supports_executable():
764
if self._tree._supports_executable():
767
765
new_executability = self._new_executability[trans_id]
768
766
abspath = self._tree.abspath(path)
769
767
current_mode = os.stat(abspath).st_mode
778
776
to_mode |= 0010 & ~umask
780
778
to_mode = current_mode & ~0111
781
os.chmod(abspath, to_mode)
779
osutils.chmod_if_possible(abspath, to_mode)
783
781
def _new_entry(self, name, parent_id, file_id):
784
782
"""Helper function to create a new filesystem entry."""
1234
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()
1236
1239
def _limbo_name(self, trans_id):
1237
1240
"""Generate the limbo name of a file"""
1238
1241
limbo_name = self._limbo_files.get(trans_id)
1398
1401
delete_any(self._limbo_name(trans_id))
1400
1403
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)
1404
conf = self._tree.get_config_stack()
1405
handle_orphan = conf.get('bzr.transform.orphan_policy')
1416
1406
handle_orphan(self, trans_id, parent_id)
1481
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.',
1484
1480
class TreeTransform(DiskTreeTransform):
1485
1481
"""Represent a tree transformation.
1558
1554
limbodir = urlutils.local_path_from_url(
1559
1555
tree._transport.abspath('limbo'))
1563
if e.errno == errno.EEXIST:
1564
raise ExistingLimbo(limbodir)
1556
osutils.ensure_empty_directory_exists(
1558
errors.ExistingLimbo)
1565
1559
deletiondir = urlutils.local_path_from_url(
1566
1560
tree._transport.abspath('pending-deletion'))
1568
os.mkdir(deletiondir)
1570
if e.errno == errno.EEXIST:
1571
raise errors.ExistingPendingDeletion(deletiondir)
1561
osutils.ensure_empty_directory_exists(
1563
errors.ExistingPendingDeletion)
1639
1631
if typefunc(mode):
1640
os.chmod(self._limbo_name(trans_id), mode)
1632
osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
1642
1634
def iter_tree_children(self, parent_id):
1643
1635
"""Iterate through the entry's tree children, if any"""
1721
1713
calculating one.
1722
1714
:param _mover: Supply an alternate FileMover, for testing
1716
for hook in MutableTree.hooks['pre_transform']:
1717
hook(self._tree, self)
1724
1718
if not no_conflicts:
1725
1719
self._check_malformed()
1726
1720
child_pb = ui.ui_factory.nested_progress_bar()
1728
1722
if precomputed_delta is None:
1729
child_pb.update('Apply phase', 0, 2)
1723
child_pb.update(gettext('Apply phase'), 0, 2)
1730
1724
inventory_delta = self._generate_inventory_delta()
1740
child_pb.update('Apply phase', 0 + offset, 2 + offset)
1734
child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1741
1735
self._apply_removals(mover)
1742
child_pb.update('Apply phase', 1 + offset, 2 + offset)
1736
child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1743
1737
modified_paths = self._apply_insertions(mover)
1745
1739
mover.rollback()
1766
1760
for num, trans_id in enumerate(self._removed_id):
1767
1761
if (num % 10) == 0:
1768
child_pb.update('removing file', num, total_entries)
1762
child_pb.update(gettext('removing file'), num, total_entries)
1769
1763
if trans_id == self._new_root:
1770
1764
file_id = self._tree.get_root_id()
1783
1777
final_kinds = {}
1784
1778
for num, (path, trans_id) in enumerate(new_paths):
1785
1779
if (num % 10) == 0:
1786
child_pb.update('adding file',
1780
child_pb.update(gettext('adding file'),
1787
1781
num + len(self._removed_id), total_entries)
1788
1782
file_id = new_path_file_ids[trans_id]
1789
1783
if file_id is None:
1833
1827
# do not attempt to move root into a subdirectory of itself.
1836
child_pb.update('removing file', num, len(tree_paths))
1830
child_pb.update(gettext('removing file'), num, len(tree_paths))
1837
1831
full_path = self._tree.abspath(path)
1838
1832
if trans_id in self._removed_contents:
1839
1833
delete_path = os.path.join(self._deletiondir, trans_id)
1869
1863
for num, (path, trans_id) in enumerate(new_paths):
1870
1864
if (num % 10) == 0:
1871
child_pb.update('adding file', num, len(new_paths))
1865
child_pb.update(gettext('adding file'), num, len(new_paths))
1872
1866
full_path = self._tree.abspath(path)
1873
1867
if trans_id in self._needs_rename:
2054
@deprecated_method(deprecated_in((2, 5, 0)))
2060
2055
def inventory(self):
2061
2056
"""This Tree does not use inventory as its backing data."""
2062
2057
raise NotImplementedError(_PreviewTree.inventory)
2060
def root_inventory(self):
2061
"""This Tree does not use inventory as its backing data."""
2062
raise NotImplementedError(_PreviewTree.root_inventory)
2064
2064
def get_root_id(self):
2065
2065
return self._transform.final_file_id(self._transform.root)
2178
2182
ordered_ids.append((trans_id, parent_file_id))
2179
2183
return ordered_ids
2185
def iter_child_entries(self, file_id, path=None):
2186
self.id2path(file_id)
2187
trans_id = self._transform.trans_id_file_id(file_id)
2188
todo = [(child_trans_id, trans_id) for child_trans_id in
2189
self._all_children(trans_id)]
2190
for entry, trans_id in self._make_inv_entries(todo):
2181
2193
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
2182
2194
# This may not be a maximally efficient implementation, but it is
2183
2195
# reasonably straightforward. An implementation that grafts the
2546
2558
file_trans_id = {}
2547
2559
top_pb = ui.ui_factory.nested_progress_bar()
2548
2560
pp = ProgressPhase("Build phase", 2, top_pb)
2549
if tree.inventory.root is not None:
2561
if tree.get_root_id() is not None:
2550
2562
# This is kind of a hack: we should be altering the root
2551
2563
# as part of the regular tree shape diff logic.
2552
2564
# The conditional test here is to avoid doing an
2582
2594
for dir, files in wt.walkdirs():
2583
2595
existing_files.update(f[0] for f in files)
2584
2596
for num, (tree_path, entry) in \
2585
enumerate(tree.inventory.iter_entries_by_dir()):
2586
pb.update("Building tree", num - len(deferred_contents), total)
2597
enumerate(tree.iter_entries_by_dir()):
2598
pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2587
2599
if entry.parent_id is None:
2589
2601
reparent = False
2673
2685
new_desired_files.append((file_id,
2674
2686
(trans_id, tree_path, text_sha1)))
2676
pb.update('Adding file contents', count + offset, total)
2688
pb.update(gettext('Adding file contents'), count + offset, total)
2678
2690
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2700
2712
contents = filtered_output_bytes(contents, filters,
2701
2713
ContentFilterContext(tree_path, tree))
2702
2714
tt.create_file(contents, trans_id, sha1=text_sha1)
2703
pb.update('Adding file contents', count + offset, total)
2715
pb.update(gettext('Adding file contents'), count + offset, total)
2706
2718
def _reparent_children(tt, old_parent, new_parent):
2820
2832
tt.set_executability(entry.executable, trans_id)
2823
@deprecated_function(deprecated_in((2, 3, 0)))
2824
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2825
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2828
@deprecated_function(deprecated_in((2, 3, 0)))
2829
def _get_backup_name(name, by_parent, parent_trans_id, tt):
2830
"""Produce a backup-style name that appears to be available"""
2834
yield "%s.~%d~" % (name, counter)
2836
for new_name in name_gen():
2837
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
2841
def _entry_changes(file_id, entry, working_tree):
2842
"""Determine in which ways the inventory entry has changed.
2844
Returns booleans: has_contents, content_mod, meta_mod
2845
has_contents means there are currently contents, but they differ
2846
contents_mod means contents need to be modified
2847
meta_mod means the metadata needs to be modified
2849
cur_entry = working_tree.inventory[file_id]
2851
working_kind = working_tree.kind(file_id)
2854
has_contents = False
2857
if has_contents is True:
2858
if entry.kind != working_kind:
2859
contents_mod, meta_mod = True, False
2861
cur_entry._read_tree_state(working_tree.id2path(file_id),
2863
contents_mod, meta_mod = entry.detect_changes(cur_entry)
2864
cur_entry._forget_tree_state()
2865
return has_contents, contents_mod, meta_mod
2868
2835
def revert(working_tree, target_tree, filenames, backups=False,
2869
2836
pb=None, change_reporter=None):
2870
2837
"""Revert a working tree's contents to those of a target tree."""
3047
3014
pb = ui.ui_factory.nested_progress_bar()
3049
3016
for n in range(10):
3050
pb.update('Resolution pass', n+1, 10)
3017
pb.update(gettext('Resolution pass'), n+1, 10)
3051
3018
conflicts = tt.find_conflicts()
3052
3019
if len(conflicts) == 0:
3053
3020
return new_conflicts
3077
3044
existing_file, new_file = conflict[2], conflict[1]
3079
3046
existing_file, new_file = conflict[1], conflict[2]
3080
new_name = tt.final_name(existing_file)+'.moved'
3047
new_name = tt.final_name(existing_file) + '.moved'
3081
3048
tt.adjust_path(new_name, final_parent, existing_file)
3082
3049
new_conflicts.add((c_type, 'Moved existing file to',
3083
3050
existing_file, new_file))