~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

Merge checkout-tags-propagation-603395-2.2.

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
 
from bzrlib.lazy_import import lazy_import
22
 
lazy_import(globals(), """
 
22
from bzrlib import (
 
23
    errors,
 
24
    lazy_import,
 
25
    registry,
 
26
    )
 
27
lazy_import.lazy_import(globals(), """
23
28
from bzrlib import (
24
29
    annotate,
 
30
    bencode,
25
31
    bzrdir,
 
32
    commit,
26
33
    delta,
27
34
    errors,
28
35
    inventory,
29
36
    multiparent,
30
37
    osutils,
31
38
    revision as _mod_revision,
 
39
    trace,
 
40
    ui,
32
41
    )
33
 
from bzrlib.util import bencode
34
42
""")
35
43
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
36
 
                           ReusingTransform, NotVersionedError, CantMoveRoot,
 
44
                           ReusingTransform, CantMoveRoot,
37
45
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
38
46
                           UnableCreateSymlink)
39
47
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
48
56
    splitpath,
49
57
    supports_executable,
50
58
)
51
 
from bzrlib.progress import DummyProgress, ProgressPhase
 
59
from bzrlib.progress import ProgressPhase
52
60
from bzrlib.symbol_versioning import (
53
61
        deprecated_function,
54
62
        deprecated_in,
 
63
        deprecated_method,
55
64
        )
56
65
from bzrlib.trace import mutter, warning
57
66
from bzrlib import tree
61
70
 
62
71
ROOT_PARENT = "root-parent"
63
72
 
64
 
 
65
73
def unique_add(map, key, value):
66
74
    if key in map:
67
75
        raise DuplicateKey(key=key)
68
76
    map[key] = value
69
77
 
70
78
 
 
79
 
71
80
class _TransformResults(object):
72
81
    def __init__(self, modified_paths, rename_count):
73
82
        object.__init__(self)
76
85
 
77
86
 
78
87
class TreeTransformBase(object):
79
 
    """The base class for TreeTransform and TreeTransformBase"""
 
88
    """The base class for TreeTransform and its kin."""
80
89
 
81
 
    def __init__(self, tree, limbodir, pb=DummyProgress(),
 
90
    def __init__(self, tree, pb=None,
82
91
                 case_sensitive=True):
83
92
        """Constructor.
84
93
 
85
94
        :param tree: The tree that will be transformed, but not necessarily
86
95
            the output tree.
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
 
96
        :param pb: ignored
90
97
        :param case_sensitive: If True, the target of the transform is
91
98
            case sensitive, not just case preserving.
92
99
        """
93
100
        object.__init__(self)
94
101
        self._tree = tree
95
 
        self._limbodir = limbodir
96
 
        self._deletiondir = None
97
102
        self._id_number = 0
98
103
        # mapping of trans_id -> new basename
99
104
        self._new_name = {}
101
106
        self._new_parent = {}
102
107
        # mapping of trans_id with new contents -> new file_kind
103
108
        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()
113
109
        # Set of trans_ids whose contents will be removed
114
110
        self._removed_contents = set()
115
111
        # Mapping of trans_id -> new execute-bit value
128
124
        self._tree_path_ids = {}
129
125
        # Mapping trans_id -> path in old tree
130
126
        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 = {}
135
127
        # The trans_id that will be used as the tree root
136
128
        root_id = tree.get_root_id()
137
129
        if root_id is not None:
147
139
        # A counter of how many files have been renamed
148
140
        self.rename_count = 0
149
141
 
 
142
    def finalize(self):
 
143
        """Release the working tree lock, if held.
 
144
 
 
145
        This is required if apply has not been invoked, but can be invoked
 
146
        even after apply.
 
147
        """
 
148
        if self._tree is None:
 
149
            return
 
150
        self._tree.unlock()
 
151
        self._tree = None
 
152
 
150
153
    def __get_root(self):
151
154
        return self._new_root
152
155
 
153
156
    root = property(__get_root)
154
157
 
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
 
 
186
158
    def _assign_id(self):
187
159
        """Produce a new tranform id"""
188
160
        new_id = "new-%s" % self._id_number
198
170
 
199
171
    def adjust_path(self, name, parent, trans_id):
200
172
        """Change the path that is assigned to a transaction id."""
 
173
        if parent is None:
 
174
            raise ValueError("Parent trans-id may not be None")
201
175
        if trans_id == self._new_root:
202
176
            raise CantMoveRoot
203
 
        previous_parent = self._new_parent.get(trans_id)
204
 
        previous_name = self._new_name.get(trans_id)
205
177
        self._new_name[trans_id] = name
206
178
        self._new_parent[trans_id] = parent
207
 
        if parent == ROOT_PARENT:
208
 
            if self._new_root is not None:
209
 
                raise ValueError("Cannot have multiple roots.")
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)
234
179
 
235
180
    def adjust_root_path(self, name, parent):
236
181
        """Emulate moving the root by moving all children, instead.
264
209
        self.version_file(old_root_file_id, old_root)
265
210
        self.unversion_file(self._new_root)
266
211
 
 
212
    def fixup_new_roots(self):
 
213
        """Reinterpret requests to change the root directory
 
214
 
 
215
        Instead of creating a root directory, or moving an existing directory,
 
216
        all the attributes and children of the new root are applied to the
 
217
        existing root directory.
 
218
 
 
219
        This means that the old root trans-id becomes obsolete, so it is
 
220
        recommended only to invoke this after the root trans-id has become
 
221
        irrelevant.
 
222
        """
 
223
        new_roots = [k for k, v in self._new_parent.iteritems() if v is
 
224
                     ROOT_PARENT]
 
225
        if len(new_roots) < 1:
 
226
            return
 
227
        if len(new_roots) != 1:
 
228
            raise ValueError('A tree cannot have two roots!')
 
229
        if self._new_root is None:
 
230
            self._new_root = new_roots[0]
 
231
            return
 
232
        old_new_root = new_roots[0]
 
233
        # TODO: What to do if a old_new_root is present, but self._new_root is
 
234
        #       not listed as being removed? This code explicitly unversions
 
235
        #       the old root and versions it with the new file_id. Though that
 
236
        #       seems like an incomplete delta
 
237
 
 
238
        # unversion the new root's directory.
 
239
        file_id = self.final_file_id(old_new_root)
 
240
        if old_new_root in self._new_id:
 
241
            self.cancel_versioning(old_new_root)
 
242
        else:
 
243
            self.unversion_file(old_new_root)
 
244
        # if, at this stage, root still has an old file_id, zap it so we can
 
245
        # stick a new one in.
 
246
        if (self.tree_file_id(self._new_root) is not None and
 
247
            self._new_root not in self._removed_id):
 
248
            self.unversion_file(self._new_root)
 
249
        self.version_file(file_id, self._new_root)
 
250
 
 
251
        # Now move children of new root into old root directory.
 
252
        # Ensure all children are registered with the transaction, but don't
 
253
        # use directly-- some tree children have new parents
 
254
        list(self.iter_tree_children(old_new_root))
 
255
        # Move all children of new root into old root directory.
 
256
        for child in self.by_parent().get(old_new_root, []):
 
257
            self.adjust_path(self.final_name(child), self._new_root, child)
 
258
 
 
259
        # Ensure old_new_root has no directory.
 
260
        if old_new_root in self._new_contents:
 
261
            self.cancel_creation(old_new_root)
 
262
        else:
 
263
            self.delete_contents(old_new_root)
 
264
 
 
265
        # prevent deletion of root directory.
 
266
        if self._new_root in self._removed_contents:
 
267
            self.cancel_deletion(self._new_root)
 
268
 
 
269
        # destroy path info for old_new_root.
 
270
        del self._new_parent[old_new_root]
 
271
        del self._new_name[old_new_root]
 
272
 
267
273
    def trans_id_tree_file_id(self, inventory_id):
268
274
        """Determine the transaction id of a working tree file.
269
275
 
298
304
            else:
299
305
                return self.trans_id_tree_file_id(file_id)
300
306
 
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
 
 
320
307
    def trans_id_tree_path(self, path):
321
308
        """Determine (and maybe set) the transaction ID for a tree path."""
322
309
        path = self.canonical_path(path)
332
319
            return ROOT_PARENT
333
320
        return self.trans_id_tree_path(os.path.dirname(path))
334
321
 
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
 
 
442
322
    def delete_contents(self, trans_id):
443
323
        """Schedule the contents of a path entry for deletion"""
444
 
        self.tree_kind(trans_id)
445
 
        self._removed_contents.add(trans_id)
 
324
        kind = self.tree_kind(trans_id)
 
325
        if kind is not None:
 
326
            self._removed_contents.add(trans_id)
446
327
 
447
328
    def cancel_deletion(self, trans_id):
448
329
        """Cancel a scheduled deletion"""
513
394
        changed_kind = set(self._removed_contents)
514
395
        changed_kind.intersection_update(self._new_contents)
515
396
        changed_kind.difference_update(new_ids)
516
 
        changed_kind = (t for t in changed_kind if self.tree_kind(t) !=
517
 
                        self.final_kind(t))
 
397
        changed_kind = (t for t in changed_kind
 
398
                        if self.tree_kind(t) != self.final_kind(t))
518
399
        new_ids.update(changed_kind)
519
400
        return sorted(FinalPaths(self).get_paths(new_ids))
520
401
 
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
 
 
537
402
    def final_kind(self, trans_id):
538
403
        """Determine the final file kind, after any changes applied.
539
404
 
540
 
        Raises NoSuchFile if the file does not exist/has no contents.
541
 
        (It is conceivable that a path would be created without the
542
 
        corresponding contents insertion command)
 
405
        :return: None if the file does not exist/has no contents.  (It is
 
406
            conceivable that a path would be created without the corresponding
 
407
            contents insertion command)
543
408
        """
544
409
        if trans_id in self._new_contents:
545
410
            return self._new_contents[trans_id]
546
411
        elif trans_id in self._removed_contents:
547
 
            raise NoSuchFile(None)
 
412
            return None
548
413
        else:
549
414
            return self.tree_kind(trans_id)
550
415
 
646
511
        conflicts.extend(self._overwrite_conflicts())
647
512
        return conflicts
648
513
 
 
514
    def _check_malformed(self):
 
515
        conflicts = self.find_conflicts()
 
516
        if len(conflicts) != 0:
 
517
            raise MalformedTransform(conflicts=conflicts)
 
518
 
649
519
    def _add_tree_children(self):
650
520
        """Add all the children of all active parents to the known paths.
651
521
 
667
537
            # ensure that all children are registered with the transaction
668
538
            list(self.iter_tree_children(parent_id))
669
539
 
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
 
 
 
540
    @deprecated_method(deprecated_in((2, 3, 0)))
690
541
    def has_named_child(self, by_parent, parent_id, name):
691
 
        try:
692
 
            children = by_parent[parent_id]
693
 
        except KeyError:
694
 
            children = []
695
 
        for child in children:
 
542
        return self._has_named_child(
 
543
            name, parent_id, known_children=by_parent.get(parent_id, []))
 
544
 
 
545
    def _has_named_child(self, name, parent_id, known_children):
 
546
        """Does a parent already have a name child.
 
547
 
 
548
        :param name: The searched for name.
 
549
 
 
550
        :param parent_id: The parent for which the check is made.
 
551
 
 
552
        :param known_children: The already known children. This should have
 
553
            been recently obtained from `self.by_parent.get(parent_id)`
 
554
            (or will be if None is passed).
 
555
        """
 
556
        if known_children is None:
 
557
            known_children = self.by_parent().get(parent_id, [])
 
558
        for child in known_children:
696
559
            if self.final_name(child) == name:
697
560
                return True
698
 
        try:
699
 
            path = self._tree_id_paths[parent_id]
700
 
        except KeyError:
 
561
        parent_path = self._tree_id_paths.get(parent_id, None)
 
562
        if parent_path is None:
 
563
            # No parent... no children
701
564
            return False
702
 
        childpath = joinpath(path, name)
703
 
        child_id = self._tree_path_ids.get(childpath)
 
565
        child_path = joinpath(parent_path, name)
 
566
        child_id = self._tree_path_ids.get(child_path, None)
704
567
        if child_id is None:
705
 
            return lexists(self._tree.abspath(childpath))
 
568
            # Not known by the tree transform yet, check the filesystem
 
569
            return osutils.lexists(self._tree.abspath(child_path))
706
570
        else:
707
 
            if self.final_parent(child_id) != parent_id:
708
 
                return False
709
 
            if child_id in self._removed_contents:
710
 
                # XXX What about dangling file-ids?
711
 
                return False
712
 
            else:
713
 
                return True
 
571
            raise AssertionError('child_id is missing: %s, %s, %s'
 
572
                                 % (name, parent_id, child_id))
 
573
 
 
574
    def _available_backup_name(self, name, target_id):
 
575
        """Find an available backup name.
 
576
 
 
577
        :param name: The basename of the file.
 
578
 
 
579
        :param target_id: The directory trans_id where the backup should 
 
580
            be placed.
 
581
        """
 
582
        known_children = self.by_parent().get(target_id, [])
 
583
        return osutils.available_backup_name(
 
584
            name,
 
585
            lambda base: self._has_named_child(
 
586
                base, target_id, known_children))
714
587
 
715
588
    def _parent_loops(self):
716
589
        """No entry should be its own ancestor"""
751
624
        """
752
625
        conflicts = []
753
626
        for trans_id in self._new_id.iterkeys():
754
 
            try:
755
 
                kind = self.final_kind(trans_id)
756
 
            except NoSuchFile:
 
627
            kind = self.final_kind(trans_id)
 
628
            if kind is None:
757
629
                conflicts.append(('versioning no contents', trans_id))
758
630
                continue
759
631
            if not InventoryEntry.versionable_kind(kind):
773
645
            if self.final_file_id(trans_id) is None:
774
646
                conflicts.append(('unversioned executability', trans_id))
775
647
            else:
776
 
                try:
777
 
                    non_file = self.final_kind(trans_id) != "file"
778
 
                except NoSuchFile:
779
 
                    non_file = True
780
 
                if non_file is True:
 
648
                if self.final_kind(trans_id) != "file":
781
649
                    conflicts.append(('non-file executability', trans_id))
782
650
        return conflicts
783
651
 
785
653
        """Check for overwrites (not permitted on Win32)"""
786
654
        conflicts = []
787
655
        for trans_id in self._new_contents:
788
 
            try:
789
 
                self.tree_kind(trans_id)
790
 
            except NoSuchFile:
 
656
            if self.tree_kind(trans_id) is None:
791
657
                continue
792
658
            if trans_id not in self._removed_contents:
793
659
                conflicts.append(('overwrite', trans_id,
800
666
        if (self._new_name, self._new_parent) == ({}, {}):
801
667
            return conflicts
802
668
        for children in by_parent.itervalues():
803
 
            name_ids = [(self.final_name(t), t) for t in children]
804
 
            if not self._case_sensitive_target:
805
 
                name_ids = [(n.lower(), t) for n, t in name_ids]
 
669
            name_ids = []
 
670
            for child_tid in children:
 
671
                name = self.final_name(child_tid)
 
672
                if name is not None:
 
673
                    # Keep children only if they still exist in the end
 
674
                    if not self._case_sensitive_target:
 
675
                        name = name.lower()
 
676
                    name_ids.append((name, child_tid))
806
677
            name_ids.sort()
807
678
            last_name = None
808
679
            last_trans_id = None
809
680
            for name, trans_id in name_ids:
810
 
                try:
811
 
                    kind = self.final_kind(trans_id)
812
 
                except NoSuchFile:
813
 
                    kind = None
 
681
                kind = self.final_kind(trans_id)
814
682
                file_id = self.final_file_id(trans_id)
815
683
                if kind is None and file_id is None:
816
684
                    continue
842
710
                continue
843
711
            if not self._any_contents(children):
844
712
                continue
845
 
            for child in children:
846
 
                try:
847
 
                    self.final_kind(child)
848
 
                except NoSuchFile:
849
 
                    continue
850
 
            try:
851
 
                kind = self.final_kind(parent_id)
852
 
            except NoSuchFile:
853
 
                kind = None
 
713
            kind = self.final_kind(parent_id)
854
714
            if kind is None:
855
715
                conflicts.append(('missing parent', parent_id))
856
716
            elif kind != "directory":
860
720
    def _any_contents(self, trans_ids):
861
721
        """Return true if any of the trans_ids, will have contents."""
862
722
        for trans_id in trans_ids:
863
 
            try:
864
 
                kind = self.final_kind(trans_id)
865
 
            except NoSuchFile:
866
 
                continue
867
 
            return True
 
723
            if self.final_kind(trans_id) is not None:
 
724
                return True
868
725
        return False
869
726
 
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
 
 
914
727
    def _set_executability(self, path, trans_id):
915
728
        """Set the executability of versioned files """
916
729
        if supports_executable():
980
793
        self.create_symlink(target, trans_id)
981
794
        return trans_id
982
795
 
 
796
    def new_orphan(self, trans_id, parent_id):
 
797
        """Schedule an item to be orphaned.
 
798
 
 
799
        When a directory is about to be removed, its children, if they are not
 
800
        versioned are moved out of the way: they don't have a parent anymore.
 
801
 
 
802
        :param trans_id: The trans_id of the existing item.
 
803
        :param parent_id: The parent trans_id of the item.
 
804
        """
 
805
        raise NotImplementedError(self.new_orphan)
 
806
 
 
807
    def _get_potential_orphans(self, dir_id):
 
808
        """Find the potential orphans in a directory.
 
809
 
 
810
        A directory can't be safely deleted if there are versioned files in it.
 
811
        If all the contained files are unversioned then they can be orphaned.
 
812
 
 
813
        The 'None' return value means that the directory contains at least one
 
814
        versioned file and should not be deleted.
 
815
 
 
816
        :param dir_id: The directory trans id.
 
817
 
 
818
        :return: A list of the orphan trans ids or None if at least one
 
819
             versioned file is present.
 
820
        """
 
821
        orphans = []
 
822
        # Find the potential orphans, stop if one item should be kept
 
823
        for c in self.by_parent()[dir_id]:
 
824
            if self.final_file_id(c) is None:
 
825
                orphans.append(c)
 
826
            else:
 
827
                # We have a versioned file here, searching for orphans is
 
828
                # meaningless.
 
829
                orphans = None
 
830
                break
 
831
        return orphans
 
832
 
983
833
    def _affected_ids(self):
984
834
        """Return the set of transform ids affected by the transform"""
985
835
        trans_ids = set(self._removed_id)
1044
894
        Return a (name, parent, kind, executable) tuple
1045
895
        """
1046
896
        to_name = self.final_name(to_trans_id)
1047
 
        try:
1048
 
            to_kind = self.final_kind(to_trans_id)
1049
 
        except NoSuchFile:
1050
 
            to_kind = None
 
897
        to_kind = self.final_kind(to_trans_id)
1051
898
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
1052
899
        if to_trans_id in self._new_executability:
1053
900
            to_executable = self._new_executability[to_trans_id]
1122
969
    def get_preview_tree(self):
1123
970
        """Return a tree representing the result of the transform.
1124
971
 
1125
 
        This tree only supports the subset of Tree functionality required
1126
 
        by show_diff_trees.  It must only be compared to tt._tree.
 
972
        The tree is a snapshot, and altering the TreeTransform will invalidate
 
973
        it.
1127
974
        """
1128
975
        return _PreviewTree(self)
1129
976
 
 
977
    def commit(self, branch, message, merge_parents=None, strict=False,
 
978
               timestamp=None, timezone=None, committer=None, authors=None,
 
979
               revprops=None, revision_id=None):
 
980
        """Commit the result of this TreeTransform to a branch.
 
981
 
 
982
        :param branch: The branch to commit to.
 
983
        :param message: The message to attach to the commit.
 
984
        :param merge_parents: Additional parent revision-ids specified by
 
985
            pending merges.
 
986
        :param strict: If True, abort the commit if there are unversioned
 
987
            files.
 
988
        :param timestamp: if not None, seconds-since-epoch for the time and
 
989
            date.  (May be a float.)
 
990
        :param timezone: Optional timezone for timestamp, as an offset in
 
991
            seconds.
 
992
        :param committer: Optional committer in email-id format.
 
993
            (e.g. "J Random Hacker <jrandom@example.com>")
 
994
        :param authors: Optional list of authors in email-id format.
 
995
        :param revprops: Optional dictionary of revision properties.
 
996
        :param revision_id: Optional revision id.  (Specifying a revision-id
 
997
            may reduce performance for some non-native formats.)
 
998
        :return: The revision_id of the revision committed.
 
999
        """
 
1000
        self._check_malformed()
 
1001
        if strict:
 
1002
            unversioned = set(self._new_contents).difference(set(self._new_id))
 
1003
            for trans_id in unversioned:
 
1004
                if self.final_file_id(trans_id) is None:
 
1005
                    raise errors.StrictCommitFailed()
 
1006
 
 
1007
        revno, last_rev_id = branch.last_revision_info()
 
1008
        if last_rev_id == _mod_revision.NULL_REVISION:
 
1009
            if merge_parents is not None:
 
1010
                raise ValueError('Cannot supply merge parents for first'
 
1011
                                 ' commit.')
 
1012
            parent_ids = []
 
1013
        else:
 
1014
            parent_ids = [last_rev_id]
 
1015
            if merge_parents is not None:
 
1016
                parent_ids.extend(merge_parents)
 
1017
        if self._tree.get_revision_id() != last_rev_id:
 
1018
            raise ValueError('TreeTransform not based on branch basis: %s' %
 
1019
                             self._tree.get_revision_id())
 
1020
        revprops = commit.Commit.update_revprops(revprops, branch, authors)
 
1021
        builder = branch.get_commit_builder(parent_ids,
 
1022
                                            timestamp=timestamp,
 
1023
                                            timezone=timezone,
 
1024
                                            committer=committer,
 
1025
                                            revprops=revprops,
 
1026
                                            revision_id=revision_id)
 
1027
        preview = self.get_preview_tree()
 
1028
        list(builder.record_iter_changes(preview, last_rev_id,
 
1029
                                         self.iter_changes()))
 
1030
        builder.finish_inventory()
 
1031
        revision_id = builder.commit(message)
 
1032
        branch.set_last_revision_info(revno + 1, revision_id)
 
1033
        return revision_id
 
1034
 
1130
1035
    def _text_parent(self, trans_id):
1131
1036
        file_id = self.tree_file_id(trans_id)
1132
1037
        try:
1176
1081
                                      (('attribs',),))
