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 (
231
new_roots = [k for k, v in self._new_parent.iteritems() if v is
236
new_roots = [k for k, v in self._new_parent.iteritems() if v ==
233
238
if len(new_roots) < 1:
234
if self.final_kind(self.root) is None:
235
self.cancel_deletion(self.root)
236
if self.final_file_id(self.root) is None:
237
self.version_file(self.tree_file_id(self.root),
240
240
if len(new_roots) != 1:
241
241
raise ValueError('A tree cannot have two roots!')
566
566
for trans_id in self._removed_id:
567
567
file_id = self.tree_file_id(trans_id)
568
568
if file_id is not None:
569
# XXX: This seems like something that should go via a different
571
if self._tree.inventory[file_id].kind == 'directory':
569
if self._tree.stored_kind(file_id) == 'directory':
572
570
parents.append(trans_id)
573
571
elif self.tree_kind(trans_id) == 'directory':
574
572
parents.append(trans_id)
577
575
# ensure that all children are registered with the transaction
578
576
list(self.iter_tree_children(parent_id))
580
@deprecated_method(deprecated_in((2, 3, 0)))
581
def has_named_child(self, by_parent, parent_id, name):
582
return self._has_named_child(
583
name, parent_id, known_children=by_parent.get(parent_id, []))
585
578
def _has_named_child(self, name, parent_id, known_children):
586
579
"""Does a parent already have a name child.
647
640
"""If parent directories are versioned, children must be versioned."""
649
642
for parent_id, children in by_parent.iteritems():
650
if parent_id is ROOT_PARENT:
643
if parent_id == ROOT_PARENT:
652
645
if self.final_file_id(parent_id) is not None:
746
739
"""Children must have a directory parent"""
748
741
for parent_id, children in by_parent.iteritems():
749
if parent_id is ROOT_PARENT:
742
if parent_id == ROOT_PARENT:
751
744
no_children = True
752
745
for child_id in children:
769
762
def _set_executability(self, path, trans_id):
770
763
"""Set the executability of versioned files """
771
if supports_executable():
764
if self._tree._supports_executable():
772
765
new_executability = self._new_executability[trans_id]
773
766
abspath = self._tree.abspath(path)
774
767
current_mode = os.stat(abspath).st_mode
783
776
to_mode |= 0010 & ~umask
785
778
to_mode = current_mode & ~0111
786
os.chmod(abspath, to_mode)
779
osutils.chmod_if_possible(abspath, to_mode)
788
781
def _new_entry(self, name, parent_id, file_id):
789
782
"""Helper function to create a new filesystem entry."""
1239
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()
1241
1239
def _limbo_name(self, trans_id):
1242
1240
"""Generate the limbo name of a file"""
1243
1241
limbo_name = self._limbo_files.get(trans_id)
1403
1401
delete_any(self._limbo_name(trans_id))
1405
1403
def new_orphan(self, trans_id, parent_id):
1406
# FIXME: There is no tree config, so we use the branch one (it's weird
1407
# to define it this way as orphaning can only occur in a working tree,
1408
# but that's all we have (for now). It will find the option in
1409
# locations.conf or bazaar.conf though) -- vila 20100916
1410
conf = self._tree.branch.get_config()
1411
conf_var_name = 'bzr.transform.orphan_policy'
1412
orphan_policy = conf.get_user_option(conf_var_name)
1413
default_policy = orphaning_registry.default_key
1414
if orphan_policy is None:
1415
orphan_policy = default_policy
1416
if orphan_policy not in orphaning_registry:
1417
trace.warning('%s (from %s) is not a known policy, defaulting '
1418
'to %s' % (orphan_policy, conf_var_name, default_policy))
1419
orphan_policy = default_policy
1420
handle_orphan = orphaning_registry.get(orphan_policy)
1404
conf = self._tree.get_config_stack()
1405
handle_orphan = conf.get('bzr.transform.orphan_policy')
1421
1406
handle_orphan(self, trans_id, parent_id)
1486
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.',
1489
1480
class TreeTransform(DiskTreeTransform):
1490
1481
"""Represent a tree transformation.
1563
1554
limbodir = urlutils.local_path_from_url(
1564
1555
tree._transport.abspath('limbo'))
1568
if e.errno == errno.EEXIST:
1569
raise ExistingLimbo(limbodir)
1556
osutils.ensure_empty_directory_exists(
1558
errors.ExistingLimbo)
1570
1559
deletiondir = urlutils.local_path_from_url(
1571
1560
tree._transport.abspath('pending-deletion'))
1573
os.mkdir(deletiondir)
1575
if e.errno == errno.EEXIST:
1576
raise errors.ExistingPendingDeletion(deletiondir)
1561
osutils.ensure_empty_directory_exists(
1563
errors.ExistingPendingDeletion)
1644
1631
if typefunc(mode):
1645
os.chmod(self._limbo_name(trans_id), mode)
1632
osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
1647
1634
def iter_tree_children(self, parent_id):
1648
1635
"""Iterate through the entry's tree children, if any"""
1726
1713
calculating one.
1727
1714
:param _mover: Supply an alternate FileMover, for testing
1716
for hook in MutableTree.hooks['pre_transform']:
1717
hook(self._tree, self)
1729
1718
if not no_conflicts:
1730
1719
self._check_malformed()
1731
1720
child_pb = ui.ui_factory.nested_progress_bar()
1733
1722
if precomputed_delta is None:
1734
child_pb.update('Apply phase', 0, 2)
1723
child_pb.update(gettext('Apply phase'), 0, 2)
1735
1724
inventory_delta = self._generate_inventory_delta()
1745
child_pb.update('Apply phase', 0 + offset, 2 + offset)
1734
child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1746
1735
self._apply_removals(mover)
1747
child_pb.update('Apply phase', 1 + offset, 2 + offset)
1736
child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1748
1737
modified_paths = self._apply_insertions(mover)
1750
1739
mover.rollback()
1769
1760
for num, trans_id in enumerate(self._removed_id):
1770
1761
if (num % 10) == 0:
1771
child_pb.update('removing file', num, total_entries)
1762
child_pb.update(gettext('removing file'), num, total_entries)
1772
1763
if trans_id == self._new_root:
1773
1764
file_id = self._tree.get_root_id()
1786
1777
final_kinds = {}
1787
1778
for num, (path, trans_id) in enumerate(new_paths):
1788
1779
if (num % 10) == 0:
1789
child_pb.update('adding file',
1780
child_pb.update(gettext('adding file'),
1790
1781
num + len(self._removed_id), total_entries)
1791
1782
file_id = new_path_file_ids[trans_id]
1792
1783
if file_id is None:
1836
1827
# do not attempt to move root into a subdirectory of itself.
1839
child_pb.update('removing file', num, len(tree_paths))
1830
child_pb.update(gettext('removing file'), num, len(tree_paths))
1840
1831
full_path = self._tree.abspath(path)
1841
1832
if trans_id in self._removed_contents:
1842
1833
delete_path = os.path.join(self._deletiondir, trans_id)
1872
1863
for num, (path, trans_id) in enumerate(new_paths):
1873
1864
if (num % 10) == 0:
1874
child_pb.update('adding file', num, len(new_paths))
1865
child_pb.update(gettext('adding file'), num, len(new_paths))
1875
1866
full_path = self._tree.abspath(path)
1876
1867
if trans_id in self._needs_rename:
2054
@deprecated_method(deprecated_in((2, 5, 0)))
2063
2055
def inventory(self):
2064
2056
"""This Tree does not use inventory as its backing data."""
2065
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)
2067
2064
def get_root_id(self):
2068
2065
return self._transform.final_file_id(self._transform.root)
2181
2182
ordered_ids.append((trans_id, parent_file_id))
2182
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):
2184
2193
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
2185
2194
# This may not be a maximally efficient implementation, but it is
2186
2195
# reasonably straightforward. An implementation that grafts the
2273
def get_file_verifier(self, file_id, path=None, stat_value=None):
2274
trans_id = self._transform.trans_id_file_id(file_id)
2275
kind = self._transform._new_contents.get(trans_id)
2277
return self._transform._tree.get_file_verifier(file_id)
2279
fileobj = self.get_file(file_id)
2281
return ("SHA1", sha_file(fileobj))
2264
2285
def get_file_sha1(self, file_id, path=None, stat_value=None):
2265
2286
trans_id = self._transform.trans_id_file_id(file_id)
2266
2287
kind = self._transform._new_contents.get(trans_id)
2537
2558
file_trans_id = {}
2538
2559
top_pb = ui.ui_factory.nested_progress_bar()
2539
2560
pp = ProgressPhase("Build phase", 2, top_pb)
2540
if tree.inventory.root is not None:
2561
if tree.get_root_id() is not None:
2541
2562
# This is kind of a hack: we should be altering the root
2542
2563
# as part of the regular tree shape diff logic.
2543
2564
# The conditional test here is to avoid doing an
2573
2594
for dir, files in wt.walkdirs():
2574
2595
existing_files.update(f[0] for f in files)
2575
2596
for num, (tree_path, entry) in \
2576
enumerate(tree.inventory.iter_entries_by_dir()):
2577
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)
2578
2599
if entry.parent_id is None:
2580
2601
reparent = False
2664
2685
new_desired_files.append((file_id,
2665
2686
(trans_id, tree_path, text_sha1)))
2667
pb.update('Adding file contents', count + offset, total)
2688
pb.update(gettext('Adding file contents'), count + offset, total)
2669
2690
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2691
2712
contents = filtered_output_bytes(contents, filters,
2692
2713
ContentFilterContext(tree_path, tree))
2693
2714
tt.create_file(contents, trans_id, sha1=text_sha1)
2694
pb.update('Adding file contents', count + offset, total)
2715
pb.update(gettext('Adding file contents'), count + offset, total)
2697
2718
def _reparent_children(tt, old_parent, new_parent):
2811
2832
tt.set_executability(entry.executable, trans_id)
2814
@deprecated_function(deprecated_in((2, 3, 0)))
2815
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2816
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2819
@deprecated_function(deprecated_in((2, 3, 0)))
2820
def _get_backup_name(name, by_parent, parent_trans_id, tt):
2821
"""Produce a backup-style name that appears to be available"""
2825
yield "%s.~%d~" % (name, counter)
2827
for new_name in name_gen():
2828
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
2832
def _entry_changes(file_id, entry, working_tree):
2833
"""Determine in which ways the inventory entry has changed.
2835
Returns booleans: has_contents, content_mod, meta_mod
2836
has_contents means there are currently contents, but they differ
2837
contents_mod means contents need to be modified
2838
meta_mod means the metadata needs to be modified
2840
cur_entry = working_tree.inventory[file_id]
2842
working_kind = working_tree.kind(file_id)
2845
has_contents = False
2848
if has_contents is True:
2849
if entry.kind != working_kind:
2850
contents_mod, meta_mod = True, False
2852
cur_entry._read_tree_state(working_tree.id2path(file_id),
2854
contents_mod, meta_mod = entry.detect_changes(cur_entry)
2855
cur_entry._forget_tree_state()
2856
return has_contents, contents_mod, meta_mod
2859
2835
def revert(working_tree, target_tree, filenames, backups=False,
2860
2836
pb=None, change_reporter=None):
2861
2837
"""Revert a working tree's contents to those of a target tree."""
3038
3014
pb = ui.ui_factory.nested_progress_bar()
3040
3016
for n in range(10):
3041
pb.update('Resolution pass', n+1, 10)
3017
pb.update(gettext('Resolution pass'), n+1, 10)
3042
3018
conflicts = tt.find_conflicts()
3043
3019
if len(conflicts) == 0:
3044
3020
return new_conflicts
3068
3044
existing_file, new_file = conflict[2], conflict[1]
3070
3046
existing_file, new_file = conflict[1], conflict[2]
3071
new_name = tt.final_name(existing_file)+'.moved'
3047
new_name = tt.final_name(existing_file) + '.moved'
3072
3048
tt.adjust_path(new_name, final_parent, existing_file)
3073
3049
new_conflicts.add((c_type, 'Moved existing file to',
3074
3050
existing_file, new_file))