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!')
243
243
self._new_root = new_roots[0]
245
245
old_new_root = new_roots[0]
246
# TODO: What to do if a old_new_root is present, but self._new_root is
247
# not listed as being removed? This code explicitly unversions
248
# the old root and versions it with the new file_id. Though that
249
# seems like an incomplete delta
251
246
# unversion the new root's directory.
252
file_id = self.final_file_id(old_new_root)
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)
253
251
if old_new_root in self._new_id:
254
252
self.cancel_versioning(old_new_root)
568
566
for trans_id in self._removed_id:
569
567
file_id = self.tree_file_id(trans_id)
570
568
if file_id is not None:
571
# XXX: This seems like something that should go via a different
573
if self._tree.inventory[file_id].kind == 'directory':
569
if self._tree.stored_kind(file_id) == 'directory':
574
570
parents.append(trans_id)
575
571
elif self.tree_kind(trans_id) == 'directory':
576
572
parents.append(trans_id)
579
575
# ensure that all children are registered with the transaction
580
576
list(self.iter_tree_children(parent_id))
582
@deprecated_method(deprecated_in((2, 3, 0)))
583
def has_named_child(self, by_parent, parent_id, name):
584
return self._has_named_child(
585
name, parent_id, known_children=by_parent.get(parent_id, []))
587
578
def _has_named_child(self, name, parent_id, known_children):
588
579
"""Does a parent already have a name child.
649
640
"""If parent directories are versioned, children must be versioned."""
651
642
for parent_id, children in by_parent.iteritems():
652
if parent_id is ROOT_PARENT:
643
if parent_id == ROOT_PARENT:
654
645
if self.final_file_id(parent_id) is not None:
748
739
"""Children must have a directory parent"""
750
741
for parent_id, children in by_parent.iteritems():
751
if parent_id is ROOT_PARENT:
742
if parent_id == ROOT_PARENT:
753
744
no_children = True
754
745
for child_id in children:
771
762
def _set_executability(self, path, trans_id):
772
763
"""Set the executability of versioned files """
773
if supports_executable():
764
if self._tree._supports_executable():
774
765
new_executability = self._new_executability[trans_id]
775
766
abspath = self._tree.abspath(path)
776
767
current_mode = os.stat(abspath).st_mode
785
776
to_mode |= 0010 & ~umask
787
778
to_mode = current_mode & ~0111
788
os.chmod(abspath, to_mode)
779
osutils.chmod_if_possible(abspath, to_mode)
790
781
def _new_entry(self, name, parent_id, file_id):
791
782
"""Helper function to create a new filesystem entry."""
1241
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()
1243
1239
def _limbo_name(self, trans_id):
1244
1240
"""Generate the limbo name of a file"""
1245
1241
limbo_name = self._limbo_files.get(trans_id)
1405
1401
delete_any(self._limbo_name(trans_id))
1407
1403
def new_orphan(self, trans_id, parent_id):
1408
# FIXME: There is no tree config, so we use the branch one (it's weird
1409
# to define it this way as orphaning can only occur in a working tree,
1410
# but that's all we have (for now). It will find the option in
1411
# locations.conf or bazaar.conf though) -- vila 20100916
1412
conf = self._tree.branch.get_config()
1413
conf_var_name = 'bzr.transform.orphan_policy'
1414
orphan_policy = conf.get_user_option(conf_var_name)
1415
default_policy = orphaning_registry.default_key
1416
if orphan_policy is None:
1417
orphan_policy = default_policy
1418
if orphan_policy not in orphaning_registry:
1419
trace.warning('%s (from %s) is not a known policy, defaulting '
1420
'to %s' % (orphan_policy, conf_var_name, default_policy))
1421
orphan_policy = default_policy
1422
handle_orphan = orphaning_registry.get(orphan_policy)
1404
conf = self._tree.get_config_stack()
1405
handle_orphan = conf.get('bzr.transform.orphan_policy')
1423
1406
handle_orphan(self, trans_id, parent_id)
1488
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.',
1491
1480
class TreeTransform(DiskTreeTransform):
1492
1481
"""Represent a tree transformation.
1565
1554
limbodir = urlutils.local_path_from_url(
1566
1555
tree._transport.abspath('limbo'))
1570
if e.errno == errno.EEXIST:
1571
raise ExistingLimbo(limbodir)
1556
osutils.ensure_empty_directory_exists(
1558
errors.ExistingLimbo)
1572
1559
deletiondir = urlutils.local_path_from_url(
1573
1560
tree._transport.abspath('pending-deletion'))
1575
os.mkdir(deletiondir)
1577
if e.errno == errno.EEXIST:
1578
raise errors.ExistingPendingDeletion(deletiondir)
1561
osutils.ensure_empty_directory_exists(
1563
errors.ExistingPendingDeletion)
1646
1631
if typefunc(mode):
1647
os.chmod(self._limbo_name(trans_id), mode)
1632
osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
1649
1634
def iter_tree_children(self, parent_id):
1650
1635
"""Iterate through the entry's tree children, if any"""
1728
1713
calculating one.
1729
1714
:param _mover: Supply an alternate FileMover, for testing
1716
for hook in MutableTree.hooks['pre_transform']:
1717
hook(self._tree, self)
1731
1718
if not no_conflicts:
1732
1719
self._check_malformed()
1733
1720
child_pb = ui.ui_factory.nested_progress_bar()
1735
1722
if precomputed_delta is None:
1736
child_pb.update('Apply phase', 0, 2)
1723
child_pb.update(gettext('Apply phase'), 0, 2)
1737
1724
inventory_delta = self._generate_inventory_delta()
1747
child_pb.update('Apply phase', 0 + offset, 2 + offset)
1734
child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1748
1735
self._apply_removals(mover)
1749
child_pb.update('Apply phase', 1 + offset, 2 + offset)
1736
child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1750
1737
modified_paths = self._apply_insertions(mover)
1752
1739
mover.rollback()
1771
1760
for num, trans_id in enumerate(self._removed_id):
1772
1761
if (num % 10) == 0:
1773
child_pb.update('removing file', num, total_entries)
1762
child_pb.update(gettext('removing file'), num, total_entries)
1774
1763
if trans_id == self._new_root:
1775
1764
file_id = self._tree.get_root_id()
1788
1777
final_kinds = {}
1789
1778
for num, (path, trans_id) in enumerate(new_paths):
1790
1779
if (num % 10) == 0:
1791
child_pb.update('adding file',
1780
child_pb.update(gettext('adding file'),
1792
1781
num + len(self._removed_id), total_entries)
1793
1782
file_id = new_path_file_ids[trans_id]
1794
1783
if file_id is None:
1838
1827
# do not attempt to move root into a subdirectory of itself.
1841
child_pb.update('removing file', num, len(tree_paths))
1830
child_pb.update(gettext('removing file'), num, len(tree_paths))
1842
1831
full_path = self._tree.abspath(path)
1843
1832
if trans_id in self._removed_contents:
1844
1833
delete_path = os.path.join(self._deletiondir, trans_id)
1874
1863
for num, (path, trans_id) in enumerate(new_paths):
1875
1864
if (num % 10) == 0:
1876
child_pb.update('adding file', num, len(new_paths))
1865
child_pb.update(gettext('adding file'), num, len(new_paths))
1877
1866
full_path = self._tree.abspath(path)
1878
1867
if trans_id in self._needs_rename:
2054
@deprecated_method(deprecated_in((2, 5, 0)))
2065
2055
def inventory(self):
2066
2056
"""This Tree does not use inventory as its backing data."""
2067
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)
2069
2064
def get_root_id(self):
2070
2065
return self._transform.final_file_id(self._transform.root)
2183
2182
ordered_ids.append((trans_id, parent_file_id))
2184
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):
2186
2193
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
2187
2194
# This may not be a maximally efficient implementation, but it is
2188
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))
2266
2285
def get_file_sha1(self, file_id, path=None, stat_value=None):
2267
2286
trans_id = self._transform.trans_id_file_id(file_id)
2268
2287
kind = self._transform._new_contents.get(trans_id)
2539
2558
file_trans_id = {}
2540
2559
top_pb = ui.ui_factory.nested_progress_bar()
2541
2560
pp = ProgressPhase("Build phase", 2, top_pb)
2542
if tree.inventory.root is not None:
2561
if tree.get_root_id() is not None:
2543
2562
# This is kind of a hack: we should be altering the root
2544
2563
# as part of the regular tree shape diff logic.
2545
2564
# The conditional test here is to avoid doing an
2575
2594
for dir, files in wt.walkdirs():
2576
2595
existing_files.update(f[0] for f in files)
2577
2596
for num, (tree_path, entry) in \
2578
enumerate(tree.inventory.iter_entries_by_dir()):
2579
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)
2580
2599
if entry.parent_id is None:
2582
2601
reparent = False
2666
2685
new_desired_files.append((file_id,
2667
2686
(trans_id, tree_path, text_sha1)))
2669
pb.update('Adding file contents', count + offset, total)
2688
pb.update(gettext('Adding file contents'), count + offset, total)
2671
2690
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2693
2712
contents = filtered_output_bytes(contents, filters,
2694
2713
ContentFilterContext(tree_path, tree))
2695
2714
tt.create_file(contents, trans_id, sha1=text_sha1)
2696
pb.update('Adding file contents', count + offset, total)
2715
pb.update(gettext('Adding file contents'), count + offset, total)
2699
2718
def _reparent_children(tt, old_parent, new_parent):
2813
2832
tt.set_executability(entry.executable, trans_id)
2816
@deprecated_function(deprecated_in((2, 3, 0)))
2817
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2818
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2821
@deprecated_function(deprecated_in((2, 3, 0)))
2822
def _get_backup_name(name, by_parent, parent_trans_id, tt):
2823
"""Produce a backup-style name that appears to be available"""
2827
yield "%s.~%d~" % (name, counter)
2829
for new_name in name_gen():
2830
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
2834
def _entry_changes(file_id, entry, working_tree):
2835
"""Determine in which ways the inventory entry has changed.
2837
Returns booleans: has_contents, content_mod, meta_mod
2838
has_contents means there are currently contents, but they differ
2839
contents_mod means contents need to be modified
2840
meta_mod means the metadata needs to be modified
2842
cur_entry = working_tree.inventory[file_id]
2844
working_kind = working_tree.kind(file_id)
2847
has_contents = False
2850
if has_contents is True:
2851
if entry.kind != working_kind:
2852
contents_mod, meta_mod = True, False
2854
cur_entry._read_tree_state(working_tree.id2path(file_id),
2856
contents_mod, meta_mod = entry.detect_changes(cur_entry)
2857
cur_entry._forget_tree_state()
2858
return has_contents, contents_mod, meta_mod
2861
2835
def revert(working_tree, target_tree, filenames, backups=False,
2862
2836
pb=None, change_reporter=None):
2863
2837
"""Revert a working tree's contents to those of a target tree."""
3040
3014
pb = ui.ui_factory.nested_progress_bar()
3042
3016
for n in range(10):
3043
pb.update('Resolution pass', n+1, 10)
3017
pb.update(gettext('Resolution pass'), n+1, 10)
3044
3018
conflicts = tt.find_conflicts()
3045
3019
if len(conflicts) == 0:
3046
3020
return new_conflicts
3070
3044
existing_file, new_file = conflict[2], conflict[1]
3072
3046
existing_file, new_file = conflict[1], conflict[2]
3073
new_name = tt.final_name(existing_file)+'.moved'
3047
new_name = tt.final_name(existing_file) + '.moved'
3074
3048
tt.adjust_path(new_name, final_parent, existing_file)
3075
3049
new_conflicts.add((c_type, 'Moved existing file to',
3076
3050
existing_file, new_file))