1177
1082
        for trans_id, kind in self._new_contents.items():
1178
1083
            if kind == 'file':
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()
 
1084
                lines = osutils.chunks_to_lines(
 
1085
                    self._read_file_chunks(trans_id))
1184
1086
                parents = self._get_parents_lines(trans_id)
1185
1087
                mpdiff = multiparent.MultiParent.from_lines(lines, parents)
1186
1088
                content = ''.join(mpdiff.to_patch())
1187
1089
            if kind == 'directory':
1188
1090
                content = ''
1189
1091
            if kind == 'symlink':
1190
 
                content = os.readlink(self._limbo_name(trans_id))
 
1092
                content = self._read_symlink_target(trans_id)
1191
1093
            yield serializer.bytes_record(content, ((trans_id, kind),))
1192
1094
 
1193
 
 
1194
1095
    def deserialize(self, records):
1195
1096
        """Deserialize a stored TreeTransform.
1196
1097
 
1227
1128
                self.create_symlink(content.decode('utf-8'), trans_id)
1228
1129
 
1229
1130
 
1230
 
class TreeTransform(TreeTransformBase):
 
1131
class DiskTreeTransform(TreeTransformBase):
 
1132
    """Tree transform storing its contents on disk."""
 
1133
 
 
1134
    def __init__(self, tree, limbodir, pb=None,
 
1135
                 case_sensitive=True):
 
1136
        """Constructor.
 
