264
221
self.version_file(old_root_file_id, old_root)
265
222
self.unversion_file(self._new_root)
224
def fixup_new_roots(self):
225
"""Reinterpret requests to change the root directory
227
Instead of creating a root directory, or moving an existing directory,
228
all the attributes and children of the new root are applied to the
229
existing root directory.
231
This means that the old root trans-id becomes obsolete, so it is
232
recommended only to invoke this after the root trans-id has become
236
new_roots = [k for k, v in self._new_parent.iteritems() if v ==
238
if len(new_roots) < 1:
240
if len(new_roots) != 1:
241
raise ValueError('A tree cannot have two roots!')
242
if self._new_root is None:
243
self._new_root = new_roots[0]
245
old_new_root = new_roots[0]
246
# 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)
251
if old_new_root in self._new_id:
252
self.cancel_versioning(old_new_root)
254
self.unversion_file(old_new_root)
255
# if, at this stage, root still has an old file_id, zap it so we can
256
# stick a new one in.
257
if (self.tree_file_id(self._new_root) is not None and
258
self._new_root not in self._removed_id):
259
self.unversion_file(self._new_root)
260
if file_id is not None:
261
self.version_file(file_id, self._new_root)
263
# Now move children of new root into old root directory.
264
# Ensure all children are registered with the transaction, but don't
265
# use directly-- some tree children have new parents
266
list(self.iter_tree_children(old_new_root))
267
# Move all children of new root into old root directory.
268
for child in self.by_parent().get(old_new_root, []):
269
self.adjust_path(self.final_name(child), self._new_root, child)
271
# Ensure old_new_root has no directory.
272
if old_new_root in self._new_contents:
273
self.cancel_creation(old_new_root)
275
self.delete_contents(old_new_root)
277
# prevent deletion of root directory.
278
if self._new_root in self._removed_contents:
279
self.cancel_deletion(self._new_root)
281
# destroy path info for old_new_root.
282
del self._new_parent[old_new_root]
283
del self._new_name[old_new_root]
267
285
def trans_id_tree_file_id(self, inventory_id):
268
286
"""Determine the transaction id of a working tree file.
332
331
return ROOT_PARENT
333
332
return self.trans_id_tree_path(os.path.dirname(path))
335
def create_file(self, contents, trans_id, mode_id=None):
336
"""Schedule creation of a new file.
340
Contents is an iterator of strings, all of which will be written
341
to the target destination.
343
New file takes the permissions of any existing file with that id,
344
unless mode_id is specified.
346
name = self._limbo_name(trans_id)
350
unique_add(self._new_contents, trans_id, 'file')
352
# Clean up the file, it never got registered so
353
# TreeTransform.finalize() won't clean it up.
358
f.writelines(contents)
361
self._set_mode(trans_id, mode_id, S_ISREG)
363
def _set_mode(self, trans_id, mode_id, typefunc):
364
"""Set the mode of new file contents.
365
The mode_id is the existing file to get the mode from (often the same
366
as trans_id). The operation is only performed if there's a mode match
367
according to typefunc.
372
old_path = self._tree_id_paths[mode_id]
376
mode = os.stat(self._tree.abspath(old_path)).st_mode
378
if e.errno in (errno.ENOENT, errno.ENOTDIR):
379
# Either old_path doesn't exist, or the parent of the
380
# target is not a directory (but will be one eventually)
381
# Either way, we know it doesn't exist *right now*
382
# See also bug #248448
387
os.chmod(self._limbo_name(trans_id), mode)
389
def create_hardlink(self, path, trans_id):
390
"""Schedule creation of a hard link"""
391
name = self._limbo_name(trans_id)
395
if e.errno != errno.EPERM:
397
raise errors.HardLinkNotSupported(path)
399
unique_add(self._new_contents, trans_id, 'file')
401
# Clean up the file, it never got registered so
402
# TreeTransform.finalize() won't clean it up.
406
def create_directory(self, trans_id):
407
"""Schedule creation of a new directory.
409
See also new_directory.
411
os.mkdir(self._limbo_name(trans_id))
412
unique_add(self._new_contents, trans_id, 'directory')
414
def create_symlink(self, target, trans_id):
415
"""Schedule creation of a new symbolic link.
417
target is a bytestring.
418
See also new_symlink.
421
os.symlink(target, self._limbo_name(trans_id))
422
unique_add(self._new_contents, trans_id, 'symlink')
425
path = FinalPaths(self).get_path(trans_id)
428
raise UnableCreateSymlink(path=path)
430
def cancel_creation(self, trans_id):
431
"""Cancel the creation of new file contents."""
432
del self._new_contents[trans_id]
433
children = self._limbo_children.get(trans_id)
434
# if this is a limbo directory with children, move them before removing
436
if children is not None:
437
self._rename_in_limbo(children)
438
del self._limbo_children[trans_id]
439
del self._limbo_children_names[trans_id]
440
delete_any(self._limbo_name(trans_id))
442
334
def delete_contents(self, trans_id):
443
335
"""Schedule the contents of a path entry for deletion"""
444
self.tree_kind(trans_id)
445
self._removed_contents.add(trans_id)
336
kind = self.tree_kind(trans_id)
338
self._removed_contents.add(trans_id)
447
340
def cancel_deletion(self, trans_id):
448
341
"""Cancel a scheduled deletion"""
505
398
return sorted(FinalPaths(self).get_paths(new_ids))
507
400
def _inventory_altered(self):
508
"""Get the trans_ids and paths of files needing new inv entries."""
510
for id_set in [self._new_name, self._new_parent, self._new_id,
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,
511
419
self._new_executability]:
512
new_ids.update(id_set)
420
changed_ids.update(id_set)
421
# removing implies a kind change
513
422
changed_kind = set(self._removed_contents)
514
424
changed_kind.intersection_update(self._new_contents)
515
changed_kind.difference_update(new_ids)
516
changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
518
new_ids.update(changed_kind)
519
return sorted(FinalPaths(self).get_paths(new_ids))
521
def tree_kind(self, trans_id):
522
"""Determine the file kind in the working tree.
524
Raises NoSuchFile if the file does not exist
526
path = self._tree_id_paths.get(trans_id)
528
raise NoSuchFile(None)
530
return file_kind(self._tree.abspath(path))
532
if e.errno != errno.ENOENT:
535
raise NoSuchFile(path)
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
428
changed_kind = (t for t in changed_kind
429
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))
537
440
def final_kind(self, trans_id):
538
441
"""Determine the final file kind, after any changes applied.
540
Raises NoSuchFile if the file does not exist/has no contents.
541
(It is conceivable that a path would be created without the
542
corresponding contents insertion command)
443
:return: None if the file does not exist/has no contents. (It is
444
conceivable that a path would be created without the corresponding
445
contents insertion command)
544
447
if trans_id in self._new_contents:
545
448
return self._new_contents[trans_id]
546
449
elif trans_id in self._removed_contents:
547
raise NoSuchFile(None)
549
452
return self.tree_kind(trans_id)
667
575
# ensure that all children are registered with the transaction
668
576
list(self.iter_tree_children(parent_id))
670
def iter_tree_children(self, parent_id):
671
"""Iterate through the entry's tree children, if any"""
673
path = self._tree_id_paths[parent_id]
677
children = os.listdir(self._tree.abspath(path))
679
if not (osutils._is_error_enotdir(e)
680
or e.errno in (errno.ENOENT, errno.ESRCH)):
684
for child in children:
685
childpath = joinpath(path, child)
686
if self._tree.is_control_filename(childpath):
688
yield self.trans_id_tree_path(childpath)
690
def has_named_child(self, by_parent, parent_id, name):
692
children = by_parent[parent_id]
695
for child in children:
578
def _has_named_child(self, name, parent_id, known_children):
579
"""Does a parent already have a name child.
581
:param name: The searched for name.
583
:param parent_id: The parent for which the check is made.
585
:param known_children: The already known children. This should have
586
been recently obtained from `self.by_parent.get(parent_id)`
587
(or will be if None is passed).
589
if known_children is None:
590
known_children = self.by_parent().get(parent_id, [])
591
for child in known_children:
696
592
if self.final_name(child) == name:
699
path = self._tree_id_paths[parent_id]
594
parent_path = self._tree_id_paths.get(parent_id, None)
595
if parent_path is None:
596
# No parent... no children
702
childpath = joinpath(path, name)
703
child_id = self._tree_path_ids.get(childpath)
598
child_path = joinpath(parent_path, name)
599
child_id = self._tree_path_ids.get(child_path, None)
704
600
if child_id is None:
705
return lexists(self._tree.abspath(childpath))
601
# Not known by the tree transform yet, check the filesystem
602
return osutils.lexists(self._tree.abspath(child_path))
707
if self.final_parent(child_id) != parent_id:
709
if child_id in self._removed_contents:
710
# XXX What about dangling file-ids?
604
raise AssertionError('child_id is missing: %s, %s, %s'
605
% (name, parent_id, child_id))
607
def _available_backup_name(self, name, target_id):
608
"""Find an available backup name.
610
:param name: The basename of the file.
612
:param target_id: The directory trans_id where the backup should
615
known_children = self.by_parent().get(target_id, [])
616
return osutils.available_backup_name(
618
lambda base: self._has_named_child(
619
base, target_id, known_children))
715
621
def _parent_loops(self):
716
622
"""No entry should be its own ancestor"""
837
738
def _parent_type_conflicts(self, by_parent):
838
"""parents must have directory 'contents'."""
739
"""Children must have a directory parent"""
840
741
for parent_id, children in by_parent.iteritems():
841
if parent_id is ROOT_PARENT:
843
if not self._any_contents(children):
845
for child in children:
847
self.final_kind(child)
851
kind = self.final_kind(parent_id)
742
if parent_id == ROOT_PARENT:
745
for child_id in children:
746
if self.final_kind(child_id) is not None:
751
# There is at least a child, so we need an existing directory to
753
kind = self.final_kind(parent_id)
755
# The directory will be deleted
855
756
conflicts.append(('missing parent', parent_id))
856
757
elif kind != "directory":
758
# Meh, we need a *directory* to put something in it
857
759
conflicts.append(('non-directory parent', parent_id))
860
def _any_contents(self, trans_ids):
861
"""Return true if any of the trans_ids, will have contents."""
862
for trans_id in trans_ids:
864
kind = self.final_kind(trans_id)
870
def _limbo_name(self, trans_id):
871
"""Generate the limbo name of a file"""
872
limbo_name = self._limbo_files.get(trans_id)
873
if limbo_name is not None:
875
parent = self._new_parent.get(trans_id)
876
# if the parent directory is already in limbo (e.g. when building a
877
# tree), choose a limbo name inside the parent, to reduce further
879
use_direct_path = False
880
if self._new_contents.get(parent) == 'directory':
881
filename = self._new_name.get(trans_id)
882
if filename is not None:
883
if parent not in self._limbo_children:
884
self._limbo_children[parent] = set()
885
self._limbo_children_names[parent] = {}
886
use_direct_path = True
887
# the direct path can only be used if no other file has
888
# already taken this pathname, i.e. if the name is unused, or
889
# if it is already associated with this trans_id.
890
elif self._case_sensitive_target:
891
if (self._limbo_children_names[parent].get(filename)
892
in (trans_id, None)):
893
use_direct_path = True
895
for l_filename, l_trans_id in\
896
self._limbo_children_names[parent].iteritems():
897
if l_trans_id == trans_id:
899
if l_filename.lower() == filename.lower():
902
use_direct_path = True
905
limbo_name = pathjoin(self._limbo_files[parent], filename)
906
self._limbo_children[parent].add(trans_id)
907
self._limbo_children_names[parent][filename] = trans_id
909
limbo_name = pathjoin(self._limbodir, trans_id)
910
self._needs_rename.add(trans_id)
911
self._limbo_files[trans_id] = limbo_name
914
762
def _set_executability(self, path, trans_id):
915
763
"""Set the executability of versioned files """
916
if supports_executable():
764
if self._tree._supports_executable():
917
765
new_executability = self._new_executability[trans_id]
918
766
abspath = self._tree.abspath(path)
919
767
current_mode = os.stat(abspath).st_mode
1122
1009
def get_preview_tree(self):
1123
1010
"""Return a tree representing the result of the transform.
1125
This tree only supports the subset of Tree functionality required
1126
by show_diff_trees. It must only be compared to tt._tree.
1012
The tree is a snapshot, and altering the TreeTransform will invalidate
1128
1015
return _PreviewTree(self)
1017
def commit(self, branch, message, merge_parents=None, strict=False,
1018
timestamp=None, timezone=None, committer=None, authors=None,
1019
revprops=None, revision_id=None):
1020
"""Commit the result of this TreeTransform to a branch.
1022
:param branch: The branch to commit to.
1023
:param message: The message to attach to the commit.
1024
:param merge_parents: Additional parent revision-ids specified by
1026
:param strict: If True, abort the commit if there are unversioned
1028
:param timestamp: if not None, seconds-since-epoch for the time and
1029
date. (May be a float.)
1030
:param timezone: Optional timezone for timestamp, as an offset in
1032
:param committer: Optional committer in email-id format.
1033
(e.g. "J Random Hacker <jrandom@example.com>")
1034
:param authors: Optional list of authors in email-id format.
1035
:param revprops: Optional dictionary of revision properties.
1036
:param revision_id: Optional revision id. (Specifying a revision-id
1037
may reduce performance for some non-native formats.)
1038
:return: The revision_id of the revision committed.
1040
self._check_malformed()
1042
unversioned = set(self._new_contents).difference(set(self._new_id))
1043
for trans_id in unversioned:
1044
if self.final_file_id(trans_id) is None:
1045
raise errors.StrictCommitFailed()
1047
revno, last_rev_id = branch.last_revision_info()
1048
if last_rev_id == _mod_revision.NULL_REVISION:
1049
if merge_parents is not None:
1050
raise ValueError('Cannot supply merge parents for first'
1054
parent_ids = [last_rev_id]
1055
if merge_parents is not None:
1056
parent_ids.extend(merge_parents)
1057
if self._tree.get_revision_id() != last_rev_id:
1058
raise ValueError('TreeTransform not based on branch basis: %s' %
1059
self._tree.get_revision_id())
1060
revprops = commit.Commit.update_revprops(revprops, branch, authors)
1061
builder = branch.get_commit_builder(parent_ids,
1062
timestamp=timestamp,
1064
committer=committer,
1066
revision_id=revision_id)
1067
preview = self.get_preview_tree()
1068
list(builder.record_iter_changes(preview, last_rev_id,
1069
self.iter_changes()))
1070
builder.finish_inventory()
1071
revision_id = builder.commit(message)
1072
branch.set_last_revision_info(revno + 1, revision_id)
1130
1075
def _text_parent(self, trans_id):
1131
1076
file_id = self.tree_file_id(trans_id)
1227
1168
self.create_symlink(content.decode('utf-8'), trans_id)
1230
class TreeTransform(TreeTransformBase):
1171
class DiskTreeTransform(TreeTransformBase):
1172
"""Tree transform storing its contents on disk."""
1174
def __init__(self, tree, limbodir, pb=None,
1175
case_sensitive=True):
1177
:param tree: The tree that will be transformed, but not necessarily
1179
:param limbodir: A directory where new files can be stored until
1180
they are installed in their proper places
1182
:param case_sensitive: If True, the target of the transform is
1183
case sensitive, not just case preserving.
1185
TreeTransformBase.__init__(self, tree, pb, case_sensitive)
1186
self._limbodir = limbodir
1187
self._deletiondir = None
1188
# A mapping of transform ids to their limbo filename
1189
self._limbo_files = {}
1190
self._possibly_stale_limbo_files = set()
1191
# A mapping of transform ids to a set of the transform ids of children
1192
# that their limbo directory has
1193
self._limbo_children = {}
1194
# Map transform ids to maps of child filename to child transform id
1195
self._limbo_children_names = {}
1196
# List of transform ids that need to be renamed from limbo into place
1197
self._needs_rename = set()
1198
self._creation_mtime = None
1201
"""Release the working tree lock, if held, clean up limbo dir.
1203
This is required if apply has not been invoked, but can be invoked
1206
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
1222
delete_any(self._limbodir)
1224
# We don't especially care *why* the dir is immortal.
1225
raise ImmortalLimbo(self._limbodir)
1227
if self._deletiondir is not None:
1228
delete_any(self._deletiondir)
1230
raise errors.ImmortalPendingDeletion(self._deletiondir)
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()
1239
def _limbo_name(self, trans_id):
1240
"""Generate the limbo name of a file"""
1241
limbo_name = self._limbo_files.get(trans_id)
1242
if limbo_name is None:
1243
limbo_name = self._generate_limbo_path(trans_id)
1244
self._limbo_files[trans_id] = limbo_name
1247
def _generate_limbo_path(self, trans_id):
1248
"""Generate a limbo path using the trans_id as the relative path.
1250
This is suitable as a fallback, and when the transform should not be
1251
sensitive to the path encoding of the limbo directory.
1253
self._needs_rename.add(trans_id)
1254
return pathjoin(self._limbodir, trans_id)
1256
def adjust_path(self, name, parent, trans_id):
1257
previous_parent = self._new_parent.get(trans_id)
1258
previous_name = self._new_name.get(trans_id)
1259
TreeTransformBase.adjust_path(self, name, parent, trans_id)
1260
if (trans_id in self._limbo_files and
1261
trans_id not in self._needs_rename):
1262
self._rename_in_limbo([trans_id])
1263
if previous_parent != parent:
1264
self._limbo_children[previous_parent].remove(trans_id)
1265
if previous_parent != parent or previous_name != name:
1266
del self._limbo_children_names[previous_parent][previous_name]
1268
def _rename_in_limbo(self, trans_ids):
1269
"""Fix limbo names so that the right final path is produced.
1271
This means we outsmarted ourselves-- we tried to avoid renaming
1272
these files later by creating them with their final names in their
1273
final parents. But now the previous name or parent is no longer
1274
suitable, so we have to rename them.
1276
Even for trans_ids that have no new contents, we must remove their
1277
entries from _limbo_files, because they are now stale.
1279
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]
1283
if trans_id not in self._new_contents:
1285
new_path = self._limbo_name(trans_id)
1286
os.rename(old_path, new_path)
1287
self._possibly_stale_limbo_files.remove(old_path)
1288
for descendant in self._limbo_descendants(trans_id):
1289
desc_path = self._limbo_files[descendant]
1290
desc_path = new_path + desc_path[len(old_path):]
1291
self._limbo_files[descendant] = desc_path
1293
def _limbo_descendants(self, trans_id):
1294
"""Return the set of trans_ids whose limbo paths descend from this."""
1295
descendants = set(self._limbo_children.get(trans_id, []))
1296
for descendant in list(descendants):
1297
descendants.update(self._limbo_descendants(descendant))
1300
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1301
"""Schedule creation of a new file.
1305
:param contents: an iterator of strings, all of which will be written
1306
to the target destination.
1307
:param trans_id: TreeTransform handle
1308
:param mode_id: If not None, force the mode of the target file to match
1309
the mode of the object referenced by mode_id.
1310
Otherwise, we will try to preserve mode bits of an existing file.
1311
:param sha1: If the sha1 of this content is already known, pass it in.
1312
We can use it to prevent future sha1 computations.
1314
name = self._limbo_name(trans_id)
1315
f = open(name, 'wb')
1317
unique_add(self._new_contents, trans_id, 'file')
1318
f.writelines(contents)
1321
self._set_mtime(name)
1322
self._set_mode(trans_id, mode_id, S_ISREG)
1323
# It is unfortunate we have to use lstat instead of fstat, but we just
1324
# used utime and chmod on the file, so we need the accurate final
1326
if sha1 is not None:
1327
self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
1329
def _read_file_chunks(self, trans_id):
1330
cur_file = open(self._limbo_name(trans_id), 'rb')
1332
return cur_file.readlines()
1336
def _read_symlink_target(self, trans_id):
1337
return os.readlink(self._limbo_name(trans_id))
1339
def _set_mtime(self, path):
1340
"""All files that are created get the same mtime.
1342
This time is set by the first object to be created.
1344
if self._creation_mtime is None:
1345
self._creation_mtime = time.time()
1346
os.utime(path, (self._creation_mtime, self._creation_mtime))
1348
def create_hardlink(self, path, trans_id):
1349
"""Schedule creation of a hard link"""
1350
name = self._limbo_name(trans_id)
1354
if e.errno != errno.EPERM:
1356
raise errors.HardLinkNotSupported(path)
1358
unique_add(self._new_contents, trans_id, 'file')
1360
# Clean up the file, it never got registered so
1361
# TreeTransform.finalize() won't clean it up.
1365
def create_directory(self, trans_id):
1366
"""Schedule creation of a new directory.
1368
See also new_directory.
1370
os.mkdir(self._limbo_name(trans_id))
1371
unique_add(self._new_contents, trans_id, 'directory')
1373
def create_symlink(self, target, trans_id):
1374
"""Schedule creation of a new symbolic link.
1376
target is a bytestring.
1377
See also new_symlink.
1380
os.symlink(target, self._limbo_name(trans_id))
1381
unique_add(self._new_contents, trans_id, 'symlink')
1384
path = FinalPaths(self).get_path(trans_id)
1387
raise UnableCreateSymlink(path=path)
1389
def cancel_creation(self, trans_id):
1390
"""Cancel the creation of new file contents."""
1391
del self._new_contents[trans_id]
1392
if trans_id in self._observed_sha1s:
1393
del self._observed_sha1s[trans_id]
1394
children = self._limbo_children.get(trans_id)
1395
# if this is a limbo directory with children, move them before removing
1397
if children is not None:
1398
self._rename_in_limbo(children)
1399
del self._limbo_children[trans_id]
1400
del self._limbo_children_names[trans_id]
1401
delete_any(self._limbo_name(trans_id))
1403
def new_orphan(self, trans_id, parent_id):
1404
conf = self._tree.get_config_stack()
1405
handle_orphan = conf.get('bzr.transform.orphan_policy')
1406
handle_orphan(self, trans_id, parent_id)
1409
class OrphaningError(errors.BzrError):
1411
# Only bugs could lead to such exception being seen by the user
1412
internal_error = True
1413
_fmt = "Error while orphaning %s in %s directory"
1415
def __init__(self, orphan, parent):
1416
errors.BzrError.__init__(self)
1417
self.orphan = orphan
1418
self.parent = parent
1421
class OrphaningForbidden(OrphaningError):
1423
_fmt = "Policy: %s doesn't allow creating orphans."
1425
def __init__(self, policy):
1426
errors.BzrError.__init__(self)
1427
self.policy = policy
1430
def move_orphan(tt, orphan_id, parent_id):
1431
"""See TreeTransformBase.new_orphan.
1433
This creates a new orphan in the `bzr-orphans` dir at the root of the
1436
:param tt: The TreeTransform orphaning `trans_id`.
1438
:param orphan_id: The trans id that should be orphaned.
1440
:param parent_id: The orphan parent trans id.
1442
# Add the orphan dir if it doesn't exist
1443
orphan_dir_basename = 'bzr-orphans'
1444
od_id = tt.trans_id_tree_path(orphan_dir_basename)
1445
if tt.final_kind(od_id) is None:
1446
tt.create_directory(od_id)
1447
parent_path = tt._tree_id_paths[parent_id]
1448
# Find a name that doesn't exist yet in the orphan dir
1449
actual_name = tt.final_name(orphan_id)
1450
new_name = tt._available_backup_name(actual_name, od_id)
1451
tt.adjust_path(new_name, od_id, orphan_id)
1452
trace.warning('%s has been orphaned in %s'
1453
% (joinpath(parent_path, actual_name), orphan_dir_basename))
1456
def refuse_orphan(tt, orphan_id, parent_id):
1457
"""See TreeTransformBase.new_orphan.
1459
This refuses to create orphan, letting the caller handle the conflict.
1461
raise OrphaningForbidden('never')
1464
orphaning_registry = registry.Registry()
1465
orphaning_registry.register(
1466
'conflict', refuse_orphan,
1467
'Leave orphans in place and create a conflict on the directory.')
1468
orphaning_registry.register(
1469
'move', move_orphan,
1470
'Move orphans into the bzr-orphans directory.')
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.',
1480
class TreeTransform(DiskTreeTransform):
1231
1481
"""Represent a tree transformation.
1233
1483
This object is designed to support incremental generation of the transform,
1304
1554
limbodir = urlutils.local_path_from_url(
1305
1555
tree._transport.abspath('limbo'))
1309
if e.errno == errno.EEXIST:
1310
raise ExistingLimbo(limbodir)
1556
osutils.ensure_empty_directory_exists(
1558
errors.ExistingLimbo)
1311
1559
deletiondir = urlutils.local_path_from_url(
1312
1560
tree._transport.abspath('pending-deletion'))
1314
os.mkdir(deletiondir)
1316
if e.errno == errno.EEXIST:
1317
raise errors.ExistingPendingDeletion(deletiondir)
1561
osutils.ensure_empty_directory_exists(
1563
errors.ExistingPendingDeletion)
1322
TreeTransformBase.__init__(self, tree, limbodir, pb,
1568
# Cache of realpath results, to speed up canonical_path
1569
self._realpaths = {}
1570
# Cache of relpath results, to speed up canonical_path
1572
DiskTreeTransform.__init__(self, tree, limbodir, pb,
1323
1573
tree.case_sensitive)
1324
1574
self._deletiondir = deletiondir
1576
def canonical_path(self, path):
1577
"""Get the canonical tree-relative path"""
1578
# don't follow final symlinks
1579
abs = self._tree.abspath(path)
1580
if abs in self._relpaths:
1581
return self._relpaths[abs]
1582
dirname, basename = os.path.split(abs)
1583
if dirname not in self._realpaths:
1584
self._realpaths[dirname] = os.path.realpath(dirname)
1585
dirname = self._realpaths[dirname]
1586
abs = pathjoin(dirname, basename)
1587
if dirname in self._relpaths:
1588
relpath = pathjoin(self._relpaths[dirname], basename)
1589
relpath = relpath.rstrip('/\\')
1591
relpath = self._tree.relpath(abs)
1592
self._relpaths[abs] = relpath
1595
def tree_kind(self, trans_id):
1596
"""Determine the file kind in the working tree.
1598
:returns: The file kind or None if the file does not exist
1600
path = self._tree_id_paths.get(trans_id)
1604
return file_kind(self._tree.abspath(path))
1605
except errors.NoSuchFile:
1608
def _set_mode(self, trans_id, mode_id, typefunc):
1609
"""Set the mode of new file contents.
1610
The mode_id is the existing file to get the mode from (often the same
1611
as trans_id). The operation is only performed if there's a mode match
1612
according to typefunc.
1617
old_path = self._tree_id_paths[mode_id]
1621
mode = os.stat(self._tree.abspath(old_path)).st_mode
1623
if e.errno in (errno.ENOENT, errno.ENOTDIR):
1624
# Either old_path doesn't exist, or the parent of the
1625
# target is not a directory (but will be one eventually)
1626
# Either way, we know it doesn't exist *right now*
1627
# See also bug #248448
1632
osutils.chmod_if_possible(self._limbo_name(trans_id), mode)
1634
def iter_tree_children(self, parent_id):
1635
"""Iterate through the entry's tree children, if any"""
1637
path = self._tree_id_paths[parent_id]
1641
children = os.listdir(self._tree.abspath(path))
1643
if not (osutils._is_error_enotdir(e)
1644
or e.errno in (errno.ENOENT, errno.ESRCH)):
1648
for child in children:
1649
childpath = joinpath(path, child)
1650
if self._tree.is_control_filename(childpath):
1652
yield self.trans_id_tree_path(childpath)
1654
def _generate_limbo_path(self, trans_id):
1655
"""Generate a limbo path using the final path if possible.
1657
This optimizes the performance of applying the tree transform by
1658
avoiding renames. These renames can be avoided only when the parent
1659
directory is already scheduled for creation.
1661
If the final path cannot be used, falls back to using the trans_id as
1664
parent = self._new_parent.get(trans_id)
1665
# if the parent directory is already in limbo (e.g. when building a
1666
# tree), choose a limbo name inside the parent, to reduce further
1668
use_direct_path = False
1669
if self._new_contents.get(parent) == 'directory':
1670
filename = self._new_name.get(trans_id)
1671
if filename is not None:
1672
if parent not in self._limbo_children:
1673
self._limbo_children[parent] = set()
1674
self._limbo_children_names[parent] = {}
1675
use_direct_path = True
1676
# the direct path can only be used if no other file has
1677
# already taken this pathname, i.e. if the name is unused, or
1678
# if it is already associated with this trans_id.
1679
elif self._case_sensitive_target:
1680
if (self._limbo_children_names[parent].get(filename)
1681
in (trans_id, None)):
1682
use_direct_path = True
1684
for l_filename, l_trans_id in\
1685
self._limbo_children_names[parent].iteritems():
1686
if l_trans_id == trans_id:
1688
if l_filename.lower() == filename.lower():
1691
use_direct_path = True
1693
if not use_direct_path:
1694
return DiskTreeTransform._generate_limbo_path(self, trans_id)
1696
limbo_name = pathjoin(self._limbo_files[parent], filename)
1697
self._limbo_children[parent].add(trans_id)
1698
self._limbo_children_names[parent][filename] = trans_id
1326
1702
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1327
1703
"""Apply all changes to the inventory and filesystem.
1478
1858
modified_paths = []
1479
1859
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1481
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1861
child_pb = ui.ui_factory.nested_progress_bar()
1483
1863
for num, (path, trans_id) in enumerate(new_paths):
1484
1864
if (num % 10) == 0:
1485
child_pb.update('adding file', num, len(new_paths))
1865
child_pb.update(gettext('adding file'), num, len(new_paths))
1486
1866
full_path = self._tree.abspath(path)
1487
1867
if trans_id in self._needs_rename:
1489
1869
mover.rename(self._limbo_name(trans_id), full_path)
1870
except errors.TransformRenameFailed, e:
1491
1871
# We may be renaming a dangling inventory id
1492
1872
if e.errno != errno.ENOENT:
1495
1875
self.rename_count += 1
1876
# TODO: if trans_id in self._observed_sha1s, we should
1877
# re-stat the final target, since ctime will be
1878
# updated by the change.
1496
1879
if (trans_id in self._new_contents or
1497
1880
self.path_changed(trans_id)):
1498
1881
if trans_id in self._new_contents:
1499
1882
modified_paths.append(full_path)
1500
1883
if trans_id in self._new_executability:
1501
1884
self._set_executability(path, trans_id)
1885
if trans_id in self._observed_sha1s:
1886
o_sha1, o_st_val = self._observed_sha1s[trans_id]
1887
st = osutils.lstat(full_path)
1888
self._observed_sha1s[trans_id] = (o_sha1, st)
1503
1890
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]
1504
1896
self._new_contents.clear()
1505
1897
return modified_paths
1508
class TransformPreview(TreeTransformBase):
1899
def _apply_observed_sha1s(self):
1900
"""After we have finished renaming everything, update observed sha1s
1902
This has to be done after self._tree.apply_inventory_delta, otherwise
1903
it doesn't know anything about the files we are updating. Also, we want
1904
to do this as late as possible, so that most entries end up cached.
1906
# TODO: this doesn't update the stat information for directories. So
1907
# the first 'bzr status' will still need to rewrite
1908
# .bzr/checkout/dirstate. However, we at least don't need to
1909
# re-read all of the files.
1910
# TODO: If the operation took a while, we could do a time.sleep(3) here
1911
# to allow the clock to tick over and ensure we won't have any
1912
# problems. (we could observe start time, and finish time, and if
1913
# it is less than eg 10% overhead, add a sleep call.)
1914
paths = FinalPaths(self)
1915
for trans_id, observed in self._observed_sha1s.iteritems():
1916
path = paths.get_path(trans_id)
1917
# We could get the file_id, but dirstate prefers to use the path
1918
# anyway, and it is 'cheaper' to determine.
1919
# file_id = self._new_id[trans_id]
1920
self._tree._observed_sha1(None, path, observed)
1923
class TransformPreview(DiskTreeTransform):
1509
1924
"""A TreeTransform for generating preview trees.
1511
1926
Unlike TreeTransform, this version works when the input tree is a
1754
2199
ordered_ids = self._list_files_by_dir()
1755
2200
for entry, trans_id in self._make_inv_entries(ordered_ids,
1757
yield unicode(self._final_paths.get_path(trans_id)), entry
1759
def list_files(self, include_root=False):
1760
"""See Tree.list_files."""
2201
specific_file_ids, yield_parents=yield_parents):
2202
yield unicode(self._final_paths.get_path(trans_id)), entry
2204
def _iter_entries_for_dir(self, dir_path):
2205
"""Return path, entry for items in a directory without recursing down."""
2206
dir_file_id = self.path2id(dir_path)
2208
for file_id in self.iter_children(dir_file_id):
2209
trans_id = self._transform.trans_id_file_id(file_id)
2210
ordered_ids.append((trans_id, file_id))
2211
for entry, trans_id in self._make_inv_entries(ordered_ids):
2212
yield unicode(self._final_paths.get_path(trans_id)), entry
2214
def list_files(self, include_root=False, from_dir=None, recursive=True):
2215
"""See WorkingTree.list_files."""
1761
2216
# XXX This should behave like WorkingTree.list_files, but is really
1762
2217
# more like RevisionTree.list_files.
1763
for path, entry in self.iter_entries_by_dir():
1764
if entry.name == '' and not include_root:
1766
yield path, 'V', entry.kind, entry.file_id, entry
2221
prefix = from_dir + '/'
2222
entries = self.iter_entries_by_dir()
2223
for path, entry in entries:
2224
if entry.name == '' and not include_root:
2227
if not path.startswith(prefix):
2229
path = path[len(prefix):]
2230
yield path, 'V', entry.kind, entry.file_id, entry
2232
if from_dir is None and include_root is True:
2233
root_entry = inventory.make_entry('directory', '',
2234
ROOT_PARENT, self.get_root_id())
2235
yield '', 'V', 'directory', root_entry.file_id, root_entry
2236
entries = self._iter_entries_for_dir(from_dir or '')
2237
for path, entry in entries:
2238
yield path, 'V', entry.kind, entry.file_id, entry
1768
2240
def kind(self, file_id):
1769
2241
trans_id = self._transform.trans_id_file_id(file_id)
2152
2666
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
2154
2668
total = len(desired_files) + offset
2155
2670
if accelerator_tree is None:
2156
2671
new_desired_files = desired_files
2158
2673
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2159
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2160
in iter if not (c or e[0] != e[1]))
2674
unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
2675
in iter if not (c or e[0] != e[1])]
2676
if accelerator_tree.supports_content_filtering():
2677
unchanged = [(f, p) for (f, p) in unchanged
2678
if not accelerator_tree.iter_search_rules([p]).next()]
2679
unchanged = dict(unchanged)
2161
2680
new_desired_files = []
2163
for file_id, (trans_id, tree_path) in desired_files:
2682
for file_id, (trans_id, tree_path, text_sha1) in desired_files:
2164
2683
accelerator_path = unchanged.get(file_id)
2165
2684
if accelerator_path is None:
2166
new_desired_files.append((file_id, (trans_id, tree_path)))
2685
new_desired_files.append((file_id,
2686
(trans_id, tree_path, text_sha1)))
2168
pb.update('Adding file contents', count + offset, total)
2688
pb.update(gettext('Adding file contents'), count + offset, total)
2170
2690
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2173
2693
contents = accelerator_tree.get_file(file_id, accelerator_path)
2174
if tree.supports_content_filtering():
2175
filters = tree._content_filter_stack(tree_path)
2694
if wt.supports_content_filtering():
2695
filters = wt._content_filter_stack(tree_path)
2176
2696
contents = filtered_output_bytes(contents, filters,
2177
2697
ContentFilterContext(tree_path, tree))
2179
tt.create_file(contents, trans_id)
2699
tt.create_file(contents, trans_id, sha1=text_sha1)
2182
2702
contents.close()
2311
2832
tt.set_executability(entry.executable, trans_id)
2314
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2315
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2318
def _get_backup_name(name, by_parent, parent_trans_id, tt):
2319
"""Produce a backup-style name that appears to be available"""
2323
yield "%s.~%d~" % (name, counter)
2325
for new_name in name_gen():
2326
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
2330
def _entry_changes(file_id, entry, working_tree):
2331
"""Determine in which ways the inventory entry has changed.
2333
Returns booleans: has_contents, content_mod, meta_mod
2334
has_contents means there are currently contents, but they differ
2335
contents_mod means contents need to be modified
2336
meta_mod means the metadata needs to be modified
2338
cur_entry = working_tree.inventory[file_id]
2340
working_kind = working_tree.kind(file_id)
2343
has_contents = False
2346
if has_contents is True:
2347
if entry.kind != working_kind:
2348
contents_mod, meta_mod = True, False
2350
cur_entry._read_tree_state(working_tree.id2path(file_id),
2352
contents_mod, meta_mod = entry.detect_changes(cur_entry)
2353
cur_entry._forget_tree_state()
2354
return has_contents, contents_mod, meta_mod
2357
2835
def revert(working_tree, target_tree, filenames, backups=False,
2358
pb=DummyProgress(), change_reporter=None):
2836
pb=None, change_reporter=None):
2359
2837
"""Revert a working tree's contents to those of a target tree."""
2360
2838
target_tree.lock_read()
2839
pb = ui.ui_factory.nested_progress_bar()
2361
2840
tt = TreeTransform(working_tree, pb)
2363
2842
pp = ProgressPhase("Revert phase", 3, pb)
2432
2919
if basis_tree is None:
2433
2920
basis_tree = working_tree.basis_tree()
2434
2921
basis_tree.lock_read()
2435
if file_id in basis_tree:
2922
if basis_tree.has_id(file_id):
2436
2923
if wt_sha1 != basis_tree.get_file_sha1(file_id):
2437
2924
keep_content = True
2438
elif kind[1] is None and not versioned[1]:
2925
elif target_kind is None and not target_versioned:
2439
2926
keep_content = True
2440
if kind[0] is not None:
2927
if wt_kind is not None:
2441
2928
if not keep_content:
2442
2929
tt.delete_contents(trans_id)
2443
elif kind[1] is not None:
2444
parent_trans_id = tt.trans_id_file_id(parent[0])
2445
by_parent = tt.by_parent()
2446
backup_name = _get_backup_name(name[0], by_parent,
2447
parent_trans_id, tt)
2930
elif target_kind is not None:
2931
parent_trans_id = tt.trans_id_file_id(wt_parent)
2932
backup_name = tt._available_backup_name(
2933
wt_name, parent_trans_id)
2448
2934
tt.adjust_path(backup_name, parent_trans_id, trans_id)
2449
new_trans_id = tt.create_path(name[0], parent_trans_id)
2450
if versioned == (True, True):
2935
new_trans_id = tt.create_path(wt_name, parent_trans_id)
2936
if wt_versioned and target_versioned:
2451
2937
tt.unversion_file(trans_id)
2452
2938
tt.version_file(file_id, new_trans_id)
2453
2939
# New contents should have the same unix perms as old
2455
2941
mode_id = trans_id
2456
2942
trans_id = new_trans_id
2457
if kind[1] in ('directory', 'tree-reference'):
2943
if target_kind in ('directory', 'tree-reference'):
2458
2944
tt.create_directory(trans_id)
2459
if kind[1] == 'tree-reference':
2945
if target_kind == 'tree-reference':
2460
2946
revision = target_tree.get_reference_revision(file_id,
2462
2948
tt.set_tree_reference(revision, trans_id)
2463
elif kind[1] == 'symlink':
2949
elif target_kind == 'symlink':
2464
2950
tt.create_symlink(target_tree.get_symlink_target(file_id),
2466
elif kind[1] == 'file':
2952
elif target_kind == 'file':
2467
2953
deferred_files.append((file_id, (trans_id, mode_id)))
2468
2954
if basis_tree is None:
2469
2955
basis_tree = working_tree.basis_tree()
2470
2956
basis_tree.lock_read()
2471
2957
new_sha1 = target_tree.get_file_sha1(file_id)
2472
if (file_id in basis_tree and new_sha1 ==
2473
basis_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)):
2474
2960
if file_id in merge_modified:
2475
2961
del merge_modified[file_id]
2477
2963
merge_modified[file_id] = new_sha1
2479
2965
# preserve the execute bit when backing up
2480
if keep_content and executable[0] == executable[1]:
2481
tt.set_executability(executable[1], trans_id)
2482
elif kind[1] is not None:
2483
raise AssertionError(kind[1])
2484
if versioned == (False, True):
2966
if keep_content and wt_executable == target_executable:
2967
tt.set_executability(target_executable, trans_id)
2968
elif target_kind is not None:
2969
raise AssertionError(target_kind)
2970
if not wt_versioned and target_versioned:
2485
2971
tt.version_file(file_id, trans_id)
2486
if versioned == (True, False):
2972
if wt_versioned and not target_versioned:
2487
2973
tt.unversion_file(trans_id)
2488
if (name[1] is not None and
2489
(name[0] != name[1] or parent[0] != parent[1])):
2490
if name[1] == '' and parent[1] is None:
2974
if (target_name is not None and
2975
(wt_name != target_name or wt_parent != target_parent)):
2976
if target_name == '' and target_parent is None:
2491
2977
parent_trans = ROOT_PARENT
2493
parent_trans = tt.trans_id_file_id(parent[1])
2494
tt.adjust_path(name[1], parent_trans, trans_id)
2495
if executable[0] != executable[1] and kind[1] == "file":
2496
tt.set_executability(executable[1], trans_id)
2497
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2499
tt.create_file(bytes, trans_id, mode_id)
2979
parent_trans = tt.trans_id_file_id(target_parent)
2980
if wt_parent is None and wt_versioned:
2981
tt.adjust_root_path(target_name, parent_trans)
2983
tt.adjust_path(target_name, parent_trans, trans_id)
2984
if wt_executable != target_executable and target_kind == "file":
2985
tt.set_executability(target_executable, trans_id)
2986
if working_tree.supports_content_filtering():
2987
for index, ((trans_id, mode_id), bytes) in enumerate(
2988
target_tree.iter_files_bytes(deferred_files)):
2989
file_id = deferred_files[index][0]
2990
# We're reverting a tree to the target tree so using the
2991
# target tree to find the file path seems the best choice
2992
# here IMO - Ian C 27/Oct/2009
2993
filter_tree_path = target_tree.id2path(file_id)
2994
filters = working_tree._content_filter_stack(filter_tree_path)
2995
bytes = filtered_output_bytes(bytes, filters,
2996
ContentFilterContext(filter_tree_path, working_tree))
2997
tt.create_file(bytes, trans_id, mode_id)
2999
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
3001
tt.create_file(bytes, trans_id, mode_id)
3002
tt.fixup_new_roots()
2501
3004
if basis_tree is not None:
2502
3005
basis_tree.unlock()
2503
3006
return merge_modified
2506
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
3009
def resolve_conflicts(tt, pb=None, pass_func=None):
2507
3010
"""Make many conflict-resolution attempts, but die if they fail"""
2508
3011
if pass_func is None:
2509
3012
pass_func = conflict_pass
2510
3013
new_conflicts = set()
3014
pb = ui.ui_factory.nested_progress_bar()
2512
3016
for n in range(10):
2513
pb.update('Resolution pass', n+1, 10)
3017
pb.update(gettext('Resolution pass'), n+1, 10)
2514
3018
conflicts = tt.find_conflicts()
2515
3019
if len(conflicts) == 0:
2516
3020
return new_conflicts
2517
3021
new_conflicts.update(pass_func(tt, conflicts))
2518
3022
raise MalformedTransform(conflicts=conflicts)
2523
3027
def conflict_pass(tt, conflicts, path_tree=None):