/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Martin Pool
  • Date: 2009-06-05 23:21:51 UTC
  • mfrom: (4415 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4416.
  • Revision ID: mbp@sourcefrog.net-20090605232151-luwmyyl95siraqyz
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
18
18
import errno
22
22
lazy_import(globals(), """
23
23
from bzrlib import (
24
24
    annotate,
 
25
    bencode,
25
26
    bzrdir,
26
27
    delta,
27
28
    errors,
30
31
    osutils,
31
32
    revision as _mod_revision,
32
33
    )
33
 
from bzrlib.util import bencode
34
34
""")
35
35
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
36
36
                           ReusingTransform, NotVersionedError, CantMoveRoot,
37
37
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
38
38
                           UnableCreateSymlink)
 
39
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
39
40
from bzrlib.inventory import InventoryEntry
40
41
from bzrlib.osutils import (
41
42
    delete_any,
75
76
 
76
77
 
77
78
class TreeTransformBase(object):
78
 
    """The base class for TreeTransform and TreeTransformBase"""
 
79
    """The base class for TreeTransform and its kin."""
79
80
 
80
 
    def __init__(self, tree, limbodir, pb=DummyProgress(),
 
81
    def __init__(self, tree, pb=DummyProgress(),
81
82
                 case_sensitive=True):
82
83
        """Constructor.
83
84
 
84
85
        :param tree: The tree that will be transformed, but not necessarily
85
86
            the output tree.
86
 
        :param limbodir: A directory where new files can be stored until
87
 
            they are installed in their proper places
88
87
        :param pb: A ProgressBar indicating how much progress is being made
89
88
        :param case_sensitive: If True, the target of the transform is
90
89
            case sensitive, not just case preserving.
91
90
        """
92
91
        object.__init__(self)
93
92
        self._tree = tree
94
 
        self._limbodir = limbodir
95
 
        self._deletiondir = None
96
93
        self._id_number = 0
97
94
        # mapping of trans_id -> new basename
98
95
        self._new_name = {}
100
97
        self._new_parent = {}
101
98
        # mapping of trans_id with new contents -> new file_kind
102
99
        self._new_contents = {}
103
 
        # A mapping of transform ids to their limbo filename
104
 
        self._limbo_files = {}
105
 
        # A mapping of transform ids to a set of the transform ids of children
106
 
        # that their limbo directory has
107
 
        self._limbo_children = {}
108
 
        # Map transform ids to maps of child filename to child transform id
109
 
        self._limbo_children_names = {}
110
 
        # List of transform ids that need to be renamed from limbo into place
111
 
        self._needs_rename = set()
112
100
        # Set of trans_ids whose contents will be removed
113
101
        self._removed_contents = set()
114
102
        # Mapping of trans_id -> new execute-bit value
127
115
        self._tree_path_ids = {}
128
116
        # Mapping trans_id -> path in old tree
129
117
        self._tree_id_paths = {}
130
 
        # Cache of realpath results, to speed up canonical_path
131
 
        self._realpaths = {}
132
 
        # Cache of relpath results, to speed up canonical_path
133
 
        self._relpaths = {}
134
118
        # The trans_id that will be used as the tree root
135
119
        root_id = tree.get_root_id()
136
120
        if root_id is not None:
146
130
        # A counter of how many files have been renamed
147
131
        self.rename_count = 0
148
132
 
 
133
    def finalize(self):
 
134
        """Release the working tree lock, if held.
 
135
 
 
136
        This is required if apply has not been invoked, but can be invoked
 
137
        even after apply.
 
138
        """
 
139
        if self._tree is None:
 
140
            return
 
141
        self._tree.unlock()
 
142
        self._tree = None
 
143
 
149
144
    def __get_root(self):
150
145
        return self._new_root
151
146
 
152
147
    root = property(__get_root)
153
148
 
154
 
    def finalize(self):
155
 
        """Release the working tree lock, if held, clean up limbo dir.
156
 
 
157
 
        This is required if apply has not been invoked, but can be invoked
158
 
        even after apply.
159
 
        """
160
 
        if self._tree is None:
161
 
            return
162
 
        try:
163
 
            entries = [(self._limbo_name(t), t, k) for t, k in
164
 
                       self._new_contents.iteritems()]
165
 
            entries.sort(reverse=True)
166
 
            for path, trans_id, kind in entries:
167
 
                if kind == "directory":
168
 
                    os.rmdir(path)
169
 
                else:
170
 
                    os.unlink(path)
171
 
            try:
172
 
                os.rmdir(self._limbodir)
173
 
            except OSError:
174
 
                # We don't especially care *why* the dir is immortal.
175
 
                raise ImmortalLimbo(self._limbodir)
176
 
            try:
177
 
                if self._deletiondir is not None:
178
 
                    os.rmdir(self._deletiondir)
179
 
            except OSError:
180
 
                raise errors.ImmortalPendingDeletion(self._deletiondir)
181
 
        finally:
182
 
            self._tree.unlock()
183
 
            self._tree = None
184
 
 
185
149
    def _assign_id(self):
186
150
        """Produce a new tranform id"""
187
151
        new_id = "new-%s" % self._id_number
199
163
        """Change the path that is assigned to a transaction id."""
200
164
        if trans_id == self._new_root:
201
165
            raise CantMoveRoot
202
 
        previous_parent = self._new_parent.get(trans_id)
203
 
        previous_name = self._new_name.get(trans_id)
204
166
        self._new_name[trans_id] = name
205
167
        self._new_parent[trans_id] = parent
206
168
        if parent == ROOT_PARENT:
207
169
            if self._new_root is not None:
208
170
                raise ValueError("Cannot have multiple roots.")
209
171
            self._new_root = trans_id
210
 
        if (trans_id in self._limbo_files and
211
 
            trans_id not in self._needs_rename):
212
 
            self._rename_in_limbo([trans_id])
213
 
            self._limbo_children[previous_parent].remove(trans_id)
214
 
            del self._limbo_children_names[previous_parent][previous_name]
215
 
 
216
 
    def _rename_in_limbo(self, trans_ids):
217
 
        """Fix limbo names so that the right final path is produced.
218
 
 
219
 
        This means we outsmarted ourselves-- we tried to avoid renaming
220
 
        these files later by creating them with their final names in their
221
 
        final parents.  But now the previous name or parent is no longer
222
 
        suitable, so we have to rename them.
223
 
 
224
 
        Even for trans_ids that have no new contents, we must remove their
225
 
        entries from _limbo_files, because they are now stale.
226
 
        """
227
 
        for trans_id in trans_ids:
228
 
            old_path = self._limbo_files.pop(trans_id)
229
 
            if trans_id not in self._new_contents:
230
 
                continue
231
 
            new_path = self._limbo_name(trans_id)
232
 
            os.rename(old_path, new_path)
233
172
 
234
173
    def adjust_root_path(self, name, parent):
235
174
        """Emulate moving the root by moving all children, instead.
297
236
            else:
298
237
                return self.trans_id_tree_file_id(file_id)
299
238
 
300
 
    def canonical_path(self, path):
301
 
        """Get the canonical tree-relative path"""
302
 
        # don't follow final symlinks
303
 
        abs = self._tree.abspath(path)
304
 
        if abs in self._relpaths:
305
 
            return self._relpaths[abs]
306
 
        dirname, basename = os.path.split(abs)
307
 
        if dirname not in self._realpaths:
308
 
            self._realpaths[dirname] = os.path.realpath(dirname)
309
 
        dirname = self._realpaths[dirname]
310
 
        abs = pathjoin(dirname, basename)
311
 
        if dirname in self._relpaths:
312
 
            relpath = pathjoin(self._relpaths[dirname], basename)
313
 
            relpath = relpath.rstrip('/\\')
314
 
        else:
315
 
            relpath = self._tree.relpath(abs)
316
 
        self._relpaths[abs] = relpath
317
 
        return relpath
318
 
 
319
239
    def trans_id_tree_path(self, path):
320
240
        """Determine (and maybe set) the transaction ID for a tree path."""
321
241
        path = self.canonical_path(path)
331
251
            return ROOT_PARENT
332
252
        return self.trans_id_tree_path(os.path.dirname(path))
333
253
 
334
 
    def create_file(self, contents, trans_id, mode_id=None):
335
 
        """Schedule creation of a new file.
336
 
 
337
 
        See also new_file.
338
 
 
339
 
        Contents is an iterator of strings, all of which will be written
340
 
        to the target destination.
341
 
 
342
 
        New file takes the permissions of any existing file with that id,
343
 
        unless mode_id is specified.
344
 
        """
345
 
        name = self._limbo_name(trans_id)
346
 
        f = open(name, 'wb')
347
 
        try:
348
 
            try:
349
 
                unique_add(self._new_contents, trans_id, 'file')
350
 
            except:
351
 
                # Clean up the file, it never got registered so
352
 
                # TreeTransform.finalize() won't clean it up.
353
 
                f.close()
354
 
                os.unlink(name)
355
 
                raise
356
 
 
357
 
            f.writelines(contents)
358
 
        finally:
359
 
            f.close()
360
 
        self._set_mode(trans_id, mode_id, S_ISREG)
361
 
 
362
 
    def _set_mode(self, trans_id, mode_id, typefunc):
363
 
        """Set the mode of new file contents.
364
 
        The mode_id is the existing file to get the mode from (often the same
365
 
        as trans_id).  The operation is only performed if there's a mode match
366
 
        according to typefunc.
367
 
        """
368
 
        if mode_id is None:
369
 
            mode_id = trans_id
370
 
        try:
371
 
            old_path = self._tree_id_paths[mode_id]
372
 
        except KeyError:
373
 
            return
374
 
        try:
375
 
            mode = os.stat(self._tree.abspath(old_path)).st_mode
376
 
        except OSError, e:
377
 
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
378
 
                # Either old_path doesn't exist, or the parent of the
379
 
                # target is not a directory (but will be one eventually)
380
 
                # Either way, we know it doesn't exist *right now*
381
 
                # See also bug #248448
382
 
                return
383
 
            else:
384
 
                raise
385
 
        if typefunc(mode):
386
 
            os.chmod(self._limbo_name(trans_id), mode)
387
 
 
388
 
    def create_hardlink(self, path, trans_id):
389
 
        """Schedule creation of a hard link"""
390
 
        name = self._limbo_name(trans_id)
391
 
        try:
392
 
            os.link(path, name)
393
 
        except OSError, e:
394
 
            if e.errno != errno.EPERM:
395
 
                raise
396
 
            raise errors.HardLinkNotSupported(path)
397
 
        try:
398
 
            unique_add(self._new_contents, trans_id, 'file')
399
 
        except:
400
 
            # Clean up the file, it never got registered so
401
 
            # TreeTransform.finalize() won't clean it up.
402
 
            os.unlink(name)
403
 
            raise
404
 
 
405
 
    def create_directory(self, trans_id):
406
 
        """Schedule creation of a new directory.
407
 
 
408
 
        See also new_directory.
409
 
        """
410
 
        os.mkdir(self._limbo_name(trans_id))
411
 
        unique_add(self._new_contents, trans_id, 'directory')
412
 
 
413
 
    def create_symlink(self, target, trans_id):
414
 
        """Schedule creation of a new symbolic link.
415
 
 
416
 
        target is a bytestring.
417
 
        See also new_symlink.
418
 
        """
419
 
        if has_symlinks():
420
 
            os.symlink(target, self._limbo_name(trans_id))
421
 
            unique_add(self._new_contents, trans_id, 'symlink')
422
 
        else:
423
 
            try:
424
 
                path = FinalPaths(self).get_path(trans_id)
425
 
            except KeyError:
426
 
                path = None
427
 
            raise UnableCreateSymlink(path=path)
428
 
 
429
 
    def cancel_creation(self, trans_id):
430
 
        """Cancel the creation of new file contents."""
431
 
        del self._new_contents[trans_id]
432
 
        children = self._limbo_children.get(trans_id)
433
 
        # if this is a limbo directory with children, move them before removing
434
 
        # the directory
435
 
        if children is not None:
436
 
            self._rename_in_limbo(children)
437
 
            del self._limbo_children[trans_id]
438
 
            del self._limbo_children_names[trans_id]
439
 
        delete_any(self._limbo_name(trans_id))
440
 
 
441
254
    def delete_contents(self, trans_id):
442
255
        """Schedule the contents of a path entry for deletion"""
443
256
        self.tree_kind(trans_id)
517
330
        new_ids.update(changed_kind)
518
331
        return sorted(FinalPaths(self).get_paths(new_ids))
519
332
 
520
 
    def tree_kind(self, trans_id):
521
 
        """Determine the file kind in the working tree.
522
 
 
523
 
        Raises NoSuchFile if the file does not exist
524
 
        """
525
 
        path = self._tree_id_paths.get(trans_id)
526
 
        if path is None:
527
 
            raise NoSuchFile(None)
528
 
        try:
529
 
            return file_kind(self._tree.abspath(path))
530
 
        except OSError, e:
531
 
            if e.errno != errno.ENOENT:
532
 
                raise
533
 
            else:
534
 
                raise NoSuchFile(path)
535
 
 
536
333
    def final_kind(self, trans_id):
537
334
        """Determine the final file kind, after any changes applied.
538
335
 
666
463
            # ensure that all children are registered with the transaction
667
464
            list(self.iter_tree_children(parent_id))
668
465
 
669
 
    def iter_tree_children(self, parent_id):
670
 
        """Iterate through the entry's tree children, if any"""
671
 
        try:
672
 
            path = self._tree_id_paths[parent_id]
673
 
        except KeyError:
674
 
            return
675
 
        try:
676
 
            children = os.listdir(self._tree.abspath(path))
677
 
        except OSError, e:
678
 
            if not (osutils._is_error_enotdir(e)
679
 
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
680
 
                raise
681
 
            return
682
 
 
683
 
        for child in children:
684
 
            childpath = joinpath(path, child)
685
 
            if self._tree.is_control_filename(childpath):
686
 
                continue
687
 
            yield self.trans_id_tree_path(childpath)
688
 
 
689
466
    def has_named_child(self, by_parent, parent_id, name):
690
467
        try:
691
468
            children = by_parent[parent_id]
866
643
            return True
867
644
        return False
868
645
 
869
 
    def _limbo_name(self, trans_id):
870
 
        """Generate the limbo name of a file"""
871
 
        limbo_name = self._limbo_files.get(trans_id)
872
 
        if limbo_name is not None:
873
 
            return limbo_name
874
 
        parent = self._new_parent.get(trans_id)
875
 
        # if the parent directory is already in limbo (e.g. when building a
876
 
        # tree), choose a limbo name inside the parent, to reduce further
877
 
        # renames.
878
 
        use_direct_path = False
879
 
        if self._new_contents.get(parent) == 'directory':
880
 
            filename = self._new_name.get(trans_id)
881
 
            if filename is not None:
882
 
                if parent not in self._limbo_children:
883
 
                    self._limbo_children[parent] = set()
884
 
                    self._limbo_children_names[parent] = {}
885
 
                    use_direct_path = True
886
 
                # the direct path can only be used if no other file has
887
 
                # already taken this pathname, i.e. if the name is unused, or
888
 
                # if it is already associated with this trans_id.
889
 
                elif self._case_sensitive_target:
890
 
                    if (self._limbo_children_names[parent].get(filename)
891
 
                        in (trans_id, None)):
892
 
                        use_direct_path = True
893
 
                else:
894
 
                    for l_filename, l_trans_id in\
895
 
                        self._limbo_children_names[parent].iteritems():
896
 
                        if l_trans_id == trans_id:
897
 
                            continue
898
 
                        if l_filename.lower() == filename.lower():
899
 
                            break
900
 
                    else:
901
 
                        use_direct_path = True
902
 
 
903
 
        if use_direct_path:
904
 
            limbo_name = pathjoin(self._limbo_files[parent], filename)
905
 
            self._limbo_children[parent].add(trans_id)
906
 
            self._limbo_children_names[parent][filename] = trans_id
907
 
        else:
908
 
            limbo_name = pathjoin(self._limbodir, trans_id)
909
 
            self._needs_rename.add(trans_id)
910
 
        self._limbo_files[trans_id] = limbo_name
911
 
        return limbo_name
912
 
 
913
646
    def _set_executability(self, path, trans_id):
914
647
        """Set the executability of versioned files """
915
648
        if supports_executable():
1175
908
                                      (('attribs',),))
1176
909
        for trans_id, kind in self._new_contents.items():
1177
910
            if kind == 'file':
1178
 
                cur_file = open(self._limbo_name(trans_id), 'rb')
1179
 
                try:
1180
 
                    lines = osutils.chunks_to_lines(cur_file.readlines())
1181
 
                finally:
1182
 
                    cur_file.close()
 
911
                lines = osutils.chunks_to_lines(
 
912
                    self._read_file_chunks(trans_id))
1183
913
                parents = self._get_parents_lines(trans_id)
1184
914
                mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1185
915
                content = ''.join(mpdiff.to_patch())
1186
916
            if kind == 'directory':
1187
917
                content = ''
1188
918
            if kind == 'symlink':
1189
 
                content = os.readlink(self._limbo_name(trans_id))
 
919
                content = self._read_symlink_target(trans_id)
1190
920
            yield serializer.bytes_record(content, ((trans_id, kind),))
1191
921
 
1192
 
 
1193
922
    def deserialize(self, records):
1194
923
        """Deserialize a stored TreeTransform.
1195
924
 
1226
955
                self.create_symlink(content.decode('utf-8'), trans_id)
1227
956
 
1228
957
 
1229
 
class TreeTransform(TreeTransformBase):
 
958
class DiskTreeTransform(TreeTransformBase):
 
959
    """Tree transform storing its contents on disk."""
 
960
 
 
961
    def __init__(self, tree, limbodir, pb=DummyProgress(),
 
962
                 case_sensitive=True):
 
963
        """Constructor.
 
964
        :param tree: The tree that will be transformed, but not necessarily
 
965
            the output tree.
 
966
        :param limbodir: A directory where new files can be stored until
 
967
            they are installed in their proper places
 
968
        :param pb: A ProgressBar indicating how much progress is being made
 
969
        :param case_sensitive: If True, the target of the transform is
 
970
            case sensitive, not just case preserving.
 
971
        """
 
972
        TreeTransformBase.__init__(self, tree, pb, case_sensitive)
 
973
        self._limbodir = limbodir
 
974
        self._deletiondir = None
 
975
        # A mapping of transform ids to their limbo filename
 
976
        self._limbo_files = {}
 
977
        # A mapping of transform ids to a set of the transform ids of children
 
978
        # that their limbo directory has
 
979
        self._limbo_children = {}
 
980
        # Map transform ids to maps of child filename to child transform id
 
981
        self._limbo_children_names = {}
 
982
        # List of transform ids that need to be renamed from limbo into place
 
983
        self._needs_rename = set()
 
984
 
 
985
    def finalize(self):
 
986
        """Release the working tree lock, if held, clean up limbo dir.
 
987
 
 
988
        This is required if apply has not been invoked, but can be invoked
 
989
        even after apply.
 
990
        """
 
991
        if self._tree is None:
 
992
            return
 
993
        try:
 
994
            entries = [(self._limbo_name(t), t, k) for t, k in
 
995
                       self._new_contents.iteritems()]
 
996
            entries.sort(reverse=True)
 
997
            for path, trans_id, kind in entries:
 
998
                if kind == "directory":
 
999
                    os.rmdir(path)
 
1000
                else:
 
1001
                    os.unlink(path)
 
1002
            try:
 
1003
                os.rmdir(self._limbodir)
 
1004
            except OSError:
 
1005
                # We don't especially care *why* the dir is immortal.
 
1006
                raise ImmortalLimbo(self._limbodir)
 
1007
            try:
 
1008
                if self._deletiondir is not None:
 
1009
                    os.rmdir(self._deletiondir)
 
1010
            except OSError:
 
1011
                raise errors.ImmortalPendingDeletion(self._deletiondir)
 
1012
        finally:
 
1013
            TreeTransformBase.finalize(self)
 
1014
 
 
1015
    def _limbo_name(self, trans_id):
 
1016
        """Generate the limbo name of a file"""
 
1017
        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
 
1057
        return limbo_name
 
1058
 
 
1059
    def adjust_path(self, name, parent, trans_id):
 
1060
        previous_parent = self._new_parent.get(trans_id)
 
1061
        previous_name = self._new_name.get(trans_id)
 
1062
        TreeTransformBase.adjust_path(self, name, parent, trans_id)
 
1063
        if (trans_id in self._limbo_files and
 
1064
            trans_id not in self._needs_rename):
 
1065
            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]
 
1068
 
 
1069
    def _rename_in_limbo(self, trans_ids):
 
1070
        """Fix limbo names so that the right final path is produced.
 
1071
 
 
1072
        This means we outsmarted ourselves-- we tried to avoid renaming
 
1073
        these files later by creating them with their final names in their
 
1074
        final parents.  But now the previous name or parent is no longer
 
1075
        suitable, so we have to rename them.
 
1076
 
 
1077
        Even for trans_ids that have no new contents, we must remove their
 
1078
        entries from _limbo_files, because they are now stale.
 
1079
        """
 
1080
        for trans_id in trans_ids:
 
1081
            old_path = self._limbo_files.pop(trans_id)
 
1082
            if trans_id not in self._new_contents:
 
1083
                continue
 
1084
            new_path = self._limbo_name(trans_id)
 
1085
            os.rename(old_path, new_path)
 
1086
 
 
1087
    def create_file(self, contents, trans_id, mode_id=None):
 
1088
        """Schedule creation of a new file.
 
1089
 
 
1090
        See also new_file.
 
1091
 
 
1092
        Contents is an iterator of strings, all of which will be written
 
1093
        to the target destination.
 
1094
 
 
1095
        New file takes the permissions of any existing file with that id,
 
1096
        unless mode_id is specified.
 
1097
        """
 
1098
        name = self._limbo_name(trans_id)
 
1099
        f = open(name, 'wb')
 
1100
        try:
 
1101
            try:
 
1102
                unique_add(self._new_contents, trans_id, 'file')
 
1103
            except:
 
1104
                # Clean up the file, it never got registered so
 
1105
                # TreeTransform.finalize() won't clean it up.
 
1106
                f.close()
 
1107
                os.unlink(name)
 
1108
                raise
 
1109
 
 
1110
            f.writelines(contents)
 
1111
        finally:
 
1112
            f.close()
 
1113
        self._set_mode(trans_id, mode_id, S_ISREG)
 
1114
 
 
1115
    def _read_file_chunks(self, trans_id):
 
1116
        cur_file = open(self._limbo_name(trans_id), 'rb')
 
1117
        try:
 
1118
            return cur_file.readlines()
 
1119
        finally:
 
1120
            cur_file.close()
 
1121
 
 
1122
    def _read_symlink_target(self, trans_id):
 
1123
        return os.readlink(self._limbo_name(trans_id))
 
1124
 
 
1125
    def create_hardlink(self, path, trans_id):
 
1126
        """Schedule creation of a hard link"""
 
1127
        name = self._limbo_name(trans_id)
 
1128
        try:
 
1129
            os.link(path, name)
 
1130
        except OSError, e:
 
1131
            if e.errno != errno.EPERM:
 
1132
                raise
 
1133
            raise errors.HardLinkNotSupported(path)
 
1134
        try:
 
1135
            unique_add(self._new_contents, trans_id, 'file')
 
1136
        except:
 
1137
            # Clean up the file, it never got registered so
 
1138
            # TreeTransform.finalize() won't clean it up.
 
1139
            os.unlink(name)
 
1140
            raise
 
1141
 
 
1142
    def create_directory(self, trans_id):
 
1143
        """Schedule creation of a new directory.
 
1144
 
 
1145
        See also new_directory.
 
1146
        """
 
1147
        os.mkdir(self._limbo_name(trans_id))
 
1148
        unique_add(self._new_contents, trans_id, 'directory')
 
1149
 
 
1150
    def create_symlink(self, target, trans_id):
 
1151
        """Schedule creation of a new symbolic link.
 
1152
 
 
1153
        target is a bytestring.
 
1154
        See also new_symlink.
 
1155
        """
 
1156
        if has_symlinks():
 
1157
            os.symlink(target, self._limbo_name(trans_id))
 
1158
            unique_add(self._new_contents, trans_id, 'symlink')
 
1159
        else:
 
1160
            try:
 
1161
                path = FinalPaths(self).get_path(trans_id)
 
1162
            except KeyError:
 
1163
                path = None
 
1164
            raise UnableCreateSymlink(path=path)
 
1165
 
 
1166
    def cancel_creation(self, trans_id):
 
1167
        """Cancel the creation of new file contents."""
 
1168
        del self._new_contents[trans_id]
 
1169
        children = self._limbo_children.get(trans_id)
 
1170
        # if this is a limbo directory with children, move them before removing
 
1171
        # the directory
 
1172
        if children is not None:
 
1173
            self._rename_in_limbo(children)
 
1174
            del self._limbo_children[trans_id]
 
1175
            del self._limbo_children_names[trans_id]
 
1176
        delete_any(self._limbo_name(trans_id))
 
1177
 
 
1178
 
 
1179
class TreeTransform(DiskTreeTransform):
1230
1180
    """Represent a tree transformation.
1231
1181
 
1232
1182
    This object is designed to support incremental generation of the transform,
1318
1268
            tree.unlock()
1319
1269
            raise
1320
1270
 
1321
 
        TreeTransformBase.__init__(self, tree, limbodir, pb,
 
1271
        # Cache of realpath results, to speed up canonical_path
 
1272
        self._realpaths = {}
 
1273
        # Cache of relpath results, to speed up canonical_path
 
1274
        self._relpaths = {}
 
1275
        DiskTreeTransform.__init__(self, tree, limbodir, pb,
1322
1276
                                   tree.case_sensitive)
1323
1277
        self._deletiondir = deletiondir
1324
1278
 
 
1279
    def canonical_path(self, path):
 
1280
        """Get the canonical tree-relative path"""
 
1281
        # don't follow final symlinks
 
1282
        abs = self._tree.abspath(path)
 
1283
        if abs in self._relpaths:
 
1284
            return self._relpaths[abs]
 
1285
        dirname, basename = os.path.split(abs)
 
1286
        if dirname not in self._realpaths:
 
1287
            self._realpaths[dirname] = os.path.realpath(dirname)
 
1288
        dirname = self._realpaths[dirname]
 
1289
        abs = pathjoin(dirname, basename)
 
1290
        if dirname in self._relpaths:
 
1291
            relpath = pathjoin(self._relpaths[dirname], basename)
 
1292
            relpath = relpath.rstrip('/\\')
 
1293
        else:
 
1294
            relpath = self._tree.relpath(abs)
 
1295
        self._relpaths[abs] = relpath
 
1296
        return relpath
 
1297
 
 
1298
    def tree_kind(self, trans_id):
 
1299
        """Determine the file kind in the working tree.
 
1300
 
 
1301
        Raises NoSuchFile if the file does not exist
 
1302
        """
 
1303
        path = self._tree_id_paths.get(trans_id)
 
1304
        if path is None:
 
1305
            raise NoSuchFile(None)
 
1306
        try:
 
1307
            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)
 
1313
 
 
1314
    def _set_mode(self, trans_id, mode_id, typefunc):
 
1315
        """Set the mode of new file contents.
 
1316
        The mode_id is the existing file to get the mode from (often the same
 
1317
        as trans_id).  The operation is only performed if there's a mode match
 
1318
        according to typefunc.
 
1319
        """
 
1320
        if mode_id is None:
 
1321
            mode_id = trans_id
 
1322
        try:
 
1323
            old_path = self._tree_id_paths[mode_id]
 
1324
        except KeyError:
 
1325
            return
 
1326
        try:
 
1327
            mode = os.stat(self._tree.abspath(old_path)).st_mode
 
1328
        except OSError, e:
 
1329
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
 
1330
                # Either old_path doesn't exist, or the parent of the
 
1331
                # target is not a directory (but will be one eventually)
 
1332
                # Either way, we know it doesn't exist *right now*
 
1333
                # See also bug #248448
 
1334
                return
 
1335
            else:
 
1336
                raise
 
1337
        if typefunc(mode):
 
1338
            os.chmod(self._limbo_name(trans_id), mode)
 
1339
 
 
1340
    def iter_tree_children(self, parent_id):
 
1341
        """Iterate through the entry's tree children, if any"""
 
1342
        try:
 
1343
            path = self._tree_id_paths[parent_id]
 
1344
        except KeyError:
 
1345
            return
 
1346
        try:
 
1347
            children = os.listdir(self._tree.abspath(path))
 
1348
        except OSError, e:
 
1349
            if not (osutils._is_error_enotdir(e)
 
1350
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
 
1351
                raise
 
1352
            return
 
1353
 
 
1354
        for child in children:
 
1355
            childpath = joinpath(path, child)
 
1356
            if self._tree.is_control_filename(childpath):
 
1357
                continue
 
1358
            yield self.trans_id_tree_path(childpath)
 
1359
 
 
1360
 
1325
1361
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1326
1362
        """Apply all changes to the inventory and filesystem.
1327
1363
 
1504
1540
        return modified_paths
1505
1541
 
1506
1542
 
1507
 
class TransformPreview(TreeTransformBase):
 
1543
class TransformPreview(DiskTreeTransform):
1508
1544
    """A TreeTransform for generating preview trees.
1509
1545
 
1510
1546
    Unlike TreeTransform, this version works when the input tree is a
1515
1551
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1516
1552
        tree.lock_read()
1517
1553
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1518
 
        TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
 
1554
        DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1519
1555
 
1520
1556
    def canonical_path(self, path):
1521
1557
        return path
1845
1881
                size = None
1846
1882
                executable = None
1847
1883
            if kind == 'symlink':
1848
 
                link_or_sha1 = os.readlink(limbo_name)
 
1884
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
1849
1885
        if supports_executable():
1850
1886
            executable = tt._new_executability.get(trans_id, executable)
1851
1887
        return kind, size, executable, link_or_sha1
1879
1915
        name = self._transform._limbo_name(trans_id)
1880
1916
        return open(name, 'rb')
1881
1917
 
 
1918
    def get_file_with_stat(self, file_id, path=None):
 
1919
        return self.get_file(file_id, path), None
 
1920
 
1882
1921
    def annotate_iter(self, file_id,
1883
1922
                      default_revision=_mod_revision.CURRENT_REVISION):
1884
1923
        changes = self._changes(file_id)
1909
1948
            return self._transform._tree.get_symlink_target(file_id)
1910
1949
        trans_id = self._transform.trans_id_file_id(file_id)
1911
1950
        name = self._transform._limbo_name(trans_id)
1912
 
        return os.readlink(name)
 
1951
        return osutils.readlink(name)
1913
1952
 
1914
1953
    def walkdirs(self, prefix=''):
1915
1954
        pending = [self._transform.root]
2113
2152
                    executable = tree.is_executable(file_id, tree_path)
2114
2153
                    if executable:
2115
2154
                        tt.set_executability(executable, trans_id)
2116
 
                    deferred_contents.append((file_id, trans_id))
 
2155
                    trans_data = (trans_id, tree_path)
 
2156
                    deferred_contents.append((file_id, trans_data))
2117
2157
                else:
2118
2158
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
2119
2159
                                                          tree)
2150
2190
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
2151
2191
                  hardlink):
2152
2192
    total = len(desired_files) + offset
 
2193
    wt = tt._tree
2153
2194
    if accelerator_tree is None:
2154
2195
        new_desired_files = desired_files
2155
2196
    else:
2158
2199
                         in iter if not (c or e[0] != e[1]))