1137
        :param tree: The tree that will be transformed, but not necessarily
 
1138
            the output tree.
 
1139
        :param limbodir: A directory where new files can be stored until
 
1140
            they are installed in their proper places
 
1141
        :param pb: ignored
 
1142
        :param case_sensitive: If True, the target of the transform is
 
1143
            case sensitive, not just case preserving.
 
1144
        """
 
1145
        TreeTransformBase.__init__(self, tree, pb, case_sensitive)
 
1146
        self._limbodir = limbodir
 
1147
        self._deletiondir = None
 
1148
        # A mapping of transform ids to their limbo filename
 
1149
        self._limbo_files = {}
 
1150
        # A mapping of transform ids to a set of the transform ids of children
 
1151
        # that their limbo directory has
 
1152
        self._limbo_children = {}
 
1153
        # Map transform ids to maps of child filename to child transform id
 
1154
        self._limbo_children_names = {}
 
1155
        # List of transform ids that need to be renamed from limbo into place
 
1156
        self._needs_rename = set()
 
1157
        self._creation_mtime = None
 
1158
 
 
1159
    def finalize(self):
 
1160
        """Release the working tree lock, if held, clean up limbo dir.
 
1161
 
 
1162
        This is required if apply has not been invoked, but can be invoked
 
1163
        even after apply.
 
1164
        """
 
1165
        if self._tree is None:
 
1166
            return
 
1167
        try:
 
1168
            entries = [(self._limbo_name(t), t, k) for t, k in
 
1169
                       self._new_contents.iteritems()]
 
1170
            entries.sort(reverse=True)
 
1171
            for path, trans_id, kind in entries:
 
1172
                delete_any(path)
 
1173
            try:
 
1174
                delete_any(self._limbodir)
 
1175
            except OSError:
 
1176
                # We don't especially care *why* the dir is immortal.
 
1177
                raise ImmortalLimbo(self._limbodir)
 
1178
            try:
 
1179
                if self._deletiondir is not None:
 
1180
                    delete_any(self._deletiondir)
 
1181
            except OSError:
 
1182
                raise errors.ImmortalPendingDeletion(self._deletiondir)
 
1183
        finally:
 
1184
            TreeTransformBase.finalize(self)
 
1185
 
 
1186
    def _limbo_name(self, trans_id):
 
1187
        """Generate the limbo name of a file"""
 
1188
        limbo_name = self._limbo_files.get(trans_id)
 
1189
        if limbo_name is None:
 
1190
            limbo_name = self._generate_limbo_path(trans_id)
 
1191
            self._limbo_files[trans_id] = limbo_name
 
1192
        return limbo_name
 
1193
 
 
1194
    def _generate_limbo_path(self, trans_id):
 
1195
        """Generate a limbo path using the trans_id as the relative path.
 
1196
 
 
1197
        This is suitable as a fallback, and when the transform should not be
 
1198
        sensitive to the path encoding of the limbo directory.
 
1199
        """
 
1200
        self._needs_rename.add(trans_id)
 
1201
        return pathjoin(self._limbodir, trans_id)
 
1202
 
 
1203
    def adjust_path(self, name, parent, trans_id):
 
1204
        previous_parent = self._new_parent.get(trans_id)
 
1205
        previous_name = self._new_name.get(trans_id)
 
1206
        TreeTransformBase.adjust_path(self, name, parent, trans_id)
 
1207
        if (trans_id in self._limbo_files and
 
1208
            trans_id not in self._needs_rename):
 
1209
            self._rename_in_limbo([trans_id])
 
1210
            if previous_parent != parent:
 
1211
                self._limbo_children[previous_parent].remove(trans_id)
 
1212
            if previous_parent != parent or previous_name != name:
 
1213
                del self._limbo_children_names[previous_parent][previous_name]
 
1214
 
 
1215
    def _rename_in_limbo(self, trans_ids):
 
1216
        """Fix limbo names so that the right final path is produced.
 
