~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-04-09 20:23:07 UTC
  • mfrom: (4265.1.4 bbc-merge)
  • Revision ID: pqm@pqm.ubuntu.com-20090409202307-n0depb16qepoe21o
(jam) Change _fetch_uses_deltas = False for CHK repos until we can
        write a better fix.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008 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
22
22
lazy_import(globals(), """
23
23
from bzrlib import (
24
24
    annotate,
25
 
    bencode,
26
25
    bzrdir,
27
26
    delta,
28
27
    errors,
31
30
    osutils,
32
31
    revision as _mod_revision,
33
32
    )
 
33
from bzrlib.util import bencode
34
34
""")
35
35
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
36
36
                           ReusingTransform, NotVersionedError, CantMoveRoot,
76
76
 
77
77
 
78
78
class TreeTransformBase(object):
79
 
    """The base class for TreeTransform and its kin."""
 
79
    """The base class for TreeTransform and TreeTransformBase"""
80
80
 
81
 
    def __init__(self, tree, pb=DummyProgress(),
 
81
    def __init__(self, tree, limbodir, pb=DummyProgress(),
82
82
                 case_sensitive=True):
83
83
        """Constructor.
84
84
 
85
85
        :param tree: The tree that will be transformed, but not necessarily
86
86
            the output tree.
87
 
        :param pb: A ProgressTask indicating how much progress is being made
 
87
        :param limbodir: A directory where new files can be stored until
 
88
            they are installed in their proper places
 
89
        :param pb: A ProgressBar indicating how much progress is being made
88
90
        :param case_sensitive: If True, the target of the transform is
89
91
            case sensitive, not just case preserving.
90
92
        """
91
93
        object.__init__(self)
92
94
        self._tree = tree
 
95
        self._limbodir = limbodir
 
96
        self._deletiondir = None
93
97
        self._id_number = 0
94
98
        # mapping of trans_id -> new basename
95
99
        self._new_name = {}
97
101
        self._new_parent = {}
98
102
        # mapping of trans_id with new contents -> new file_kind
99
103
        self._new_contents = {}
 
104
        # A mapping of transform ids to their limbo filename
 
105
        self._limbo_files = {}
 
106
        # A mapping of transform ids to a set of the transform ids of children
 
107
        # that their limbo directory has
 
108
        self._limbo_children = {}
 
109
        # Map transform ids to maps of child filename to child transform id
 
110
        self._limbo_children_names = {}
 
111
        # List of transform ids that need to be renamed from limbo into place
 
112
        self._needs_rename = set()
100
113
        # Set of trans_ids whose contents will be removed
101
114
        self._removed_contents = set()
102
115
        # Mapping of trans_id -> new execute-bit value
115
128
        self._tree_path_ids = {}
116
129
        # Mapping trans_id -> path in old tree
117
130
        self._tree_id_paths = {}
 
131
        # Cache of realpath results, to speed up canonical_path
 
132
        self._realpaths = {}
 
133
        # Cache of relpath results, to speed up canonical_path
 
134
        self._relpaths = {}
118
135
        # The trans_id that will be used as the tree root
119
136
        root_id = tree.get_root_id()
120
137
        if root_id is not None:
130
147
        # A counter of how many files have been renamed
131
148
        self.rename_count = 0
132
149
 
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
 
 
144
150
    def __get_root(self):
145
151
        return self._new_root
146
152
 
147
153
    root = property(__get_root)
148
154
 
 
155
    def finalize(self):
 
156
        """Release the working tree lock, if held, clean up limbo dir.
 
157
 
 
158
        This is required if apply has not been invoked, but can be invoked
 
159
        even after apply.
 
160
        """
 
161
        if self._tree is None:
 
162
            return
 
163
        try:
 
164
            entries = [(self._limbo_name(t), t, k) for t, k in
 
165
                       self._new_contents.iteritems()]
 
166
            entries.sort(reverse=True)
 
167
            for path, trans_id, kind in entries:
 
168
                if kind == "directory":
 
169
                    os.rmdir(path)
 
170
                else:
 
171
                    os.unlink(path)
 
172
            try:
 
173
                os.rmdir(self._limbodir)
 
174
            except OSError:
 
175
                # We don't especially care *why* the dir is immortal.
 
176
                raise ImmortalLimbo(self._limbodir)
 
177
            try:
 
178
                if self._deletiondir is not None:
 
179
                    os.rmdir(self._deletiondir)
 
180
            except OSError:
 
181
                raise errors.ImmortalPendingDeletion(self._deletiondir)
 
182
        finally:
 
183
            self._tree.unlock()
 
184
            self._tree = None
 
185
 
149
186
    def _assign_id(self):
150
187
        """Produce a new tranform id"""
151
188
        new_id = "new-%s" % self._id_number
163
200
        """Change the path that is assigned to a transaction id."""
164
201
        if trans_id == self._new_root:
165
202
            raise CantMoveRoot
 
203
        previous_parent = self._new_parent.get(trans_id)
 
204
        previous_name = self._new_name.get(trans_id)
166
205
        self._new_name[trans_id] = name
167
206
        self._new_parent[trans_id] = parent
168
207
        if parent == ROOT_PARENT:
169
208
            if self._new_root is not None:
170
209
                raise ValueError("Cannot have multiple roots.")
171
210
            self._new_root = trans_id
 
211
        if (trans_id in self._limbo_files and
 
212
            trans_id not in self._needs_rename):
 
213
            self._rename_in_limbo([trans_id])
 
214
            self._limbo_children[previous_parent].remove(trans_id)
 
215
            del self._limbo_children_names[previous_parent][previous_name]
 
216
 
 
217
    def _rename_in_limbo(self, trans_ids):
 
218
        """Fix limbo names so that the right final path is produced.
 
219
 
 
220
        This means we outsmarted ourselves-- we tried to avoid renaming
 
221
        these files later by creating them with their final names in their
 
222
        final parents.  But now the previous name or parent is no longer
 
223
        suitable, so we have to rename them.
 
224
 
 
225
        Even for trans_ids that have no new contents, we must remove their
 
226
        entries from _limbo_files, because they are now stale.
 
227
        """
 
228
        for trans_id in trans_ids:
 
229
            old_path = self._limbo_files.pop(trans_id)
 
230
            if trans_id not in self._new_contents:
 
231
                continue
 
232
            new_path = self._limbo_name(trans_id)
 
233
            os.rename(old_path, new_path)
172
234
 
173
235
    def adjust_root_path(self, name, parent):
174
236
        """Emulate moving the root by moving all children, instead.
236
298
            else:
237
299
                return self.trans_id_tree_file_id(file_id)
238
300
 
 
301
    def canonical_path(self, path):
 
302
        """Get the canonical tree-relative path"""
 
303
        # don't follow final symlinks
 
304
        abs = self._tree.abspath(path)
 
305
        if abs in self._relpaths:
 
306
            return self._relpaths[abs]
 
307
        dirname, basename = os.path.split(abs)
 
308
        if dirname not in self._realpaths:
 
309
            self._realpaths[dirname] = os.path.realpath(dirname)
 
310
        dirname = self._realpaths[dirname]
 
311
        abs = pathjoin(dirname, basename)
 
312
        if dirname in self._relpaths:
 
313
            relpath = pathjoin(self._relpaths[dirname], basename)
 
314
            relpath = relpath.rstrip('/\\')
 
315
        else:
 
316
            relpath = self._tree.relpath(abs)
 
317
        self._relpaths[abs] = relpath
 
318
        return relpath
 
319
 
239
320
    def trans_id_tree_path(self, path):
240
321
        """Determine (and maybe set) the transaction ID for a tree path."""
241
322
        path = self.canonical_path(path)
251
332
            return ROOT_PARENT
252
333
        return self.trans_id_tree_path(os.path.dirname(path))
253
334
 
 
335
    def create_file(self, contents, trans_id, mode_id=None):
 
336
        """Schedule creation of a new file.
 
337
 
 
338
        See also new_file.
 
339
 
 
340
        Contents is an iterator of strings, all of which will be written
 
341
        to the target destination.
 
342
 
 
343
        New file takes the permissions of any existing file with that id,
 
344
        unless mode_id is specified.
 
345
        """
 
346
        name = self._limbo_name(trans_id)
 
347
        f = open(name, 'wb')
 
348
        try:
 
349
            try:
 
350
                unique_add(self._new_contents, trans_id, 'file')
 
351
            except:
 
352
                # Clean up the file, it never got registered so
 
353
                # TreeTransform.finalize() won't clean it up.
 
354
                f.close()
 
355
                os.unlink(name)
 
356
                raise
 
357
 
 
358
            f.writelines(contents)
 
359
        finally:
 
360
            f.close()
 
361
        self._set_mode(trans_id, mode_id, S_ISREG)
 
362
 
 
363
    def _set_mode(self, trans_id, mode_id, typefunc):
 
364
        """Set the mode of new file contents.
 
365
        The mode_id is the existing file to get the mode from (often the same
 
366
        as trans_id).  The operation is only performed if there's a mode match
 
367
        according to typefunc.
 
368
        """
 
369
        if mode_id is None:
 
370
            mode_id = trans_id
 
371
        try:
 
372
            old_path = self._tree_id_paths[mode_id]
 
373
        except KeyError:
 
374
            return
 
375
        try:
 
376
            mode = os.stat(self._tree.abspath(old_path)).st_mode
 
377
        except OSError, e:
 
378
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
 
379
                # Either old_path doesn't exist, or the parent of the
 
380
                # target is not a directory (but will be one eventually)
 
381
                # Either way, we know it doesn't exist *right now*
 
382
                # See also bug #248448
 
383
                return
 
384
            else:
 
385
                raise
 
386
        if typefunc(mode):
 
387
            os.chmod(self._limbo_name(trans_id), mode)
 
388
 
 
389
    def create_hardlink(self, path, trans_id):
 
390
        """Schedule creation of a hard link"""
 
391
        name = self._limbo_name(trans_id)
 
392
        try:
 
393
            os.link(path, name)
 
394
        except OSError, e:
 
395
            if e.errno != errno.EPERM:
 
396
                raise
 
397
            raise errors.HardLinkNotSupported(path)
 
398
        try:
 
399
            unique_add(self._new_contents, trans_id, 'file')
 
400
        except:
 
401
            # Clean up the file, it never got registered so
 
402
            # TreeTransform.finalize() won't clean it up.
 
403
            os.unlink(name)
 
404
            raise
 
405
 
 
406
    def create_directory(self, trans_id):
 
407
        """Schedule creation of a new directory.
 
408
 
 
409
        See also new_directory.
 
410
        """
 
411
        os.mkdir(self._limbo_name(trans_id))
 
412
        unique_add(self._new_contents, trans_id, 'directory')
 
413
 
 
414
    def create_symlink(self, target, trans_id):
 
415
        """Schedule creation of a new symbolic link.
 
416
 
 
417
        target is a bytestring.
 
418
        See also new_symlink.
 
419
        """
 
420
        if has_symlinks():
 
421
            os.symlink(target, self._limbo_name(trans_id))
 
422
            unique_add(self._new_contents, trans_id, 'symlink')
 
423
        else:
 
424
            try:
 
425
                path = FinalPaths(self).get_path(trans_id)
 
426
            except KeyError:
 
427
                path = None
 
428
            raise UnableCreateSymlink(path=path)
 
429
 
 
430
    def cancel_creation(self, trans_id):
 
431
        """Cancel the creation of new file contents."""
 
432
        del self._new_contents[trans_id]
 
433
        children = self._limbo_children.get(trans_id)
 
434
        # if this is a limbo directory with children, move them before removing
 
435
        # the directory
 
436
        if children is not None:
 
437
            self._rename_in_limbo(children)
 
438
            del self._limbo_children[trans_id]
 
439
            del self._limbo_children_names[trans_id]
 
440
        delete_any(self._limbo_name(trans_id))
 
441
 
254
442
    def delete_contents(self, trans_id):
255
443
        """Schedule the contents of a path entry for deletion"""
256
444
        self.tree_kind(trans_id)
330
518
        new_ids.update(changed_kind)
331
519
        return sorted(FinalPaths(self).get_paths(new_ids))
332
520
 
 
521
    def tree_kind(self, trans_id):
 
522
        """Determine the file kind in the working tree.
 
523
 
 
524
        Raises NoSuchFile if the file does not exist
 
525
        """
 
526
        path = self._tree_id_paths.get(trans_id)
 
527
        if path is None:
 
528
            raise NoSuchFile(None)
 
529
        try:
 
530
            return file_kind(self._tree.abspath(path))
 
531
        except OSError, e:
 
532
            if e.errno != errno.ENOENT:
 
533
                raise
 
534
            else:
 
535
                raise NoSuchFile(path)
 
536
 
333
537
    def final_kind(self, trans_id):
334
538
        """Determine the final file kind, after any changes applied.
335
539
 
442
646
        conflicts.extend(self._overwrite_conflicts())
443
647
        return conflicts
444
648
 
445
 
    def _check_malformed(self):
446
 
        conflicts = self.find_conflicts()
447
 
        if len(conflicts) != 0:
448
 
            raise MalformedTransform(conflicts=conflicts)
449
 
 
450
649
    def _add_tree_children(self):
451
650
        """Add all the children of all active parents to the known paths.
452
651
 
468
667
            # ensure that all children are registered with the transaction
469
668
            list(self.iter_tree_children(parent_id))
470
669
 
 
670
    def iter_tree_children(self, parent_id):
 
671
        """Iterate through the entry's tree children, if any"""
 
672
        try:
 
673
            path = self._tree_id_paths[parent_id]
 
674
        except KeyError:
 
675
            return
 
676
        try:
 
677
            children = os.listdir(self._tree.abspath(path))
 
678
        except OSError, e:
 
679
            if not (osutils._is_error_enotdir(e)
 
680
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
 
681
                raise
 
682
            return
 
683
 
 
684
        for child in children:
 
685
            childpath = joinpath(path, child)
 
686
            if self._tree.is_control_filename(childpath):
 
687
                continue
 
688
            yield self.trans_id_tree_path(childpath)
 
689
 
471
690
    def has_named_child(self, by_parent, parent_id, name):
472
691
        try:
473
692
            children = by_parent[parent_id]
648
867
            return True
649
868
        return False
650
869
 
 
870
    def _limbo_name(self, trans_id):
 
871
        """Generate the limbo name of a file"""
 
872
        limbo_name = self._limbo_files.get(trans_id)
 
873
        if limbo_name is not None:
 
874
            return limbo_name
 
875
        parent = self._new_parent.get(trans_id)
 
876
        # if the parent directory is already in limbo (e.g. when building a
 
877
        # tree), choose a limbo name inside the parent, to reduce further
 
878
        # renames.
 
879
        use_direct_path = False
 
880
        if self._new_contents.get(parent) == 'directory':
 
881
            filename = self._new_name.get(trans_id)
 
882
            if filename is not None:
 
883
                if parent not in self._limbo_children:
 
884
                    self._limbo_children[parent] = set()
 
885
                    self._limbo_children_names[parent] = {}
 
886
                    use_direct_path = True
 
887
                # the direct path can only be used if no other file has
 
888
                # already taken this pathname, i.e. if the name is unused, or
 
889
                # if it is already associated with this trans_id.
 
890
                elif self._case_sensitive_target:
 
891
                    if (self._limbo_children_names[parent].get(filename)
 
892
                        in (trans_id, None)):
 
893
                        use_direct_path = True
 
894
                else:
 
895
                    for l_filename, l_trans_id in\
 
896
                        self._limbo_children_names[parent].iteritems():
 
897
                        if l_trans_id == trans_id:
 
898
                            continue
 
899
                        if l_filename.lower() == filename.lower():
 
900
                            break
 
901
                    else:
 
902
                        use_direct_path = True
 
903
 
 
904
        if use_direct_path:
 
905
            limbo_name = pathjoin(self._limbo_files[parent], filename)
 
906
            self._limbo_children[parent].add(trans_id)
 
907
            self._limbo_children_names[parent][filename] = trans_id
 
908
        else:
 
909
            limbo_name = pathjoin(self._limbodir, trans_id)
 
910
            self._needs_rename.add(trans_id)
 
911
        self._limbo_files[trans_id] = limbo_name
 
912
        return limbo_name
 
913
 
651
914
    def _set_executability(self, path, trans_id):
652
915
        """Set the executability of versioned files """
653
916
        if supports_executable():
864
1127
        """
865
1128
        return _PreviewTree(self)
866
1129
 
867
 
    def commit(self, branch, message, merge_parents=None, strict=False):
868
 
        """Commit the result of this TreeTransform to a branch.
869
 
 
870
 
        :param branch: The branch to commit to.
871
 
        :param message: The message to attach to the commit.
872
 
        :param merge_parents: Additional parents specified by pending merges.
873
 
        :return: The revision_id of the revision committed.
874
 
        """
875
 
        self._check_malformed()
876
 
        if strict:
877
 
            unversioned = set(self._new_contents).difference(set(self._new_id))
878
 
            for trans_id in unversioned:
879
 
                if self.final_file_id(trans_id) is None:
880
 
                    raise errors.StrictCommitFailed()
881
 
 
882
 
        revno, last_rev_id = branch.last_revision_info()
883
 
        if last_rev_id == _mod_revision.NULL_REVISION:
884
 
            if merge_parents is not None:
885
 
                raise ValueError('Cannot supply merge parents for first'
886
 
                                 ' commit.')
887
 
            parent_ids = []
888
 
        else:
889
 
            parent_ids = [last_rev_id]
890
 
            if merge_parents is not None:
891
 
                parent_ids.extend(merge_parents)
892
 
        if self._tree.get_revision_id() != last_rev_id:
893
 
            raise ValueError('TreeTransform not based on branch basis: %s' %
894
 
                             self._tree.get_revision_id())
895
 
        builder = branch.get_commit_builder(parent_ids)
896
 
        preview = self.get_preview_tree()
897
 
        list(builder.record_iter_changes(preview, last_rev_id,
898
 
                                         self.iter_changes()))
899
 
        builder.finish_inventory()
900
 
        revision_id = builder.commit(message)
901
 
        branch.set_last_revision_info(revno + 1, revision_id)
902
 
        return revision_id
903
 
 
904
1130
    def _text_parent(self, trans_id):
905
1131
        file_id = self.tree_file_id(trans_id)
906
1132
        try:
950
1176
                                      (('attribs',),))
951
1177
        for trans_id, kind in self._new_contents.items():
952
1178
            if kind == 'file':
953
 
                lines = osutils.chunks_to_lines(
954
 
                    self._read_file_chunks(trans_id))
 
1179
                cur_file = open(self._limbo_name(trans_id), 'rb')
 
1180
                try:
 
1181
                    lines = osutils.chunks_to_lines(cur_file.readlines())
 
1182
                finally:
 
1183
                    cur_file.close()
955
1184
                parents = self._get_parents_lines(trans_id)
956
1185
                mpdiff = multiparent.MultiParent.from_lines(lines, parents)
957
1186
                content = ''.join(mpdiff.to_patch())
958
1187
            if kind == 'directory':
959
1188
                content = ''
960
1189
            if kind == 'symlink':
961
 
                content = self._read_symlink_target(trans_id)
 
1190
                content = os.readlink(self._limbo_name(trans_id))
962
1191
            yield serializer.bytes_record(content, ((trans_id, kind),))
963
1192
 
 
1193
 
964
1194
    def deserialize(self, records):
965
1195
        """Deserialize a stored TreeTransform.
966
1196
 
997
1227
                self.create_symlink(content.decode('utf-8'), trans_id)
998
1228
 
999
1229
 
1000
 
class DiskTreeTransform(TreeTransformBase):
1001
 
    """Tree transform storing its contents on disk."""
1002
 
 
1003
 
    def __init__(self, tree, limbodir, pb=DummyProgress(),
1004
 
                 case_sensitive=True):
1005
 
        """Constructor.
1006
 
        :param tree: The tree that will be transformed, but not necessarily
1007
 
            the output tree.
1008
 
        :param limbodir: A directory where new files can be stored until
1009
 
            they are installed in their proper places
1010
 
        :param pb: A ProgressBar indicating how much progress is being made
1011
 
        :param case_sensitive: If True, the target of the transform is
1012
 
            case sensitive, not just case preserving.
1013
 
        """
1014
 
        TreeTransformBase.__init__(self, tree, pb, case_sensitive)
1015
 
        self._limbodir = limbodir
1016
 
        self._deletiondir = None
1017
 
        # A mapping of transform ids to their limbo filename
1018
 
        self._limbo_files = {}
1019
 
        # A mapping of transform ids to a set of the transform ids of children
1020
 
        # that their limbo directory has
1021
 
        self._limbo_children = {}
1022
 
        # Map transform ids to maps of child filename to child transform id
1023
 
        self._limbo_children_names = {}
1024
 
        # List of transform ids that need to be renamed from limbo into place
1025
 
        self._needs_rename = set()
1026
 
 
1027
 
    def finalize(self):
1028
 
        """Release the working tree lock, if held, clean up limbo dir.
1029
 
 
1030
 
        This is required if apply has not been invoked, but can be invoked
1031
 
        even after apply.
1032
 
        """
1033
 
        if self._tree is None:
1034
 
            return
1035
 
        try:
1036
 
            entries = [(self._limbo_name(t), t, k) for t, k in
1037
 
                       self._new_contents.iteritems()]
1038
 
            entries.sort(reverse=True)
1039
 
            for path, trans_id, kind in entries:
1040
 
                delete_any(path)
1041
 
            try:
1042
 
                delete_any(self._limbodir)
1043
 
            except OSError:
1044
 
                # We don't especially care *why* the dir is immortal.
1045
 
                raise ImmortalLimbo(self._limbodir)
1046
 
            try:
1047
 
                if self._deletiondir is not None:
1048
 
                    delete_any(self._deletiondir)
1049
 
            except OSError:
1050
 
                raise errors.ImmortalPendingDeletion(self._deletiondir)
1051
 
        finally:
1052
 
            TreeTransformBase.finalize(self)
1053
 
 
1054
 
    def _limbo_name(self, trans_id):
1055
 
        """Generate the limbo name of a file"""
1056
 
        limbo_name = self._limbo_files.get(trans_id)
1057
 
        if limbo_name is not None:
1058
 
            return limbo_name
1059
 
        parent = self._new_parent.get(trans_id)
1060
 
        # if the parent directory is already in limbo (e.g. when building a
1061
 
        # tree), choose a limbo name inside the parent, to reduce further
1062
 
        # renames.
1063
 
        use_direct_path = False
1064
 
        if self._new_contents.get(parent) == 'directory':
1065
 
            filename = self._new_name.get(trans_id)
1066
 
            if filename is not None:
1067
 
                if parent not in self._limbo_children:
1068
 
                    self._limbo_children[parent] = set()
1069
 
                    self._limbo_children_names[parent] = {}
1070
 
                    use_direct_path = True
1071
 
                # the direct path can only be used if no other file has
1072
 
                # already taken this pathname, i.e. if the name is unused, or
1073
 
                # if it is already associated with this trans_id.
1074
 
                elif self._case_sensitive_target:
1075
 
                    if (self._limbo_children_names[parent].get(filename)
1076
 
                        in (trans_id, None)):
1077
 
                        use_direct_path = True
1078
 
                else:
1079
 
                    for l_filename, l_trans_id in\
1080
 
                        self._limbo_children_names[parent].iteritems():
1081
 
                        if l_trans_id == trans_id:
1082
 
                            continue
1083
 
                        if l_filename.lower() == filename.lower():
1084
 
                            break
1085
 
                    else:
1086
 
                        use_direct_path = True
1087
 
 
1088
 
        if use_direct_path:
1089
 
            limbo_name = pathjoin(self._limbo_files[parent], filename)
1090
 
            self._limbo_children[parent].add(trans_id)
1091
 
            self._limbo_children_names[parent][filename] = trans_id
1092
 
        else:
1093
 
            limbo_name = pathjoin(self._limbodir, trans_id)
1094
 
            self._needs_rename.add(trans_id)
1095
 
        self._limbo_files[trans_id] = limbo_name
1096
 
        return limbo_name
1097
 
 
1098
 
    def adjust_path(self, name, parent, trans_id):
1099
 
        previous_parent = self._new_parent.get(trans_id)
1100
 
        previous_name = self._new_name.get(trans_id)
1101
 
        TreeTransformBase.adjust_path(self, name, parent, trans_id)
1102
 
        if (trans_id in self._limbo_files and
1103
 
            trans_id not in self._needs_rename):
1104
 
            self._rename_in_limbo([trans_id])
1105
 
            self._limbo_children[previous_parent].remove(trans_id)
1106
 
            del self._limbo_children_names[previous_parent][previous_name]
1107
 
 
1108
 
    def _rename_in_limbo(self, trans_ids):
1109
 
        """Fix limbo names so that the right final path is produced.
1110
 
 
1111
 
        This means we outsmarted ourselves-- we tried to avoid renaming
1112
 
        these files later by creating them with their final names in their
1113
 
        final parents.  But now the previous name or parent is no longer
1114
 
        suitable, so we have to rename them.
1115
 
 
1116
 
        Even for trans_ids that have no new contents, we must remove their
1117
 
        entries from _limbo_files, because they are now stale.
1118
 
        """
1119
 
        for trans_id in trans_ids:
1120
 
            old_path = self._limbo_files.pop(trans_id)
1121
 
            if trans_id not in self._new_contents:
1122
 
                continue
1123
 
            new_path = self._limbo_name(trans_id)
1124
 
            os.rename(old_path, new_path)
1125
 
 
1126
 
    def create_file(self, contents, trans_id, mode_id=None):
1127
 
        """Schedule creation of a new file.
1128
 
 
1129
 
        See also new_file.
1130
 
 
1131
 
        Contents is an iterator of strings, all of which will be written
1132
 
        to the target destination.
1133
 
 
1134
 
        New file takes the permissions of any existing file with that id,
1135
 
        unless mode_id is specified.
1136
 
        """
1137
 
        name = self._limbo_name(trans_id)
1138
 
        f = open(name, 'wb')
1139
 
        try:
1140
 
            try:
1141
 
                unique_add(self._new_contents, trans_id, 'file')
1142
 
            except:
1143
 
                # Clean up the file, it never got registered so
1144
 
                # TreeTransform.finalize() won't clean it up.
1145
 
                f.close()
1146
 
                os.unlink(name)
1147
 
                raise
1148
 
 
1149
 
            f.writelines(contents)
1150
 
        finally:
1151
 
            f.close()
1152
 
        self._set_mode(trans_id, mode_id, S_ISREG)
1153
 
 
1154
 
    def _read_file_chunks(self, trans_id):
1155
 
        cur_file = open(self._limbo_name(trans_id), 'rb')
1156
 
        try:
1157
 
            return cur_file.readlines()
1158
 
        finally:
1159
 
            cur_file.close()
1160
 
 
1161
 
    def _read_symlink_target(self, trans_id):
1162
 
        return os.readlink(self._limbo_name(trans_id))
1163
 
 
1164
 
    def create_hardlink(self, path, trans_id):
1165
 
        """Schedule creation of a hard link"""
1166
 
        name = self._limbo_name(trans_id)
1167
 
        try:
1168
 
            os.link(path, name)
1169
 
        except OSError, e:
1170
 
            if e.errno != errno.EPERM:
1171
 
                raise
1172
 
            raise errors.HardLinkNotSupported(path)
1173
 
        try:
1174
 
            unique_add(self._new_contents, trans_id, 'file')
1175
 
        except:
1176
 
            # Clean up the file, it never got registered so
1177
 
            # TreeTransform.finalize() won't clean it up.
1178
 
            os.unlink(name)
1179
 
            raise
1180
 
 
1181
 
    def create_directory(self, trans_id):
1182
 
        """Schedule creation of a new directory.
1183
 
 
1184
 
        See also new_directory.
1185
 
        """
1186
 
        os.mkdir(self._limbo_name(trans_id))
1187
 
        unique_add(self._new_contents, trans_id, 'directory')
1188
 
 
1189
 
    def create_symlink(self, target, trans_id):
1190
 
        """Schedule creation of a new symbolic link.
1191
 
 
1192
 
        target is a bytestring.
1193
 
        See also new_symlink.
1194
 
        """
1195
 
        if has_symlinks():
1196
 
            os.symlink(target, self._limbo_name(trans_id))
1197
 
            unique_add(self._new_contents, trans_id, 'symlink')
1198
 
        else:
1199
 
            try:
1200
 
                path = FinalPaths(self).get_path(trans_id)
1201
 
            except KeyError:
1202
 
                path = None
1203
 
            raise UnableCreateSymlink(path=path)
1204
 
 
1205
 
    def cancel_creation(self, trans_id):
1206
 
        """Cancel the creation of new file contents."""
1207
 
        del self._new_contents[trans_id]
1208
 
        children = self._limbo_children.get(trans_id)
1209
 
        # if this is a limbo directory with children, move them before removing
1210
 
        # the directory
1211
 
        if children is not None:
1212
 
            self._rename_in_limbo(children)
1213
 
            del self._limbo_children[trans_id]
1214
 
            del self._limbo_children_names[trans_id]
1215
 
        delete_any(self._limbo_name(trans_id))
1216
 
 
1217
 
 
1218
 
class TreeTransform(DiskTreeTransform):
 
1230
class TreeTransform(TreeTransformBase):
1219
1231
    """Represent a tree transformation.
1220
1232
 
1221
1233
    This object is designed to support incremental generation of the transform,
1307
1319
            tree.unlock()
1308
1320
            raise
1309
1321
 
1310
 
        # Cache of realpath results, to speed up canonical_path
1311
 
        self._realpaths = {}
1312
 
        # Cache of relpath results, to speed up canonical_path
1313
 
        self._relpaths = {}
1314
 
        DiskTreeTransform.__init__(self, tree, limbodir, pb,
 
1322
        TreeTransformBase.__init__(self, tree, limbodir, pb,
1315
1323
                                   tree.case_sensitive)
1316
1324
        self._deletiondir = deletiondir
1317
1325
 
1318
 
    def canonical_path(self, path):
1319
 
        """Get the canonical tree-relative path"""
1320
 
        # don't follow final symlinks
1321
 
        abs = self._tree.abspath(path)
1322
 
        if abs in self._relpaths:
1323
 
            return self._relpaths[abs]
1324
 
        dirname, basename = os.path.split(abs)
1325
 
        if dirname not in self._realpaths:
1326
 
            self._realpaths[dirname] = os.path.realpath(dirname)
1327
 
        dirname = self._realpaths[dirname]
1328
 
        abs = pathjoin(dirname, basename)
1329
 
        if dirname in self._relpaths:
1330
 
            relpath = pathjoin(self._relpaths[dirname], basename)
1331
 
            relpath = relpath.rstrip('/\\')
1332
 
        else:
1333
 
            relpath = self._tree.relpath(abs)
1334
 
        self._relpaths[abs] = relpath
1335
 
        return relpath
1336
 
 
1337
 
    def tree_kind(self, trans_id):
1338
 
        """Determine the file kind in the working tree.
1339
 
 
1340
 
        Raises NoSuchFile if the file does not exist
1341
 
        """
1342
 
        path = self._tree_id_paths.get(trans_id)
1343
 
        if path is None:
1344
 
            raise NoSuchFile(None)
1345
 
        try:
1346
 
            return file_kind(self._tree.abspath(path))
1347
 
        except OSError, e:
1348
 
            if e.errno != errno.ENOENT:
1349
 
                raise
1350
 
            else:
1351
 
                raise NoSuchFile(path)
1352
 
 
1353
 
    def _set_mode(self, trans_id, mode_id, typefunc):
1354
 
        """Set the mode of new file contents.
1355
 
        The mode_id is the existing file to get the mode from (often the same
1356
 
        as trans_id).  The operation is only performed if there's a mode match
1357
 
        according to typefunc.
1358
 
        """
1359
 
        if mode_id is None:
1360
 
            mode_id = trans_id
1361
 
        try:
1362
 
            old_path = self._tree_id_paths[mode_id]
1363
 
        except KeyError:
1364
 
            return
1365
 
        try:
1366
 
            mode = os.stat(self._tree.abspath(old_path)).st_mode
1367
 
        except OSError, e:
1368
 
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
1369
 
                # Either old_path doesn't exist, or the parent of the
1370
 
                # target is not a directory (but will be one eventually)
1371
 
                # Either way, we know it doesn't exist *right now*
1372
 
                # See also bug #248448
1373
 
                return
1374
 
            else:
1375
 
                raise
1376
 
        if typefunc(mode):
1377
 
            os.chmod(self._limbo_name(trans_id), mode)
1378
 
 
1379
 
    def iter_tree_children(self, parent_id):
1380
 
        """Iterate through the entry's tree children, if any"""
1381
 
        try:
1382
 
            path = self._tree_id_paths[parent_id]
1383
 
        except KeyError:
1384
 
            return
1385
 
        try:
1386
 
            children = os.listdir(self._tree.abspath(path))
1387
 
        except OSError, e:
1388
 
            if not (osutils._is_error_enotdir(e)
1389
 
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
1390
 
                raise
1391
 
            return
1392
 
 
1393
 
        for child in children:
1394
 
            childpath = joinpath(path, child)
1395
 
            if self._tree.is_control_filename(childpath):
1396
 
                continue
1397
 
            yield self.trans_id_tree_path(childpath)
1398
 
 
1399
1326
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1400
1327
        """Apply all changes to the inventory and filesystem.
1401
1328
 
1411
1338
        :param _mover: Supply an alternate FileMover, for testing
1412
1339
        """
1413
1340
        if not no_conflicts:
1414
 
            self._check_malformed()
 
1341
            conflicts = self.find_conflicts()
 
1342
            if len(conflicts) != 0:
 
1343
                raise MalformedTransform(conflicts=conflicts)
1415
1344
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1416
1345
        try:
1417
1346
            if precomputed_delta is None:
1576
1505
        return modified_paths
1577
1506
 
1578
1507
 
1579
 
class TransformPreview(DiskTreeTransform):
 
1508
class TransformPreview(TreeTransformBase):
1580
1509
    """A TreeTransform for generating preview trees.
1581
1510
 
1582
1511
    Unlike TreeTransform, this version works when the input tree is a
1587
1516
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
1588
1517
        tree.lock_read()
1589
1518
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1590
 
        DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
 
1519
        TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
1591
1520
 
1592
1521
    def canonical_path(self, path):
1593
1522
        return path
1718
1647
    def __iter__(self):
1719
1648
        return iter(self.all_file_ids())
1720
1649
 
1721
 
    def _has_id(self, file_id, fallback_check):
 
1650
    def has_id(self, file_id):
1722
1651
        if file_id in self._transform._r_new_id:
1723
1652
            return True
1724
1653
        elif file_id in set([self._transform.tree_file_id(trans_id) for
1725
1654
            trans_id in self._transform._removed_id]):
1726
1655
            return False
1727
1656
        else:
1728
 
            return fallback_check(file_id)
1729
 
 
1730
 
    def has_id(self, file_id):
1731
 
        return self._has_id(file_id, self._transform._tree.has_id)
1732
 
 
1733
 
    def has_or_had_id(self, file_id):
1734
 
        return self._has_id(file_id, self._transform._tree.has_or_had_id)
 
1657
            return self._transform._tree.has_id(file_id)
1735
1658
 
1736
1659
    def _path2trans_id(self, path):
1737
1660
        # We must not use None here, because that is a valid value to store.
1790
1713
            if self._transform.final_file_id(trans_id) is None:
1791
1714
                yield self._final_paths._determine_path(trans_id)
1792
1715
 
1793
 
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None):
 
1716
    def _make_inv_entries(self, ordered_entries, specific_file_ids):
1794
1717
        for trans_id, parent_file_id in ordered_entries:
1795
1718
            file_id = self._transform.final_file_id(trans_id)
1796
1719
            if file_id is None:
1833
1756
                                                      specific_file_ids):
1834
1757
            yield unicode(self._final_paths.get_path(trans_id)), entry
1835
1758
 
1836
 
    def _iter_entries_for_dir(self, dir_path):
1837
 
        """Return path, entry for items in a directory without recursing down."""
1838
 
        dir_file_id = self.path2id(dir_path)
1839
 
        ordered_ids = []
1840
 
        for file_id in self.iter_children(dir_file_id):
1841
 
            trans_id = self._transform.trans_id_file_id(file_id)
1842
 
            ordered_ids.append((trans_id, file_id))
1843
 
        for entry, trans_id in self._make_inv_entries(ordered_ids):
1844
 
            yield unicode(self._final_paths.get_path(trans_id)), entry
1845
 
 
1846
 
    def list_files(self, include_root=False, from_dir=None, recursive=True):
1847
 
        """See WorkingTree.list_files."""
 
1759
    def list_files(self, include_root=False):
 
1760
        """See Tree.list_files."""
1848
1761
        # XXX This should behave like WorkingTree.list_files, but is really
1849
1762
        # more like RevisionTree.list_files.
1850
 
        if recursive:
1851
 
            prefix = None
1852
 
            if from_dir:
1853
 
                prefix = from_dir + '/'
1854
 
            entries = self.iter_entries_by_dir()
1855
 
            for path, entry in entries:
1856
 
                if entry.name == '' and not include_root:
1857
 
                    continue
1858
 
                if prefix:
1859
 
                    if not path.startswith(prefix):
1860
 
                        continue
1861
 
                    path = path[len(prefix):]
1862
 
                yield path, 'V', entry.kind, entry.file_id, entry
1863
 
        else:
1864
 
            if from_dir is None and include_root is True:
1865
 
                root_entry = inventory.make_entry('directory', '',
1866
 
                    ROOT_PARENT, self.get_root_id())
1867
 
                yield '', 'V', 'directory', root_entry.file_id, root_entry
1868
 
            entries = self._iter_entries_for_dir(from_dir or '')
1869
 
            for path, entry in entries:
1870
 
                yield path, 'V', entry.kind, entry.file_id, entry
 
1763
        for path, entry in self.iter_entries_by_dir():
 
1764
            if entry.name == '' and not include_root:
 
1765
                continue
 
1766
            yield path, 'V', entry.kind, entry.file_id, entry
1871
1767
 
1872
1768
    def kind(self, file_id):
1873
1769
        trans_id = self._transform.trans_id_file_id(file_id)
1984
1880
        name = self._transform._limbo_name(trans_id)
1985
1881
        return open(name, 'rb')
1986
1882
 
1987
 
    def get_file_with_stat(self, file_id, path=None):
1988
 
        return self.get_file(file_id, path), None
1989
 
 
1990
1883
    def annotate_iter(self, file_id,
1991
1884
                      default_revision=_mod_revision.CURRENT_REVISION):
1992
1885
        changes = self._changes(file_id)
2007
1900
            return old_annotation
2008
1901
        if not changed_content:
2009
1902
            return old_annotation
2010
 
        # TODO: This is doing something similar to what WT.annotate_iter is
2011
 
        #       doing, however it fails slightly because it doesn't know what
2012
 
        #       the *other* revision_id is, so it doesn't know how to give the
2013
 
        #       other as the origin for some lines, they all get
2014
 
        #       'default_revision'
2015
 
        #       It would be nice to be able to use the new Annotator based
2016
 
        #       approach, as well.
2017
1903
        return annotate.reannotate([old_annotation],
2018
1904
                                   self.get_file(file_id).readlines(),
2019
1905
                                   default_revision)
2024
1910
            return self._transform._tree.get_symlink_target(file_id)
2025
1911
        trans_id = self._transform.trans_id_file_id(file_id)
2026
1912
        name = self._transform._limbo_name(trans_id)
2027
 
        return osutils.readlink(name)
 
1913
        return os.readlink(name)
2028
1914
 
2029
1915
    def walkdirs(self, prefix=''):
2030
1916
        pending = [self._transform.root]
2085
1971
        self.transform = transform
2086
1972
 
2087
1973
    def _determine_path(self, trans_id):
2088
 
        if (trans_id == self.transform.root or trans_id == ROOT_PARENT):
 
1974
        if trans_id == self.transform.root:
2089
1975
            return ""
2090
1976
        name = self.transform.final_name(trans_id)
2091
1977
        parent_id = self.transform.final_parent(trans_id)