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,
45
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
50
46
ReusingTransform, CantMoveRoot,
51
ImmortalLimbo, NoFinalPath,
47
ExistingLimbo, ImmortalLimbo, NoFinalPath,
52
48
UnableCreateSymlink)
53
49
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
54
from bzrlib.mutabletree import MutableTree
55
50
from bzrlib.osutils import (
141
137
# A counter of how many files have been renamed
142
138
self.rename_count = 0
145
"""Support Context Manager API."""
148
def __exit__(self, exc_type, exc_val, exc_tb):
149
"""Support Context Manager API."""
152
140
def finalize(self):
153
141
"""Release the working tree lock, if held.
243
228
self._new_root = new_roots[0]
245
230
old_new_root = new_roots[0]
231
# TODO: What to do if a old_new_root is present, but self._new_root is
232
# not listed as being removed? This code explicitly unversions
233
# the old root and versions it with the new file_id. Though that
234
# seems like an incomplete delta
246
236
# unversion the new root's directory.
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)
237
file_id = self.final_file_id(old_new_root)
251
238
if old_new_root in self._new_id:
252
239
self.cancel_versioning(old_new_root)
257
244
if (self.tree_file_id(self._new_root) is not None and
258
245
self._new_root not in self._removed_id):
259
246
self.unversion_file(self._new_root)
260
if file_id is not None:
261
self.version_file(file_id, self._new_root)
247
self.version_file(file_id, self._new_root)
263
249
# Now move children of new root into old root directory.
264
250
# Ensure all children are registered with the transaction, but don't
398
384
return sorted(FinalPaths(self).get_paths(new_ids))
400
386
def _inventory_altered(self):
401
"""Determine which trans_ids need new Inventory entries.
403
An new entry is needed when anything that would be reflected by an
404
inventory entry changes, including file name, file_id, parent file_id,
405
file kind, and the execute bit.
407
Some care is taken to return entries with real changes, not cases
408
where the value is deleted and then restored to its original value,
409
but some actually unchanged values may be returned.
411
:returns: A list of (path, trans_id) for all items requiring an
412
inventory change. Ordered by path.
415
# Find entries whose file_ids are new (or changed).
416
new_file_id = set(t for t in self._new_id
417
if self._new_id[t] != self.tree_file_id(t))
418
for id_set in [self._new_name, self._new_parent, new_file_id,
387
"""Get the trans_ids and paths of files needing new inv entries."""
389
for id_set in [self._new_name, self._new_parent, self._new_id,
419
390
self._new_executability]:
420
changed_ids.update(id_set)
421
# removing implies a kind change
391
new_ids.update(id_set)
422
392
changed_kind = set(self._removed_contents)
424
393
changed_kind.intersection_update(self._new_contents)
425
# Ignore entries that are already known to have changed.
426
changed_kind.difference_update(changed_ids)
427
# to keep only the truly changed ones
394
changed_kind.difference_update(new_ids)
428
395
changed_kind = (t for t in changed_kind
429
396
if self.tree_kind(t) != self.final_kind(t))
430
# all kind changes will alter the inventory
431
changed_ids.update(changed_kind)
432
# To find entries with changed parent_ids, find parents which existed,
433
# but changed file_id.
434
changed_file_id = set(t for t in new_file_id if t in self._removed_id)
435
# Now add all their children to the set.
436
for parent_trans_id in new_file_id:
437
changed_ids.update(self.iter_tree_children(parent_trans_id))
438
return sorted(FinalPaths(self).get_paths(changed_ids))
397
new_ids.update(changed_kind)
398
return sorted(FinalPaths(self).get_paths(new_ids))
440
400
def final_kind(self, trans_id):
441
401
"""Determine the final file kind, after any changes applied.
575
537
# ensure that all children are registered with the transaction
576
538
list(self.iter_tree_children(parent_id))
540
@deprecated_method(deprecated_in((2, 3, 0)))
541
def has_named_child(self, by_parent, parent_id, name):
542
return self._has_named_child(
543
name, parent_id, known_children=by_parent.get(parent_id, []))
578
545
def _has_named_child(self, name, parent_id, known_children):
579
546
"""Does a parent already have a name child.
762
729
def _set_executability(self, path, trans_id):
763
730
"""Set the executability of versioned files """
764
if self._tree._supports_executable():
731
if supports_executable():
765
732
new_executability = self._new_executability[trans_id]
766
733
abspath = self._tree.abspath(path)
767
734
current_mode = os.stat(abspath).st_mode
776
743
to_mode |= 0010 & ~umask
778
745
to_mode = current_mode & ~0111
779
osutils.chmod_if_possible(abspath, to_mode)
746
os.chmod(abspath, to_mode)
781
748
def _new_entry(self, name, parent_id, file_id):
782
749
"""Helper function to create a new filesystem entry."""
1206
1172
if self._tree is None:
1209
limbo_paths = self._limbo_files.values() + list(
1210
self._possibly_stale_limbo_files)
1211
limbo_paths = sorted(limbo_paths, reverse=True)
1212
for path in limbo_paths:
1216
if e.errno != errno.ENOENT:
1218
# XXX: warn? perhaps we just got interrupted at an
1219
# inconvenient moment, but perhaps files are disappearing
1175
entries = [(self._limbo_name(t), t, k) for t, k in
1176
self._new_contents.iteritems()]
1177
entries.sort(reverse=True)
1178
for path, trans_id, kind in entries:
1222
1181
delete_any(self._limbodir)
1223
1182
except OSError:
1232
1191
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
1193
def _limbo_name(self, trans_id):
1240
1194
"""Generate the limbo name of a file"""
1241
1195
limbo_name = self._limbo_files.get(trans_id)
1277
1231
entries from _limbo_files, because they are now stale.
1279
1233
for trans_id in trans_ids:
1280
old_path = self._limbo_files[trans_id]
1281
self._possibly_stale_limbo_files.add(old_path)
1282
del self._limbo_files[trans_id]
1234
old_path = self._limbo_files.pop(trans_id)
1283
1235
if trans_id not in self._new_contents:
1285
1237
new_path = self._limbo_name(trans_id)
1286
1238
os.rename(old_path, new_path)
1287
self._possibly_stale_limbo_files.remove(old_path)
1288
1239
for descendant in self._limbo_descendants(trans_id):
1289
1240
desc_path = self._limbo_files[descendant]
1290
1241
desc_path = new_path + desc_path[len(old_path):]
1314
1265
name = self._limbo_name(trans_id)
1315
1266
f = open(name, 'wb')
1317
unique_add(self._new_contents, trans_id, 'file')
1269
unique_add(self._new_contents, trans_id, 'file')
1271
# Clean up the file, it never got registered so
1272
# TreeTransform.finalize() won't clean it up.
1318
1276
f.writelines(contents)
1401
1359
delete_any(self._limbo_name(trans_id))
1403
1361
def new_orphan(self, trans_id, parent_id):
1404
conf = self._tree.get_config_stack()
1405
handle_orphan = conf.get('bzr.transform.orphan_policy')
1362
# FIXME: There is no tree config, so we use the branch one (it's weird
1363
# to define it this way as orphaning can only occur in a working tree,
1364
# but that's all we have (for now). It will find the option in
1365
# locations.conf or bazaar.conf though) -- vila 20100916
1366
conf = self._tree.branch.get_config()
1367
conf_var_name = 'bzr.transform.orphan_policy'
1368
orphan_policy = conf.get_user_option(conf_var_name)
1369
default_policy = orphaning_registry.default_key
1370
if orphan_policy is None:
1371
orphan_policy = default_policy
1372
if orphan_policy not in orphaning_registry:
1373
trace.warning('%s (from %s) is not a known policy, defaulting '
1374
'to %s' % (orphan_policy, conf_var_name, default_policy))
1375
orphan_policy = default_policy
1376
handle_orphan = orphaning_registry.get(orphan_policy)
1406
1377
handle_orphan(self, trans_id, parent_id)
1471
1442
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
1445
class TreeTransform(DiskTreeTransform):
1481
1446
"""Represent a tree transformation.
1554
1519
limbodir = urlutils.local_path_from_url(
1555
1520
tree._transport.abspath('limbo'))
1556
osutils.ensure_empty_directory_exists(
1558
errors.ExistingLimbo)
1524
if e.errno == errno.EEXIST:
1525
raise ExistingLimbo(limbodir)
1559
1526
deletiondir = urlutils.local_path_from_url(
1560
1527
tree._transport.abspath('pending-deletion'))
1561
osutils.ensure_empty_directory_exists(
1563
errors.ExistingPendingDeletion)
1529
os.mkdir(deletiondir)
1531
if e.errno == errno.EEXIST:
1532
raise errors.ExistingPendingDeletion(deletiondir)
1713
1682
calculating one.
1714
1683
:param _mover: Supply an alternate FileMover, for testing
1716
for hook in MutableTree.hooks['pre_transform']:
1717
hook(self._tree, self)
1718
1685
if not no_conflicts:
1719
1686
self._check_malformed()
1720
1687
child_pb = ui.ui_factory.nested_progress_bar()
1722
1689
if precomputed_delta is None:
1723
child_pb.update(gettext('Apply phase'), 0, 2)
1690
child_pb.update('Apply phase', 0, 2)
1724
1691
inventory_delta = self._generate_inventory_delta()
1734
child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1701
child_pb.update('Apply phase', 0 + offset, 2 + offset)
1735
1702
self._apply_removals(mover)
1736
child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1703
child_pb.update('Apply phase', 1 + offset, 2 + offset)
1737
1704
modified_paths = self._apply_insertions(mover)
1739
1706
mover.rollback()
1760
1725
for num, trans_id in enumerate(self._removed_id):
1761
1726
if (num % 10) == 0:
1762
child_pb.update(gettext('removing file'), num, total_entries)
1727
child_pb.update('removing file', num, total_entries)
1763
1728
if trans_id == self._new_root:
1764
1729
file_id = self._tree.get_root_id()
1777
1742
final_kinds = {}
1778
1743
for num, (path, trans_id) in enumerate(new_paths):
1779
1744
if (num % 10) == 0:
1780
child_pb.update(gettext('adding file'),
1745
child_pb.update('adding file',
1781
1746
num + len(self._removed_id), total_entries)
1782
1747
file_id = new_path_file_ids[trans_id]
1783
1748
if file_id is None:
1823
1788
tree_paths.sort(reverse=True)
1824
1789
child_pb = ui.ui_factory.nested_progress_bar()
1826
for num, (path, trans_id) in enumerate(tree_paths):
1827
# do not attempt to move root into a subdirectory of itself.
1830
child_pb.update(gettext('removing file'), num, len(tree_paths))
1791
for num, data in enumerate(tree_paths):
1792
path, trans_id = data
1793
child_pb.update('removing file', num, len(tree_paths))
1831
1794
full_path = self._tree.abspath(path)
1832
1795
if trans_id in self._removed_contents:
1833
1796
delete_path = os.path.join(self._deletiondir, trans_id)
1863
1826
for num, (path, trans_id) in enumerate(new_paths):
1864
1827
if (num % 10) == 0:
1865
child_pb.update(gettext('adding file'), num, len(new_paths))
1828
child_pb.update('adding file', num, len(new_paths))
1866
1829
full_path = self._tree.abspath(path)
1867
1830
if trans_id in self._needs_rename:
1888
1851
self._observed_sha1s[trans_id] = (o_sha1, st)
1890
1853
child_pb.finished()
1891
for path, trans_id in new_paths:
1892
# new_paths includes stuff like workingtree conflicts. Only the
1893
# stuff in new_contents actually comes from limbo.
1894
if trans_id in self._limbo_files:
1895
del self._limbo_files[trans_id]
1896
1854
self._new_contents.clear()
1897
1855
return modified_paths
2054
@deprecated_method(deprecated_in((2, 5, 0)))
2055
2012
def inventory(self):
2056
2013
"""This Tree does not use inventory as its backing data."""
2057
2014
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
2016
def get_root_id(self):
2065
2017
return self._transform.final_file_id(self._transform.root)
2182
2130
ordered_ids.append((trans_id, parent_file_id))
2183
2131
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
2133
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
2194
2134
# This may not be a maximally efficient implementation, but it is
2195
2135
# 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))
2285
2213
def get_file_sha1(self, file_id, path=None, stat_value=None):
2286
2214
trans_id = self._transform.trans_id_file_id(file_id)
2287
2215
kind = self._transform._new_contents.get(trans_id)
2558
2486
file_trans_id = {}
2559
2487
top_pb = ui.ui_factory.nested_progress_bar()
2560
2488
pp = ProgressPhase("Build phase", 2, top_pb)
2561
if tree.get_root_id() is not None:
2489
if tree.inventory.root is not None:
2562
2490
# This is kind of a hack: we should be altering the root
2563
2491
# as part of the regular tree shape diff logic.
2564
2492
# The conditional test here is to avoid doing an
2594
2522
for dir, files in wt.walkdirs():
2595
2523
existing_files.update(f[0] for f in files)
2596
2524
for num, (tree_path, entry) in \
2597
enumerate(tree.iter_entries_by_dir()):
2598
pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2525
enumerate(tree.inventory.iter_entries_by_dir()):
2526
pb.update("Building tree", num - len(deferred_contents), total)
2599
2527
if entry.parent_id is None:
2601
2529
reparent = False
2685
2613
new_desired_files.append((file_id,
2686
2614
(trans_id, tree_path, text_sha1)))
2688
pb.update(gettext('Adding file contents'), count + offset, total)
2616
pb.update('Adding file contents', count + offset, total)
2690
2618
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2712
2640
contents = filtered_output_bytes(contents, filters,
2713
2641
ContentFilterContext(tree_path, tree))
2714
2642
tt.create_file(contents, trans_id, sha1=text_sha1)
2715
pb.update(gettext('Adding file contents'), count + offset, total)
2643
pb.update('Adding file contents', count + offset, total)
2718
2646
def _reparent_children(tt, old_parent, new_parent):
2832
2760
tt.set_executability(entry.executable, trans_id)
2763
@deprecated_function(deprecated_in((2, 3, 0)))
2764
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2765
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2768
@deprecated_function(deprecated_in((2, 3, 0)))
2769
def _get_backup_name(name, by_parent, parent_trans_id, tt):
2770
"""Produce a backup-style name that appears to be available"""
2774
yield "%s.~%d~" % (name, counter)
2776
for new_name in name_gen():
2777
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
2781
def _entry_changes(file_id, entry, working_tree):
2782
"""Determine in which ways the inventory entry has changed.
2784
Returns booleans: has_contents, content_mod, meta_mod
2785
has_contents means there are currently contents, but they differ
2786
contents_mod means contents need to be modified
2787
meta_mod means the metadata needs to be modified
2789
cur_entry = working_tree.inventory[file_id]
2791
working_kind = working_tree.kind(file_id)
2794
has_contents = False
2797
if has_contents is True:
2798
if entry.kind != working_kind:
2799
contents_mod, meta_mod = True, False
2801
cur_entry._read_tree_state(working_tree.id2path(file_id),
2803
contents_mod, meta_mod = entry.detect_changes(cur_entry)
2804
cur_entry._forget_tree_state()
2805
return has_contents, contents_mod, meta_mod
2835
2808
def revert(working_tree, target_tree, filenames, backups=False,
2836
2809
pb=None, change_reporter=None):
2837
2810
"""Revert a working tree's contents to those of a target tree."""
2955
2928
basis_tree = working_tree.basis_tree()
2956
2929
basis_tree.lock_read()
2957
2930
new_sha1 = target_tree.get_file_sha1(file_id)
2958
if (basis_tree.has_id(file_id) and
2959
new_sha1 == basis_tree.get_file_sha1(file_id)):
2931
if (file_id in basis_tree and new_sha1 ==
2932
basis_tree.get_file_sha1(file_id)):
2960
2933
if file_id in merge_modified:
2961
2934
del merge_modified[file_id]
3044
3017
existing_file, new_file = conflict[2], conflict[1]
3046
3019
existing_file, new_file = conflict[1], conflict[2]
3047
new_name = tt.final_name(existing_file) + '.moved'
3020
new_name = tt.final_name(existing_file)+'.moved'
3048
3021
tt.adjust_path(new_name, final_parent, existing_file)
3049
3022
new_conflicts.add((c_type, 'Moved existing file to',
3050
3023
existing_file, new_file))
3113
3086
elif c_type == 'unversioned parent':
3114
3087
file_id = tt.inactive_file_id(conflict[1])
3115
3088
# special-case the other tree root (move its children instead)
3116
if path_tree and path_tree.has_id(file_id):
3089
if path_tree and file_id in path_tree:
3117
3090
if path_tree.path2id('') == file_id:
3118
3091
# This is the root entry, skip it
3138
3111
def cook_conflicts(raw_conflicts, tt):
3139
3112
"""Generate a list of cooked conflicts, sorted by file path"""
3113
from bzrlib.conflicts import Conflict
3140
3114
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
3141
return sorted(conflict_iter, key=conflicts.Conflict.sort_key)
3115
return sorted(conflict_iter, key=Conflict.sort_key)
3144
3118
def iter_cook_conflicts(raw_conflicts, tt):
3119
from bzrlib.conflicts import Conflict
3145
3120
fp = FinalPaths(tt)
3146
3121
for conflict in raw_conflicts:
3147
3122
c_type = conflict[0]
3149
3124
modified_path = fp.get_path(conflict[2])
3150
3125
modified_id = tt.final_file_id(conflict[2])
3151
3126
if len(conflict) == 3:
3152
yield conflicts.Conflict.factory(
3153
c_type, action=action, path=modified_path, file_id=modified_id)
3127
yield Conflict.factory(c_type, action=action, path=modified_path,
3128
file_id=modified_id)
3156
3131
conflicting_path = fp.get_path(conflict[3])
3157
3132
conflicting_id = tt.final_file_id(conflict[3])
3158
yield conflicts.Conflict.factory(
3159
c_type, action=action, path=modified_path,
3160
file_id=modified_id,
3161
conflict_path=conflicting_path,
3162
conflict_file_id=conflicting_id)
3133
yield Conflict.factory(c_type, action=action, path=modified_path,
3134
file_id=modified_id,
3135
conflict_path=conflicting_path,
3136
conflict_file_id=conflicting_id)
3165
3139
class _FileMover(object):