1217
 
 
1218
        This means we outsmarted ourselves-- we tried to avoid renaming
 
1219
        these files later by creating them with their final names in their
 
1220
        final parents.  But now the previous name or parent is no longer
 
1221
        suitable, so we have to rename them.
 
1222
 
 
1223
        Even for trans_ids that have no new contents, we must remove their
 
1224
        entries from _limbo_files, because they are now stale.
 
1225
        """
 
1226
        for trans_id in trans_ids:
 
1227
            old_path = self._limbo_files.pop(trans_id)
 
1228
            if trans_id not in self._new_contents:
 
1229
                continue
 
1230
            new_path = self._limbo_name(trans_id)
 
1231
            os.rename(old_path, new_path)
 
1232
            for descendant in self._limbo_descendants(trans_id):
 
1233
                desc_path = self._limbo_files[descendant]
 
1234
                desc_path = new_path + desc_path[len(old_path):]
 
1235
                self._limbo_files[descendant] = desc_path
 
1236
 
 
1237
    def _limbo_descendants(self, trans_id):
 
1238
        """Return the set of trans_ids whose limbo paths descend from this."""
 
1239
        descendants = set(self._limbo_children.get(trans_id, []))
 
1240
        for descendant in list(descendants):
 
1241
            descendants.update(self._limbo_descendants(descendant))
 
1242
        return descendants
 
1243
 
 
1244
    def create_file(self, contents, trans_id, mode_id=None):
 
1245
        """Schedule creation of a new file.
 
1246
 
 
1247
        See also new_file.
 
1248
 
 
1249
        Contents is an iterator of strings, all of which will be written
 
1250
        to the target destination.
 
1251
 
 
1252
        New file takes the permissions of any existing file with that id,
 
1253
        unless mode_id is specified.
 
1254
        """
 
1255
        name = self._limbo_name(trans_id)
 
1256
        f = open(name, 'wb')
 
1257
        try:
 
1258
            try:
 
1259
                unique_add(self._new_contents, trans_id, 'file')
 
1260
            except:
 
1261
                # Clean up the file, it never got registered so
 
1262
                # TreeTransform.finalize() won't clean it up.
 
1263
                f.close()
 
1264
                os.unlink(name)
 
1265
                raise
 
1266
 
 
1267
            f.writelines(contents)
 
1268
        finally:
 
1269
            f.close()
 
1270
        self._set_mtime(name)
 
1271
        self._set_mode(trans_id, mode_id, S_ISREG)
 
1272
 
 
1273
    def _read_file_chunks(self, trans_id):
 
1274
        cur_file = open(self._limbo_name(trans_id), 'rb')
 
1275
        try:
 
1276
            return cur_file.readlines()
 
1277
        finally:
 
1278
            cur_file.close()
 
1279
 
 
1280
    def _read_symlink_target(self, trans_id):
 
1281
        return os.readlink(self._limbo_name(trans_id))
 
1282
 
 
1283
    def _set_mtime(self, path):
 
1284
        """All files that are created get the same mtime.
 
1285
 
 
1286
        This time is set by the first object to be created.
 
1287
        """
 
1288
        if self._creation_mtime is None:
 
1289
            self._creation_mtime = time.time()
 
1290
        os.utime(path, (self._creation_mtime, self._creation_mtime))
 
1291
 
 
1292
    def create_hardlink(self, path, trans_id):
 
1293
        """Schedule creation of a hard link"""
 
1294
        name = self._limbo_name(trans_id)
 
1295
        try:
 
1296
            os.link(path, name)
 
1297
        except OSError, e:
 
1298
            if e.errno != errno.EPERM:
 
1299
                raise
 
1300
            raise errors.HardLinkNotSupported(path)
 
1301
        try:
 
1302
            unique_add(self._new_contents, trans_id, 'file')
 
1303
        except:
 
1304
            # Clean up the file, it never got registered so
 
1305
            # TreeTransform.finalize() won't clean it up.
 
1306
            os.unlink(name)
 
1307
            raise
 
1308
 
 
1309
    def create_directory(self, trans_id):
 
1310
        """Schedule creation of a new directory.
 
1311
 
 
1312
        See also new_directory.
 
1313
        """
 
1314
        os.mkdir(self._limbo_name(trans_id))
 
1315
        unique_add(self._new_contents, trans_id, 'directory')
 
1316
 
 
1317
    def create_symlink(self, target, trans_id):
 
1318
        """Schedule creation of a new symbolic link.
 
1319
 
 
1320
        target is a bytestring.
 
1321
        See also new_symlink.
 
1322
        """
 
1323
        if has_symlinks():
 
1324
            os.symlink(target, self._limbo_name(trans_id))
 
1325
            unique_add(self._new_contents, trans_id, 'symlink')
 
1326
        else:
 
1327
            try:
 
1328
                path = FinalPaths(self).get_path(trans_id)
 
1329
            except KeyError:
 
1330
                path = None
 
1331
            raise UnableCreateSymlink(path=path)
 
1332
 
 
1333
    def cancel_creation(self, trans_id):
 
1334
        """Cancel the creation of new file contents."""
 
1335
        del self._new_contents[trans_id]
 
1336
        children = self._limbo_children.get(trans_id)
 
1337
        # if this is a limbo directory with children, move them before removing
 
1338
        # the directory
 
1339
        if children is not None:
 
1340
            self._rename_in_limbo(children)
 
1341
            del self._limbo_children[trans_id]
 
1342
            del self._limbo_children_names[trans_id]
 
1343
        delete_any(self._limbo_name(trans_id))
 
1344
 
 
1345
    def new_orphan(self, trans_id, parent_id):
 
1346
        # FIXME: There is no tree config, so we use the branch one (it's weird
 
1347
        # to define it this way as orphaning can only occur in a working tree,
 
1348
        # but that's all we have (for now). It will find the option in
 
1349
        # locations.conf or bazaar.conf though) -- vila 20100916
 
1350
        conf = self._tree.branch.get_config()
 
1351
        conf_var_name = 'bzr.transform.orphan_policy'
 
1352
        orphan_policy = conf.get_user_option(conf_var_name)
 
1353
        default_policy = orphaning_registry.default_key
 
1354
        if orphan_policy is None:
 
1355
            orphan_policy = default_policy
 
1356
        if orphan_policy not in orphaning_registry:
 
1357
            trace.warning('%s (from %s) is not a known policy, defaulting to %s'
 
1358
                          % (orphan_policy, conf_var_name, default_policy))
 
1359
            orphan_policy = default_policy
 
1360
        handle_orphan = orphaning_registry.get(orphan_policy)
 
1361
        handle_orphan(self, trans_id, parent_id)
 
1362
 
 
1363
 
 
1364
class OrphaningError(errors.BzrError):
 
1365
 
 
1366
    # Only bugs could lead to such exception being seen by the user
 
1367
    internal_error = True
 
1368
    _fmt = "Error while orphaning %s in %s directory"
 
1369
 
 
1370
    def __init__(self, orphan, parent):
 
1371
        errors.BzrError.__init__(self)
 
1372
        self.orphan = orphan
 
1373
        self.parent = parent
 
1374
 
 
1375
 
 
1376
class OrphaningForbidden(OrphaningError):
 
1377
 
 
1378
    _fmt = "Policy: %s doesn't allow creating orphans."
 
1379
 
 
1380
    def __init__(self, policy):
 
1381
        errors.BzrError.__init__(self)
 
1382
        self.policy = policy
 
1383
 
 
1384
 
 
1385
def move_orphan(tt, orphan_id, parent_id):
 
1386
    """See TreeTransformBase.new_orphan.
 
1387
 
 
1388
    This creates a new orphan in the `bzr-orphans` dir at the root of the
 
1389
    `TreeTransform`.
 
1390
 
 
1391
    :param tt: The TreeTransform orphaning `trans_id`.
 
1392
 
 
1393
    :param orphan_id: The trans id that should be orphaned.
 
1394
 
 
1395
    :param parent_id: The orphan parent trans id.
 
1396
    """
 
1397
    # Add the orphan dir if it doesn't exist
 
1398
    orphan_dir_basename = 'bzr-orphans'
 
1399
    od_id = tt.trans_id_tree_path(orphan_dir_basename)
 
1400
    if tt.final_kind(od_id) is None:
 
1401
        tt.create_directory(od_id)
 
1402
    parent_path = tt._tree_id_paths[parent_id]
 
1403
    # Find a name that doesn't exist yet in the orphan dir
 
1404
    actual_name = tt.final_name(orphan_id)
 
1405
    new_name = tt._available_backup_name(actual_name, od_id)
 
1406
    tt.adjust_path(new_name, od_id, orphan_id)
 
1407
    trace.warning('%s has been orphaned in %s'
 
1408
                  % (joinpath(parent_path, actual_name), orphan_dir_basename))
 
1409
 
 
1410
 
 
1411
def refuse_orphan(tt, orphan_id, parent_id):
 
1412
    """See TreeTransformBase.new_orphan.
 
