~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: John Arbash Meinel
  • Date: 2010-09-29 20:56:18 UTC
  • mto: This revision was merged to the branch mainline in revision 5452.
  • Revision ID: john@arbash-meinel.com-20100929205618-qlldxw4ykwt5511n
Move the new NEWS entry to the appropriate section.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
import os
18
18
import errno
19
19
from stat import S_ISREG, S_IEXEC
 
20
import time
20
21
 
21
22
from bzrlib.lazy_import import lazy_import
22
23
lazy_import(globals(), """
24
25
    annotate,
25
26
    bencode,
26
27
    bzrdir,
 
28
    commit,
27
29
    delta,
28
30
    errors,
29
31
    inventory,
30
32
    multiparent,
31
33
    osutils,
32
34
    revision as _mod_revision,
 
35
    ui,
33
36
    )
34
37
""")
35
38
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
36
 
                           ReusingTransform, NotVersionedError, CantMoveRoot,
 
39
                           ReusingTransform, CantMoveRoot,
37
40
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
38
41
                           UnableCreateSymlink)
39
42
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
48
51
    splitpath,
49
52
    supports_executable,
50
53
)
51
 
from bzrlib.progress import DummyProgress, ProgressPhase
 
54
from bzrlib.progress import ProgressPhase
52
55
from bzrlib.symbol_versioning import (
53
56
        deprecated_function,
54
57
        deprecated_in,
78
81
class TreeTransformBase(object):
79
82
    """The base class for TreeTransform and its kin."""
80
83
 
81
 
    def __init__(self, tree, pb=DummyProgress(),
 
84
    def __init__(self, tree, pb=None,
82
85
                 case_sensitive=True):
83
86
        """Constructor.
84
87
 
85
88
        :param tree: The tree that will be transformed, but not necessarily
86
89
            the output tree.
87
 
        :param pb: A ProgressBar indicating how much progress is being made
 
90
        :param pb: ignored
88
91
        :param case_sensitive: If True, the target of the transform is
89
92
            case sensitive, not just case preserving.
90
93
        """
161
164
 
162
165
    def adjust_path(self, name, parent, trans_id):
163
166
        """Change the path that is assigned to a transaction id."""
 
167
        if parent is None:
 
168
            raise ValueError("Parent trans-id may not be None")
164
169
        if trans_id == self._new_root:
165
170
            raise CantMoveRoot
166
171
        self._new_name[trans_id] = name
167
172
        self._new_parent[trans_id] = parent
168
 
        if parent == ROOT_PARENT:
169
 
            if self._new_root is not None:
170
 
                raise ValueError("Cannot have multiple roots.")
171
 
            self._new_root = trans_id
172
173
 
173
174
    def adjust_root_path(self, name, parent):
174
175
        """Emulate moving the root by moving all children, instead.
202
203
        self.version_file(old_root_file_id, old_root)
203
204
        self.unversion_file(self._new_root)
204
205
 
 
206
    def fixup_new_roots(self):
 
207
        """Reinterpret requests to change the root directory
 
208
 
 
209
        Instead of creating a root directory, or moving an existing directory,
 
210
        all the attributes and children of the new root are applied to the
 
211
        existing root directory.
 
212
 
 
213
        This means that the old root trans-id becomes obsolete, so it is
 
214
        recommended only to invoke this after the root trans-id has become
 
215
        irrelevant.
 
216
        """
 
217
        new_roots = [k for k, v in self._new_parent.iteritems() if v is
 
218
                     ROOT_PARENT]
 
219
        if len(new_roots) < 1:
 
220
            return
 
221
        if len(new_roots) != 1:
 
222
            raise ValueError('A tree cannot have two roots!')
 
223
        if self._new_root is None:
 
224
            self._new_root = new_roots[0]
 
225
            return
 
226
        old_new_root = new_roots[0]
 
227
        # TODO: What to do if a old_new_root is present, but self._new_root is
 
228
        #       not listed as being removed? This code explicitly unversions
 
229
        #       the old root and versions it with the new file_id. Though that
 
230
        #       seems like an incomplete delta
 
231
 
 
232
        # unversion the new root's directory.
 
233
        file_id = self.final_file_id(old_new_root)
 
234
        if old_new_root in self._new_id:
 
235
            self.cancel_versioning(old_new_root)
 
236
        else:
 
237
            self.unversion_file(old_new_root)
 
238
        # if, at this stage, root still has an old file_id, zap it so we can
 
239
        # stick a new one in.
 
240
        if (self.tree_file_id(self._new_root) is not None and
 
241
            self._new_root not in self._removed_id):
 
242
            self.unversion_file(self._new_root)
 
243
        self.version_file(file_id, self._new_root)
 
244
 
 
245
        # Now move children of new root into old root directory.
 
246
        # Ensure all children are registered with the transaction, but don't
 
247
        # use directly-- some tree children have new parents
 
248
        list(self.iter_tree_children(old_new_root))
 
249
        # Move all children of new root into old root directory.
 
250
        for child in self.by_parent().get(old_new_root, []):
 
251
            self.adjust_path(self.final_name(child), self._new_root, child)
 
252
 
 
253
        # Ensure old_new_root has no directory.
 
254
        if old_new_root in self._new_contents:
 
255
            self.cancel_creation(old_new_root)
 
256
        else:
 
257
            self.delete_contents(old_new_root)
 
258
 
 
259
        # prevent deletion of root directory.
 
260
        if self._new_root in self._removed_contents:
 
261
            self.cancel_deletion(self._new_root)
 
262
 
 
263
        # destroy path info for old_new_root.
 
264
        del self._new_parent[old_new_root]
 
265
        del self._new_name[old_new_root]
 
266
 
205
267
    def trans_id_tree_file_id(self, inventory_id):
206
268
        """Determine the transaction id of a working tree file.
207
269
 
253
315
 
254
316
    def delete_contents(self, trans_id):
255
317
        """Schedule the contents of a path entry for deletion"""
256
 
        self.tree_kind(trans_id)
257
 
        self._removed_contents.add(trans_id)
 
318
        kind = self.tree_kind(trans_id)
 
319
        if kind is not None:
 
320
            self._removed_contents.add(trans_id)
258
321
 
259
322
    def cancel_deletion(self, trans_id):
260
323
        """Cancel a scheduled deletion"""
325
388
        changed_kind = set(self._removed_contents)
326
389
        changed_kind.intersection_update(self._new_contents)
327
390
        changed_kind.difference_update(new_ids)
328
 
        changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
329
 
                        self.final_kind(t))
 
391
        changed_kind = (t for t in changed_kind
 
392
                        if self.tree_kind(t) != self.final_kind(t))
330
393
        new_ids.update(changed_kind)
331
394
        return sorted(FinalPaths(self).get_paths(new_ids))
332
395
 
333
396
    def final_kind(self, trans_id):
334
397
        """Determine the final file kind, after any changes applied.
335
398
 
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)
 
399
        :return: None if the file does not exist/has no contents.  (It is
 
400
            conceivable that a path would be created without the corresponding
 
401
            contents insertion command)
339
402
        """
340
403
        if trans_id in self._new_contents:
341
404
            return self._new_contents[trans_id]
342
405
        elif trans_id in self._removed_contents:
343
 
            raise NoSuchFile(None)
 
406
            return None
344
407
        else:
345
408
            return self.tree_kind(trans_id)
346
409
 
442
505
        conflicts.extend(self._overwrite_conflicts())
443
506
        return conflicts
444
507
 
 
508
    def _check_malformed(self):
 
509
        conflicts = self.find_conflicts()
 
510
        if len(conflicts) != 0:
 
511
            raise MalformedTransform(conflicts=conflicts)
 
512
 
445
513
    def _add_tree_children(self):
446
514
        """Add all the children of all active parents to the known paths.
447
515
 
527
595
        """
528
596
        conflicts = []
529
597
        for trans_id in self._new_id.iterkeys():
530
 
            try:
531
 
                kind = self.final_kind(trans_id)
532
 
            except NoSuchFile:
 
598
            kind = self.final_kind(trans_id)
 
599
            if kind is None:
533
600
                conflicts.append(('versioning no contents', trans_id))
534
601
                continue
535
602
            if not InventoryEntry.versionable_kind(kind):
549
616
            if self.final_file_id(trans_id) is None:
550
617
                conflicts.append(('unversioned executability', trans_id))
551
618
            else:
552
 
                try:
553
 
                    non_file = self.final_kind(trans_id) != "file"
554
 
                except NoSuchFile:
555
 
                    non_file = True
556
 
                if non_file is True:
 
619
                if self.final_kind(trans_id) != "file":
557
620
                    conflicts.append(('non-file executability', trans_id))
558
621
        return conflicts
559
622
 
561
624
        """Check for overwrites (not permitted on Win32)"""
562
625
        conflicts = []
563
626
        for trans_id in self._new_contents:
564
 
            try:
565
 
                self.tree_kind(trans_id)
566
 
            except NoSuchFile:
 
627
            if self.tree_kind(trans_id) is None:
567
628
                continue
568
629
            if trans_id not in self._removed_contents:
569
630
                conflicts.append(('overwrite', trans_id,
583
644
            last_name = None
584
645
            last_trans_id = None
585
646
            for name, trans_id in name_ids:
586
 
                try:
587
 
                    kind = self.final_kind(trans_id)
588
 
                except NoSuchFile:
589
 
                    kind = None
 
647
                kind = self.final_kind(trans_id)
590
648
                file_id = self.final_file_id(trans_id)
591
649
                if kind is None and file_id is None:
592
650
                    continue
618
676
                continue
619
677
            if not self._any_contents(children):
620
678
                continue
621
 
            for child in children:
622
 
                try:
623
 
                    self.final_kind(child)
624
 
                except NoSuchFile:
625
 
                    continue
626
 
            try:
627
 
                kind = self.final_kind(parent_id)
628
 
            except NoSuchFile:
629
 
                kind = None
 
679
            kind = self.final_kind(parent_id)
630
680
            if kind is None:
631
681
                conflicts.append(('missing parent', parent_id))
632
682
            elif kind != "directory":
636
686
    def _any_contents(self, trans_ids):
637
687
        """Return true if any of the trans_ids, will have contents."""
638
688
        for trans_id in trans_ids:
639
 
            try:
640
 
                kind = self.final_kind(trans_id)
641
 
            except NoSuchFile:
642
 
                continue
643
 
            return True
 
689
            if self.final_kind(trans_id) is not None:
 
690
                return True
644
691
        return False
645
692
 
646
693
    def _set_executability(self, path, trans_id):
776
823
        Return a (name, parent, kind, executable) tuple
777
824
        """
778
825
        to_name = self.final_name(to_trans_id)
779
 
        try:
780
 
            to_kind = self.final_kind(to_trans_id)
781
 
        except NoSuchFile:
782
 
            to_kind = None
 
826
        to_kind = self.final_kind(to_trans_id)
783
827
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
784
828
        if to_trans_id in self._new_executability:
785
829
            to_executable = self._new_executability[to_trans_id]
854
898
    def get_preview_tree(self):
855
899
        """Return a tree representing the result of the transform.
856
900
 
857
 
        This tree only supports the subset of Tree functionality required
858
 
        by show_diff_trees.  It must only be compared to tt._tree.
 
901
        The tree is a snapshot, and altering the TreeTransform will invalidate
 
902
        it.
859
903
        """
860
904
        return _PreviewTree(self)
861
905
 
 
906
    def commit(self, branch, message, merge_parents=None, strict=False,
 
907
               timestamp=None, timezone=None, committer=None, authors=None,
 
908
               revprops=None, revision_id=None):
 
909
        """Commit the result of this TreeTransform to a branch.
 
910
 
 
911
        :param branch: The branch to commit to.
 
912
        :param message: The message to attach to the commit.
 
913
        :param merge_parents: Additional parent revision-ids specified by
 
914
            pending merges.
 
915
        :param strict: If True, abort the commit if there are unversioned
 
916
            files.
 
917
        :param timestamp: if not None, seconds-since-epoch for the time and
 
918
            date.  (May be a float.)
 
919
        :param timezone: Optional timezone for timestamp, as an offset in
 
920
            seconds.
 
921
        :param committer: Optional committer in email-id format.
 
922
            (e.g. "J Random Hacker <jrandom@example.com>")
 
923
        :param authors: Optional list of authors in email-id format.
 
924
        :param revprops: Optional dictionary of revision properties.
 
925
        :param revision_id: Optional revision id.  (Specifying a revision-id
 
926
            may reduce performance for some non-native formats.)
 
927
        :return: The revision_id of the revision committed.
 
928
        """
 
929
        self._check_malformed()
 
930
        if strict:
 
931
            unversioned = set(self._new_contents).difference(set(self._new_id))
 
932
            for trans_id in unversioned:
 
933
                if self.final_file_id(trans_id) is None:
 
934
                    raise errors.StrictCommitFailed()
 
935
 
 
936
        revno, last_rev_id = branch.last_revision_info()
 
937
        if last_rev_id == _mod_revision.NULL_REVISION:
 
938
            if merge_parents is not None:
 
939
                raise ValueError('Cannot supply merge parents for first'
 
940
                                 ' commit.')
 
941
            parent_ids = []
 
942
        else:
 
943
            parent_ids = [last_rev_id]
 
944
            if merge_parents is not None:
 
945
                parent_ids.extend(merge_parents)
 
946
        if self._tree.get_revision_id() != last_rev_id:
 
947
            raise ValueError('TreeTransform not based on branch basis: %s' %
 
948
                             self._tree.get_revision_id())
 
949
        revprops = commit.Commit.update_revprops(revprops, branch, authors)
 
950
        builder = branch.get_commit_builder(parent_ids,
 
951
                                            timestamp=timestamp,
 
952
                                            timezone=timezone,
 
953
                                            committer=committer,
 
954
                                            revprops=revprops,
 
955
                                            revision_id=revision_id)
 
956
        preview = self.get_preview_tree()
 
957
        list(builder.record_iter_changes(preview, last_rev_id,
 
958
                                         self.iter_changes()))
 
959
        builder.finish_inventory()
 
960
        revision_id = builder.commit(message)
 
961
        branch.set_last_revision_info(revno + 1, revision_id)
 
962
        return revision_id
 
963
 
862
964
    def _text_parent(self, trans_id):
863
965
        file_id = self.tree_file_id(trans_id)
864
966
        try:
958
1060
class DiskTreeTransform(TreeTransformBase):
959
1061
    """Tree transform storing its contents on disk."""
960
1062
 
961
 
    def __init__(self, tree, limbodir, pb=DummyProgress(),
 
1063
    def __init__(self, tree, limbodir, pb=None,
962
1064
                 case_sensitive=True):
963
1065
        """Constructor.
964
1066
        :param tree: The tree that will be transformed, but not necessarily
965
1067
            the output tree.
966
1068
        :param limbodir: A directory where new files can be stored until
967
1069
            they are installed in their proper places
968
 
        :param pb: A ProgressBar indicating how much progress is being made
 
1070
        :param pb: ignored
969
1071
        :param case_sensitive: If True, the target of the transform is
970
1072
            case sensitive, not just case preserving.
971
1073
        """
981
1083
        self._limbo_children_names = {}
982
1084
        # List of transform ids that need to be renamed from limbo into place
983
1085
        self._needs_rename = set()
 
1086
        self._creation_mtime = None
984
1087
 
985
1088
    def finalize(self):
986
1089
        """Release the working tree lock, if held, clean up limbo dir.
995
1098
                       self._new_contents.iteritems()]
996
1099
            entries.sort(reverse=True)
997
1100
            for path, trans_id, kind in entries:
998
 
                if kind == "directory":
999
 
                    os.rmdir(path)
1000
 
                else:
1001
 
                    os.unlink(path)
 
1101
                delete_any(path)
1002
1102
            try:
1003
 
                os.rmdir(self._limbodir)
 
1103
                delete_any(self._limbodir)
1004
1104
            except OSError:
1005
1105
                # We don't especially care *why* the dir is immortal.
1006
1106
                raise ImmortalLimbo(self._limbodir)
1007
1107
            try:
1008
1108
                if self._deletiondir is not None:
1009
 
                    os.rmdir(self._deletiondir)
 
1109
                    delete_any(self._deletiondir)
1010
1110
            except OSError:
1011
1111
                raise errors.ImmortalPendingDeletion(self._deletiondir)
1012
1112
        finally:
1015
1115
    def _limbo_name(self, trans_id):
1016
1116
        """Generate the limbo name of a file"""
1017
1117
        limbo_name = self._limbo_files.get(trans_id)
1018
 
        if limbo_name is not None:
1019
 
            return limbo_name
1020
 
        parent = self._new_parent.get(trans_id)
1021
 
        # if the parent directory is already in limbo (e.g. when building a
1022
 
        # tree), choose a limbo name inside the parent, to reduce further
1023
 
        # renames.
1024
 
        use_direct_path = False
1025
 
        if self._new_contents.get(parent) == 'directory':
1026
 
            filename = self._new_name.get(trans_id)
1027
 
            if filename is not None:
1028
 
                if parent not in self._limbo_children:
1029
 
                    self._limbo_children[parent] = set()
1030
 
                    self._limbo_children_names[parent] = {}
1031
 
                    use_direct_path = True
1032
 
                # the direct path can only be used if no other file has
1033
 
                # already taken this pathname, i.e. if the name is unused, or
1034
 
                # if it is already associated with this trans_id.
1035
 
                elif self._case_sensitive_target:
1036
 
                    if (self._limbo_children_names[parent].get(filename)
1037
 
                        in (trans_id, None)):
1038
 
                        use_direct_path = True
1039
 
                else:
1040
 
                    for l_filename, l_trans_id in\
1041
 
                        self._limbo_children_names[parent].iteritems():
1042
 
                        if l_trans_id == trans_id:
1043
 
                            continue
1044
 
                        if l_filename.lower() == filename.lower():
1045
 
                            break
1046
 
                    else:
1047
 
                        use_direct_path = True
1048
 
 
1049
 
        if use_direct_path:
1050
 
            limbo_name = pathjoin(self._limbo_files[parent], filename)
1051
 
            self._limbo_children[parent].add(trans_id)
1052
 
            self._limbo_children_names[parent][filename] = trans_id
1053
 
        else:
1054
 
            limbo_name = pathjoin(self._limbodir, trans_id)
1055
 
            self._needs_rename.add(trans_id)
1056
 
        self._limbo_files[trans_id] = limbo_name
 
1118
        if limbo_name is None:
 
1119
            limbo_name = self._generate_limbo_path(trans_id)
 
1120
            self._limbo_files[trans_id] = limbo_name
1057
1121
        return limbo_name
1058
1122
 
 
1123
    def _generate_limbo_path(self, trans_id):
 
1124
        """Generate a limbo path using the trans_id as the relative path.
 
1125
 
 
1126
        This is suitable as a fallback, and when the transform should not be
 
1127
        sensitive to the path encoding of the limbo directory.
 
1128
        """
 
1129
        self._needs_rename.add(trans_id)
 
1130
        return pathjoin(self._limbodir, trans_id)
 
1131
 
1059
1132
    def adjust_path(self, name, parent, trans_id):
1060
1133
        previous_parent = self._new_parent.get(trans_id)
1061
1134
        previous_name = self._new_name.get(trans_id)
1063
1136
        if (trans_id in self._limbo_files and
1064
1137
            trans_id not in self._needs_rename):
1065
1138
            self._rename_in_limbo([trans_id])
1066
 
            self._limbo_children[previous_parent].remove(trans_id)
1067
 
            del self._limbo_children_names[previous_parent][previous_name]
 
1139
            if previous_parent != parent:
 
1140
                self._limbo_children[previous_parent].remove(trans_id)
 
1141
            if previous_parent != parent or previous_name != name:
 
1142
                del self._limbo_children_names[previous_parent][previous_name]
1068
1143
 
1069
1144
    def _rename_in_limbo(self, trans_ids):
1070
1145
        """Fix limbo names so that the right final path is produced.
1083
1158
                continue
1084
1159
            new_path = self._limbo_name(trans_id)
1085
1160
            os.rename(old_path, new_path)
 
1161
            for descendant in self._limbo_descendants(trans_id):
 
1162
                desc_path = self._limbo_files[descendant]
 
1163
                desc_path = new_path + desc_path[len(old_path):]
 
1164
                self._limbo_files[descendant] = desc_path
 
1165
 
 
1166
    def _limbo_descendants(self, trans_id):
 
1167
        """Return the set of trans_ids whose limbo paths descend from this."""
 
1168
        descendants = set(self._limbo_children.get(trans_id, []))
 
1169
        for descendant in list(descendants):
 
1170
            descendants.update(self._limbo_descendants(descendant))
 
1171
        return descendants
1086
1172
 
1087
1173
    def create_file(self, contents, trans_id, mode_id=None):
1088
1174
        """Schedule creation of a new file.
1110
1196
            f.writelines(contents)
1111
1197
        finally:
1112
1198
            f.close()
 
1199
        self._set_mtime(name)
1113
1200
        self._set_mode(trans_id, mode_id, S_ISREG)
1114
1201
 
1115
1202
    def _read_file_chunks(self, trans_id):
1122
1209
    def _read_symlink_target(self, trans_id):
1123
1210
        return os.readlink(self._limbo_name(trans_id))
1124
1211
 
 
1212
    def _set_mtime(self, path):
 
1213
        """All files that are created get the same mtime.
 
1214
 
 
1215
        This time is set by the first object to be created.
 
1216
        """
 
1217
        if self._creation_mtime is None:
 
1218
            self._creation_mtime = time.time()
 
1219
        os.utime(path, (self._creation_mtime, self._creation_mtime))
 
1220
 
1125
1221
    def create_hardlink(self, path, trans_id):
1126
1222
        """Schedule creation of a hard link"""
1127
1223
        name = self._limbo_name(trans_id)
1241
1337
    FileMover does not delete files until it is sure that a rollback will not
1242
1338
    happen.
1243
1339
    """
1244
 
    def __init__(self, tree, pb=DummyProgress()):
 
1340
    def __init__(self, tree, pb=None):
1245
1341
        """Note: a tree_write lock is taken on the tree.
1246
1342
 
1247
1343
        Use TreeTransform.finalize() to release the lock (can be omitted if
1298
1394
    def tree_kind(self, trans_id):
1299
1395
        """Determine the file kind in the working tree.
1300
1396
 
1301
 
        Raises NoSuchFile if the file does not exist
 
1397
        :returns: The file kind or None if the file does not exist
1302
1398
        """
1303
1399
        path = self._tree_id_paths.get(trans_id)
1304
1400
        if path is None:
1305
 
            raise NoSuchFile(None)
 
1401
            return None
1306
1402
        try:
1307
1403
            return file_kind(self._tree.abspath(path))
1308
 
        except OSError, e:
1309
 
            if e.errno != errno.ENOENT:
1310
 
                raise
1311
 
            else:
1312
 
                raise NoSuchFile(path)
 
1404
        except errors.NoSuchFile:
 
1405
            return None
1313
1406
 
1314
1407
    def _set_mode(self, trans_id, mode_id, typefunc):
1315
1408
        """Set the mode of new file contents.
1357
1450
                continue
1358
1451
            yield self.trans_id_tree_path(childpath)
1359
1452
 
 
1453
    def _generate_limbo_path(self, trans_id):
 
1454
        """Generate a limbo path using the final path if possible.
 
1455
 
 
1456
        This optimizes the performance of applying the tree transform by
 
1457
        avoiding renames.  These renames can be avoided only when the parent
 
1458
        directory is already scheduled for creation.
 
1459
 
 
1460
        If the final path cannot be used, falls back to using the trans_id as
 
1461
        the relpath.
 
1462
        """
 
1463
        parent = self._new_parent.get(trans_id)
 
1464
        # if the parent directory is already in limbo (e.g. when building a
 
1465
        # tree), choose a limbo name inside the parent, to reduce further
 
1466
        # renames.
 
1467
        use_direct_path = False
 
1468
        if self._new_contents.get(parent) == 'directory':
 
1469
            filename = self._new_name.get(trans_id)
 
1470
            if filename is not None:
 
1471
                if parent not in self._limbo_children:
 
1472
                    self._limbo_children[parent] = set()
 
1473
                    self._limbo_children_names[parent] = {}
 
1474
                    use_direct_path = True
 
1475
                # the direct path can only be used if no other file has
 
1476
                # already taken this pathname, i.e. if the name is unused, or
 
1477
                # if it is already associated with this trans_id.
 
1478
                elif self._case_sensitive_target:
 
1479
                    if (self._limbo_children_names[parent].get(filename)
 
1480
                        in (trans_id, None)):
 
1481
                        use_direct_path = True
 
1482
                else:
 
1483
                    for l_filename, l_trans_id in\
 
1484
                        self._limbo_children_names[parent].iteritems():
 
1485
                        if l_trans_id == trans_id:
 
1486
                            continue
 
1487
                        if l_filename.lower() == filename.lower():
 
1488
                            break
 
1489
                    else:
 
1490
                        use_direct_path = True
 
1491
 
 
1492
        if not use_direct_path:
 
1493
            return DiskTreeTransform._generate_limbo_path(self, trans_id)
 
1494
 
 
1495
        limbo_name = pathjoin(self._limbo_files[parent], filename)
 
1496
        self._limbo_children[parent].add(trans_id)
 
1497
        self._limbo_children_names[parent][filename] = trans_id
 
1498
        return limbo_name
 
1499
 
1360
1500
 
1361
1501
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1362
1502
        """Apply all changes to the inventory and filesystem.
1373
1513
        :param _mover: Supply an alternate FileMover, for testing
1374
1514
        """
1375
1515
        if not no_conflicts:
1376
 
            conflicts = self.find_conflicts()
1377
 
            if len(conflicts) != 0:
1378
 
                raise MalformedTransform(conflicts=conflicts)
 
1516
            self._check_malformed()
1379
1517
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1380
1518
        try:
1381
1519
            if precomputed_delta is None:
1439
1577
                if file_id is None:
1440
1578
                    continue
1441
1579
                needs_entry = False
1442
 
                try:
1443
 
                    kind = self.final_kind(trans_id)
1444
 
                except NoSuchFile:
 
1580
                kind = self.final_kind(trans_id)
 
1581
                if kind is None:
1445
1582
                    kind = self._tree.stored_kind(file_id)
1446
1583
                parent_trans_id = self.final_parent(trans_id)
1447
1584
                parent_file_id = new_path_file_ids.get(parent_trans_id)
1485
1622
                child_pb.update('removing file', num, len(tree_paths))
1486
1623
                full_path = self._tree.abspath(path)
1487
1624
                if trans_id in self._removed_contents:
1488
 
                    mover.pre_delete(full_path, os.path.join(self._deletiondir,
1489
 
                                     trans_id))
1490
 
                elif trans_id in self._new_name or trans_id in \
1491
 
                    self._new_parent:
 
1625
                    delete_path = os.path.join(self._deletiondir, trans_id)
 
1626
                    mover.pre_delete(full_path, delete_path)
 
1627
                elif (trans_id in self._new_name
 
1628
                      or trans_id in self._new_parent):
1492
1629
                    try:
1493
1630
                        mover.rename(full_path, self._limbo_name(trans_id))
1494
 
                    except OSError, e:
 
1631
                    except errors.TransformRenameFailed, e:
1495
1632
                        if e.errno != errno.ENOENT:
1496
1633
                            raise
1497
1634
                    else:
1522
1659
                if trans_id in self._needs_rename:
1523
1660
                    try:
1524
1661
                        mover.rename(self._limbo_name(trans_id), full_path)
1525
 
                    except OSError, e:
 
1662
                    except errors.TransformRenameFailed, e:
1526
1663
                        # We may be renaming a dangling inventory id
1527
1664
                        if e.errno != errno.ENOENT:
1528
1665
                            raise
1548
1685
    unversioned files in the input tree.
1549
1686
    """
1550
1687
 
1551
 
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
 
1688
    def __init__(self, tree, pb=None, case_sensitive=True):
1552
1689
        tree.lock_read()
1553
1690
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1554
1691
        DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1559
1696
    def tree_kind(self, trans_id):
1560
1697
        path = self._tree_id_paths.get(trans_id)
1561
1698
        if path is None:
1562
 
            raise NoSuchFile(None)
 
1699
            return None
1563
1700
        file_id = self._tree.path2id(path)
1564
 
        return self._tree.kind(file_id)
 
1701
        try:
 
1702
            return self._tree.kind(file_id)
 
1703
        except errors.NoSuchFile:
 
1704
            return None
1565
1705
 
1566
1706
    def _set_mode(self, trans_id, mode_id, typefunc):
1567
1707
        """Set the mode of new file contents.
1599
1739
        self._all_children_cache = {}
1600
1740
        self._path2trans_id_cache = {}
1601
1741
        self._final_name_cache = {}
1602
 
 
1603
 
    def _changes(self, file_id):
1604
 
        for changes in self._transform.iter_changes():
1605
 
            if changes[0] == file_id:
1606
 
                return changes
 
1742
        self._iter_changes_cache = dict((c[0], c) for c in
 
1743
                                        self._transform.iter_changes())
1607
1744
 
1608
1745
    def _content_change(self, file_id):
1609
1746
        """Return True if the content of this file changed"""
1610
 
        changes = self._changes(file_id)
 
1747
        changes = self._iter_changes_cache.get(file_id)
1611
1748
        # changes[2] is true if the file content changed.  See
1612
1749
        # InterTree.iter_changes.
1613
1750
        return (changes is not None and changes[2])
1629
1766
        parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1630
1767
                       self._iter_parent_trees()]
1631
1768
        vf.add_lines((file_id, tree_revision), parent_keys,
1632
 
                     self.get_file(file_id).readlines())
 
1769
                     self.get_file_lines(file_id))
1633
1770
        repo = self._get_repository()
1634
1771
        base_vf = repo.texts
1635
1772
        if base_vf not in vf.fallback_versionedfiles:
1657
1794
            executable = self.is_executable(file_id, path)
1658
1795
        return kind, executable, None
1659
1796
 
 
1797
    def is_locked(self):
 
1798
        return False
 
1799
 
1660
1800
    def lock_read(self):
1661
1801
        # Perhaps in theory, this should lock the TreeTransform?
1662
 
        pass
 
1802
        return self
1663
1803
 
1664
1804
    def unlock(self):
1665
1805
        pass
1682
1822
    def __iter__(self):
1683
1823
        return iter(self.all_file_ids())
1684
1824
 
1685
 
    def has_id(self, file_id):
 
1825
    def _has_id(self, file_id, fallback_check):
1686
1826
        if file_id in self._transform._r_new_id:
1687
1827
            return True
1688
1828
        elif file_id in set([self._transform.tree_file_id(trans_id) for
1689
1829
            trans_id in self._transform._removed_id]):
1690
1830
            return False
1691
1831
        else:
1692
 
            return self._transform._tree.has_id(file_id)
 
1832
            return fallback_check(file_id)
 
1833
 
 
1834
    def has_id(self, file_id):
 
1835
        return self._has_id(file_id, self._transform._tree.has_id)
 
1836
 
 
1837
    def has_or_had_id(self, file_id):
 
1838
        return self._has_id(file_id, self._transform._tree.has_or_had_id)
1693
1839
 
1694
1840
    def _path2trans_id(self, path):
1695
1841
        # We must not use None here, because that is a valid value to store.
1748
1894
            if self._transform.final_file_id(trans_id) is None:
1749
1895
                yield self._final_paths._determine_path(trans_id)
1750
1896
 
1751
 
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None):
 
1897
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
 
1898
        yield_parents=False):
1752
1899
        for trans_id, parent_file_id in ordered_entries:
1753
1900
            file_id = self._transform.final_file_id(trans_id)
1754
1901
            if file_id is None:
1756
1903
            if (specific_file_ids is not None
1757
1904
                and file_id not in specific_file_ids):
1758
1905
                continue
1759
 
            try:
1760
 
                kind = self._transform.final_kind(trans_id)
1761
 
            except NoSuchFile:
 
1906
            kind = self._transform.final_kind(trans_id)
 
1907
            if kind is None:
1762
1908
                kind = self._transform._tree.stored_kind(file_id)
1763
1909
            new_entry = inventory.make_entry(
1764
1910
                kind,
1780
1926
                ordered_ids.append((trans_id, parent_file_id))
1781
1927
        return ordered_ids
1782
1928
 
1783
 
    def iter_entries_by_dir(self, specific_file_ids=None):
 
1929
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1784
1930
        # This may not be a maximally efficient implementation, but it is
1785
1931
        # reasonably straightforward.  An implementation that grafts the
1786
1932
        # TreeTransform changes onto the tree's iter_entries_by_dir results
1788
1934
        # position.
1789
1935
        ordered_ids = self._list_files_by_dir()
1790
1936
        for entry, trans_id in self._make_inv_entries(ordered_ids,
1791
 
                                                      specific_file_ids):
 
1937
            specific_file_ids, yield_parents=yield_parents):
1792
1938
            yield unicode(self._final_paths.get_path(trans_id)), entry
1793
1939
 
1794
1940
    def _iter_entries_for_dir(self, dir_path):
1841
1987
    def get_file_mtime(self, file_id, path=None):
1842
1988
        """See Tree.get_file_mtime"""
1843
1989
        if not self._content_change(file_id):
1844
 
            return self._transform._tree.get_file_mtime(file_id, path)
 
1990
            return self._transform._tree.get_file_mtime(file_id)
1845
1991
        return self._stat_limbo_file(file_id).st_mtime
1846
1992
 
1847
1993
    def _file_size(self, entry, stat_value):
1901
2047
                statval = os.lstat(limbo_name)
1902
2048
                size = statval.st_size
1903
2049
                if not supports_executable():
1904
 
                    executable = None
 
2050
                    executable = False
1905
2051
                else:
1906
2052
                    executable = statval.st_mode & S_IEXEC
1907
2053
            else:
1909
2055
                executable = None
1910
2056
            if kind == 'symlink':
1911
2057
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
1912
 
        if supports_executable():
1913
 
            executable = tt._new_executability.get(trans_id, executable)
 
2058
        executable = tt._new_executability.get(trans_id, executable)
1914
2059
        return kind, size, executable, link_or_sha1
1915
2060
 
1916
2061
    def iter_changes(self, from_tree, include_unchanged=False,
1947
2092
 
1948
2093
    def annotate_iter(self, file_id,
1949
2094
                      default_revision=_mod_revision.CURRENT_REVISION):
1950
 
        changes = self._changes(file_id)
 
2095
        changes = self._iter_changes_cache.get(file_id)
1951
2096
        if changes is None:
1952
2097
            get_old = True
1953
2098
        else:
1965
2110
            return old_annotation
1966
2111
        if not changed_content:
1967
2112
            return old_annotation
 
2113
        # TODO: This is doing something similar to what WT.annotate_iter is
 
2114
        #       doing, however it fails slightly because it doesn't know what
 
2115
        #       the *other* revision_id is, so it doesn't know how to give the
 
2116
        #       other as the origin for some lines, they all get
 
2117
        #       'default_revision'
 
2118
        #       It would be nice to be able to use the new Annotator based
 
2119
        #       approach, as well.
1968
2120
        return annotate.reannotate([old_annotation],
1969
2121
                                   self.get_file(file_id).readlines(),
1970
2122
                                   default_revision)
1990
2142
                path_from_root = self._final_paths.get_path(child_id)
1991
2143
                basename = self._transform.final_name(child_id)
1992
2144
                file_id = self._transform.final_file_id(child_id)
1993
 
                try:
1994
 
                    kind = self._transform.final_kind(child_id)
 
2145
                kind  = self._transform.final_kind(child_id)
 
2146
                if kind is not None:
1995
2147
                    versioned_kind = kind
1996
 
                except NoSuchFile:
 
2148
                else:
1997
2149
                    kind = 'unknown'
1998
2150
                    versioned_kind = self._transform._tree.stored_kind(file_id)
1999
2151
                if versioned_kind == 'directory':
2036
2188
        self.transform = transform
2037
2189
 
2038
2190
    def _determine_path(self, trans_id):
2039
 
        if trans_id == self.transform.root:
 
2191
        if (trans_id == self.transform.root or trans_id == ROOT_PARENT):
2040
2192
            return ""
2041
2193
        name = self.transform.final_name(trans_id)
2042
2194
        parent_id = self.transform.final_parent(trans_id)
2112
2264
    for num, _unused in enumerate(wt.all_file_ids()):
2113
2265
        if num > 0:  # more than just a root
2114
2266
            raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
2115
 
    existing_files = set()
2116
 
    for dir, files in wt.walkdirs():
2117
 
        existing_files.update(f[0] for f in files)
2118
2267
    file_trans_id = {}
2119
2268
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2120
2269
    pp = ProgressPhase("Build phase", 2, top_pb)
2144
2293
                precomputed_delta = []
2145
2294
            else:
2146
2295
                precomputed_delta = None
 
2296
            # Check if tree inventory has content. If so, we populate
 
2297
            # existing_files with the directory content. If there are no
 
2298
            # entries we skip populating existing_files as its not used.
 
2299
            # This improves performance and unncessary work on large
 
2300
            # directory trees. (#501307)
 
2301
            if total > 0:
 
2302
                existing_files = set()
 
2303
                for dir, files in wt.walkdirs():
 
2304
                    existing_files.update(f[0] for f in files)
2147
2305
            for num, (tree_path, entry) in \
2148
2306
                enumerate(tree.inventory.iter_entries_by_dir()):
2149
2307
                pb.update("Building tree", num - len(deferred_contents), total)
2222
2380
        new_desired_files = desired_files
2223
2381
    else:
2224
2382
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2225
 
        unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2226
 
                         in iter if not (c or e[0] != e[1]))
 
2383
        unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
 
2384
                     in iter if not (c or e[0] != e[1])]
 
2385
        if accelerator_tree.supports_content_filtering():
 
2386
            unchanged = [(f, p) for (f, p) in unchanged
 
2387
                         if not accelerator_tree.iter_search_rules([p]).next()]
 
2388
        unchanged = dict(unchanged)
2227
2389
        new_desired_files = []
2228
2390
        count = 0
2229
2391
        for file_id, (trans_id, tree_path) in desired_files:
2277
2439
    if entry.kind == "directory":
2278
2440
        return True
2279
2441
    if entry.kind == "file":
2280
 
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
2281
 
            return True
 
2442
        f = file(target_path, 'rb')
 
2443
        try:
 
2444
            if tree.get_file_text(file_id) == f.read():
 
2445
                return True
 
2446
        finally:
 
2447
            f.close()
2282
2448
    elif entry.kind == "symlink":
2283
2449
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
2284
2450
            return True
2336
2502
        raise errors.BadFileKindError(name, kind)
2337
2503
 
2338
2504
 
2339
 
@deprecated_function(deprecated_in((1, 9, 0)))
2340
 
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
2341
 
    """Create new file contents according to an inventory entry.
2342
 
 
2343
 
    DEPRECATED.  Use create_from_tree instead.
 
2505
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
 
2506
    filter_tree_path=None):
 
2507
    """Create new file contents according to tree contents.
 
2508
    
 
2509
    :param filter_tree_path: the tree path to use to lookup
 
2510
      content filters to apply to the bytes output in the working tree.
 
2511
      This only applies if the working tree supports content filtering.
2344
2512
    """
2345
 
    if entry.kind == "file":
2346
 
        if lines is None:
2347
 
            lines = tree.get_file(entry.file_id).readlines()
2348
 
        tt.create_file(lines, trans_id, mode_id=mode_id)
2349
 
    elif entry.kind == "symlink":
2350
 
        tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
2351
 
    elif entry.kind == "directory":
2352
 
        tt.create_directory(trans_id)
2353
 
 
2354
 
 
2355
 
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2356
 
    """Create new file contents according to tree contents."""
2357
2513
    kind = tree.kind(file_id)
2358
2514
    if kind == 'directory':
2359
2515
        tt.create_directory(trans_id)
2364
2520
                bytes = tree_file.readlines()
2365
2521
            finally:
2366
2522
                tree_file.close()
 
2523
        wt = tt._tree
 
2524
        if wt.supports_content_filtering() and filter_tree_path is not None:
 
2525
            filters = wt._content_filter_stack(filter_tree_path)
 
2526
            bytes = filtered_output_bytes(bytes, filters,
 
2527
                ContentFilterContext(filter_tree_path, tree))
2367
2528
        tt.create_file(bytes, trans_id)
2368
2529
    elif kind == "symlink":
2369
2530
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2421
2582
 
2422
2583
 
2423
2584
def revert(working_tree, target_tree, filenames, backups=False,
2424
 
           pb=DummyProgress(), change_reporter=None):
 
2585
           pb=None, change_reporter=None):
2425
2586
    """Revert a working tree's contents to those of a target tree."""
2426
2587
    target_tree.lock_read()
 
2588
    pb = ui.ui_factory.nested_progress_bar()
2427
2589
    tt = TreeTransform(working_tree, pb)
2428
2590
    try:
2429
2591
        pp = ProgressPhase("Revert phase", 3, pb)
2448
2610
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2449
2611
                              backups, pp, basis_tree=None,
2450
2612
                              merge_modified=None):
