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