14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
21
19
from stat import S_ISREG, S_IEXEC
25
config as _mod_config,
32
lazy_import.lazy_import(globals(), """
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
44
32
revision as _mod_revision,
48
from breezy.bzr import (
52
from breezy.i18n import gettext
54
from .errors import (DuplicateKey, MalformedTransform,
55
ReusingTransform, CantMoveRoot,
56
ImmortalLimbo, NoFinalPath)
57
from .filters import filtered_output_bytes, ContentFilterContext
58
from .mutabletree import MutableTree
59
from .osutils import (
35
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
36
ReusingTransform, NotVersionedError, CantMoveRoot,
37
ExistingLimbo, ImmortalLimbo, NoFinalPath,
39
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
40
from bzrlib.inventory import InventoryEntry
41
from bzrlib.osutils import (
67
from .progress import ProgressPhase
51
from bzrlib.progress import DummyProgress, ProgressPhase
52
from bzrlib.symbol_versioning import (
56
from bzrlib.trace import mutter, warning
57
from bzrlib import tree
59
import bzrlib.urlutils as urlutils
79
62
ROOT_PARENT = "root-parent"
227
202
self.version_file(old_root_file_id, old_root)
228
203
self.unversion_file(self._new_root)
230
def fixup_new_roots(self):
231
"""Reinterpret requests to change the root directory
233
Instead of creating a root directory, or moving an existing directory,
234
all the attributes and children of the new root are applied to the
235
existing root directory.
237
This means that the old root trans-id becomes obsolete, so it is
238
recommended only to invoke this after the root trans-id has become
205
def trans_id_tree_file_id(self, inventory_id):
206
"""Determine the transaction id of a working tree file.
208
This reflects only files that already exist, not ones that will be
209
added by transactions.
242
new_roots = [k for k, v in viewitems(self._new_parent)
244
if len(new_roots) < 1:
246
if len(new_roots) != 1:
247
raise ValueError('A tree cannot have two roots!')
248
if self._new_root is None:
249
self._new_root = new_roots[0]
251
old_new_root = new_roots[0]
252
# unversion the new root's directory.
253
if self.final_kind(self._new_root) is None:
254
file_id = self.final_file_id(old_new_root)
256
file_id = self.final_file_id(self._new_root)
257
if old_new_root in self._new_id:
258
self.cancel_versioning(old_new_root)
260
self.unversion_file(old_new_root)
261
# if, at this stage, root still has an old file_id, zap it so we can
262
# stick a new one in.
263
if (self.tree_file_id(self._new_root) is not None
264
and self._new_root not in self._removed_id):
265
self.unversion_file(self._new_root)
266
if file_id is not None:
267
self.version_file(file_id, self._new_root)
269
# Now move children of new root into old root directory.
270
# Ensure all children are registered with the transaction, but don't
271
# use directly-- some tree children have new parents
272
list(self.iter_tree_children(old_new_root))
273
# Move all children of new root into old root directory.
274
for child in self.by_parent().get(old_new_root, []):
275
self.adjust_path(self.final_name(child), self._new_root, child)
277
# Ensure old_new_root has no directory.
278
if old_new_root in self._new_contents:
279
self.cancel_creation(old_new_root)
281
self.delete_contents(old_new_root)
283
# prevent deletion of root directory.
284
if self._new_root in self._removed_contents:
285
self.cancel_deletion(self._new_root)
287
# destroy path info for old_new_root.
288
del self._new_parent[old_new_root]
289
del self._new_name[old_new_root]
211
if inventory_id is None:
212
raise ValueError('None is not a valid file id')
213
path = self._tree.id2path(inventory_id)
214
return self.trans_id_tree_path(path)
291
216
def trans_id_file_id(self, file_id):
292
217
"""Determine or set the transaction id associated with a file ID.
393
317
return sorted(FinalPaths(self).get_paths(new_ids))
395
319
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,
320
"""Get the trans_ids and paths of files needing new inv entries."""
322
for id_set in [self._new_name, self._new_parent, self._new_id,
414
323
self._new_executability]:
415
changed_ids.update(id_set)
416
# removing implies a kind change
324
new_ids.update(id_set)
417
325
changed_kind = set(self._removed_contents)
419
326
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
# Now add all their children to the set.
430
for parent_trans_id in new_file_id:
431
changed_ids.update(self.iter_tree_children(parent_trans_id))
432
return sorted(FinalPaths(self).get_paths(changed_ids))
327
changed_kind.difference_update(new_ids)
328
changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
330
new_ids.update(changed_kind)
331
return sorted(FinalPaths(self).get_paths(new_ids))
434
333
def final_kind(self, trans_id):
435
334
"""Determine the final file kind, after any changes applied.
437
:return: None if the file does not exist/has no contents. (It is
438
conceivable that a path would be created without the corresponding
439
contents insertion command)
336
Raises NoSuchFile if the file does not exist/has no contents.
337
(It is conceivable that a path would be created without the
338
corresponding contents insertion command)
441
340
if trans_id in self._new_contents:
442
341
return self._new_contents[trans_id]
443
342
elif trans_id in self._removed_contents:
343
raise NoSuchFile(None)
446
345
return self.tree_kind(trans_id)
448
def tree_path(self, trans_id):
449
"""Determine the tree path associated with the trans_id."""
450
return self._tree_id_paths.get(trans_id)
452
347
def tree_file_id(self, trans_id):
453
348
"""Determine the file id associated with the trans_id in the tree"""
454
path = self.tree_path(trans_id)
350
path = self._tree_id_paths[trans_id]
352
# the file is a new, unversioned file, or invalid trans_id
457
354
# the file is old; the old id is still valid
458
355
if self._new_root == trans_id:
459
return self._tree.path2id('')
356
return self._tree.get_root_id()
460
357
return self._tree.path2id(path)
462
359
def final_file_id(self, trans_id):
571
463
# ensure that all children are registered with the transaction
572
464
list(self.iter_tree_children(parent_id))
574
def _has_named_child(self, name, parent_id, known_children):
575
"""Does a parent already have a name child.
577
:param name: The searched for name.
579
:param parent_id: The parent for which the check is made.
581
:param known_children: The already known children. This should have
582
been recently obtained from `self.by_parent.get(parent_id)`
583
(or will be if None is passed).
585
if known_children is None:
586
known_children = self.by_parent().get(parent_id, [])
587
for child in known_children:
466
def has_named_child(self, by_parent, parent_id, name):
468
children = by_parent[parent_id]
471
for child in children:
588
472
if self.final_name(child) == name:
590
parent_path = self._tree_id_paths.get(parent_id, None)
591
if parent_path is None:
592
# No parent... no children
475
path = self._tree_id_paths[parent_id]
594
child_path = joinpath(parent_path, name)
595
child_id = self._tree_path_ids.get(child_path, None)
478
childpath = joinpath(path, name)
479
child_id = self._tree_path_ids.get(childpath)
596
480
if child_id is None:
597
# Not known by the tree transform yet, check the filesystem
598
return osutils.lexists(self._tree.abspath(child_path))
481
return lexists(self._tree.abspath(childpath))
600
raise AssertionError('child_id is missing: %s, %s, %s'
601
% (name, parent_id, child_id))
603
def _available_backup_name(self, name, target_id):
604
"""Find an available backup name.
606
:param name: The basename of the file.
608
:param target_id: The directory trans_id where the backup should
611
known_children = self.by_parent().get(target_id, [])
612
return osutils.available_backup_name(
614
lambda base: self._has_named_child(
615
base, target_id, known_children))
483
if self.final_parent(child_id) != parent_id:
485
if child_id in self._removed_contents:
486
# XXX What about dangling file-ids?
617
491
def _parent_loops(self):
618
492
"""No entry should be its own ancestor"""
724
600
def _duplicate_ids(self):
725
601
"""Each inventory id may only be used once"""
728
all_ids = self._tree.all_file_ids()
729
except errors.UnsupportedOperation:
730
# it's okay for non-file-id trees to raise UnsupportedOperation.
732
603
removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
733
604
self._removed_id))
605
all_ids = self._tree.all_file_ids()
734
606
active_tree_ids = all_ids.difference(removed_tree_ids)
735
for trans_id, file_id in viewitems(self._new_id):
607
for trans_id, file_id in self._new_id.iteritems():
736
608
if file_id in active_tree_ids:
737
path = self._tree.id2path(file_id)
738
old_trans_id = self.trans_id_tree_path(path)
609
old_trans_id = self.trans_id_tree_file_id(file_id)
739
610
conflicts.append(('duplicate id', old_trans_id, trans_id))
742
613
def _parent_type_conflicts(self, by_parent):
743
"""Children must have a directory parent"""
614
"""parents must have directory 'contents'."""
745
for parent_id, children in viewitems(by_parent):
746
if parent_id == ROOT_PARENT:
749
for child_id in children:
750
if self.final_kind(child_id) is not None:
755
# There is at least a child, so we need an existing directory to
757
kind = self.final_kind(parent_id)
616
for parent_id, children in by_parent.iteritems():
617
if parent_id is ROOT_PARENT:
619
if not self._any_contents(children):
621
for child in children:
623
self.final_kind(child)
627
kind = self.final_kind(parent_id)
759
# The directory will be deleted
760
631
conflicts.append(('missing parent', parent_id))
761
632
elif kind != "directory":
762
# Meh, we need a *directory* to put something in it
763
633
conflicts.append(('non-directory parent', parent_id))
636
def _any_contents(self, trans_ids):
637
"""Return true if any of the trans_ids, will have contents."""
638
for trans_id in trans_ids:
640
kind = self.final_kind(trans_id)
766
646
def _set_executability(self, path, trans_id):
767
647
"""Set the executability of versioned files """
768
if self._tree._supports_executable():
648
if supports_executable():
769
649
new_executability = self._new_executability[trans_id]
770
650
abspath = self._tree.abspath(path)
771
651
current_mode = os.stat(abspath).st_mode
772
652
if new_executability:
773
653
umask = os.umask(0)
775
to_mode = current_mode | (0o100 & ~umask)
655
to_mode = current_mode | (0100 & ~umask)
776
656
# Enable x-bit for others only if they can read it.
777
if current_mode & 0o004:
778
to_mode |= 0o001 & ~umask
779
if current_mode & 0o040:
780
to_mode |= 0o010 & ~umask
657
if current_mode & 0004:
658
to_mode |= 0001 & ~umask
659
if current_mode & 0040:
660
to_mode |= 0010 & ~umask
782
to_mode = current_mode & ~0o111
783
osutils.chmod_if_possible(abspath, to_mode)
662
to_mode = current_mode & ~0111
663
os.chmod(abspath, to_mode)
785
665
def _new_entry(self, name, parent_id, file_id):
786
666
"""Helper function to create a new filesystem entry."""
832
712
self.create_symlink(target, trans_id)
835
def new_orphan(self, trans_id, parent_id):
836
"""Schedule an item to be orphaned.
838
When a directory is about to be removed, its children, if they are not
839
versioned are moved out of the way: they don't have a parent anymore.
841
:param trans_id: The trans_id of the existing item.
842
:param parent_id: The parent trans_id of the item.
844
raise NotImplementedError(self.new_orphan)
846
def _get_potential_orphans(self, dir_id):
847
"""Find the potential orphans in a directory.
849
A directory can't be safely deleted if there are versioned files in it.
850
If all the contained files are unversioned then they can be orphaned.
852
The 'None' return value means that the directory contains at least one
853
versioned file and should not be deleted.
855
:param dir_id: The directory trans id.
857
:return: A list of the orphan trans ids or None if at least one
858
versioned file is present.
861
# Find the potential orphans, stop if one item should be kept
862
for child_tid in self.by_parent()[dir_id]:
863
if child_tid in self._removed_contents:
864
# The child is removed as part of the transform. Since it was
865
# versioned before, it's not an orphan
867
elif self.final_file_id(child_tid) is None:
868
# The child is not versioned
869
orphans.append(child_tid)
871
# We have a versioned file here, searching for orphans is
877
715
def _affected_ids(self):
878
716
"""Return the set of transform ids affected by the transform"""
879
717
trans_ids = set(self._removed_id)
880
trans_ids.update(self._new_id)
718
trans_ids.update(self._new_id.keys())
881
719
trans_ids.update(self._removed_contents)
882
trans_ids.update(self._new_contents)
883
trans_ids.update(self._new_executability)
884
trans_ids.update(self._new_name)
885
trans_ids.update(self._new_parent)
720
trans_ids.update(self._new_contents.keys())
721
trans_ids.update(self._new_executability.keys())
722
trans_ids.update(self._new_name.keys())
723
trans_ids.update(self._new_parent.keys())
888
726
def _get_file_id_maps(self):
989
835
to_path = final_paths.get_path(to_trans_id)
991
from_name, from_parent, from_kind, from_executable = \
992
self._from_file_data(from_trans_id, from_versioned, from_path)
994
to_name, to_parent, to_kind, to_executable = \
995
self._to_file_data(to_trans_id, from_trans_id, from_executable)
997
836
if from_kind != to_kind:
999
838
elif to_kind in ('file', 'symlink') and (
1000
to_trans_id != from_trans_id
1001
or to_trans_id in self._new_contents):
839
to_trans_id != from_trans_id or
840
to_trans_id in self._new_contents):
1003
if (not modified and from_versioned == to_versioned
1004
and from_parent == to_parent and from_name == to_name
1005
and from_executable == to_executable):
842
if (not modified and from_versioned == to_versioned and
843
from_parent==to_parent and from_name == to_name and
844
from_executable == to_executable):
1009
file_id, (from_path, to_path), modified,
1010
(from_versioned, to_versioned),
1011
(from_parent, to_parent),
1012
(from_name, to_name),
1013
(from_kind, to_kind),
1014
(from_executable, to_executable)))
1017
return (c.path[0] or '', c.path[1] or '')
1018
return iter(sorted(results, key=path_key))
846
results.append((file_id, (from_path, to_path), modified,
847
(from_versioned, to_versioned),
848
(from_parent, to_parent),
849
(from_name, to_name),
850
(from_kind, to_kind),
851
(from_executable, to_executable)))
852
return iter(sorted(results, key=lambda x:x[1]))
1020
854
def get_preview_tree(self):
1021
855
"""Return a tree representing the result of the transform.
1023
The tree is a snapshot, and altering the TreeTransform will invalidate
857
This tree only supports the subset of Tree functionality required
858
by show_diff_trees. It must only be compared to tt._tree.
1026
860
return _PreviewTree(self)
1028
def commit(self, branch, message, merge_parents=None, strict=False,
1029
timestamp=None, timezone=None, committer=None, authors=None,
1030
revprops=None, revision_id=None):
1031
"""Commit the result of this TreeTransform to a branch.
1033
:param branch: The branch to commit to.
1034
:param message: The message to attach to the commit.
1035
:param merge_parents: Additional parent revision-ids specified by
1037
:param strict: If True, abort the commit if there are unversioned
1039
:param timestamp: if not None, seconds-since-epoch for the time and
1040
date. (May be a float.)
1041
:param timezone: Optional timezone for timestamp, as an offset in
1043
:param committer: Optional committer in email-id format.
1044
(e.g. "J Random Hacker <jrandom@example.com>")
1045
:param authors: Optional list of authors in email-id format.
1046
:param revprops: Optional dictionary of revision properties.
1047
:param revision_id: Optional revision id. (Specifying a revision-id
1048
may reduce performance for some non-native formats.)
1049
:return: The revision_id of the revision committed.
1051
self._check_malformed()
1053
unversioned = set(self._new_contents).difference(set(self._new_id))
1054
for trans_id in unversioned:
1055
if self.final_file_id(trans_id) is None:
1056
raise errors.StrictCommitFailed()
1058
revno, last_rev_id = branch.last_revision_info()
1059
if last_rev_id == _mod_revision.NULL_REVISION:
1060
if merge_parents is not None:
1061
raise ValueError('Cannot supply merge parents for first'
1065
parent_ids = [last_rev_id]
1066
if merge_parents is not None:
1067
parent_ids.extend(merge_parents)
1068
if self._tree.get_revision_id() != last_rev_id:
1069
raise ValueError('TreeTransform not based on branch basis: %s' %
1070
self._tree.get_revision_id().decode('utf-8'))
1071
revprops = commit.Commit.update_revprops(revprops, branch, authors)
1072
builder = branch.get_commit_builder(parent_ids,
1073
timestamp=timestamp,
1075
committer=committer,
1077
revision_id=revision_id)
1078
preview = self.get_preview_tree()
1079
list(builder.record_iter_changes(preview, last_rev_id,
1080
self.iter_changes()))
1081
builder.finish_inventory()
1082
revision_id = builder.commit(message)
1083
branch.set_last_revision_info(revno + 1, revision_id)
1086
862
def _text_parent(self, trans_id):
1087
path = self.tree_path(trans_id)
863
file_id = self.tree_file_id(trans_id)
1089
if path is None or self._tree.kind(path) != 'file':
865
if file_id is None or self._tree.kind(file_id) != 'file':
1091
867
except errors.NoSuchFile:
1095
871
def _get_parents_texts(self, trans_id):
1096
872
"""Get texts for compression parents of this file."""
1097
path = self._text_parent(trans_id)
873
file_id = self._text_parent(trans_id)
1100
return (self._tree.get_file_text(path),)
876
return (self._tree.get_file_text(file_id),)
1102
878
def _get_parents_lines(self, trans_id):
1103
879
"""Get lines for compression parents of this file."""
1104
path = self._text_parent(trans_id)
880
file_id = self._text_parent(trans_id)
1107
return (self._tree.get_file_lines(path),)
883
return (self._tree.get_file_lines(file_id),)
1109
885
def serialize(self, serializer):
1110
886
"""Serialize this TreeTransform.
1112
888
:param serializer: A Serialiser like pack.ContainerSerializer.
1114
new_name = {k.encode('utf-8'): v.encode('utf-8')
1115
for k, v in viewitems(self._new_name)}
1116
new_parent = {k.encode('utf-8'): v.encode('utf-8')
1117
for k, v in viewitems(self._new_parent)}
1118
new_id = {k.encode('utf-8'): v
1119
for k, v in viewitems(self._new_id)}
1120
new_executability = {k.encode('utf-8'): int(v)
1121
for k, v in viewitems(self._new_executability)}
1122
tree_path_ids = {k.encode('utf-8'): v.encode('utf-8')
1123
for k, v in viewitems(self._tree_path_ids)}
1124
non_present_ids = {k: v.encode('utf-8')
1125
for k, v in viewitems(self._non_present_ids)}
1126
removed_contents = [trans_id.encode('utf-8')
1127
for trans_id in self._removed_contents]
1128
removed_id = [trans_id.encode('utf-8')
1129
for trans_id in self._removed_id]
890
new_name = dict((k, v.encode('utf-8')) for k, v in
891
self._new_name.items())
892
new_executability = dict((k, int(v)) for k, v in
893
self._new_executability.items())
894
tree_path_ids = dict((k.encode('utf-8'), v)
895
for k, v in self._tree_path_ids.items())
1131
b'_id_number': self._id_number,
1132
b'_new_name': new_name,
1133
b'_new_parent': new_parent,
1134
b'_new_executability': new_executability,
1136
b'_tree_path_ids': tree_path_ids,
1137
b'_removed_id': removed_id,
1138
b'_removed_contents': removed_contents,
1139
b'_non_present_ids': non_present_ids,
897
'_id_number': self._id_number,
898
'_new_name': new_name,
899
'_new_parent': self._new_parent,
900
'_new_executability': new_executability,
901
'_new_id': self._new_id,
902
'_tree_path_ids': tree_path_ids,
903
'_removed_id': list(self._removed_id),
904
'_removed_contents': list(self._removed_contents),
905
'_non_present_ids': self._non_present_ids,
1141
907
yield serializer.bytes_record(bencode.bencode(attribs),
1143
for trans_id, kind in sorted(viewitems(self._new_contents)):
909
for trans_id, kind in self._new_contents.items():
1144
910
if kind == 'file':
1145
with open(self._limbo_name(trans_id), 'rb') as cur_file:
1146
lines = cur_file.readlines()
911
lines = osutils.chunks_to_lines(
912
self._read_file_chunks(trans_id))
1147
913
parents = self._get_parents_lines(trans_id)
1148
914
mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1149
content = b''.join(mpdiff.to_patch())
915
content = ''.join(mpdiff.to_patch())
1150
916
if kind == 'directory':
1152
918
if kind == 'symlink':
1153
919
content = self._read_symlink_target(trans_id)
1154
if not isinstance(content, bytes):
1155
content = content.encode('utf-8')
1156
yield serializer.bytes_record(
1157
content, ((trans_id.encode('utf-8'), kind.encode('ascii')),))
920
yield serializer.bytes_record(content, ((trans_id, kind),))
1159
922
def deserialize(self, records):
1160
923
"""Deserialize a stored TreeTransform.
1162
925
:param records: An iterable of (names, content) tuples, as per
1163
926
pack.ContainerPushParser.
1165
names, content = next(records)
928
names, content = records.next()
1166
929
attribs = bencode.bdecode(content)
1167
self._id_number = attribs[b'_id_number']
1168
self._new_name = {k.decode('utf-8'): v.decode('utf-8')
1169
for k, v in viewitems(attribs[b'_new_name'])}
1170
self._new_parent = {k.decode('utf-8'): v.decode('utf-8')
1171
for k, v in viewitems(attribs[b'_new_parent'])}
1172
self._new_executability = {
1173
k.decode('utf-8'): bool(v)
1174
for k, v in viewitems(attribs[b'_new_executability'])}
1175
self._new_id = {k.decode('utf-8'): v
1176
for k, v in viewitems(attribs[b'_new_id'])}
1177
self._r_new_id = {v: k for k, v in viewitems(self._new_id)}
930
self._id_number = attribs['_id_number']
931
self._new_name = dict((k, v.decode('utf-8'))
932
for k, v in attribs['_new_name'].items())
933
self._new_parent = attribs['_new_parent']
934
self._new_executability = dict((k, bool(v)) for k, v in
935
attribs['_new_executability'].items())
936
self._new_id = attribs['_new_id']
937
self._r_new_id = dict((v, k) for k, v in self._new_id.items())
1178
938
self._tree_path_ids = {}
1179
939
self._tree_id_paths = {}
1180
for bytepath, trans_id in viewitems(attribs[b'_tree_path_ids']):
940
for bytepath, trans_id in attribs['_tree_path_ids'].items():
1181
941
path = bytepath.decode('utf-8')
1182
trans_id = trans_id.decode('utf-8')
1183
942
self._tree_path_ids[path] = trans_id
1184
943
self._tree_id_paths[trans_id] = path
1185
self._removed_id = {trans_id.decode('utf-8')
1186
for trans_id in attribs[b'_removed_id']}
1187
self._removed_contents = set(
1188
trans_id.decode('utf-8')
1189
for trans_id in attribs[b'_removed_contents'])
1190
self._non_present_ids = {
1191
k: v.decode('utf-8')
1192
for k, v in viewitems(attribs[b'_non_present_ids'])}
944
self._removed_id = set(attribs['_removed_id'])
945
self._removed_contents = set(attribs['_removed_contents'])
946
self._non_present_ids = attribs['_non_present_ids']
1193
947
for ((trans_id, kind),), content in records:
1194
trans_id = trans_id.decode('utf-8')
1195
kind = kind.decode('ascii')
1196
948
if kind == 'file':
1197
949
mpdiff = multiparent.MultiParent.from_patch(content)
1198
950
lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
1202
954
if kind == 'symlink':
1203
955
self.create_symlink(content.decode('utf-8'), trans_id)
1205
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1206
"""Schedule creation of a new file.
1210
:param contents: an iterator of strings, all of which will be written
1211
to the target destination.
1212
:param trans_id: TreeTransform handle
1213
:param mode_id: If not None, force the mode of the target file to match
1214
the mode of the object referenced by mode_id.
1215
Otherwise, we will try to preserve mode bits of an existing file.
1216
:param sha1: If the sha1 of this content is already known, pass it in.
1217
We can use it to prevent future sha1 computations.
1219
raise NotImplementedError(self.create_file)
1221
def create_directory(self, trans_id):
1222
"""Schedule creation of a new directory.
1224
See also new_directory.
1226
raise NotImplementedError(self.create_directory)
1228
def create_symlink(self, target, trans_id):
1229
"""Schedule creation of a new symbolic link.
1231
target is a bytestring.
1232
See also new_symlink.
1234
raise NotImplementedError(self.create_symlink)
1236
def create_hardlink(self, path, trans_id):
1237
"""Schedule creation of a hard link"""
1238
raise NotImplementedError(self.create_hardlink)
1240
def cancel_creation(self, trans_id):
1241
"""Cancel the creation of new file contents."""
1242
raise NotImplementedError(self.cancel_creation)
1245
958
class DiskTreeTransform(TreeTransformBase):
1246
959
"""Tree transform storing its contents on disk."""
1248
def __init__(self, tree, limbodir, pb=None, case_sensitive=True):
961
def __init__(self, tree, limbodir, pb=DummyProgress(),
962
case_sensitive=True):
1250
964
:param tree: The tree that will be transformed, but not necessarily
1251
965
the output tree.
1252
966
:param limbodir: A directory where new files can be stored until
1253
967
they are installed in their proper places
968
:param pb: A ProgressBar indicating how much progress is being made
1255
969
:param case_sensitive: If True, the target of the transform is
1256
970
case sensitive, not just case preserving.
1306
1010
TreeTransformBase.finalize(self)
1308
def _limbo_supports_executable(self):
1309
"""Check if the limbo path supports the executable bit."""
1310
return osutils.supports_executable(self._limbodir)
1312
1012
def _limbo_name(self, trans_id):
1313
1013
"""Generate the limbo name of a file"""
1314
1014
limbo_name = self._limbo_files.get(trans_id)
1315
if limbo_name is None:
1316
limbo_name = self._generate_limbo_path(trans_id)
1317
self._limbo_files[trans_id] = limbo_name
1015
if limbo_name is not None:
1017
parent = self._new_parent.get(trans_id)
1018
# if the parent directory is already in limbo (e.g. when building a
1019
# tree), choose a limbo name inside the parent, to reduce further
1021
use_direct_path = False
1022
if self._new_contents.get(parent) == 'directory':
1023
filename = self._new_name.get(trans_id)
1024
if filename is not None:
1025
if parent not in self._limbo_children:
1026
self._limbo_children[parent] = set()
1027
self._limbo_children_names[parent] = {}
1028
use_direct_path = True
1029
# the direct path can only be used if no other file has
1030
# already taken this pathname, i.e. if the name is unused, or
1031
# if it is already associated with this trans_id.
1032
elif self._case_sensitive_target:
1033
if (self._limbo_children_names[parent].get(filename)
1034
in (trans_id, None)):
1035
use_direct_path = True
1037
for l_filename, l_trans_id in\
1038
self._limbo_children_names[parent].iteritems():
1039
if l_trans_id == trans_id:
1041
if l_filename.lower() == filename.lower():
1044
use_direct_path = True
1047
limbo_name = pathjoin(self._limbo_files[parent], filename)
1048
self._limbo_children[parent].add(trans_id)
1049
self._limbo_children_names[parent][filename] = trans_id
1051
limbo_name = pathjoin(self._limbodir, trans_id)
1052
self._needs_rename.add(trans_id)
1053
self._limbo_files[trans_id] = limbo_name
1318
1054
return limbo_name
1320
def _generate_limbo_path(self, trans_id):
1321
"""Generate a limbo path using the trans_id as the relative path.
1323
This is suitable as a fallback, and when the transform should not be
1324
sensitive to the path encoding of the limbo directory.
1326
self._needs_rename.add(trans_id)
1327
return pathjoin(self._limbodir, trans_id)
1329
1056
def adjust_path(self, name, parent, trans_id):
1330
1057
previous_parent = self._new_parent.get(trans_id)
1331
1058
previous_name = self._new_name.get(trans_id)
1332
1059
TreeTransformBase.adjust_path(self, name, parent, trans_id)
1333
if (trans_id in self._limbo_files
1334
and trans_id not in self._needs_rename):
1060
if (trans_id in self._limbo_files and
1061
trans_id not in self._needs_rename):
1335
1062
self._rename_in_limbo([trans_id])
1336
if previous_parent != parent:
1337
self._limbo_children[previous_parent].remove(trans_id)
1338
if previous_parent != parent or previous_name != name:
1339
del self._limbo_children_names[previous_parent][previous_name]
1063
self._limbo_children[previous_parent].remove(trans_id)
1064
del self._limbo_children_names[previous_parent][previous_name]
1341
1066
def _rename_in_limbo(self, trans_ids):
1342
1067
"""Fix limbo names so that the right final path is produced.
1350
1075
entries from _limbo_files, because they are now stale.
1352
1077
for trans_id in trans_ids:
1353
old_path = self._limbo_files[trans_id]
1354
self._possibly_stale_limbo_files.add(old_path)
1355
del self._limbo_files[trans_id]
1078
old_path = self._limbo_files.pop(trans_id)
1356
1079
if trans_id not in self._new_contents:
1358
1081
new_path = self._limbo_name(trans_id)
1359
1082
os.rename(old_path, new_path)
1360
self._possibly_stale_limbo_files.remove(old_path)
1361
for descendant in self._limbo_descendants(trans_id):
1362
desc_path = self._limbo_files[descendant]
1363
desc_path = new_path + desc_path[len(old_path):]
1364
self._limbo_files[descendant] = desc_path
1366
def _limbo_descendants(self, trans_id):
1367
"""Return the set of trans_ids whose limbo paths descend from this."""
1368
descendants = set(self._limbo_children.get(trans_id, []))
1369
for descendant in list(descendants):
1370
descendants.update(self._limbo_descendants(descendant))
1373
def create_file(self, contents, trans_id, mode_id=None, sha1=None):
1084
def create_file(self, contents, trans_id, mode_id=None):
1374
1085
"""Schedule creation of a new file.
1378
:param contents: an iterator of strings, all of which will be written
1379
to the target destination.
1380
:param trans_id: TreeTransform handle
1381
:param mode_id: If not None, force the mode of the target file to match
1382
the mode of the object referenced by mode_id.
1383
Otherwise, we will try to preserve mode bits of an existing file.
1384
:param sha1: If the sha1 of this content is already known, pass it in.
1385
We can use it to prevent future sha1 computations.
1089
Contents is an iterator of strings, all of which will be written
1090
to the target destination.
1092
New file takes the permissions of any existing file with that id,
1093
unless mode_id is specified.
1387
1095
name = self._limbo_name(trans_id)
1388
with open(name, 'wb') as f:
1389
unique_add(self._new_contents, trans_id, 'file')
1096
f = open(name, 'wb')
1099
unique_add(self._new_contents, trans_id, 'file')
1101
# Clean up the file, it never got registered so
1102
# TreeTransform.finalize() won't clean it up.
1390
1107
f.writelines(contents)
1391
self._set_mtime(name)
1392
1110
self._set_mode(trans_id, mode_id, S_ISREG)
1393
# It is unfortunate we have to use lstat instead of fstat, but we just
1394
# used utime and chmod on the file, so we need the accurate final
1396
if sha1 is not None:
1397
self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
1112
def _read_file_chunks(self, trans_id):
1113
cur_file = open(self._limbo_name(trans_id), 'rb')
1115
return cur_file.readlines()
1399
1119
def _read_symlink_target(self, trans_id):
1400
1120
return os.readlink(self._limbo_name(trans_id))
1402
def _set_mtime(self, path):
1403
"""All files that are created get the same mtime.
1405
This time is set by the first object to be created.
1407
if self._creation_mtime is None:
1408
self._creation_mtime = time.time()
1409
os.utime(path, (self._creation_mtime, self._creation_mtime))
1411
1122
def create_hardlink(self, path, trans_id):
1412
1123
"""Schedule creation of a hard link"""
1413
1124
name = self._limbo_name(trans_id)
1415
1126
os.link(path, name)
1416
except OSError as e:
1417
1128
if e.errno != errno.EPERM:
1419
1130
raise errors.HardLinkNotSupported(path)
1421
1132
unique_add(self._new_contents, trans_id, 'file')
1422
except BaseException:
1423
1134
# Clean up the file, it never got registered so
1424
1135
# TreeTransform.finalize() won't clean it up.
1425
1136
os.unlink(name)
1467
1172
del self._limbo_children_names[trans_id]
1468
1173
delete_any(self._limbo_name(trans_id))
1470
def new_orphan(self, trans_id, parent_id):
1471
conf = self._tree.get_config_stack()
1472
handle_orphan = conf.get('transform.orphan_policy')
1473
handle_orphan(self, trans_id, parent_id)
1476
class OrphaningError(errors.BzrError):
1478
# Only bugs could lead to such exception being seen by the user
1479
internal_error = True
1480
_fmt = "Error while orphaning %s in %s directory"
1482
def __init__(self, orphan, parent):
1483
errors.BzrError.__init__(self)
1484
self.orphan = orphan
1485
self.parent = parent
1488
class OrphaningForbidden(OrphaningError):
1490
_fmt = "Policy: %s doesn't allow creating orphans."
1492
def __init__(self, policy):
1493
errors.BzrError.__init__(self)
1494
self.policy = policy
1497
def move_orphan(tt, orphan_id, parent_id):
1498
"""See TreeTransformBase.new_orphan.
1500
This creates a new orphan in the `brz-orphans` dir at the root of the
1503
:param tt: The TreeTransform orphaning `trans_id`.
1505
:param orphan_id: The trans id that should be orphaned.
1507
:param parent_id: The orphan parent trans id.
1509
# Add the orphan dir if it doesn't exist
1510
orphan_dir_basename = 'brz-orphans'
1511
od_id = tt.trans_id_tree_path(orphan_dir_basename)
1512
if tt.final_kind(od_id) is None:
1513
tt.create_directory(od_id)
1514
parent_path = tt._tree_id_paths[parent_id]
1515
# Find a name that doesn't exist yet in the orphan dir
1516
actual_name = tt.final_name(orphan_id)
1517
new_name = tt._available_backup_name(actual_name, od_id)
1518
tt.adjust_path(new_name, od_id, orphan_id)
1519
trace.warning('%s has been orphaned in %s'
1520
% (joinpath(parent_path, actual_name), orphan_dir_basename))
1523
def refuse_orphan(tt, orphan_id, parent_id):
1524
"""See TreeTransformBase.new_orphan.
1526
This refuses to create orphan, letting the caller handle the conflict.
1528
raise OrphaningForbidden('never')
1531
orphaning_registry = registry.Registry()
1532
orphaning_registry.register(
1533
u'conflict', refuse_orphan,
1534
'Leave orphans in place and create a conflict on the directory.')
1535
orphaning_registry.register(
1536
u'move', move_orphan,
1537
'Move orphans into the brz-orphans directory.')
1538
orphaning_registry._set_default_key(u'conflict')
1541
opt_transform_orphan = _mod_config.RegistryOption(
1542
'transform.orphan_policy', orphaning_registry,
1543
help='Policy for orphaned files during transform operations.',
1547
1176
class TreeTransform(DiskTreeTransform):
1548
1177
"""Represent a tree transformation.
1719
1355
yield self.trans_id_tree_path(childpath)
1721
def _generate_limbo_path(self, trans_id):
1722
"""Generate a limbo path using the final path if possible.
1724
This optimizes the performance of applying the tree transform by
1725
avoiding renames. These renames can be avoided only when the parent
1726
directory is already scheduled for creation.
1728
If the final path cannot be used, falls back to using the trans_id as
1731
parent = self._new_parent.get(trans_id)
1732
# if the parent directory is already in limbo (e.g. when building a
1733
# tree), choose a limbo name inside the parent, to reduce further
1735
use_direct_path = False
1736
if self._new_contents.get(parent) == 'directory':
1737
filename = self._new_name.get(trans_id)
1738
if filename is not None:
1739
if parent not in self._limbo_children:
1740
self._limbo_children[parent] = set()
1741
self._limbo_children_names[parent] = {}
1742
use_direct_path = True
1743
# the direct path can only be used if no other file has
1744
# already taken this pathname, i.e. if the name is unused, or
1745
# if it is already associated with this trans_id.
1746
elif self._case_sensitive_target:
1747
if (self._limbo_children_names[parent].get(filename)
1748
in (trans_id, None)):
1749
use_direct_path = True
1751
for l_filename, l_trans_id in viewitems(
1752
self._limbo_children_names[parent]):
1753
if l_trans_id == trans_id:
1755
if l_filename.lower() == filename.lower():
1758
use_direct_path = True
1760
if not use_direct_path:
1761
return DiskTreeTransform._generate_limbo_path(self, trans_id)
1763
limbo_name = pathjoin(self._limbo_files[parent], filename)
1764
self._limbo_children[parent].add(trans_id)
1765
self._limbo_children_names[parent][filename] = trans_id
1768
1358
def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1769
1359
"""Apply all changes to the inventory and filesystem.
1912
1509
new_paths = self.new_paths(filesystem_only=True)
1913
1510
modified_paths = []
1914
with ui.ui_factory.nested_progress_bar() as child_pb:
1511
new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
1513
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1915
1515
for num, (path, trans_id) in enumerate(new_paths):
1916
1516
if (num % 10) == 0:
1917
child_pb.update(gettext('adding file'),
1918
num, len(new_paths))
1517
child_pb.update('adding file', num, len(new_paths))
1919
1518
full_path = self._tree.abspath(path)
1920
1519
if trans_id in self._needs_rename:
1922
1521
mover.rename(self._limbo_name(trans_id), full_path)
1923
except errors.TransformRenameFailed as e:
1924
1523
# We may be renaming a dangling inventory id
1925
1524
if e.errno != errno.ENOENT:
1928
1527
self.rename_count += 1
1929
# TODO: if trans_id in self._observed_sha1s, we should
1930
# re-stat the final target, since ctime will be
1931
# updated by the change.
1932
if (trans_id in self._new_contents
1933
or self.path_changed(trans_id)):
1528
if (trans_id in self._new_contents or
1529
self.path_changed(trans_id)):
1934
1530
if trans_id in self._new_contents:
1935
1531
modified_paths.append(full_path)
1936
1532
if trans_id in self._new_executability:
1937
1533
self._set_executability(path, trans_id)
1938
if trans_id in self._observed_sha1s:
1939
o_sha1, o_st_val = self._observed_sha1s[trans_id]
1940
st = osutils.lstat(full_path)
1941
self._observed_sha1s[trans_id] = (o_sha1, st)
1942
for path, trans_id in new_paths:
1943
# new_paths includes stuff like workingtree conflicts. Only the
1944
# stuff in new_contents actually comes from limbo.
1945
if trans_id in self._limbo_files:
1946
del self._limbo_files[trans_id]
1947
1536
self._new_contents.clear()
1948
1537
return modified_paths
1950
def _apply_observed_sha1s(self):
1951
"""After we have finished renaming everything, update observed sha1s
1953
This has to be done after self._tree.apply_inventory_delta, otherwise
1954
it doesn't know anything about the files we are updating. Also, we want
1955
to do this as late as possible, so that most entries end up cached.
1957
# TODO: this doesn't update the stat information for directories. So
1958
# the first 'bzr status' will still need to rewrite
1959
# .bzr/checkout/dirstate. However, we at least don't need to
1960
# re-read all of the files.
1961
# TODO: If the operation took a while, we could do a time.sleep(3) here
1962
# to allow the clock to tick over and ensure we won't have any
1963
# problems. (we could observe start time, and finish time, and if
1964
# it is less than eg 10% overhead, add a sleep call.)
1965
paths = FinalPaths(self)
1966
for trans_id, observed in viewitems(self._observed_sha1s):
1967
path = paths.get_path(trans_id)
1968
self._tree._observed_sha1(path, observed)
1971
1540
class TransformPreview(DiskTreeTransform):
1972
1541
"""A TreeTransform for generating preview trees.
2274
1814
if not path.startswith(prefix):
2276
1816
path = path[len(prefix):]
2277
yield path, 'V', entry.kind, entry
1817
yield path, 'V', entry.kind, entry.file_id, entry
2279
1819
if from_dir is None and include_root is True:
2280
root_entry = inventory.make_entry(
2281
'directory', '', ROOT_PARENT, self.path2id(''))
2282
yield '', 'V', 'directory', root_entry
1820
root_entry = inventory.make_entry('directory', '',
1821
ROOT_PARENT, self.get_root_id())
1822
yield '', 'V', 'directory', root_entry.file_id, root_entry
2283
1823
entries = self._iter_entries_for_dir(from_dir or '')
2284
1824
for path, entry in entries:
2285
yield path, 'V', entry.kind, entry
1825
yield path, 'V', entry.kind, entry.file_id, entry
2287
def kind(self, path):
2288
trans_id = self._path2trans_id(path)
2289
if trans_id is None:
2290
raise errors.NoSuchFile(path)
1827
def kind(self, file_id):
1828
trans_id = self._transform.trans_id_file_id(file_id)
2291
1829
return self._transform.final_kind(trans_id)
2293
def stored_kind(self, path):
2294
trans_id = self._path2trans_id(path)
2295
if trans_id is None:
2296
raise errors.NoSuchFile(path)
1831
def stored_kind(self, file_id):
1832
trans_id = self._transform.trans_id_file_id(file_id)
2298
1834
return self._transform._new_contents[trans_id]
2299
1835
except KeyError:
2300
return self._transform._tree.stored_kind(path)
1836
return self._transform._tree.stored_kind(file_id)
2302
def get_file_mtime(self, path):
1838
def get_file_mtime(self, file_id, path=None):
2303
1839
"""See Tree.get_file_mtime"""
2304
file_id = self.path2id(path)
2306
raise errors.NoSuchFile(path)
2307
1840
if not self._content_change(file_id):
2308
return self._transform._tree.get_file_mtime(
2309
self._transform._tree.id2path(file_id))
2310
trans_id = self._path2trans_id(path)
2311
return self._stat_limbo_file(trans_id).st_mtime
2313
def get_file_size(self, path):
1841
return self._transform._tree.get_file_mtime(file_id, path)
1842
return self._stat_limbo_file(file_id).st_mtime
1844
def _file_size(self, entry, stat_value):
1845
return self.get_file_size(entry.file_id)
1847
def get_file_size(self, file_id):
2314
1848
"""See Tree.get_file_size"""
2315
trans_id = self._path2trans_id(path)
2316
if trans_id is None:
2317
raise errors.NoSuchFile(path)
2318
kind = self._transform.final_kind(trans_id)
2321
if trans_id in self._transform._new_contents:
2322
return self._stat_limbo_file(trans_id).st_size
2323
if self.kind(path) == 'file':
2324
return self._transform._tree.get_file_size(path)
1849
if self.kind(file_id) == 'file':
1850
return self._transform._tree.get_file_size(file_id)
2328
def get_file_verifier(self, path, stat_value=None):
2329
trans_id = self._path2trans_id(path)
2330
if trans_id is None:
2331
raise errors.NoSuchFile(path)
2332
kind = self._transform._new_contents.get(trans_id)
2334
return self._transform._tree.get_file_verifier(path)
2336
with self.get_file(path) as fileobj:
2337
return ("SHA1", sha_file(fileobj))
2339
def get_file_sha1(self, path, stat_value=None):
2340
trans_id = self._path2trans_id(path)
2341
if trans_id is None:
2342
raise errors.NoSuchFile(path)
2343
kind = self._transform._new_contents.get(trans_id)
2345
return self._transform._tree.get_file_sha1(path)
2347
with self.get_file(path) as fileobj:
1854
def get_file_sha1(self, file_id, path=None, stat_value=None):
1855
trans_id = self._transform.trans_id_file_id(file_id)
1856
kind = self._transform._new_contents.get(trans_id)
1858
return self._transform._tree.get_file_sha1(file_id)
1860
fileobj = self.get_file(file_id)
2348
1862
return sha_file(fileobj)
2350
def get_reference_revision(self, path):
2351
trans_id = self._path2trans_id(path)
2352
if trans_id is None:
2353
raise errors.NoSuchFile(path)
2354
reference_revision = self._transform._new_reference_revision.get(trans_id)
2355
if reference_revision is None:
2356
return self._transform._tree.get_reference_revision(path)
2357
return reference_revision
2359
def is_executable(self, path):
2360
trans_id = self._path2trans_id(path)
2361
if trans_id is None:
1866
def is_executable(self, file_id, path=None):
1869
trans_id = self._transform.trans_id_file_id(file_id)
2364
1871
return self._transform._new_executability[trans_id]
2365
1872
except KeyError:
2367
return self._transform._tree.is_executable(path)
2368
except OSError as e:
1874
return self._transform._tree.is_executable(file_id, path)
2369
1876
if e.errno == errno.ENOENT:
2372
except errors.NoSuchFile:
1879
except errors.NoSuchId:
2375
def has_filename(self, path):
2376
trans_id = self._path2trans_id(path)
2377
if trans_id in self._transform._new_contents:
2379
elif trans_id in self._transform._removed_contents:
2382
return self._transform._tree.has_filename(path)
2384
1882
def path_content_summary(self, path):
2385
1883
trans_id = self._path2trans_id(path)
2386
1884
tt = self._transform
2399
1897
if kind == 'file':
2400
1898
statval = os.lstat(limbo_name)
2401
1899
size = statval.st_size
2402
if not tt._limbo_supports_executable():
1900
if not supports_executable():
2405
1903
executable = statval.st_mode & S_IEXEC
2408
1906
executable = None
2409
1907
if kind == 'symlink':
2410
link_or_sha1 = os.readlink(limbo_name)
2411
if not isinstance(link_or_sha1, text_type):
2412
link_or_sha1 = link_or_sha1.decode(osutils._fs_enc)
2413
executable = tt._new_executability.get(trans_id, executable)
1908
link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
1909
if supports_executable():
1910
executable = tt._new_executability.get(trans_id, executable)
2414
1911
return kind, size, executable, link_or_sha1
2416
1913
def iter_changes(self, from_tree, include_unchanged=False,
2417
specific_files=None, pb=None, extra_trees=None,
2418
require_versioned=True, want_unversioned=False):
1914
specific_files=None, pb=None, extra_trees=None,
1915
require_versioned=True, want_unversioned=False):
2419
1916
"""See InterTree.iter_changes.
2421
1918
This has a fast path that is only used when the from_tree matches
2422
1919
the transform tree, and no fancy options are supplied.
2424
if (from_tree is not self._transform._tree or include_unchanged
2425
or specific_files or want_unversioned):
1921
if (from_tree is not self._transform._tree or include_unchanged or
1922
specific_files or want_unversioned):
2426
1923
return tree.InterTree(from_tree, self).iter_changes(
2427
1924
include_unchanged=include_unchanged,
2428
1925
specific_files=specific_files,
2434
1931
raise ValueError('want_unversioned is not supported')
2435
1932
return self._transform.iter_changes()
2437
def get_file(self, path):
1934
def get_file(self, file_id, path=None):
2438
1935
"""See Tree.get_file"""
2439
file_id = self.path2id(path)
2440
1936
if not self._content_change(file_id):
2441
return self._transform._tree.get_file(path)
2442
trans_id = self._path2trans_id(path)
1937
return self._transform._tree.get_file(file_id, path)
1938
trans_id = self._transform.trans_id_file_id(file_id)
2443
1939
name = self._transform._limbo_name(trans_id)
2444
1940
return open(name, 'rb')
2446
def get_file_with_stat(self, path):
2447
return self.get_file(path), None
1942
def get_file_with_stat(self, file_id, path=None):
1943
return self.get_file(file_id, path), None
2449
def annotate_iter(self, path,
1945
def annotate_iter(self, file_id,
2450
1946
default_revision=_mod_revision.CURRENT_REVISION):
2451
file_id = self.path2id(path)
2452
changes = self._iter_changes_cache.get(file_id)
1947
changes = self._changes(file_id)
2453
1948
if changes is None:
2456
changed_content, versioned, kind = (
2457
changes.changed_content, changes.versioned, changes.kind)
1951
changed_content, versioned, kind = (changes[2], changes[3],
2458
1953
if kind[1] is None:
2460
1955
get_old = (kind[0] == 'file' and versioned[0])
2462
old_annotation = self._transform._tree.annotate_iter(
2463
path, default_revision=default_revision)
1957
old_annotation = self._transform._tree.annotate_iter(file_id,
1958
default_revision=default_revision)
2465
1960
old_annotation = []
2466
1961
if changes is None:
2593
2093
:param delta_from_tree: If true, build_tree may use the input Tree to
2594
2094
generate the inventory delta.
2596
with cleanup.ExitStack() as exit_stack:
2597
exit_stack.enter_context(wt.lock_tree_write())
2598
exit_stack.enter_context(tree.lock_read())
2599
if accelerator_tree is not None:
2600
exit_stack.enter_context(accelerator_tree.lock_read())
2601
return _build_tree(tree, wt, accelerator_tree, hardlink,
2096
wt.lock_tree_write()
2100
if accelerator_tree is not None:
2101
accelerator_tree.lock_read()
2103
return _build_tree(tree, wt, accelerator_tree, hardlink,
2106
if accelerator_tree is not None:
2107
accelerator_tree.unlock()
2605
2114
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
2606
2115
"""See build_tree."""
2607
for num, _unused in enumerate(wt.all_versioned_paths()):
2116
for num, _unused in enumerate(wt.all_file_ids()):
2608
2117
if num > 0: # more than just a root
2609
2118
raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
2119
existing_files = set()
2120
for dir, files in wt.walkdirs():
2121
existing_files.update(f[0] for f in files)
2610
2122
file_trans_id = {}
2611
top_pb = ui.ui_factory.nested_progress_bar()
2123
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2612
2124
pp = ProgressPhase("Build phase", 2, top_pb)
2613
if tree.path2id('') is not None:
2125
if tree.inventory.root is not None:
2614
2126
# This is kind of a hack: we should be altering the root
2615
2127
# as part of the regular tree shape diff logic.
2616
2128
# The conditional test here is to avoid doing an
2723
2226
new_desired_files = desired_files
2725
2228
iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2727
change.path for change in iter
2728
if not (change.changed_content or change.executable[0] != change.executable[1])]
2729
if accelerator_tree.supports_content_filtering():
2730
unchanged = [(tp, ap) for (tp, ap) in unchanged
2731
if not next(accelerator_tree.iter_search_rules([ap]))]
2732
unchanged = dict(unchanged)
2229
unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2230
in iter if not (c or e[0] != e[1]))
2733
2231
new_desired_files = []
2735
for unused_tree_path, (trans_id, file_id, tree_path, text_sha1) in desired_files:
2736
accelerator_path = unchanged.get(tree_path)
2233
for file_id, (trans_id, tree_path) in desired_files:
2234
accelerator_path = unchanged.get(file_id)
2737
2235
if accelerator_path is None:
2738
new_desired_files.append((tree_path,
2739
(trans_id, file_id, tree_path, text_sha1)))
2236
new_desired_files.append((file_id, (trans_id, tree_path)))
2741
pb.update(gettext('Adding file contents'), count + offset, total)
2238
pb.update('Adding file contents', count + offset, total)
2743
2240
tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
2746
with accelerator_tree.get_file(accelerator_path) as f:
2747
chunks = osutils.file_iterator(f)
2748
if wt.supports_content_filtering():
2749
filters = wt._content_filter_stack(tree_path)
2750
chunks = filtered_output_bytes(chunks, filters,
2751
ContentFilterContext(tree_path, tree))
2752
tt.create_file(chunks, trans_id, sha1=text_sha1)
2243
contents = accelerator_tree.get_file(file_id, accelerator_path)
2244
if wt.supports_content_filtering():
2245
filters = wt._content_filter_stack(tree_path)
2246
contents = filtered_output_bytes(contents, filters,
2247
ContentFilterContext(tree_path, tree))
2249
tt.create_file(contents, trans_id)
2253
except AttributeError:
2254
# after filtering, contents may no longer be file-like
2754
2257
offset += count
2755
for count, ((trans_id, file_id, tree_path, text_sha1), contents) in enumerate(
2258
for count, ((trans_id, tree_path), contents) in enumerate(
2756
2259
tree.iter_files_bytes(new_desired_files)):
2757
2260
if wt.supports_content_filtering():
2758
2261
filters = wt._content_filter_stack(tree_path)
2759
2262
contents = filtered_output_bytes(contents, filters,
2760
ContentFilterContext(tree_path, tree))
2761
tt.create_file(contents, trans_id, sha1=text_sha1)
2762
pb.update(gettext('Adding file contents'), count + offset, total)
2263
ContentFilterContext(tree_path, tree))
2264
tt.create_file(contents, trans_id)
2265
pb.update('Adding file contents', count + offset, total)
2765
2268
def _reparent_children(tt, old_parent, new_parent):
2766
2269
for child in tt.iter_tree_children(old_parent):
2767
2270
tt.adjust_path(tt.final_name(child), new_parent, child)
2770
2272
def _reparent_transform_children(tt, old_parent, new_parent):
2771
2273
by_parent = tt.by_parent()
2772
2274
for child in by_parent[old_parent]:
2773
2275
tt.adjust_path(tt.final_name(child), new_parent, child)
2774
2276
return by_parent[old_parent]
2777
def _content_match(tree, entry, tree_path, kind, target_path):
2278
def _content_match(tree, entry, file_id, kind, target_path):
2778
2279
if entry.kind != kind:
2780
2281
if entry.kind == "directory":
2782
2283
if entry.kind == "file":
2783
with open(target_path, 'rb') as f1, \
2784
tree.get_file(tree_path) as f2:
2785
if osutils.compare_files(f1, f2):
2284
if tree.get_file(file_id).read() == file(target_path, 'rb').read():
2787
2286
elif entry.kind == "symlink":
2788
if tree.get_symlink_target(tree_path) == os.readlink(target_path):
2287
if tree.get_symlink_target(file_id) == os.readlink(target_path):
2809
2308
final_parent = tt.final_parent(old_file)
2810
2309
if new_file in divert:
2811
new_name = tt.final_name(old_file) + '.diverted'
2310
new_name = tt.final_name(old_file)+'.diverted'
2812
2311
tt.adjust_path(new_name, final_parent, new_file)
2813
2312
new_conflicts.add((c_type, 'Diverted to',
2814
2313
new_file, old_file))
2816
new_name = tt.final_name(old_file) + '.moved'
2315
new_name = tt.final_name(old_file)+'.moved'
2817
2316
tt.adjust_path(new_name, final_parent, old_file)
2818
2317
new_conflicts.add((c_type, 'Moved existing file to',
2819
2318
old_file, new_file))
2820
2319
return new_conflicts
2823
def new_by_entry(path, tt, entry, parent_id, tree):
2322
def new_by_entry(tt, entry, parent_id, tree):
2824
2323
"""Create a new file according to its inventory entry"""
2825
2324
name = entry.name
2826
2325
kind = entry.kind
2827
2326
if kind == 'file':
2828
with tree.get_file(path) as f:
2829
executable = tree.is_executable(path)
2831
name, parent_id, osutils.file_iterator(f), entry.file_id,
2327
contents = tree.get_file(entry.file_id).readlines()
2328
executable = tree.is_executable(entry.file_id)
2329
return tt.new_file(name, parent_id, contents, entry.file_id,
2833
2331
elif kind in ('directory', 'tree-reference'):
2834
2332
trans_id = tt.new_directory(name, parent_id, entry.file_id)
2835
2333
if kind == 'tree-reference':
2836
2334
tt.set_tree_reference(entry.reference_revision, trans_id)
2837
2335
return trans_id
2838
2336
elif kind == 'symlink':
2839
target = tree.get_symlink_target(path)
2337
target = tree.get_symlink_target(entry.file_id)
2840
2338
return tt.new_symlink(name, parent_id, target, entry.file_id)
2842
2340
raise errors.BadFileKindError(name, kind)
2845
def create_from_tree(tt, trans_id, tree, path, chunks=None,
2846
filter_tree_path=None):
2847
"""Create new file contents according to tree contents.
2343
@deprecated_function(deprecated_in((1, 9, 0)))
2344
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
2345
"""Create new file contents according to an inventory entry.
2849
:param filter_tree_path: the tree path to use to lookup
2850
content filters to apply to the bytes output in the working tree.
2851
This only applies if the working tree supports content filtering.
2347
DEPRECATED. Use create_from_tree instead.
2853
kind = tree.kind(path)
2349
if entry.kind == "file":
2351
lines = tree.get_file(entry.file_id).readlines()
2352
tt.create_file(lines, trans_id, mode_id=mode_id)
2353
elif entry.kind == "symlink":
2354
tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
2355
elif entry.kind == "directory":
2356
tt.create_directory(trans_id)
2359
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2360
"""Create new file contents according to tree contents."""
2361
kind = tree.kind(file_id)
2854
2362
if kind == 'directory':
2855
2363
tt.create_directory(trans_id)
2856
2364
elif kind == "file":
2858
f = tree.get_file(path)
2859
chunks = osutils.file_iterator(f)
2864
if wt.supports_content_filtering() and filter_tree_path is not None:
2865
filters = wt._content_filter_stack(filter_tree_path)
2866
chunks = filtered_output_bytes(
2868
ContentFilterContext(filter_tree_path, tree))
2869
tt.create_file(chunks, trans_id)
2366
tree_file = tree.get_file(file_id)
2368
bytes = tree_file.readlines()
2371
tt.create_file(bytes, trans_id)
2873
2372
elif kind == "symlink":
2874
tt.create_symlink(tree.get_symlink_target(path), trans_id)
2373
tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2876
2375
raise AssertionError('Unknown kind %r' % kind)
2882
2381
tt.set_executability(entry.executable, trans_id)
2384
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2385
return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2388
def _get_backup_name(name, by_parent, parent_trans_id, tt):
2389
"""Produce a backup-style name that appears to be available"""
2393
yield "%s.~%d~" % (name, counter)
2395
for new_name in name_gen():
2396
if not tt.has_named_child(by_parent, parent_trans_id, new_name):
2400
def _entry_changes(file_id, entry, working_tree):
2401
"""Determine in which ways the inventory entry has changed.
2403
Returns booleans: has_contents, content_mod, meta_mod
2404
has_contents means there are currently contents, but they differ
2405
contents_mod means contents need to be modified
2406
meta_mod means the metadata needs to be modified
2408
cur_entry = working_tree.inventory[file_id]
2410
working_kind = working_tree.kind(file_id)
2413
has_contents = False
2416
if has_contents is True:
2417
if entry.kind != working_kind:
2418
contents_mod, meta_mod = True, False
2420
cur_entry._read_tree_state(working_tree.id2path(file_id),
2422
contents_mod, meta_mod = entry.detect_changes(cur_entry)
2423
cur_entry._forget_tree_state()
2424
return has_contents, contents_mod, meta_mod
2885
2427
def revert(working_tree, target_tree, filenames, backups=False,
2886
pb=None, change_reporter=None):
2428
pb=DummyProgress(), change_reporter=None):
2887
2429
"""Revert a working tree's contents to those of a target tree."""
2888
pb = ui.ui_factory.nested_progress_bar()
2430
target_tree.lock_read()
2431
tt = TreeTransform(working_tree, pb)
2890
with target_tree.lock_read(), working_tree.get_transform(pb) as tt:
2891
pp = ProgressPhase("Revert phase", 3, pb)
2892
conflicts, merge_modified = _prepare_revert_transform(
2893
working_tree, target_tree, tt, filenames, backups, pp)
2895
change_reporter = delta._ChangeReporter(
2896
unversioned_filter=working_tree.is_ignored)
2897
delta.report_changes(tt.iter_changes(), change_reporter)
2898
for conflict in conflicts:
2899
trace.warning(text_type(conflict))
2902
if working_tree.supports_merge_modified():
2903
working_tree.set_merge_modified(merge_modified)
2433
pp = ProgressPhase("Revert phase", 3, pb)
2434
conflicts, merge_modified = _prepare_revert_transform(
2435
working_tree, target_tree, tt, filenames, backups, pp)
2437
change_reporter = delta._ChangeReporter(
2438
unversioned_filter=working_tree.is_ignored)
2439
delta.report_changes(tt.iter_changes(), change_reporter)
2440
for conflict in conflicts:
2444
working_tree.set_merge_modified(merge_modified)
2446
target_tree.unlock()
2906
2449
return conflicts
2926
2477
backups, merge_modified, basis_tree=None):
2927
2478
if basis_tree is not None:
2928
2479
basis_tree.lock_read()
2929
# We ask the working_tree for its changes relative to the target, rather
2930
# than the target changes relative to the working tree. Because WT4 has an
2931
# optimizer to compare itself to a target, but no optimizer for the
2933
change_list = working_tree.iter_changes(
2934
target_tree, specific_files=specific_files, pb=pb)
2935
if not target_tree.is_versioned(u''):
2480
change_list = target_tree.iter_changes(working_tree,
2481
specific_files=specific_files, pb=pb)
2482
if target_tree.get_root_id() is None:
2936
2483
skip_root = True
2938
2485
skip_root = False
2940
2487
deferred_files = []
2941
for id_num, change in enumerate(change_list):
2942
file_id = change.file_id
2943
target_path, wt_path = change.path
2944
target_versioned, wt_versioned = change.versioned
2945
target_parent, wt_parent = change.parent_id
2946
target_name, wt_name = change.name
2947
target_kind, wt_kind = change.kind
2948
target_executable, wt_executable = change.executable
2949
if skip_root and wt_parent is None:
2488
for id_num, (file_id, path, changed_content, versioned, parent, name,
2489
kind, executable) in enumerate(change_list):
2490
if skip_root and file_id[0] is not None and parent[0] is None:
2951
2492
trans_id = tt.trans_id_file_id(file_id)
2953
if change.changed_content:
2954
2495
keep_content = False
2955
if wt_kind == 'file' and (backups or target_kind is None):
2956
wt_sha1 = working_tree.get_file_sha1(wt_path)
2957
if merge_modified.get(wt_path) != wt_sha1:
2496
if kind[0] == 'file' and (backups or kind[1] is None):
2497
wt_sha1 = working_tree.get_file_sha1(file_id)
2498
if merge_modified.get(file_id) != wt_sha1:
2958
2499
# acquire the basis tree lazily to prevent the
2959
2500
# expense of accessing it when it's not needed ?
2960
2501
# (Guessing, RBC, 200702)
2961
2502
if basis_tree is None:
2962
2503
basis_tree = working_tree.basis_tree()
2963
2504
basis_tree.lock_read()
2964
basis_inter = InterTree.get(basis_tree, working_tree)
2965
basis_path = basis_inter.find_source_path(wt_path)
2966
if basis_path is None:
2967
if target_kind is None and not target_versioned:
2970
if wt_sha1 != basis_tree.get_file_sha1(basis_path):
2972
if wt_kind is not None:
2505
if file_id in basis_tree:
2506
if wt_sha1 != basis_tree.get_file_sha1(file_id):
2508
elif kind[1] is None and not versioned[1]:
2510
if kind[0] is not None:
2973
2511
if not keep_content:
2974
2512
tt.delete_contents(trans_id)
2975
elif target_kind is not None:
2976
parent_trans_id = tt.trans_id_file_id(wt_parent)
2977
backup_name = tt._available_backup_name(
2978
wt_name, parent_trans_id)
2513
elif kind[1] is not None:
2514
parent_trans_id = tt.trans_id_file_id(parent[0])
2515
by_parent = tt.by_parent()
2516
backup_name = _get_backup_name(name[0], by_parent,
2517
parent_trans_id, tt)
2979
2518
tt.adjust_path(backup_name, parent_trans_id, trans_id)
2980
new_trans_id = tt.create_path(wt_name, parent_trans_id)
2981
if wt_versioned and target_versioned:
2519
new_trans_id = tt.create_path(name[0], parent_trans_id)
2520
if versioned == (True, True):
2982
2521
tt.unversion_file(trans_id)
2983
2522
tt.version_file(file_id, new_trans_id)
2984
2523
# New contents should have the same unix perms as old
2986
2525
mode_id = trans_id
2987
2526
trans_id = new_trans_id
2988
if target_kind in ('directory', 'tree-reference'):
2527
if kind[1] in ('directory', 'tree-reference'):
2989
2528
tt.create_directory(trans_id)
2990
if target_kind == 'tree-reference':
2991
revision = target_tree.get_reference_revision(
2529
if kind[1] == 'tree-reference':
2530
revision = target_tree.get_reference_revision(file_id,
2993
2532
tt.set_tree_reference(revision, trans_id)
2994
elif target_kind == 'symlink':
2995
tt.create_symlink(target_tree.get_symlink_target(
2996
target_path), trans_id)
2997
elif target_kind == 'file':
2998
deferred_files.append(
2999
(target_path, (trans_id, mode_id, file_id)))
2533
elif kind[1] == 'symlink':
2534
tt.create_symlink(target_tree.get_symlink_target(file_id),
2536
elif kind[1] == 'file':
2537
deferred_files.append((file_id, (trans_id, mode_id)))
3000
2538
if basis_tree is None:
3001
2539
basis_tree = working_tree.basis_tree()
3002
2540
basis_tree.lock_read()
3003
new_sha1 = target_tree.get_file_sha1(target_path)
3004
basis_inter = InterTree.get(basis_tree, target_tree)
3005
basis_path = basis_inter.find_source_path(target_path)
3006
if (basis_path is not None and
3007
new_sha1 == basis_tree.get_file_sha1(basis_path)):
3008
# If the new contents of the file match what is in basis,
3009
# then there is no need to store in merge_modified.
3010
if basis_path in merge_modified:
3011
del merge_modified[basis_path]
2541
new_sha1 = target_tree.get_file_sha1(file_id)
2542
if (file_id in basis_tree and new_sha1 ==
2543
basis_tree.get_file_sha1(file_id)):
2544
if file_id in merge_modified:
2545
del merge_modified[file_id]
3013
merge_modified[target_path] = new_sha1
2547
merge_modified[file_id] = new_sha1
3015
2549
# preserve the execute bit when backing up
3016
if keep_content and wt_executable == target_executable:
3017
tt.set_executability(target_executable, trans_id)
3018
elif target_kind is not None:
3019
raise AssertionError(target_kind)
3020
if not wt_versioned and target_versioned:
2550
if keep_content and executable[0] == executable[1]:
2551
tt.set_executability(executable[1], trans_id)
2552
elif kind[1] is not None:
2553
raise AssertionError(kind[1])
2554
if versioned == (False, True):
3021
2555
tt.version_file(file_id, trans_id)
3022
if wt_versioned and not target_versioned:
2556
if versioned == (True, False):
3023
2557
tt.unversion_file(trans_id)
3024
if (target_name is not None
3025
and (wt_name != target_name or wt_parent != target_parent)):
3026
if target_name == '' and target_parent is None:
2558
if (name[1] is not None and
2559
(name[0] != name[1] or parent[0] != parent[1])):
2560
if name[1] == '' and parent[1] is None:
3027
2561
parent_trans = ROOT_PARENT
3029
parent_trans = tt.trans_id_file_id(target_parent)
3030
if wt_parent is None and wt_versioned:
3031
tt.adjust_root_path(target_name, parent_trans)
3033
tt.adjust_path(target_name, parent_trans, trans_id)
3034
if wt_executable != target_executable and target_kind == "file":
3035
tt.set_executability(target_executable, trans_id)
3036
if working_tree.supports_content_filtering():
3037
for (trans_id, mode_id, file_id), bytes in (
3038
target_tree.iter_files_bytes(deferred_files)):
3039
# We're reverting a tree to the target tree so using the
3040
# target tree to find the file path seems the best choice
3041
# here IMO - Ian C 27/Oct/2009
3042
filter_tree_path = target_tree.id2path(file_id)
3043
filters = working_tree._content_filter_stack(filter_tree_path)
3044
bytes = filtered_output_bytes(
3046
ContentFilterContext(filter_tree_path, working_tree))
3047
tt.create_file(bytes, trans_id, mode_id)
3049
for (trans_id, mode_id, file_id), bytes in target_tree.iter_files_bytes(
3051
tt.create_file(bytes, trans_id, mode_id)
3052
tt.fixup_new_roots()
2563
parent_trans = tt.trans_id_file_id(parent[1])
2564
tt.adjust_path(name[1], parent_trans, trans_id)
2565
if executable[0] != executable[1] and kind[1] == "file":
2566
tt.set_executability(executable[1], trans_id)
2567
for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2569
tt.create_file(bytes, trans_id, mode_id)
3054
2571
if basis_tree is not None:
3055
2572
basis_tree.unlock()
3056
2573
return merge_modified
3059
def resolve_conflicts(tt, pb=None, pass_func=None):
2576
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
3060
2577
"""Make many conflict-resolution attempts, but die if they fail"""
3061
2578
if pass_func is None:
3062
2579
pass_func = conflict_pass
3063
2580
new_conflicts = set()
3064
with ui.ui_factory.nested_progress_bar() as pb:
3065
2582
for n in range(10):
3066
pb.update(gettext('Resolution pass'), n + 1, 10)
2583
pb.update('Resolution pass', n+1, 10)
3067
2584
conflicts = tt.find_conflicts()
3068
2585
if len(conflicts) == 0:
3069
2586
return new_conflicts
3070
2587
new_conflicts.update(pass_func(tt, conflicts))
3071
2588
raise MalformedTransform(conflicts=conflicts)
3074
2593
def conflict_pass(tt, conflicts, path_tree=None):