2451
 
    pp.next_phase()
2452
2613
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2453
2614
    try:
2454
2615
        if merge_modified is None:
2458
2619
                                      merge_modified, basis_tree)
2459
2620
    finally:
2460
2621
        child_pb.finished()
2461
 
    pp.next_phase()
2462
2622
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2463
2623
    try:
2464
2624
        raw_conflicts = resolve_conflicts(tt, child_pb,
2557
2717
                    parent_trans = ROOT_PARENT
2558
2718
                else:
2559
2719
                    parent_trans = tt.trans_id_file_id(parent[1])
2560
 
                tt.adjust_path(name[1], parent_trans, trans_id)
 
2720
                if parent[0] is None and versioned[0]:
 
2721
                    tt.adjust_root_path(name[1], parent_trans)
 
2722
                else:
 
2723
                    tt.adjust_path(name[1], parent_trans, trans_id)
2561
2724
            if executable[0] != executable[1] and kind[1] == "file":
2562
2725
                tt.set_executability(executable[1], trans_id)
2563
 
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2564
 
            deferred_files):
2565
 
            tt.create_file(bytes, trans_id, mode_id)
 
2726
        if working_tree.supports_content_filtering():
 
2727
            for index, ((trans_id, mode_id), bytes) in enumerate(
 
2728
                target_tree.iter_files_bytes(deferred_files)):
 