2159
2200
        new_desired_files = []
2160
2201
        count = 0
2161
 
        for file_id, trans_id in desired_files:
 
2202
        for file_id, (trans_id, tree_path) in desired_files:
2162
2203
            accelerator_path = unchanged.get(file_id)
2163
2204
            if accelerator_path is None:
2164
 
                new_desired_files.append((file_id, trans_id))
 
2205
                new_desired_files.append((file_id, (trans_id, tree_path)))
2165
2206
                continue
2166
2207
            pb.update('Adding file contents', count + offset, total)
2167
2208
            if hardlink:
2169
2210
                                   trans_id)
2170
2211
            else:
2171
2212
                contents = accelerator_tree.get_file(file_id, accelerator_path)
 
2213
                if wt.supports_content_filtering():
 
2214
                    filters = wt._content_filter_stack(tree_path)
 
2215
                    contents = filtered_output_bytes(contents, filters,
 
2216
                        ContentFilterContext(tree_path, tree))
2172
2217
                try:
2173
2218
                    tt.create_file(contents, trans_id)
2174
2219
                finally:
2175
 
                    contents.close()
 
2220
                    try:
 
2221
                        contents.close()
 
2222
                    except AttributeError:
 
2223
                        # after filtering, contents may no longer be file-like
 
2224
                        pass
2176
2225
            count += 1
2177
2226
        offset += count
2178
 
    for count, (trans_id, contents) in enumerate(tree.iter_files_bytes(
2179
 
                                                 new_desired_files)):
 
2227
    for count, ((trans_id, tree_path), contents) in enumerate(
 
2228
            tree.iter_files_bytes(new_desired_files)):
 
2229
        if wt.supports_content_filtering():
 
2230
            filters = wt._content_filter_stack(tree_path)
 
2231
            contents = filtered_output_bytes(contents, filters,
 
2232
                ContentFilterContext(tree_path, tree))
2180
2233
        tt.create_file(contents, trans_id)
2181
2234
        pb.update('Adding file contents', count + offset, total)
2182
2235