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
46
from bzrlib.i18n import gettext
45
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
48
from bzrlib.errors import (DuplicateKey, MalformedTransform,
46
49
ReusingTransform, CantMoveRoot,
47
50
ExistingLimbo, ImmortalLimbo, NoFinalPath,
48
51
UnableCreateSymlink)
137
140
# A counter of how many files have been renamed
138
141
self.rename_count = 0
144
"""Support Context Manager API."""
147
def __exit__(self, exc_type, exc_val, exc_tb):
148
"""Support Context Manager API."""
140
151
def finalize(self):
141
152
"""Release the working tree lock, if held.
228
240
self._new_root = new_roots[0]
230
242
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
236
243
# unversion the new root's directory.
237
file_id = self.final_file_id(old_new_root)
244
if self.final_kind(self._new_root) is None:
245
file_id = self.final_file_id(old_new_root)
247
file_id = self.final_file_id(self._new_root)
238
248
if old_new_root in self._new_id:
239
249
self.cancel_versioning(old_new_root)
244
254
if (self.tree_file_id(self._new_root) is not None and
245
255
self._new_root not in self._removed_id):
246
256
self.unversion_file(self._new_root)
247
self.version_file(file_id, self._new_root)
257
if file_id is not None:
258
self.version_file(file_id, self._new_root)
249
260
# Now move children of new root into old root directory.
250
261
# Ensure all children are registered with the transaction, but don't
384
395
return sorted(FinalPaths(self).get_paths(new_ids))
386
397
def _inventory_altered(self):
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,
398
"""Determine which trans_ids need new Inventory entries.
400
An new entry is needed when anything that would be reflected by an
401
inventory entry changes, including file name, file_id, parent file_id,
402
file kind, and the execute bit.
404
Some care is taken to return entries with real changes, not cases
405
where the value is deleted and then restored to its original value,
406
but some actually unchanged values may be returned.
408
:returns: A list of (path, trans_id) for all items requiring an
409
inventory change. Ordered by path.
412
# Find entries whose file_ids are new (or changed).
413
new_file_id = set(t for t in self._new_id
414
if self._new_id[t] != self.tree_file_id(t))
415
for id_set in [self._new_name, self._new_parent, new_file_id,
390
416
self._new_executability]:
391
new_ids.update(id_set)
417
changed_ids.update(id_set)
418
# removing implies a kind change
392
419
changed_kind = set(self._removed_contents)
393
421
changed_kind.intersection_update(self._new_contents)
394
changed_kind.difference_update(new_ids)
422
# Ignore entries that are already known to have changed.
423
changed_kind.difference_update(changed_ids)
424
# to keep only the truly changed ones
395
425
changed_kind = (t for t in changed_kind
396
426
if self.tree_kind(t) != self.final_kind(t))
397
new_ids.update(changed_kind)
398
return sorted(FinalPaths(self).get_paths(new_ids))
427
# all kind changes will alter the inventory
428
changed_ids.update(changed_kind)
429
# To find entries with changed parent_ids, find parents which existed,
430
# but changed file_id.
431
changed_file_id = set(t for t in new_file_id if t in self._removed_id)
432
# Now add all their children to the set.
433
for parent_trans_id in new_file_id:
434
changed_ids.update(self.iter_tree_children(parent_trans_id))
435
return sorted(FinalPaths(self).get_paths(changed_ids))
400
437
def final_kind(self, trans_id):
401
438
"""Determine the final file kind, after any changes applied.
729
764
def _set_executability(self, path, trans_id):
730
765
"""Set the executability of versioned files """
731
if supports_executable():
766
if self._tree._supports_executable():
732
767
new_executability = self._new_executability[trans_id]
733
768
abspath = self._tree.abspath(path)
734
769
current_mode = os.stat(abspath).st_mode
743
778
to_mode |= 0010 & ~umask
745
780
to_mode = current_mode & ~0111
746
os.chmod(abspath, to_mode)
781
osutils.chmod_if_possible(abspath, to_mode)
748
783
def _new_entry(self, name, parent_id, file_id):
749
784
"""Helper function to create a new filesystem entry."""
1196
1234
TreeTransformBase.finalize(self)
1236
def _limbo_supports_executable(self):
1237
"""Check if the limbo path supports the executable bit."""
1238
# FIXME: Check actual file system capabilities of limbodir
1239
return osutils.supports_executable()
1198
1241
def _limbo_name(self, trans_id):
1199
1242
"""Generate the limbo name of a file"""
1200
1243
limbo_name = self._limbo_files.get(trans_id)
1236
1279
entries from _limbo_files, because they are now stale.
1238
1281
for trans_id in trans_ids:
1239
old_path = self._limbo_files.pop(trans_id)
1282
old_path = self._limbo_files[trans_id]
1283
self._possibly_stale_limbo_files.add(old_path)
1284
del self._limbo_files[trans_id]
1240
1285
if trans_id not in self._new_contents:
1242
1287
new_path = self._limbo_name(trans_id)
1243
1288
os.rename(old_path, new_path)
1289
self._possibly_stale_limbo_files.remove(old_path)
1244
1290
for descendant in self._limbo_descendants(trans_id):
1245
1291
desc_path = self._limbo_files[descendant]
1246
1292
desc_path = new_path + desc_path[len(old_path):]
1517
1563
limbodir = urlutils.local_path_from_url(
1518
1564
tree._transport.abspath('limbo'))
1522
if e.errno == errno.EEXIST:
1523
raise ExistingLimbo(limbodir)
1565
osutils.ensure_empty_directory_exists(
1567
errors.ExistingLimbo)
1524
1568
deletiondir = urlutils.local_path_from_url(
1525
1569
tree._transport.abspath('pending-deletion'))
1527
os.mkdir(deletiondir)
1529
if e.errno == errno.EEXIST:
1530
raise errors.ExistingPendingDeletion(deletiondir)
1570
osutils.ensure_empty_directory_exists(
1572
errors.ExistingPendingDeletion)
1685
1727
child_pb = ui.ui_factory.nested_progress_bar()
1687
1729
if precomputed_delta is None:
1688
child_pb.update('Apply phase', 0, 2)
1730
child_pb.update(gettext('Apply phase'), 0, 2)
1689
1731
inventory_delta = self._generate_inventory_delta()
1699
child_pb.update('Apply phase', 0 + offset, 2 + offset)
1741
child_pb.update(gettext('Apply phase'), 0 + offset, 2 + offset)
1700
1742
self._apply_removals(mover)
1701
child_pb.update('Apply phase', 1 + offset, 2 + offset)
1743
child_pb.update(gettext('Apply phase'), 1 + offset, 2 + offset)
1702
1744
modified_paths = self._apply_insertions(mover)
1704
1746
mover.rollback()
1723
1767
for num, trans_id in enumerate(self._removed_id):
1724
1768
if (num % 10) == 0:
1725
child_pb.update('removing file', num, total_entries)
1769
child_pb.update(gettext('removing file'), num, total_entries)
1726
1770
if trans_id == self._new_root:
1727
1771
file_id = self._tree.get_root_id()
1740
1784
final_kinds = {}
1741
1785
for num, (path, trans_id) in enumerate(new_paths):
1742
1786
if (num % 10) == 0:
1743
child_pb.update('adding file',
1787
child_pb.update(gettext('adding file'),
1744
1788
num + len(self._removed_id), total_entries)
1745
1789
file_id = new_path_file_ids[trans_id]
1746
1790
if file_id is None:
1786
1830
tree_paths.sort(reverse=True)
1787
1831
child_pb = ui.ui_factory.nested_progress_bar()
1789
for num, data in enumerate(tree_paths):
1790
path, trans_id = data
1791
child_pb.update('removing file', num, len(tree_paths))
1833
for num, (path, trans_id) in enumerate(tree_paths):
1834
# do not attempt to move root into a subdirectory of itself.
1837
child_pb.update(gettext('removing file'), num, len(tree_paths))
1792
1838
full_path = self._tree.abspath(path)
1793
1839
if trans_id in self._removed_contents:
1794
1840
delete_path = os.path.join(self._deletiondir, trans_id)
1824
1870
for num, (path, trans_id) in enumerate(new_paths):
1825
1871
if (num % 10) == 0:
1826
child_pb.update('adding file', num, len(new_paths))
1872
child_pb.update(gettext('adding file'), num, len(new_paths))
1827
1873
full_path = self._tree.abspath(path)
1828
1874
if trans_id in self._needs_rename:
2262
def get_file_verifier(self, file_id, path=None, stat_value=None):
2263
trans_id = self._transform.trans_id_file_id(file_id)
2264
kind = self._transform._new_contents.get(trans_id)
2266
return self._transform._tree.get_file_verifier(file_id)
2268
fileobj = self.get_file(file_id)
2270
return ("SHA1", sha_file(fileobj))
2216
2274
def get_file_sha1(self, file_id, path=None, stat_value=None):
2217
2275
trans_id = self._transform.trans_id_file_id(file_id)
2218
2276
kind = self._transform._new_contents.get(trans_id)
2489
2547
file_trans_id = {}
2490
2548
top_pb = ui.ui_factory.nested_progress_bar()
2491
2549
pp = ProgressPhase("Build phase", 2, top_pb)
2492
if tree.inventory.root is not None:
2550
if tree.get_root_id() is not None:
2493
2551
# This is kind of a hack: we should be altering the root
2494
2552
# as part of the regular tree shape diff logic.
2495
2553
# The conditional test here is to avoid doing an
2525
2583
for dir, files in wt.walkdirs():
2526
2584
existing_files.update(f[0] for f in files)
2527
2585
for num, (tree_path, entry) in \
2528
enumerate(tree.inventory.iter_entries_by_dir()):
2529
pb.update("Building tree", num - len(deferred_contents), total)
2586
enumerate(tree.iter_entries_by_dir()):
2587
pb.update(gettext("Building tree"), num - len(deferred_contents), total)
2530
2588
if entry.parent_id is None:
2532
2590
reparent = False
2616
2674
new_desired_files.append((file_id,
2617
2675
(trans_id, tree_path, text_sha1)))
2619
pb.update('Adding file contents', count + offset, total)
2677
pb.update(gettext('Adding file contents'), count + offset, total)
2621
2679
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2643
2701
contents = filtered_output_bytes(contents, filters,
2644
2702
ContentFilterContext(tree_path, tree))
2645
2703
tt.create_file(contents, trans_id, sha1=text_sha1)
2646
pb.update('Adding file contents', count + offset, total)
2704
pb.update(gettext('Adding file contents'), count + offset, total)
2649
2707
def _reparent_children(tt, old_parent, new_parent):
2781
2839
return new_name
2784
def _entry_changes(file_id, entry, working_tree):
2785
"""Determine in which ways the inventory entry has changed.
2787
Returns booleans: has_contents, content_mod, meta_mod
2788
has_contents means there are currently contents, but they differ
2789
contents_mod means contents need to be modified
2790
meta_mod means the metadata needs to be modified
2792
cur_entry = working_tree.inventory[file_id]
2794
working_kind = working_tree.kind(file_id)
2797
has_contents = False
2800
if has_contents is True:
2801
if entry.kind != working_kind:
2802
contents_mod, meta_mod = True, False
2804
cur_entry._read_tree_state(working_tree.id2path(file_id),
2806
contents_mod, meta_mod = entry.detect_changes(cur_entry)
2807
cur_entry._forget_tree_state()
2808
return has_contents, contents_mod, meta_mod
2811
2842
def revert(working_tree, target_tree, filenames, backups=False,
2812
2843
pb=None, change_reporter=None):
2813
2844
"""Revert a working tree's contents to those of a target tree."""
2931
2962
basis_tree = working_tree.basis_tree()
2932
2963
basis_tree.lock_read()
2933
2964
new_sha1 = target_tree.get_file_sha1(file_id)
2934
if (file_id in basis_tree and new_sha1 ==
2935
basis_tree.get_file_sha1(file_id)):
2965
if (basis_tree.has_id(file_id) and
2966
new_sha1 == basis_tree.get_file_sha1(file_id)):
2936
2967
if file_id in merge_modified:
2937
2968
del merge_modified[file_id]
3020
3051
existing_file, new_file = conflict[2], conflict[1]
3022
3053
existing_file, new_file = conflict[1], conflict[2]
3023
new_name = tt.final_name(existing_file)+'.moved'
3054
new_name = tt.final_name(existing_file) + '.moved'
3024
3055
tt.adjust_path(new_name, final_parent, existing_file)
3025
3056
new_conflicts.add((c_type, 'Moved existing file to',
3026
3057
existing_file, new_file))
3089
3120
elif c_type == 'unversioned parent':
3090
3121
file_id = tt.inactive_file_id(conflict[1])
3091
3122
# special-case the other tree root (move its children instead)
3092
if path_tree and file_id in path_tree:
3123
if path_tree and path_tree.has_id(file_id):
3093
3124
if path_tree.path2id('') == file_id:
3094
3125
# This is the root entry, skip it
3114
3145
def cook_conflicts(raw_conflicts, tt):
3115
3146
"""Generate a list of cooked conflicts, sorted by file path"""
3116
from bzrlib.conflicts import Conflict
3117
3147
conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
3118
return sorted(conflict_iter, key=Conflict.sort_key)
3148
return sorted(conflict_iter, key=conflicts.Conflict.sort_key)
3121
3151
def iter_cook_conflicts(raw_conflicts, tt):
3122
from bzrlib.conflicts import Conflict
3123
3152
fp = FinalPaths(tt)
3124
3153
for conflict in raw_conflicts:
3125
3154
c_type = conflict[0]
3127
3156
modified_path = fp.get_path(conflict[2])
3128
3157
modified_id = tt.final_file_id(conflict[2])
3129
3158
if len(conflict) == 3:
3130
yield Conflict.factory(c_type, action=action, path=modified_path,
3131
file_id=modified_id)
3159
yield conflicts.Conflict.factory(
3160
c_type, action=action, path=modified_path, file_id=modified_id)
3134
3163
conflicting_path = fp.get_path(conflict[3])
3135
3164
conflicting_id = tt.final_file_id(conflict[3])
3136
yield Conflict.factory(c_type, action=action, path=modified_path,
3137
file_id=modified_id,
3138
conflict_path=conflicting_path,
3139
conflict_file_id=conflicting_id)
3165
yield conflicts.Conflict.factory(
3166
c_type, action=action, path=modified_path,
3167
file_id=modified_id,
3168
conflict_path=conflicting_path,
3169
conflict_file_id=conflicting_id)
3142
3172
class _FileMover(object):