1413
 
 
1414
    This refuses to create orphan, letting the caller handle the conflict.
 
1415
    """
 
1416
    raise OrphaningForbidden('never')
 
1417
 
 
1418
 
 
1419
orphaning_registry = registry.Registry()
 
1420
orphaning_registry.register(
 
1421
    'conflict', refuse_orphan,
 
1422
    'Leave orphans in place and create a conflict on the directory.')
 
1423
orphaning_registry.register(
 
1424
    'move', move_orphan,
 
1425
    'Move orphans into the bzr-orphans directory.')
 
1426
orphaning_registry._set_default_key('conflict')
 
1427
 
 
1428
 
 
1429
class TreeTransform(DiskTreeTransform):
1231
1430
    """Represent a tree transformation.
1232
1431
 
1233
1432
    This object is designed to support incremental generation of the transform,
1292
1491
    FileMover does not delete files until it is sure that a rollback will not
1293
1492
    happen.
1294
1493
    """
1295
 
    def __init__(self, tree, pb=DummyProgress()):
 
1494
    def __init__(self, tree, pb=None):
1296
1495
        """Note: a tree_write lock is taken on the tree.
1297
1496
 
1298
1497
        Use TreeTransform.finalize() to release the lock (can be omitted if
1319
1518
            tree.unlock()
1320
1519
            raise
1321
1520
 
1322
 
        TreeTransformBase.__init__(self, tree, limbodir, pb,
 
1521
        # Cache of realpath results, to speed up canonical_path
 
1522
        self._realpaths = {}
 
1523
        # Cache of relpath results, to speed up canonical_path
 
1524
        self._relpaths = {}
 
1525
        DiskTreeTransform.__init__(self, tree, limbodir, pb,
1323
1526
                                   tree.case_sensitive)
1324
1527
        self._deletiondir = deletiondir
1325
1528
 
 
1529
    def canonical_path(self, path):
 
1530
        """Get the canonical tree-relative path"""
 
1531
        # don't follow final symlinks
 
1532
        abs = self._tree.abspath(path)
 
1533
        if abs in self._relpaths:
 
1534
            return self._relpaths[abs]
 
1535
        dirname, basename = os.path.split(abs)
 
1536
        if dirname not in self._realpaths:
 
1537
            self._realpaths[dirname] = os.path.realpath(dirname)
 
1538
        dirname = self._realpaths[dirname]
 
1539
        abs = pathjoin(dirname, basename)
 
1540
        if dirname in self._relpaths:
 
1541
            relpath = pathjoin(self._relpaths[dirname], basename)
 
1542
            relpath = relpath.rstrip('/\\')
 
1543
        else:
 
1544
            relpath = self._tree.relpath(abs)
 
1545
        self._relpaths[abs] = relpath
 
1546
        return relpath
 
1547
 
 
1548
    def tree_kind(self, trans_id):
 
1549
        """Determine the file kind in the working tree.
 
1550
 
 
1551
        :returns: The file kind or None if the file does not exist
 
1552
        """
 
1553
        path = self._tree_id_paths.get(trans_id)
 
1554
        if path is None:
 
1555
            return None
 
1556
        try:
 
1557
            return file_kind(self._tree.abspath(path))
 
1558
        except errors.NoSuchFile:
 
1559
            return None
 
1560
 
 
1561
    def _set_mode(self, trans_id, mode_id, typefunc):
 
1562
        """Set the mode of new file contents.
 
1563
        The mode_id is the existing file to get the mode from (often the same
 
1564
        as trans_id).  The operation is only performed if there's a mode match
 
1565
        according to typefunc.
 
1566
        """
 
1567
        if mode_id is None:
 
1568
            mode_id = trans_id
 
1569
        try:
 
1570
            old_path = self._tree_id_paths[mode_id]
 
1571
        except KeyError:
 
1572
            return
 
1573
        try:
 
1574
            mode = os.stat(self._tree.abspath(old_path)).st_mode
 
1575
        except OSError, e:
 
1576
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
 
1577
                # Either old_path doesn't exist, or the parent of the
 
1578
                # target is not a directory (but will be one eventually)
 
1579
                # Either way, we know it doesn't exist *right now*
 
1580
                # See also bug #248448
 
1581
                return
 
1582
            else:
 
1583
                raise
 
1584
        if typefunc(mode):
 
1585
            os.chmod(self._limbo_name(trans_id), mode)
 
1586
 
 
1587
    def iter_tree_children(self, parent_id):
 
1588
        """Iterate through the entry's tree children, if any"""
 
1589
        try:
 
1590
            path = self._tree_id_paths[parent_id]
 
1591
        except KeyError:
 
1592
            return
 
1593
        try:
 
1594
            children = os.listdir(self._tree.abspath(path))
 
1595
        except OSError, e:
 
1596
            if not (osutils._is_error_enotdir(e)
 
1597
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
 
1598
                raise
 
1599
            return
 
1600
 
 
1601
        for child in children:
 
1602
            childpath = joinpath(path, child)
 
1603
            if self._tree.is_control_filename(childpath):
 
1604
                continue
 
1605
            yield self.trans_id_tree_path(childpath)
 
1606
 
 
1607
    def _generate_limbo_path(self, trans_id):
 
1608
        """Generate a limbo path using the final path if possible.
 
1609
 
 
1610
        This optimizes the performance of applying the tree transform by
 
1611
        avoiding renames.  These renames can be avoided only when the parent
 
1612
        directory is already scheduled for creation.
 
1613
 
 
1614
        If the final path cannot be used, falls back to using the trans_id as
 
1615
        the relpath.
 
1616
        """
 
1617
        parent = self._new_parent.get(trans_id)
 
1618
        # if the parent directory is already in limbo (e.g. when building a
 
1619
        # tree), choose a limbo name inside the parent, to reduce further
 
1620
        # renames.
 
1621
        use_direct_path = False
 
1622
        if self._new_contents.get(parent) == 'directory':
 
1623
            filename = self._new_name.get(trans_id)
 
1624
            if filename is not None:
 
1625
                if parent not in self._limbo_children:
 
1626
                    self._limbo_children[parent] = set()
 
1627
                    self._limbo_children_names[parent] = {}
 
1628
                    use_direct_path = True
 
1629
                # the direct path can only be used if no other file has
 
1630
                # already taken this pathname, i.e. if the name is unused, or
 
1631
                # if it is already associated with this trans_id.
 
1632
                elif self._case_sensitive_target:
 
1633
                    if (self._limbo_children_names[parent].get(filename)
 
1634
                        in (trans_id, None)):
 
1635
                        use_direct_path = True
 
1636
                else:
 
1637
                    for l_filename, l_trans_id in\
 
1638
                        self._limbo_children_names[parent].iteritems():
 
1639
                        if l_trans_id == trans_id:
 
1640
                            continue
 
1641
                        if l_filename.lower() == filename.lower():
 
1642
                            break
 
1643
                    else:
 
1644
                        use_direct_path = True
 
1645
 
 
1646
        if not use_direct_path:
 
1647
            return DiskTreeTransform._generate_limbo_path(self, trans_id)
 
1648
 
 
1649
        limbo_name = pathjoin(self._limbo_files[parent], filename)
 
1650
        self._limbo_children[parent].add(trans_id)
 
1651
        self._limbo_children_names[parent][filename] = trans_id
 
1652
        return limbo_name
 
1653
 
 
1654
 
1326
1655
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1327
1656
        """Apply all changes to the inventory and filesystem.
1328
1657
 
1338
1667
        :param _mover: Supply an alternate FileMover, for testing
1339
1668
        """
1340
1669
        if not no_conflicts:
1341
 
            conflicts = self.find_conflicts()
1342
 
            if len(conflicts) != 0:
1343
 
                raise MalformedTransform(conflicts=conflicts)
 
1670
            self._check_malformed()
1344
1671
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1345
1672
        try:
1346
1673
            if precomputed_delta is None:
1404
1731
                if file_id is None:
1405
1732
                    continue
1406
1733
                needs_entry = False
1407
 
                try:
1408
 
                    kind = self.final_kind(trans_id)
1409
 
                except NoSuchFile:
 
1734
                kind = self.final_kind(trans_id)
 
1735
                if kind is None:
1410
1736
                    kind = self._tree.stored_kind(file_id)
1411
1737
                parent_trans_id = self.final_parent(trans_id)
1412
1738
                parent_file_id = new_path_file_ids.get(parent_trans_id)
1450
1776
                child_pb.update('removing file', num, len(tree_paths))
1451
1777
                full_path = self._tree.abspath(path)
1452
1778
                if trans_id in self._removed_contents:
1453
 
                    mover.pre_delete(full_path, os.path.join(self._deletiondir,
1454
 
                                     trans_id))
1455
 
                elif trans_id in self._new_name or trans_id in \
1456
 
                    self._new_parent:
 
1779
                    delete_path = os.path.join(self._deletiondir, trans_id)
 
1780
                    mover.pre_delete(full_path, delete_path)
 
1781
                elif (trans_id in self._new_name
 
1782
                      or trans_id in self._new_parent):
1457
1783
                    try:
1458
1784
                        mover.rename(full_path, self._limbo_name(trans_id))
1459
 
                    except OSError, e:
 
1785
                    except errors.TransformRenameFailed, e:
1460
1786
                        if e.errno != errno.ENOENT:
1461
1787
                            raise
1462
1788
                    else:
1487
1813
                if trans_id in self._needs_rename:
1488
1814
                    try:
1489
1815
                        mover.rename(self._limbo_name(trans_id), full_path)
1490
 
                    except OSError, e:
 
1816
                    except errors.TransformRenameFailed, e:
1491
1817
                        # We may be renaming a dangling inventory id
1492
1818
                        if e.errno != errno.ENOENT:
1493
1819
                            raise
1505
1831
        return modified_paths
1506
1832
 
1507
1833
 
1508
 
class TransformPreview(TreeTransformBase):
 
1834
class TransformPreview(DiskTreeTransform):
1509
1835
    """A TreeTransform for generating preview trees.
1510
1836
 
1511
1837
    Unlike TreeTransform, this version works when the input tree is a
1513
1839
    unversioned files in the input tree.
1514
1840
    """
1515
1841
 
1516
 
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
 
1842
    def __init__(self, tree, pb=None, case_sensitive=True):
1517
1843
        tree.lock_read()
1518
1844
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
1519
 
        TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
 
1845
        DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1520
1846
 
1521
1847
    def canonical_path(self, path):
1522
1848
        return path
1524
1850
    def tree_kind(self, trans_id):
1525
1851
        path = self._tree_id_paths.get(trans_id)
1526
1852
        if path is None:
1527
 
            raise NoSuchFile(None)
 
1853
            return None
1528
1854
        file_id = self._tree.path2id(path)
1529
 
        return self._tree.kind(file_id)
 
1855
        try:
 
1856
            return self._tree.kind(file_id)
 
1857
        except errors.NoSuchFile:
 
1858
            return None
1530
1859
 
1531
1860
    def _set_mode(self, trans_id, mode_id, typefunc):
1532
1861
        """Set the mode of new file contents.
1552
1881
            childpath = joinpath(path, child)
1553
1882
            yield self.trans_id_tree_path(childpath)
1554
1883
 
 
1884
    def new_orphan(self, trans_id, parent_id):
 
1885
        raise NotImplementedError(self.new_orphan)
 
1886
 
1555
1887
 
1556
1888
class _PreviewTree(tree.Tree):
1557
1889
    """Partial implementation of Tree to support show_diff_trees"""
1564
1896
        self._all_children_cache = {}
1565
1897
        self._path2trans_id_cache = {}
1566
1898
        self._final_name_cache = {}
1567
 
 
1568
 
    def _changes(self, file_id):
1569
 
        for changes in self._transform.iter_changes():
1570
 
            if changes[0] == file_id:
1571
 
                return changes
 
1899
        self._iter_changes_cache = dict((c[0], c) for c in
 
1900
                                        self._transform.iter_changes())
1572
1901
 
1573
1902
    def _content_change(self, file_id):
1574
1903
        """Return True if the content of this file changed"""
1575
 
        changes = self._changes(file_id)
 
1904
        changes = self._iter_changes_cache.get(file_id)
1576
1905
        # changes[2] is true if the file content changed.  See
1577
1906
        # InterTree.iter_changes.
1578
1907
        return (changes is not None and changes[2])
1594
1923
        parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
1595
1924
                       self._iter_parent_trees()]
1596
1925
        vf.add_lines((file_id, tree_revision), parent_keys,
1597
 
                     self.get_file(file_id).readlines())
 
1926
                     self.get_file_lines(file_id))
1598
1927
        repo = self._get_repository()
1599
1928
        base_vf = repo.texts
1600
1929
        if base_vf not in vf.fallback_versionedfiles:
1622
1951
            executable = self.is_executable(file_id, path)
1623
1952
        return kind, executable, None
1624
1953
 
 
1954
    def is_locked(self):
 
1955
        return False
 
1956
 
1625
1957
    def lock_read(self):
1626
1958
        # Perhaps in theory, this should lock the TreeTransform?
1627
 
        pass
 
1959
        return self
1628
1960
 
1629
1961
    def unlock(self):
1630
1962
        pass
1647
1979
    def __iter__(self):
1648
1980
        return iter(self.all_file_ids())
1649
1981
 
1650
 
    def has_id(self, file_id):
 
1982
    def _has_id(self, file_id, fallback_check):
1651
1983
        if file_id in self._transform._r_new_id:
1652
1984
            return True
1653
1985
        elif file_id in set([self._transform.tree_file_id(trans_id) for
1654
1986
            trans_id in self._transform._removed_id]):
1655
1987
            return False
1656
1988
        else:
1657
 
            return self._transform._tree.has_id(file_id)
 
1989
            return fallback_check(file_id)
 
1990
 
 
1991
    def has_id(self, file_id):
 
1992
        return self._has_id(file_id, self._transform._tree.has_id)
 
1993
 
 
1994
    def has_or_had_id(self, file_id):
 
1995
        return self._has_id(file_id, self._transform._tree.has_or_had_id)
1658
1996
 
1659
1997
    def _path2trans_id(self, path):
1660
1998
        # We must not use None here, because that is a valid value to store.
1713
2051
            if self._transform.final_file_id(trans_id) is None:
1714
2052
                yield self._final_paths._determine_path(trans_id)
1715
2053
 
1716
 
    def _make_inv_entries(self, ordered_entries, specific_file_ids):
 
2054
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
 
2055
        yield_parents=False):
1717
2056
        for trans_id, parent_file_id in ordered_entries:
1718
2057
            file_id = self._transform.final_file_id(trans_id)
1719
2058
            if file_id is None:
1721
2060
            if (specific_file_ids is not None
1722
2061
                and file_id not in specific_file_ids):
1723
2062
                continue
1724
 
            try:
1725
 
                kind = self._transform.final_kind(trans_id)
1726
 
            except NoSuchFile:
 
2063
            kind = self._transform.final_kind(trans_id)
 
2064
            if kind is None:
1727
2065
                kind = self._transform._tree.stored_kind(file_id)
1728
2066
            new_entry = inventory.make_entry(
1729
2067
                kind,
1745
2083
                ordered_ids.append((trans_id, parent_file_id))
1746
2084
        return ordered_ids
1747
2085
 
1748
 
    def iter_entries_by_dir(self, specific_file_ids=None):
 
2086
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1749
2087
        # This may not be a maximally efficient implementation, but it is
1750
2088
        # reasonably straightforward.  An implementation that grafts the
1751
2089
        # TreeTransform changes onto the tree's iter_entries_by_dir results
1753
2091
        # position.
1754
2092
        ordered_ids = self._list_files_by_dir()
1755
2093
        for entry, trans_id in self._make_inv_entries(ordered_ids,
1756
 
                                                      specific_file_ids):
1757
 
            yield unicode(self._final_paths.get_path(trans_id)), entry
1758
 
 
1759
 
    def list_files(self, include_root=False):
1760
 
        """See Tree.list_files."""
 
2094
            specific_file_ids, yield_parents=yield_parents):
 
2095
            yield unicode(self._final_paths.get_path(trans_id)), entry
 
2096
 
 
2097
    def _iter_entries_for_dir(self, dir_path):
 
2098
        """Return path, entry for items in a directory without recursing down."""
 
2099
        dir_file_id = self.path2id(dir_path)
 
2100
        ordered_ids = []
 
2101
        for file_id in self.iter_children(dir_file_id):
 
2102
            trans_id = self._transform.trans_id_file_id(file_id)
 
2103
            ordered_ids.append((trans_id, file_id))
 
2104
        for entry, trans_id in self._make_inv_entries(ordered_ids):
 
2105
            yield unicode(self._final_paths.get_path(trans_id)), entry
 
2106
 
 
2107
    def list_files(self, include_root=False, from_dir=None, recursive=True):
 
2108
        """See WorkingTree.list_files."""
1761
2109
        # XXX This should behave like WorkingTree.list_files, but is really
1762
2110
        # more like RevisionTree.list_files.
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
 
2111
        if recursive:
 
2112
            prefix = None
 
2113
            if from_dir:
 
2114
                prefix = from_dir + '/'
 
2115
            entries = self.iter_entries_by_dir()
 
2116
            for path, entry in entries:
 
2117
                if entry.name == '' and not include_root:
 
2118
                    continue
 
2119
                if prefix:
 
2120
                    if not path.startswith(prefix):
 
2121
                        continue
 
2122
                    path = path[len(prefix):]
 
2123
                yield path, 'V', entry.kind, entry.file_id, entry
 
2124
        else:
 
2125
            if from_dir is None and include_root is True:
 
2126
                root_entry = inventory.make_entry('directory', '',
 
2127
                    ROOT_PARENT, self.get_root_id())
 
2128
                yield '', 'V', 'directory', root_entry.file_id, root_entry
 
2129
            entries = self._iter_entries_for_dir(from_dir or '')
 
2130
            for path, entry in entries:
 
2131
                yield path, 'V', entry.kind, entry.file_id, entry
1767
2132
 
1768
2133
    def kind(self, file_id):
1769
2134
        trans_id = self._transform.trans_id_file_id(file_id)
1779
2144
    def get_file_mtime(self, file_id, path=None):
1780
2145
        """See Tree.get_file_mtime"""
1781
2146
        if not self._content_change(file_id):
1782
 
            return self._transform._tree.get_file_mtime(file_id, path)
 
2147
            return self._transform._tree.get_file_mtime(file_id)
1783
2148
        return self._stat_limbo_file(file_id).st_mtime
1784
2149
 
1785
2150
    def _file_size(self, entry, stat_value):
1839
2204
                statval = os.lstat(limbo_name)
1840
2205
                size = statval.st_size
1841
2206
                if not supports_executable():
1842
 
                    executable = None
 
2207
                    executable = False
1843
2208
                else:
1844
2209
                    executable = statval.st_mode & S_IEXEC
1845
2210
            else:
1847
2212
                executable = None
1848
2213
            if kind == 'symlink':
1849
2214
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
1850
 
        if supports_executable():
1851
 
            executable = tt._new_executability.get(trans_id, executable)
 
2215
        executable = tt._new_executability.get(trans_id, executable)
1852
2216
        return kind, size, executable, link_or_sha1
1853
2217
 
1854
2218
    def iter_changes(self, from_tree, include_unchanged=False,
1880
2244
        name = self._transform._limbo_name(trans_id)
1881
2245
        return open(name, 'rb')
1882
2246
 
 
2247
    def get_file_with_stat(self, file_id, path=None):
 
2248
        return self.get_file(file_id, path), None
 
2249
 
1883
2250
    def annotate_iter(self, file_id,
1884
2251
                      default_revision=_mod_revision.CURRENT_REVISION):
1885
 
        changes = self._changes(file_id)
 
2252
        changes = self._iter_changes_cache.get(file_id)
1886
2253
        if changes is None:
1887
2254
            get_old = True
1888
2255
        else:
1900
2267
            return old_annotation
1901
2268
        if not changed_content:
1902
2269
            return old_annotation
 
2270
        # TODO: This is doing something similar to what WT.annotate_iter is
 
2271
        #       doing, however it fails slightly because it doesn't know what
 
2272
        #       the *other* revision_id is, so it doesn't know how to give the
 
2273
        #       other as the origin for some lines, they all get
 
2274
        #       'default_revision'
 
2275
        #       It would be nice to be able to use the new Annotator based
 
2276
        #       approach, as well.
1903
2277
        return annotate.reannotate([old_annotation],
1904
2278
                                   self.get_file(file_id).readlines(),
1905
2279
                                   default_revision)
1910
2284
            return self._transform._tree.get_symlink_target(file_id)
1911
2285
        trans_id = self._transform.trans_id_file_id(file_id)
1912
2286
        name = self._transform._limbo_name(trans_id)
1913
 
        return os.readlink(name)
 
2287
        return osutils.readlink(name)
1914
2288
 
1915
2289
    def walkdirs(self, prefix=''):
1916
2290
        pending = [self._transform.root]
1925
2299
                path_from_root = self._final_paths.get_path(child_id)
1926
2300
                basename = self._transform.final_name(child_id)
1927
2301
                file_id = self._transform.final_file_id(child_id)
1928
 
                try:
1929
 
                    kind = self._transform.final_kind(child_id)
 
2302
                kind  = self._transform.final_kind(child_id)
 
2303
                if kind is not None:
1930
2304
                    versioned_kind = kind
1931
 
                except NoSuchFile:
 
2305
                else:
1932
2306
                    kind = 'unknown'
1933
2307
                    versioned_kind = self._transform._tree.stored_kind(file_id)
1934
2308
                if versioned_kind == 'directory':
1971
2345
        self.transform = transform
1972
2346
 
1973
2347
    def _determine_path(self, trans_id):
1974
 
        if trans_id == self.transform.root:
 
2348
        if (trans_id == self.transform.root or trans_id == ROOT_PARENT):
1975
2349
            return ""
1976
2350
        name = self.transform.final_name(trans_id)
1977
2351
        parent_id = self.transform.final_parent(trans_id)
2047
2421
    for num, _unused in enumerate(wt.all_file_ids()):
2048
2422
        if num > 0:  # more than just a root
2049
2423
            raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
2050
 
    existing_files = set()
2051
 
    for dir, files in wt.walkdirs():
2052
 
        existing_files.update(f[0] for f in files)
2053
2424
    file_trans_id = {}
2054
2425
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2055
2426
    pp = ProgressPhase("Build phase", 2, top_pb)
2079
2450
                precomputed_delta = []
2080
2451
            else:
2081
2452
                precomputed_delta = None
 
2453
            # Check if tree inventory has content. If so, we populate
 
2454
            # existing_files with the directory content. If there are no
 
2455
            # entries we skip populating existing_files as its not used.
 
2456
            # This improves performance and unncessary work on large
 
2457
            # directory trees. (#501307)
 
2458
            if total > 0:
 
2459
                existing_files = set()
 
2460
                for dir, files in wt.walkdirs():
 
2461
                    existing_files.update(f[0] for f in files)
2082
2462
            for num, (tree_path, entry) in \
2083
2463
                enumerate(tree.inventory.iter_entries_by_dir()):
2084
2464
                pb.update("Building tree", num - len(deferred_contents), total)
2152
2532
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
2153
2533
                  hardlink):
2154
2534
    total = len(desired_files) + offset
 
2535
    wt = tt._tree
2155
2536
    if accelerator_tree is None:
2156
2537
        new_desired_files = desired_files
2157
2538
    else:
2158
2539
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
2159
 
        unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
2160
 
                         in iter if not (c or e[0] != e[1]))
 
2540
        unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
 
2541
                     in iter if not (c or e[0] != e[1])]
 
2542
        if accelerator_tree.supports_content_filtering():
 
2543
            unchanged = [(f, p) for (f, p) in unchanged
 
2544
                         if not accelerator_tree.iter_search_rules([p]).next()]
 
2545
        unchanged = dict(unchanged)
2161
2546
        new_desired_files = []
2162
2547
        count = 0
2163
2548
        for file_id, (trans_id, tree_path) in desired_files:
2171
2556
                                   trans_id)
2172
2557
            else:
2173
2558
                contents = accelerator_tree.get_file(file_id, accelerator_path)
2174
 
                if tree.supports_content_filtering():
2175
 
                    filters = tree._content_filter_stack(tree_path)
 
2559
                if wt.supports_content_filtering():
 
2560
                    filters = wt._content_filter_stack(tree_path)
2176
2561
                    contents = filtered_output_bytes(contents, filters,
2177
2562
                        ContentFilterContext(tree_path, tree))
2178
2563
                try:
2187
2572
        offset += count
2188
2573
    for count, ((trans_id, tree_path), contents) in enumerate(
2189
2574
            tree.iter_files_bytes(new_desired_files)):
2190
 
        if tree.supports_content_filtering():
2191
 
            filters = tree._content_filter_stack(tree_path)
 
2575
        if wt.supports_content_filtering():
 
2576
            filters = wt._content_filter_stack(tree_path)
2192
2577
            contents = filtered_output_bytes(contents, filters,
2193
2578
                ContentFilterContext(tree_path, tree))
2194
2579
        tt.create_file(contents, trans_id)
2199
2584
    for child in tt.iter_tree_children(old_parent):
2200
2585
        tt.adjust_path(tt.final_name(child), new_parent, child)
2201
2586
 
 
2587
 
2202
2588
def _reparent_transform_children(tt, old_parent, new_parent):
2203
2589
    by_parent = tt.by_parent()
2204
2590
    for child in by_parent[old_parent]:
2205
2591
        tt.adjust_path(tt.final_name(child), new_parent, child)
2206
2592
    return by_parent[old_parent]
2207
2593
 
 
2594
 
2208
2595
def _content_match(tree, entry, file_id, kind, target_path):
2209
2596
    if entry.kind != kind:
2210
2597
        return False
2211
2598
    if entry.kind == "directory":
2212
2599
        return True
2213
2600
    if entry.kind == "file":
2214
 
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
2215
 
            return True
 
2601
        f = file(target_path, 'rb')
 
2602
        try:
 
2603
            if tree.get_file_text(file_id) == f.read():
 
2604
                return True
 
2605
        finally:
 
2606
            f.close()
2216
2607
    elif entry.kind == "symlink":
2217
2608
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
2218
2609
            return True
2270
2661
        raise errors.BadFileKindError(name, kind)
2271
2662
 
2272
2663
 
2273
 
@deprecated_function(deprecated_in((1, 9, 0)))
2274
 
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
2275
 
    """Create new file contents according to an inventory entry.
2276
 
 
2277
 
    DEPRECATED.  Use create_from_tree instead.
 
2664
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
 
2665
    filter_tree_path=None):
 
2666
    """Create new file contents according to tree contents.
 
2667
    
 
2668
    :param filter_tree_path: the tree path to use to lookup
 
2669
      content filters to apply to the bytes output in the working tree.
 
2670
      This only applies if the working tree supports content filtering.
2278
2671
    """
2279
 
    if entry.kind == "file":
2280
 
        if lines is None:
2281
 
            lines = tree.get_file(entry.file_id).readlines()
2282
 
        tt.create_file(lines, trans_id, mode_id=mode_id)
2283
 
    elif entry.kind == "symlink":
2284
 
        tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
2285
 
    elif entry.kind == "directory":
2286
 
        tt.create_directory(trans_id)
2287
 
 
2288
 
 
2289
 
def create_from_tree(tt, trans_id, tree, file_id, bytes=None):
2290
 
    """Create new file contents according to tree contents."""
2291
2672
    kind = tree.kind(file_id)
2292
2673
    if kind == 'directory':
2293
2674
        tt.create_directory(trans_id)
2298
2679
                bytes = tree_file.readlines()
2299
2680
            finally:
2300
2681
                tree_file.close()
 
2682
        wt = tt._tree
 
2683
        if wt.supports_content_filtering() and filter_tree_path is not None:
 
2684
            filters = wt._content_filter_stack(filter_tree_path)
 
2685
            bytes = filtered_output_bytes(bytes, filters,
 
2686
                ContentFilterContext(filter_tree_path, tree))
2301
2687
        tt.create_file(bytes, trans_id)
2302
2688
    elif kind == "symlink":
2303
2689
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
2311
2697
        tt.set_executability(entry.executable, trans_id)
2312
2698
 
2313
2699
 
 
2700
@deprecated_function(deprecated_in((2, 3, 0)))
2314
2701
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2315
2702
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
2316
2703
 
2317
2704
 
 
2705
@deprecated_function(deprecated_in((2, 3, 0)))
2318
2706
def _get_backup_name(name, by_parent, parent_trans_id, tt):
2319
2707
    """Produce a backup-style name that appears to be available"""
2320
2708
    def name_gen():
2355
2743
 
2356
2744
 
2357
2745
def revert(working_tree, target_tree, filenames, backups=False,
2358
 
           pb=DummyProgress(), change_reporter=None):
 
2746
           pb=None, change_reporter=None):
2359
2747
    """Revert a working tree's contents to those of a target tree."""
2360
2748
    target_tree.lock_read()
 
2749
    pb = ui.ui_factory.nested_progress_bar()
2361
2750
    tt = TreeTransform(working_tree, pb)
2362
2751
    try:
2363
2752
        pp = ProgressPhase("Revert phase", 3, pb)
2382
2771
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
2383
2772
                              backups, pp, basis_tree=None,
2384
2773
                              merge_modified=None):
