216
264
self.version_file(old_root_file_id, old_root)
217
265
self.unversion_file(self._new_root)
219
def fixup_new_roots(self):
220
"""Reinterpret requests to change the root directory
222
Instead of creating a root directory, or moving an existing directory,
223
all the attributes and children of the new root are applied to the
224
existing root directory.
226
This means that the old root trans-id becomes obsolete, so it is
227
recommended only to invoke this after the root trans-id has become
231
new_roots = [k for k, v in self._new_parent.iteritems() if v is
233
if len(new_roots) < 1:
235
if len(new_roots) != 1:
236
raise ValueError('A tree cannot have two roots!')
237
if self._new_root is None:
238
self._new_root = new_roots[0]
240
old_new_root = new_roots[0]
241
# unversion the new root's directory.
242
if self.final_kind(self._new_root) is None:
243
file_id = self.final_file_id(old_new_root)
245
file_id = self.final_file_id(self._new_root)
246
if old_new_root in self._new_id:
247
self.cancel_versioning(old_new_root)
249
self.unversion_file(old_new_root)
250
# if, at this stage, root still has an old file_id, zap it so we can
251
# stick a new one in.
252
if (self.tree_file_id(self._new_root) is not None and
253
self._new_root not in self._removed_id):
254
self.unversion_file(self._new_root)
255
if file_id is not None:
256
self.version_file(file_id, self._new_root)
258
# Now move children of new root into old root directory.
259
# Ensure all children are registered with the transaction, but don't
260
# use directly-- some tree children have new parents
261
list(self.iter_tree_children(old_new_root))
262
# Move all children of new root into old root directory.
263
for child in self.by_parent().get(old_new_root, []):
264
self.adjust_path(self.final_name(child), self._new_root, child)
266
# Ensure old_new_root has no directory.
267
if old_new_root in self._new_contents:
268
self.cancel_creation(old_new_root)
270
self.delete_contents(old_new_root)
272
# prevent deletion of root directory.
273
if self._new_root in self._removed_contents:
274
self.cancel_deletion(self._new_root)
276
# destroy path info for old_new_root.
277
del self._new_parent[old_new_root]
278
del self._new_name[old_new_root]
280
267
def trans_id_tree_file_id(self, inventory_id):
281
268
"""Determine the transaction id of a working tree file.
326
332
return ROOT_PARENT
327
333
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))
329
442
def delete_contents(self, trans_id):
330
443
"""Schedule the contents of a path entry for deletion"""
331
kind = self.tree_kind(trans_id)
333
self._removed_contents.add(trans_id)
444
self.tree_kind(trans_id)
445
self._removed_contents.add(trans_id)
335
447
def cancel_deletion(self, trans_id):
336
448
"""Cancel a scheduled deletion"""
393
505
return sorted(FinalPaths(self).get_paths(new_ids))
395
507
def _inventory_altered(self):
396
"""Determine which trans_ids need new Inventory entries.
398
An new entry is needed when anything that would be reflected by an
399
inventory entry changes, including file name, file_id, parent file_id,
400
file kind, and the execute bit.
402
Some care is taken to return entries with real changes, not cases
403
where the value is deleted and then restored to its original value,
404
but some actually unchanged values may be returned.
406
:returns: A list of (path, trans_id) for all items requiring an
407
inventory change. Ordered by path.
410
# Find entries whose file_ids are new (or changed).
411
new_file_id = set(t for t in self._new_id
412
if self._new_id[t] != self.tree_file_id(t))
413
for id_set in [self._new_name, self._new_parent, new_file_id,
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,
414
511
self._new_executability]:
415
changed_ids.update(id_set)
416
# removing implies a kind change
512
new_ids.update(id_set)
417
513
changed_kind = set(self._removed_contents)
419
514
changed_kind.intersection_update(self._new_contents)
420
# Ignore entries that are already known to have changed.
421
changed_kind.difference_update(changed_ids)
422
# to keep only the truly changed ones
423
changed_kind = (t for t in changed_kind
424
if self.tree_kind(t) != self.final_kind(t))
425
# all kind changes will alter the inventory
426
changed_ids.update(changed_kind)
427
# To find entries with changed parent_ids, find parents which existed,
428
# but changed file_id.
429
changed_file_id = set(t for t in new_file_id if t in self._removed_id)
430
# Now add all their children to the set.
431
for parent_trans_id in new_file_id:
432
changed_ids.update(self.iter_tree_children(parent_trans_id))
433
return sorted(FinalPaths(self).get_paths(changed_ids))
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)
435
537
def final_kind(self, trans_id):
436
538
"""Determine the final file kind, after any changes applied.
438
:return: None if the file does not exist/has no contents. (It is
439
conceivable that a path would be created without the corresponding
440
contents insertion command)
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)
442
544
if trans_id in self._new_contents:
443
545
return self._new_contents[trans_id]
444
546
elif trans_id in self._removed_contents:
547
raise NoSuchFile(None)
447
549
return self.tree_kind(trans_id)
572
667
# ensure that all children are registered with the transaction
573
668
list(self.iter_tree_children(parent_id))
575
@deprecated_method(deprecated_in((2, 3, 0)))
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)
576
690
def has_named_child(self, by_parent, parent_id, name):
577
return self._has_named_child(
578
name, parent_id, known_children=by_parent.get(parent_id, []))
580
def _has_named_child(self, name, parent_id, known_children):
581
"""Does a parent already have a name child.
583
:param name: The searched for name.
585
:param parent_id: The parent for which the check is made.
587
:param known_children: The already known children. This should have
588
been recently obtained from `self.by_parent.get(parent_id)`
589
(or will be if None is passed).
591
if known_children is None:
592
known_children = self.by_parent().get(parent_id, [])
593
for child in known_children:
692
children = by_parent[parent_id]
695
for child in children:
594
696
if self.final_name(child) == name:
596
parent_path = self._tree_id_paths.get(parent_id, None)
597
if parent_path is None:
598
# No parent... no children
699
path = self._tree_id_paths[parent_id]
600
child_path = joinpath(parent_path, name)
601
child_id = self._tree_path_ids.get(child_path, None)
702
childpath = joinpath(path, name)
703
child_id = self._tree_path_ids.get(childpath)
602
704
if child_id is None:
603
# Not known by the tree transform yet, check the filesystem
604
return osutils.lexists(self._tree.abspath(child_path))
705
return lexists(self._tree.abspath(childpath))
606
raise AssertionError('child_id is missing: %s, %s, %s'
607
% (name, parent_id, child_id))
609
def _available_backup_name(self, name, target_id):
610
"""Find an available backup name.
612
:param name: The basename of the file.
614
:param target_id: The directory trans_id where the backup should
617
known_children = self.by_parent().get(target_id, [])
618
return osutils.available_backup_name(
620
lambda base: self._has_named_child(
621
base, target_id, known_children))
707
if self.final_parent(child_id) != parent_id:
709
if child_id in self._removed_contents:
710
# XXX What about dangling file-ids?
623
715
def _parent_loops(self):
624
716
"""No entry should be its own ancestor"""
740
837
def _parent_type_conflicts(self, by_parent):
741
"""Children must have a directory parent"""
838
"""parents must have directory 'contents'."""
743
840
for parent_id, children in by_parent.iteritems():
744
841
if parent_id is ROOT_PARENT:
747
for child_id in children:
748
if self.final_kind(child_id) is not None:
843
if not self._any_contents(children):
753
# There is at least a child, so we need an existing directory to
755
kind = self.final_kind(parent_id)
845
for child in children:
847
self.final_kind(child)
851
kind = self.final_kind(parent_id)
757
# The directory will be deleted
758
855
conflicts.append(('missing parent', parent_id))
759
856
elif kind != "directory":
760
# Meh, we need a *directory* to put something in it
761
857
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
764
914
def _set_executability(self, path, trans_id):
765
915
"""Set the executability of versioned files """
766
916
if supports_executable():
1011
1122
def get_preview_tree(self):
1012
1123
"""Return a tree representing the result of the transform.
1014
The tree is a snapshot, and altering the TreeTransform will invalidate
1125
This tree only supports the subset of Tree functionality required
1126
by show_diff_trees. It must only be compared to tt._tree.
1017
1128
return _PreviewTree(self)
1019
def commit(self, branch, message, merge_parents=None, strict=False,
1020
timestamp=None, timezone=None, committer=None, authors=None,
1021
revprops=None, revision_id=None):
1022
"""Commit the result of this TreeTransform to a branch.
1024
:param branch: The branch to commit to.
1025
:param message: The message to attach to the commit.
1026
:param merge_parents: Additional parent revision-ids specified by
1028
:param strict: If True, abort the commit if there are unversioned
1030
:param timestamp: if not None, seconds-since-epoch for the time and
1031
date. (May be a float.)
1032
:param timezone: Optional timezone for timestamp, as an offset in
1034
:param committer: Optional committer in email-id format.
1035
(e.g. "J Random Hacker <jrandom@example.com>")
1036
:param authors: Optional list of authors in email-id format.
1037
:param revprops: Optional dictionary of revision properties.
1038
:param revision_id: Optional revision id. (Specifying a revision-id
1039
may reduce performance for some non-native formats.)
1040
:return: The revision_id of the revision committed.
1042
self._check_malformed()
1044
unversioned = set(self._new_contents).difference(set(self._new_id))
1045
for trans_id in unversioned:
1046
if self.final_file_id(trans_id) is None:
1047
raise errors.StrictCommitFailed()
1049
revno, last_rev_id = branch.last_revision_info()
1050
if last_rev_id == _mod_revision.NULL_REVISION:
1051
if merge_parents is not None:
1052
raise ValueError('Cannot supply merge parents for first'
1056
parent_ids = [last_rev_id]
1057
if merge_parents is not None:
1058
parent_ids.extend(merge_parents)
1059
if self._tree.get_revision_id() != last_rev_id:
1060
raise ValueError('TreeTransform not based on branch basis: %s' %
1061
self._tree.get_revision_id())
1062
revprops = commit.Commit.update_revprops(revprops, branch, authors)
1063
builder = branch.get_commit_builder(parent_ids,
1064
timestamp=timestamp,
1066
committer=committer,
1068
revision_id=revision_id)
1069
preview = self.get_preview_tree()
1070
list(builder.record_iter_changes(preview, last_rev_id,
1071
self.iter_changes()))
1072
builder.finish_inventory()
1073
revision_id = builder.commit(message)
1074
branch.set_last_revision_info(revno + 1, revision_id)
1077
1130
def _text_parent(self, trans_id):
1078
1131
file_id = self.tree_file_id(trans_id)
1170
1227
self.create_symlink(content.decode('utf-8'), trans_id)
1173
class DiskTreeTransform(TreeTransformBase):
1174
"""Tree transform storing its contents on disk."""
1176
def __init__(self, tree, limbodir, pb=None,
1177
case_sensitive=True):
1179
:param tree: The tree that will be transformed, but not necessarily
1181
:param limbodir: A directory where new files can be stored until
1182
they are installed in their proper places
1184
:param case_sensitive: If True, the target of the transform is
1185
case sensitive, not just case preserving.
1187
TreeTransformBase.__init__(self, tree, pb, case_sensitive)
1188
self._limbodir = limbodir
1189
self._deletiondir = None
1190
# A mapping of transform ids to their limbo filename
1191
self._limbo_files = {}
1192
self._possibly_stale_limbo_files = set()
1193
# A mapping of transform ids to a set of the transform ids of children
1194
# that their limbo directory has
1195
self._limbo_children = {}
1196
# Map transform ids to maps of child filename to child transform id
1197
self._limbo_children_names = {}
1198
# List of transform ids that need to be renamed from limbo into place
1199
self._needs_rename = set()
1200
self._creation_mtime = None
1203
"""Release the working tree lock, if held, clean up limbo dir.
1205
This is required if apply has not been invoked, but can be invoked
1208
if self._tree is None:
1211
limbo_paths = self._limbo_files.values() + list(
1212
self._possibly_stale_limbo_files)
1213
limbo_paths = sorted(limbo_paths, reverse=True)
1214
for path in limbo_paths:
1218
if e.errno != errno.ENOENT:
1220
# XXX: warn? perhaps we just got interrupted at an
1221
# inconvenient moment, but perhaps files are disappearing
1224
delete_any(self._limbodir)
1226
# We don't especially care *why* the dir is immortal.
1227
raise ImmortalLimbo(self._limbodir)
1229
if self._deletiondir is not None:
1230
delete_any(self._deletiondir)
1232
raise errors.ImmortalPendingDeletion(self._deletiondir)
1234
TreeTransformBase.finalize(self)
1236
def _limbo_name(self, trans_id):
1237
"""Generate the limbo name of a file"""
1238
limbo_name = self._limbo_files.get(trans_id)
1239
if limbo_name is None:
1240
limbo_name = self._generate_limbo_path(trans_id)
1241
self._limbo_files[trans_id] = limbo_name
1244
def _generate_limbo_path(self, trans_id):
1245
"""Generate a limbo path using the trans_id as the relative path.
1247
This is suitable as a fallback, and when the transform should not be
1248
sensitive to the path encoding of the limbo directory.
1250
self._needs_rename.add(trans_id)
1251
return pathjoin(self._limbodir, trans_id)
1253
def adjust_path(self, name, parent, trans_id):
1254
previous_parent = self._new_parent.get(trans_id)
1255
previous_name = self._new_name.get(trans_id)
1256
TreeTransformBase.adjust_path(self, name, parent, trans_id)
1257
if (trans_id in self._limbo_files and
1258
trans_id not in self._needs_rename):
1259
self._rename_in_limbo([trans_id])
1260
if previous_parent != parent:
1261
self._limbo_children[previous_parent].remove(trans_id)
1262
if previous_parent != parent or previous_name != name:
1263
del self._limbo_children_names[previous_parent][previous_name]
1265
def _rename_in_limbo(self, trans_ids):
1266
"""Fix limbo names so that the right final path is produced.
1268
This means we outsmarted ourselves-- we tried to avoid renaming
1269
these files later by creating them with their final names in their
1270
final parents. But now the previous name or parent is no longer
1271
suitable, so we have to rename them.
1273
Even for trans_ids that have no new contents, we must remove their
1274
entries from _limbo_files, because they are now stale.
1276
for trans_id in trans_ids:
1277
old_path = self._limbo_files[trans_id]
1278
self._possibly_stale_limbo_files.add(old_path)
1279
del self._limbo_files[trans_id]
1280
if trans_id not in self._new_contents:
1282
new_path = self._limbo_name(trans_id)
1283
os.rename(old_path, new_path)
1284
self._possibly_stale_limbo_files.remove(old_path)
1285
for descendant in self._limbo_descendants(trans_id):
1286
desc_path = self._limbo_files[descendant]
1287
desc_path = new_path + desc_path[len(old_path):]
1288
self._limbo_files[descendant] = desc_path
1290
def _limbo_descendants(self, trans_id):
1291
"""Return the set of trans_ids whose limbo paths descend from this."""
1292
descendants = set(self._limbo_children.get(trans_id, []))
1293
for descendant in list(descendants):
1294
descendants.update(self._limbo_descendants(descendant))
1297
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1298
"""Schedule creation of a new file.
1302
:param contents: an iterator of strings, all of which will be written
1303
to the target destination.
1304
:param trans_id: TreeTransform handle
1305
:param mode_id: If not None, force the mode of the target file to match
1306
the mode of the object referenced by mode_id.
1307
Otherwise, we will try to preserve mode bits of an existing file.
1308
:param sha1: If the sha1 of this content is already known, pass it in.
1309
We can use it to prevent future sha1 computations.
1311
name = self._limbo_name(trans_id)
1312
f = open(name, 'wb')
1314
unique_add(self._new_contents, trans_id, 'file')
1315
f.writelines(contents)
1318
self._set_mtime(name)
1319
self._set_mode(trans_id, mode_id, S_ISREG)
1320
# It is unfortunate we have to use lstat instead of fstat, but we just
1321
# used utime and chmod on the file, so we need the accurate final
1323
if sha1 is not None:
1324
self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
1326
def _read_file_chunks(self, trans_id):
1327
cur_file = open(self._limbo_name(trans_id), 'rb')
1329
return cur_file.readlines()
1333
def _read_symlink_target(self, trans_id):
1334
return os.readlink(self._limbo_name(trans_id))
1336
def _set_mtime(self, path):
1337
"""All files that are created get the same mtime.
1339
This time is set by the first object to be created.
1341
if self._creation_mtime is None:
1342
self._creation_mtime = time.time()
1343
os.utime(path, (self._creation_mtime, self._creation_mtime))
1345
def create_hardlink(self, path, trans_id):
1346
"""Schedule creation of a hard link"""
1347
name = self._limbo_name(trans_id)
1351
if e.errno != errno.EPERM:
1353
raise errors.HardLinkNotSupported(path)
1355
unique_add(self._new_contents, trans_id, 'file')
1357
# Clean up the file, it never got registered so
1358
# TreeTransform.finalize() won't clean it up.
1362
def create_directory(self, trans_id):
1363
"""Schedule creation of a new directory.
1365
See also new_directory.
1367
os.mkdir(self._limbo_name(trans_id))
1368
unique_add(self._new_contents, trans_id, 'directory')
1370
def create_symlink(self, target, trans_id):
1371
"""Schedule creation of a new symbolic link.
1373
target is a bytestring.
1374
See also new_symlink.
1377
os.symlink(target, self._limbo_name(trans_id))
1378
unique_add(self._new_contents, trans_id, 'symlink')
1381
path = FinalPaths(self).get_path(trans_id)
1384
raise UnableCreateSymlink(path=path)
1386
def cancel_creation(self, trans_id):
1387
"""Cancel the creation of new file contents."""
1388
del self._new_contents[trans_id]
1389
if trans_id in self._observed_sha1s:
1390
del self._observed_sha1s[trans_id]
1391
children = self._limbo_children.get(trans_id)
1392
# if this is a limbo directory with children, move them before removing
1394
if children is not None:
1395
self._rename_in_limbo(children)
1396
del self._limbo_children[trans_id]
1397
del self._limbo_children_names[trans_id]
1398
delete_any(self._limbo_name(trans_id))
1400
def new_orphan(self, trans_id, parent_id):
1401
# FIXME: There is no tree config, so we use the branch one (it's weird
1402
# to define it this way as orphaning can only occur in a working tree,
1403
# but that's all we have (for now). It will find the option in
1404
# locations.conf or bazaar.conf though) -- vila 20100916
1405
conf = self._tree.branch.get_config()
1406
conf_var_name = 'bzr.transform.orphan_policy'
1407
orphan_policy = conf.get_user_option(conf_var_name)
1408
default_policy = orphaning_registry.default_key
1409
if orphan_policy is None:
1410
orphan_policy = default_policy
1411
if orphan_policy not in orphaning_registry:
1412
trace.warning('%s (from %s) is not a known policy, defaulting '
1413
'to %s' % (orphan_policy, conf_var_name, default_policy))
1414
orphan_policy = default_policy
1415
handle_orphan = orphaning_registry.get(orphan_policy)
1416
handle_orphan(self, trans_id, parent_id)
1419
class OrphaningError(errors.BzrError):
1421
# Only bugs could lead to such exception being seen by the user
1422
internal_error = True
1423
_fmt = "Error while orphaning %s in %s directory"
1425
def __init__(self, orphan, parent):
1426
errors.BzrError.__init__(self)
1427
self.orphan = orphan
1428
self.parent = parent
1431
class OrphaningForbidden(OrphaningError):
1433
_fmt = "Policy: %s doesn't allow creating orphans."
1435
def __init__(self, policy):
1436
errors.BzrError.__init__(self)
1437
self.policy = policy
1440
def move_orphan(tt, orphan_id, parent_id):
1441
"""See TreeTransformBase.new_orphan.
1443
This creates a new orphan in the `bzr-orphans` dir at the root of the
1446
:param tt: The TreeTransform orphaning `trans_id`.
1448
:param orphan_id: The trans id that should be orphaned.
1450
:param parent_id: The orphan parent trans id.
1452
# Add the orphan dir if it doesn't exist
1453
orphan_dir_basename = 'bzr-orphans'
1454
od_id = tt.trans_id_tree_path(orphan_dir_basename)
1455
if tt.final_kind(od_id) is None:
1456
tt.create_directory(od_id)
1457
parent_path = tt._tree_id_paths[parent_id]
1458
# Find a name that doesn't exist yet in the orphan dir
1459
actual_name = tt.final_name(orphan_id)
1460
new_name = tt._available_backup_name(actual_name, od_id)
1461
tt.adjust_path(new_name, od_id, orphan_id)
1462
trace.warning('%s has been orphaned in %s'
1463
% (joinpath(parent_path, actual_name), orphan_dir_basename))
1466
def refuse_orphan(tt, orphan_id, parent_id):
1467
"""See TreeTransformBase.new_orphan.
1469
This refuses to create orphan, letting the caller handle the conflict.
1471
raise OrphaningForbidden('never')
1474
orphaning_registry = registry.Registry()
1475
orphaning_registry.register(
1476
'conflict', refuse_orphan,
1477
'Leave orphans in place and create a conflict on the directory.')
1478
orphaning_registry.register(
1479
'move', move_orphan,
1480
'Move orphans into the bzr-orphans directory.')
1481
orphaning_registry._set_default_key('conflict')
1484
class TreeTransform(DiskTreeTransform):
1230
class TreeTransform(TreeTransformBase):
1485
1231
"""Represent a tree transformation.
1487
1233
This object is designed to support incremental generation of the transform,
1576
# Cache of realpath results, to speed up canonical_path
1577
self._realpaths = {}
1578
# Cache of relpath results, to speed up canonical_path
1580
DiskTreeTransform.__init__(self, tree, limbodir, pb,
1322
TreeTransformBase.__init__(self, tree, limbodir, pb,
1581
1323
tree.case_sensitive)
1582
1324
self._deletiondir = deletiondir
1584
def canonical_path(self, path):
1585
"""Get the canonical tree-relative path"""
1586
# don't follow final symlinks
1587
abs = self._tree.abspath(path)
1588
if abs in self._relpaths:
1589
return self._relpaths[abs]
1590
dirname, basename = os.path.split(abs)
1591
if dirname not in self._realpaths:
1592
self._realpaths[dirname] = os.path.realpath(dirname)
1593
dirname = self._realpaths[dirname]
1594
abs = pathjoin(dirname, basename)
1595
if dirname in self._relpaths:
1596
relpath = pathjoin(self._relpaths[dirname], basename)
1597
relpath = relpath.rstrip('/\\')
1599
relpath = self._tree.relpath(abs)
1600
self._relpaths[abs] = relpath
1603
def tree_kind(self, trans_id):
1604
"""Determine the file kind in the working tree.
1606
:returns: The file kind or None if the file does not exist
1608
path = self._tree_id_paths.get(trans_id)
1612
return file_kind(self._tree.abspath(path))
1613
except errors.NoSuchFile:
1616
def _set_mode(self, trans_id, mode_id, typefunc):
1617
"""Set the mode of new file contents.
1618
The mode_id is the existing file to get the mode from (often the same
1619
as trans_id). The operation is only performed if there's a mode match
1620
according to typefunc.
1625
old_path = self._tree_id_paths[mode_id]
1629
mode = os.stat(self._tree.abspath(old_path)).st_mode
1631
if e.errno in (errno.ENOENT, errno.ENOTDIR):
1632
# Either old_path doesn't exist, or the parent of the
1633
# target is not a directory (but will be one eventually)
1634
# Either way, we know it doesn't exist *right now*
1635
# See also bug #248448
1640
os.chmod(self._limbo_name(trans_id), mode)
1642
def iter_tree_children(self, parent_id):
1643
"""Iterate through the entry's tree children, if any"""
1645
path = self._tree_id_paths[parent_id]
1649
children = os.listdir(self._tree.abspath(path))
1651
if not (osutils._is_error_enotdir(e)
1652
or e.errno in (errno.ENOENT, errno.ESRCH)):
1656
for child in children:
1657
childpath = joinpath(path, child)
1658
if self._tree.is_control_filename(childpath):
1660
yield self.trans_id_tree_path(childpath)
1662
def _generate_limbo_path(self, trans_id):
1663
"""Generate a limbo path using the final path if possible.
1665
This optimizes the performance of applying the tree transform by
1666
avoiding renames. These renames can be avoided only when the parent
1667
directory is already scheduled for creation.
1669
If the final path cannot be used, falls back to using the trans_id as
1672
parent = self._new_parent.get(trans_id)
1673
# if the parent directory is already in limbo (e.g. when building a
1674
# tree), choose a limbo name inside the parent, to reduce further
1676
use_direct_path = False
1677
if self._new_contents.get(parent) == 'directory':
1678
filename = self._new_name.get(trans_id)
1679
if filename is not None:
1680
if parent not in self._limbo_children:
1681
self._limbo_children[parent] = set()
1682
self._limbo_children_names[parent] = {}
1683
use_direct_path = True
1684
# the direct path can only be used if no other file has
1685
# already taken this pathname, i.e. if the name is unused, or
1686
# if it is already associated with this trans_id.
1687
elif self._case_sensitive_target:
1688
if (self._limbo_children_names[parent].get(filename)
1689
in (trans_id, None)):
1690
use_direct_path = True
1692
for l_filename, l_trans_id in\
1693
self._limbo_children_names[parent].iteritems():
1694
if l_trans_id == trans_id:
1696
if l_filename.lower() == filename.lower():
1699
use_direct_path = True
1701
if not use_direct_path:
1702
return DiskTreeTransform._generate_limbo_path(self, trans_id)
1704
limbo_name = pathjoin(self._limbo_files[parent], filename)
1705
self._limbo_children[parent].add(trans_id)
1706
self._limbo_children_names[parent][filename] = trans_id
1710
1326
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1711
1327
"""Apply all changes to the inventory and filesystem.
1873
1487
if trans_id in self._needs_rename:
1875
1489
mover.rename(self._limbo_name(trans_id), full_path)
1876
except errors.TransformRenameFailed, e:
1877
1491
# We may be renaming a dangling inventory id
1878
1492
if e.errno != errno.ENOENT:
1881
1495
self.rename_count += 1
1882
# TODO: if trans_id in self._observed_sha1s, we should
1883
# re-stat the final target, since ctime will be
1884
# updated by the change.
1885
1496
if (trans_id in self._new_contents or
1886
1497
self.path_changed(trans_id)):
1887
1498
if trans_id in self._new_contents:
1888
1499
modified_paths.append(full_path)
1889
1500
if trans_id in self._new_executability:
1890
1501
self._set_executability(path, trans_id)
1891
if trans_id in self._observed_sha1s:
1892
o_sha1, o_st_val = self._observed_sha1s[trans_id]
1893
st = osutils.lstat(full_path)
1894
self._observed_sha1s[trans_id] = (o_sha1, st)
1896
1503
child_pb.finished()
1897
for path, trans_id in new_paths:
1898
# new_paths includes stuff like workingtree conflicts. Only the
1899
# stuff in new_contents actually comes from limbo.
1900
if trans_id in self._limbo_files:
1901
del self._limbo_files[trans_id]
1902
1504
self._new_contents.clear()
1903
1505
return modified_paths
1905
def _apply_observed_sha1s(self):
1906
"""After we have finished renaming everything, update observed sha1s
1908
This has to be done after self._tree.apply_inventory_delta, otherwise
1909
it doesn't know anything about the files we are updating. Also, we want
1910
to do this as late as possible, so that most entries end up cached.
1912
# TODO: this doesn't update the stat information for directories. So
1913
# the first 'bzr status' will still need to rewrite
1914
# .bzr/checkout/dirstate. However, we at least don't need to
1915
# re-read all of the files.
1916
# TODO: If the operation took a while, we could do a time.sleep(3) here
1917
# to allow the clock to tick over and ensure we won't have any
1918
# problems. (we could observe start time, and finish time, and if
1919
# it is less than eg 10% overhead, add a sleep call.)
1920
paths = FinalPaths(self)
1921
for trans_id, observed in self._observed_sha1s.iteritems():
1922
path = paths.get_path(trans_id)
1923
# We could get the file_id, but dirstate prefers to use the path
1924
# anyway, and it is 'cheaper' to determine.
1925
# file_id = self._new_id[trans_id]
1926
self._tree._observed_sha1(None, path, observed)
1929
class TransformPreview(DiskTreeTransform):
1508
class TransformPreview(TreeTransformBase):
1930
1509
"""A TreeTransform for generating preview trees.
1932
1511
Unlike TreeTransform, this version works when the input tree is a
2187
1754
ordered_ids = self._list_files_by_dir()
2188
1755
for entry, trans_id in self._make_inv_entries(ordered_ids,
2189
specific_file_ids, yield_parents=yield_parents):
2190
yield unicode(self._final_paths.get_path(trans_id)), entry
2192
def _iter_entries_for_dir(self, dir_path):
2193
"""Return path, entry for items in a directory without recursing down."""
2194
dir_file_id = self.path2id(dir_path)
2196
for file_id in self.iter_children(dir_file_id):
2197
trans_id = self._transform.trans_id_file_id(file_id)
2198
ordered_ids.append((trans_id, file_id))
2199
for entry, trans_id in self._make_inv_entries(ordered_ids):
2200
yield unicode(self._final_paths.get_path(trans_id)), entry
2202
def list_files(self, include_root=False, from_dir=None, recursive=True):
2203
"""See WorkingTree.list_files."""
1757
yield unicode(self._final_paths.get_path(trans_id)), entry
1759
def list_files(self, include_root=False):
1760
"""See Tree.list_files."""
2204
1761
# XXX This should behave like WorkingTree.list_files, but is really
2205
1762
# more like RevisionTree.list_files.
2209
prefix = from_dir + '/'
2210
entries = self.iter_entries_by_dir()
2211
for path, entry in entries:
2212
if entry.name == '' and not include_root:
2215
if not path.startswith(prefix):
2217
path = path[len(prefix):]
2218
yield path, 'V', entry.kind, entry.file_id, entry
2220
if from_dir is None and include_root is True:
2221
root_entry = inventory.make_entry('directory', '',
2222
ROOT_PARENT, self.get_root_id())
2223
yield '', 'V', 'directory', root_entry.file_id, root_entry
2224
entries = self._iter_entries_for_dir(from_dir or '')
2225
for path, entry in entries:
2226
yield path, 'V', entry.kind, entry.file_id, entry
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
2228
1768
def kind(self, file_id):
2229
1769
trans_id = self._transform.trans_id_file_id(file_id)
2952
2433
if basis_tree is None:
2953
2434
basis_tree = working_tree.basis_tree()
2954
2435
basis_tree.lock_read()
2955
if basis_tree.has_id(file_id):
2436
if file_id in basis_tree:
2956
2437
if wt_sha1 != basis_tree.get_file_sha1(file_id):
2957
2438
keep_content = True
2958
elif target_kind is None and not target_versioned:
2439
elif kind[1] is None and not versioned[1]:
2959
2440
keep_content = True
2960
if wt_kind is not None:
2441
if kind[0] is not None:
2961
2442
if not keep_content:
2962
2443
tt.delete_contents(trans_id)
2963
elif target_kind is not None:
2964
parent_trans_id = tt.trans_id_file_id(wt_parent)
2965
backup_name = tt._available_backup_name(
2966
wt_name, parent_trans_id)
2444
elif kind[1] is not None:
2445
parent_trans_id = tt.trans_id_file_id(parent[0])
2446
by_parent = tt.by_parent()
2447
backup_name = _get_backup_name(name[0], by_parent,
2448
parent_trans_id, tt)
2967
2449
tt.adjust_path(backup_name, parent_trans_id, trans_id)
2968
new_trans_id = tt.create_path(wt_name, parent_trans_id)
2969
if wt_versioned and target_versioned:
2450
new_trans_id = tt.create_path(name[0], parent_trans_id)
2451
if versioned == (True, True):
2970
2452
tt.unversion_file(trans_id)
2971
2453
tt.version_file(file_id, new_trans_id)
2972
2454
# New contents should have the same unix perms as old
2974
2456
mode_id = trans_id
2975
2457
trans_id = new_trans_id
2976
if target_kind in ('directory', 'tree-reference'):
2458
if kind[1] in ('directory', 'tree-reference'):
2977
2459
tt.create_directory(trans_id)
2978
if target_kind == 'tree-reference':
2460
if kind[1] == 'tree-reference':
2979
2461
revision = target_tree.get_reference_revision(file_id,
2981
2463
tt.set_tree_reference(revision, trans_id)
2982
elif target_kind == 'symlink':
2464
elif kind[1] == 'symlink':
2983
2465
tt.create_symlink(target_tree.get_symlink_target(file_id),
2985
elif target_kind == 'file':
2467
elif kind[1] == 'file':
2986
2468
deferred_files.append((file_id, (trans_id, mode_id)))
2987
2469
if basis_tree is None:
2988
2470
basis_tree = working_tree.basis_tree()
2989
2471
basis_tree.lock_read()
2990
2472
new_sha1 = target_tree.get_file_sha1(file_id)
2991
if (basis_tree.has_id(file_id) and
2992
new_sha1 == basis_tree.get_file_sha1(file_id)):
2473
if (file_id in basis_tree and new_sha1 ==
2474
basis_tree.get_file_sha1(file_id)):
2993
2475
if file_id in merge_modified:
2994
2476
del merge_modified[file_id]
2996
2478
merge_modified[file_id] = new_sha1
2998
2480
# preserve the execute bit when backing up
2999
if keep_content and wt_executable == target_executable:
3000
tt.set_executability(target_executable, trans_id)
3001
elif target_kind is not None:
3002
raise AssertionError(target_kind)
3003
if not wt_versioned and target_versioned:
2481
if keep_content and executable[0] == executable[1]:
2482
tt.set_executability(executable[1], trans_id)
2483
elif kind[1] is not None:
2484
raise AssertionError(kind[1])
2485
if versioned == (False, True):
3004
2486
tt.version_file(file_id, trans_id)
3005
if wt_versioned and not target_versioned:
2487
if versioned == (True, False):
3006
2488
tt.unversion_file(trans_id)
3007
if (target_name is not None and
3008
(wt_name != target_name or wt_parent != target_parent)):
3009
if target_name == '' and target_parent is None:
2489
if (name[1] is not None and
2490
(name[0] != name[1] or parent[0] != parent[1])):
2491
if name[1] == '' and parent[1] is None:
3010
2492
parent_trans = ROOT_PARENT
3012
parent_trans = tt.trans_id_file_id(target_parent)
3013
if wt_parent is None and wt_versioned:
3014
tt.adjust_root_path(target_name, parent_trans)
3016
tt.adjust_path(target_name, parent_trans, trans_id)
3017
if wt_executable != target_executable and target_kind == "file":
3018
tt.set_executability(target_executable, trans_id)
3019
if working_tree.supports_content_filtering():
3020
for index, ((trans_id, mode_id), bytes) in enumerate(
3021
target_tree.iter_files_bytes(deferred_files)):
3022
file_id = deferred_files[index][0]
3023
# We're reverting a tree to the target tree so using the
3024
# target tree to find the file path seems the best choice
3025
# here IMO - Ian C 27/Oct/2009
3026
filter_tree_path = target_tree.id2path(file_id)
3027
filters = working_tree._content_filter_stack(filter_tree_path)
3028
bytes = filtered_output_bytes(bytes, filters,
3029
ContentFilterContext(filter_tree_path, working_tree))
3030
tt.create_file(bytes, trans_id, mode_id)
3032
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
3034
tt.create_file(bytes, trans_id, mode_id)
3035
tt.fixup_new_roots()
2494
parent_trans = tt.trans_id_file_id(parent[1])
2495
tt.adjust_path(name[1], parent_trans, trans_id)
2496
if executable[0] != executable[1] and kind[1] == "file":
2497
tt.set_executability(executable[1], trans_id)
2498
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2500
tt.create_file(bytes, trans_id, mode_id)
3037
2502
if basis_tree is not None:
3038
2503
basis_tree.unlock()
3039
2504
return merge_modified
3042
def resolve_conflicts(tt, pb=None, pass_func=None):
2507
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
3043
2508
"""Make many conflict-resolution attempts, but die if they fail"""
3044
2509
if pass_func is None:
3045
2510
pass_func = conflict_pass
3046
2511
new_conflicts = set()
3047
pb = ui.ui_factory.nested_progress_bar()
3049
2513
for n in range(10):
3050
2514
pb.update('Resolution pass', n+1, 10)