2729
                file_id = deferred_files[index][0]
 
2730
                # We're reverting a tree to the target tree so using the
 
2731
                # target tree to find the file path seems the best choice
 
2732
                # here IMO - Ian C 27/Oct/2009
 
2733
                filter_tree_path = target_tree.id2path(file_id)
 
2734
                filters = working_tree._content_filter_stack(filter_tree_path)
 
2735
                bytes = filtered_output_bytes(bytes, filters,
 
2736
                    ContentFilterContext(filter_tree_path, working_tree))
 
2737
                tt.create_file(bytes, trans_id, mode_id)
 
2738
        else:
 
2739
            for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
 
2740
                deferred_files):
 
2741
                tt.create_file(bytes, trans_id, mode_id)
 
2742
        tt.fixup_new_roots()
2566
2743
    finally:
2567
2744
        if basis_tree is not None:
2568
2745
            basis_tree.unlock()
2569
2746
    return merge_modified
2570
2747
 
2571
2748
 
2572
 
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
 
2749
def resolve_conflicts(tt, pb=None, pass_func=None):
2573
2750
    """Make many conflict-resolution attempts, but die if they fail"""
2574
2751
    if pass_func is None:
2575
2752
        pass_func = conflict_pass
2576
2753
    new_conflicts = set()
 