2385
 
    pp.next_phase()
2386
2774
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2387
2775
    try:
2388
2776
        if merge_modified is None:
2392
2780
                                      merge_modified, basis_tree)
2393
2781
    finally:
2394
2782
        child_pb.finished()
2395
 
    pp.next_phase()
2396
2783
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
2397
2784
    try:
2398
2785
        raw_conflicts = resolve_conflicts(tt, child_pb,
2442
2829
                        tt.delete_contents(trans_id)
2443
2830
                    elif kind[1] is not None:
2444
2831
                        parent_trans_id = tt.trans_id_file_id(parent[0])
2445
 
                        by_parent = tt.by_parent()
2446
 
                        backup_name = _get_backup_name(name[0], by_parent,
2447
 
                                                       parent_trans_id, tt)
 
2832
                        backup_name = tt._available_backup_name(
 
2833
                            name[0], parent_trans_id)
2448
2834
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
2449
2835
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
2450
2836
                        if versioned == (True, True):
2491
2877
                    parent_trans = ROOT_PARENT
2492
2878
                else:
2493
2879
                    parent_trans = tt.trans_id_file_id(parent[1])
2494
 
                tt.adjust_path(name[1], parent_trans, trans_id)
 
2880
                if parent[0] is None and versioned[0]:
 
2881
                    tt.adjust_root_path(name[1], parent_trans)
 
2882
                else:
 
2883
                    tt.adjust_path(name[1], parent_trans, trans_id)
2495
2884
            if executable[0] != executable[1] and kind[1] == "file":
2496
2885
                tt.set_executability(executable[1], trans_id)
2497
 
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
2498
 
            deferred_files):
2499
 
            tt.create_file(bytes, trans_id, mode_id)
 
2886
        if working_tree.supports_content_filtering():
 
2887
            for index, ((trans_id, mode_id), bytes) in enumerate(
 
2888
                target_tree.iter_files_bytes(deferred_files)):
 
2889
                file_id = deferred_files[index][0]
 
2890
                # We're reverting a tree to the target tree so using the
 
2891
                # target tree to find the file path seems the best choice
 
2892
                # here IMO - Ian C 27/Oct/2009
 
2893
                filter_tree_path = target_tree.id2path(file_id)
 
2894
                filters = working_tree._content_filter_stack(filter_tree_path)
 
2895
                bytes = filtered_output_bytes(bytes, filters,
 
2896
                    ContentFilterContext(filter_tree_path, working_tree))
 
2897
                tt.create_file(bytes, trans_id, mode_id)
 
2898
        else:
 
2899
            for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
 
2900
                deferred_files):
 
2901
                tt.create_file(bytes, trans_id, mode_id)
 
2902
        tt.fixup_new_roots()
2500
2903
    finally:
2501
2904
        if basis_tree is not None:
2502
2905
            basis_tree.unlock()
2503
2906
    return merge_modified
2504
2907
 
2505
2908
 
2506
 
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
 