2754
    pb = ui.ui_factory.nested_progress_bar()
2577
2755
    try:
2578
2756
        for n in range(10):
2579
2757
            pb.update('Resolution pass', n+1, 10)
2583
2761
            new_conflicts.update(pass_func(tt, conflicts))
2584
2762
        raise MalformedTransform(conflicts=conflicts)
2585
2763
    finally:
2586
 
        pb.clear()
 
2764
        pb.finished()
2587
2765
 
2588
2766
 
2589
2767
def conflict_pass(tt, conflicts, path_tree=None):
2638
2816
                        # special-case the other tree root (move its
2639
2817
                        # children to current root)
2640
2818
                        if entry.parent_id is None:
2641
 
                            create=False
 
2819
                            create = False
2642
2820
                            moved = _reparent_transform_children(
2643
2821
                                tt, trans_id, tt.root)
2644
2822
                            for child in moved:
2712
2890
        self.pending_deletions = []
2713
2891
 
2714
2892
    def rename(self, from_, to):
2715
 
        """Rename a file from one path to another.  Functions like os.rename"""
 
2893
        """Rename a file from one path to another."""
2716
2894
        try:
2717
2895
            os.rename(from_, to)
2718
2896
        except OSError, e:
2719
2897
            if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2720
2898
                raise errors.FileExists(to, str(e))
2721
 
            raise
 
2899
            # normal OSError doesn't include filenames so it's hard to see where
 
2900
            # the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
 
2901
            raise errors.TransformRenameFailed(from_, to, str(e), e.errno)
2722
2902
        self.past_renames.append((from_, to))
2723
2903
 
2724
2904
    def pre_delete(self, from_, to):
2734
2914
    def rollback(self):
2735
2915
        """Reverse all renames that have been performed"""
2736
2916
        for from_, to in reversed(self.past_renames):
2737
 
            os.rename(to, from_)
 
2917
            try:
 
2918
                os.rename(to, from_)
 
2919
            except OSError, e:
 
2920
                raise errors.TransformRenameFailed(to, from_, str(e), e.errno)                
2738
2921
        # after rollback, don't reuse _FileMover
2739
2922
        past_renames = None
2740
2923
        pending_deletions = None