2909
def resolve_conflicts(tt, pb=None, pass_func=None):
2507
2910
    """Make many conflict-resolution attempts, but die if they fail"""
2508
2911
    if pass_func is None:
2509
2912
        pass_func = conflict_pass
2510
2913
    new_conflicts = set()
 
2914
    pb = ui.ui_factory.nested_progress_bar()
2511
2915
    try:
2512
2916
        for n in range(10):
2513
2917
            pb.update('Resolution pass', n+1, 10)
2517
2921
            new_conflicts.update(pass_func(tt, conflicts))
2518
2922
        raise MalformedTransform(conflicts=conflicts)
2519
2923
    finally:
2520
 
        pb.clear()
 
2924
        pb.finished()
2521
2925
 
2522
2926
 
2523
2927
def conflict_pass(tt, conflicts, path_tree=None):
2555
2959
 
2556
2960
        elif c_type == 'missing parent':
2557
2961
            trans_id = conflict[1]
2558
 
            try:
2559
 
                tt.cancel_deletion(trans_id)
2560
 
                new_conflicts.add(('deleting parent', 'Not deleting',
2561
 
                                   trans_id))
2562
 
            except KeyError:
 
2962
            if trans_id in tt._removed_contents:
 
2963
                cancel_deletion = True
 
2964
                orphans = tt._get_potential_orphans(trans_id)
 
2965
                if orphans:
 
2966
                    cancel_deletion = False
 
2967
                    # All children are orphans
 
2968
                    for o in orphans:
 
2969
                        try:
 
2970
                            tt.new_orphan(o, trans_id)
 
2971
                        except OrphaningError:
 
2972
                            # Something bad happened so we cancel the directory
 
2973
                            # deletion which will leave it in place with a
 
2974
                            # conflict. The user can deal with it from there.
 
2975
                            # Note that this also catch the case where we don't
 
2976
                            # want to create orphans and leave the directory in
 
2977
                            # place.
 
2978
                            cancel_deletion = True
 
2979
                            break
 
2980
                if cancel_deletion:
 
2981
                    # Cancel the directory deletion
 
2982
                    tt.cancel_deletion(trans_id)
 
2983
                    new_conflicts.add(('deleting parent', 'Not deleting',
 
2984
                                       trans_id))
 
2985
            else:
2563
2986
                create = True
2564
2987
                try:
2565
2988
                    tt.final_name(trans_id)
2572
2995
                        # special-case the other tree root (move its
2573
2996
                        # children to current root)
2574
2997
                        if entry.parent_id is None:
2575
 
                            create=False
 
2998
                            create = False
2576
2999
                            moved = _reparent_transform_children(
2577
3000
                                tt, trans_id, tt.root)
2578
3001
                            for child in moved:
2646
3069
        self.pending_deletions = []
2647
3070
 
2648
3071
    def rename(self, from_, to):
2649
 
        """Rename a file from one path to another.  Functions like os.rename"""
 
3072
        """Rename a file from one path to another."""
2650
3073
        try:
2651
3074
            os.rename(from_, to)
2652
3075
        except OSError, e:
2653
3076
            if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2654
3077
                raise errors.FileExists(to, str(e))
2655
 
            raise
 
3078
            # normal OSError doesn't include filenames so it's hard to see where
 
3079
            # the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
 
3080
            raise errors.TransformRenameFailed(from_, to, str(e), e.errno)
2656
3081
        self.past_renames.append((from_, to))
2657
3082
 
2658
3083
    def pre_delete(self, from_, to):
2668
3093
    def rollback(self):
2669
3094
        """Reverse all renames that have been performed"""
2670
3095
        for from_, to in reversed(self.past_renames):
2671
 
            os.rename(to, from_)
 
3096
            try:
 
3097
                os.rename(to, from_)
 
3098
            except OSError, e:
 
3099
                raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
2672
3100
        # after rollback, don't reuse _FileMover
2673
3101
        past_renames = None
2674
3102
        pending_deletions = None