~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-11-11 08:45:19 UTC
  • mfrom: (4597.9.22 reports-conflict-resolved)
  • Revision ID: pqm@pqm.ubuntu.com-20101111084519-bmk1zmblp7kex41a
(vila) More feedback about the conflicts just resolved and the remaining
 ones. (Vincent Ladeuil)

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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
18
18
import errno
19
 
from stat import S_ISREG
20
 
import tempfile
 
19
from stat import S_ISREG, S_IEXEC
 
20
import time
21
21
 
22
 
from bzrlib.lazy_import import lazy_import
23
 
lazy_import(globals(), """
24
 
from bzrlib import (
 
22
from bzrlib import (
 
23
    errors,
 
24
    lazy_import,
 
25
    registry,
 
26
    )
 
27
lazy_import.lazy_import(globals(), """
 
28
from bzrlib import (
 
29
    annotate,
 
30
    bencode,
25
31
    bzrdir,
 
32
    commit,
26
33
    delta,
27
34
    errors,
28
 
    inventory
 
35
    inventory,
 
36
    multiparent,
 
37
    osutils,
 
38
    revision as _mod_revision,
 
39
    trace,
 
40
    ui,
29
41
    )
30
42
""")
31
43
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
32
 
                           ReusingTransform, NotVersionedError, CantMoveRoot,
 
44
                           ReusingTransform, CantMoveRoot,
33
45
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
34
46
                           UnableCreateSymlink)
 
47
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
35
48
from bzrlib.inventory import InventoryEntry
36
 
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
37
 
                            delete_any, has_symlinks)
38
 
from bzrlib.progress import DummyProgress, ProgressPhase
 
49
from bzrlib.osutils import (
 
50
    delete_any,
 
51
    file_kind,
 
52
    has_symlinks,
 
53
    lexists,
 
54
    pathjoin,
 
55
    sha_file,
 
56
    splitpath,
 
57
    supports_executable,
 
58
)
 
59
from bzrlib.progress import ProgressPhase
39
60
from bzrlib.symbol_versioning import (
40
61
        deprecated_function,
41
 
        zero_fifteen,
42
 
        zero_ninety,
 
62
        deprecated_in,
 
63
        deprecated_method,
43
64
        )
44
65
from bzrlib.trace import mutter, warning
45
66
from bzrlib import tree
49
70
 
50
71
ROOT_PARENT = "root-parent"
51
72
 
52
 
 
53
73
def unique_add(map, key, value):
54
74
    if key in map:
55
75
        raise DuplicateKey(key=key)
56
76
    map[key] = value
57
77
 
58
78
 
 
79
 
59
80
class _TransformResults(object):
60
81
    def __init__(self, modified_paths, rename_count):
61
82
        object.__init__(self)
64
85
 
65
86
 
66
87
class TreeTransformBase(object):
67
 
    """The base class for TreeTransform and TreeTransformBase"""
 
88
    """The base class for TreeTransform and its kin."""
68
89
 
69
 
    def __init__(self, tree, limbodir, pb=DummyProgress(),
 
90
    def __init__(self, tree, pb=None,
70
91
                 case_sensitive=True):
71
92
        """Constructor.
72
93
 
73
94
        :param tree: The tree that will be transformed, but not necessarily
74
95
            the output tree.
75
 
        :param limbodir: A directory where new files can be stored until
76
 
            they are installed in their proper places
77
 
        :param pb: A ProgressBar indicating how much progress is being made
 
96
        :param pb: ignored
78
97
        :param case_sensitive: If True, the target of the transform is
79
98
            case sensitive, not just case preserving.
80
99
        """
81
100
        object.__init__(self)
82
101
        self._tree = tree
83
 
        self._limbodir = limbodir
84
 
        self._deletiondir = None
85
102
        self._id_number = 0
86
103
        # mapping of trans_id -> new basename
87
104
        self._new_name = {}
89
106
        self._new_parent = {}
90
107
        # mapping of trans_id with new contents -> new file_kind
91
108
        self._new_contents = {}
92
 
        # A mapping of transform ids to their limbo filename
93
 
        self._limbo_files = {}
94
 
        # A mapping of transform ids to a set of the transform ids of children
95
 
        # that their limbo directory has
96
 
        self._limbo_children = {}
97
 
        # Map transform ids to maps of child filename to child transform id
98
 
        self._limbo_children_names = {}
99
 
        # List of transform ids that need to be renamed from limbo into place
100
 
        self._needs_rename = set()
101
109
        # Set of trans_ids whose contents will be removed
102
110
        self._removed_contents = set()
103
111
        # Mapping of trans_id -> new execute-bit value
110
118
        self._non_present_ids = {}
111
119
        # Mapping of new file_id -> trans_id
112
120
        self._r_new_id = {}
113
 
        # Set of file_ids that will be removed
 
121
        # Set of trans_ids that will be removed
114
122
        self._removed_id = set()
115
123
        # Mapping of path in old tree -> trans_id
116
124
        self._tree_path_ids = {}
117
125
        # Mapping trans_id -> path in old tree
118
126
        self._tree_id_paths = {}
119
 
        # Cache of realpath results, to speed up canonical_path
120
 
        self._realpaths = {}
121
 
        # Cache of relpath results, to speed up canonical_path
122
 
        self._relpaths = {}
123
127
        # The trans_id that will be used as the tree root
124
 
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
 
128
        root_id = tree.get_root_id()
 
129
        if root_id is not None:
 
130
            self._new_root = self.trans_id_tree_file_id(root_id)
 
131
        else:
 
132
            self._new_root = None
125
133
        # Indictor of whether the transform has been applied
126
134
        self._done = False
127
135
        # A progress bar
131
139
        # A counter of how many files have been renamed
132
140
        self.rename_count = 0
133
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
 
134
153
    def __get_root(self):
135
154
        return self._new_root
136
155
 
137
156
    root = property(__get_root)
138
157
 
139
 
    def finalize(self):
140
 
        """Release the working tree lock, if held, clean up limbo dir.
141
 
 
142
 
        This is required if apply has not been invoked, but can be invoked
143
 
        even after apply.
144
 
        """
145
 
        if self._tree is None:
146
 
            return
147
 
        try:
148
 
            entries = [(self._limbo_name(t), t, k) for t, k in
149
 
                       self._new_contents.iteritems()]
150
 
            entries.sort(reverse=True)
151
 
            for path, trans_id, kind in entries:
152
 
                if kind == "directory":
153
 
                    os.rmdir(path)
154
 
                else:
155
 
                    os.unlink(path)
156
 
            try:
157
 
                os.rmdir(self._limbodir)
158
 
            except OSError:
159
 
                # We don't especially care *why* the dir is immortal.
160
 
                raise ImmortalLimbo(self._limbodir)
161
 
            try:
162
 
                if self._deletiondir is not None:
163
 
                    os.rmdir(self._deletiondir)
164
 
            except OSError:
165
 
                raise errors.ImmortalPendingDeletion(self._deletiondir)
166
 
        finally:
167
 
            self._tree.unlock()
168
 
            self._tree = None
169
 
 
170
158
    def _assign_id(self):
171
159
        """Produce a new tranform id"""
172
160
        new_id = "new-%s" % self._id_number
182
170
 
183
171
    def adjust_path(self, name, parent, trans_id):
184
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")
185
175
        if trans_id == self._new_root:
186
176
            raise CantMoveRoot
187
 
        previous_parent = self._new_parent.get(trans_id)
188
 
        previous_name = self._new_name.get(trans_id)
189
177
        self._new_name[trans_id] = name
190
178
        self._new_parent[trans_id] = parent
191
 
        if (trans_id in self._limbo_files and
192
 
            trans_id not in self._needs_rename):
193
 
            self._rename_in_limbo([trans_id])
194
 
            self._limbo_children[previous_parent].remove(trans_id)
195
 
            del self._limbo_children_names[previous_parent][previous_name]
196
 
 
197
 
    def _rename_in_limbo(self, trans_ids):
198
 
        """Fix limbo names so that the right final path is produced.
199
 
 
200
 
        This means we outsmarted ourselves-- we tried to avoid renaming
201
 
        these files later by creating them with their final names in their
202
 
        final parents.  But now the previous name or parent is no longer
203
 
        suitable, so we have to rename them.
204
 
 
205
 
        Even for trans_ids that have no new contents, we must remove their
206
 
        entries from _limbo_files, because they are now stale.
207
 
        """
208
 
        for trans_id in trans_ids:
209
 
            old_path = self._limbo_files.pop(trans_id)
210
 
            if trans_id not in self._new_contents:
211
 
                continue
212
 
            new_path = self._limbo_name(trans_id)
213
 
            os.rename(old_path, new_path)
214
179
 
215
180
    def adjust_root_path(self, name, parent):
216
181
        """Emulate moving the root by moving all children, instead.
217
 
        
 
182
 
218
183
        We do this by undoing the association of root's transaction id with the
219
184
        current tree.  This allows us to create a new directory with that
220
 
        transaction id.  We unversion the root directory and version the 
 
185
        transaction id.  We unversion the root directory and version the
221
186
        physically new directory, and hope someone versions the tree root
222
187
        later.
223
188
        """
226
191
        # force moving all children of root
227
192
        for child_id in self.iter_tree_children(old_root):
228
193
            if child_id != parent:
229
 
                self.adjust_path(self.final_name(child_id), 
 
194
                self.adjust_path(self.final_name(child_id),
230
195
                                 self.final_parent(child_id), child_id)
231
196
            file_id = self.final_file_id(child_id)
232
197
            if file_id is not None:
233
198
                self.unversion_file(child_id)
234
199
            self.version_file(file_id, child_id)
235
 
        
 
200
 
236
201
        # the physical root needs a new transaction id
237
202
        self._tree_path_ids.pop("")
238
203
        self._tree_id_paths.pop(old_root)
244
209
        self.version_file(old_root_file_id, old_root)
245
210
        self.unversion_file(self._new_root)
246
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
 
247
273
    def trans_id_tree_file_id(self, inventory_id):
248
274
        """Determine the transaction id of a working tree file.
249
 
        
 
275
 
250
276
        This reflects only files that already exist, not ones that will be
251
277
        added by transactions.
252
278
        """
253
 
        path = self._tree.inventory.id2path(inventory_id)
 
279
        if inventory_id is None:
 
280
            raise ValueError('None is not a valid file id')
 
281
        path = self._tree.id2path(inventory_id)
254
282
        return self.trans_id_tree_path(path)
255
283
 
256
284
    def trans_id_file_id(self, file_id):
259
287
        a transaction has been unversioned, it is deliberately still returned.
260
288
        (this will likely lead to an unversioned parent conflict.)
261
289
        """
 
290
        if file_id is None:
 
291
            raise ValueError('None is not a valid file id')
262
292
        if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
263
293
            return self._r_new_id[file_id]
264
 
        elif file_id in self._tree.inventory:
265
 
            return self.trans_id_tree_file_id(file_id)
266
 
        elif file_id in self._non_present_ids:
267
 
            return self._non_present_ids[file_id]
268
 
        else:
269
 
            trans_id = self._assign_id()
270
 
            self._non_present_ids[file_id] = trans_id
271
 
            return trans_id
272
 
 
273
 
    def canonical_path(self, path):
274
 
        """Get the canonical tree-relative path"""
275
 
        # don't follow final symlinks
276
 
        abs = self._tree.abspath(path)
277
 
        if abs in self._relpaths:
278
 
            return self._relpaths[abs]
279
 
        dirname, basename = os.path.split(abs)
280
 
        if dirname not in self._realpaths:
281
 
            self._realpaths[dirname] = os.path.realpath(dirname)
282
 
        dirname = self._realpaths[dirname]
283
 
        abs = pathjoin(dirname, basename)
284
 
        if dirname in self._relpaths:
285
 
            relpath = pathjoin(self._relpaths[dirname], basename)
286
 
            relpath = relpath.rstrip('/\\')
287
 
        else:
288
 
            relpath = self._tree.relpath(abs)
289
 
        self._relpaths[abs] = relpath
290
 
        return relpath
 
294
        else:
 
295
            try:
 
296
                self._tree.iter_entries_by_dir([file_id]).next()
 
297
            except StopIteration:
 
298
                if file_id in self._non_present_ids:
 
299
                    return self._non_present_ids[file_id]
 
300
                else:
 
301
                    trans_id = self._assign_id()
 
302
                    self._non_present_ids[file_id] = trans_id
 
303
                    return trans_id
 
304
            else:
 
305
                return self.trans_id_tree_file_id(file_id)
291
306
 
292
307
    def trans_id_tree_path(self, path):
293
308
        """Determine (and maybe set) the transaction ID for a tree path."""
304
319
            return ROOT_PARENT
305
320
        return self.trans_id_tree_path(os.path.dirname(path))
306
321
 
307
 
    def create_file(self, contents, trans_id, mode_id=None):
308
 
        """Schedule creation of a new file.
309
 
 
310
 
        See also new_file.
311
 
        
312
 
        Contents is an iterator of strings, all of which will be written
313
 
        to the target destination.
314
 
 
315
 
        New file takes the permissions of any existing file with that id,
316
 
        unless mode_id is specified.
317
 
        """
318
 
        name = self._limbo_name(trans_id)
319
 
        f = open(name, 'wb')
320
 
        try:
321
 
            try:
322
 
                unique_add(self._new_contents, trans_id, 'file')
323
 
            except:
324
 
                # Clean up the file, it never got registered so
325
 
                # TreeTransform.finalize() won't clean it up.
326
 
                f.close()
327
 
                os.unlink(name)
328
 
                raise
329
 
 
330
 
            f.writelines(contents)
331
 
        finally:
332
 
            f.close()
333
 
        self._set_mode(trans_id, mode_id, S_ISREG)
334
 
 
335
 
    def _set_mode(self, trans_id, mode_id, typefunc):
336
 
        """Set the mode of new file contents.
337
 
        The mode_id is the existing file to get the mode from (often the same
338
 
        as trans_id).  The operation is only performed if there's a mode match
339
 
        according to typefunc.
340
 
        """
341
 
        if mode_id is None:
342
 
            mode_id = trans_id
343
 
        try:
344
 
            old_path = self._tree_id_paths[mode_id]
345
 
        except KeyError:
346
 
            return
347
 
        try:
348
 
            mode = os.stat(self._tree.abspath(old_path)).st_mode
349
 
        except OSError, e:
350
 
            if e.errno == errno.ENOENT:
351
 
                return
352
 
            else:
353
 
                raise
354
 
        if typefunc(mode):
355
 
            os.chmod(self._limbo_name(trans_id), mode)
356
 
 
357
 
    def create_directory(self, trans_id):
358
 
        """Schedule creation of a new directory.
359
 
        
360
 
        See also new_directory.
361
 
        """
362
 
        os.mkdir(self._limbo_name(trans_id))
363
 
        unique_add(self._new_contents, trans_id, 'directory')
364
 
 
365
 
    def create_symlink(self, target, trans_id):
366
 
        """Schedule creation of a new symbolic link.
367
 
 
368
 
        target is a bytestring.
369
 
        See also new_symlink.
370
 
        """
371
 
        if has_symlinks():
372
 
            os.symlink(target, self._limbo_name(trans_id))
373
 
            unique_add(self._new_contents, trans_id, 'symlink')
374
 
        else:
375
 
            try:
376
 
                path = FinalPaths(self).get_path(trans_id)
377
 
            except KeyError:
378
 
                path = None
379
 
            raise UnableCreateSymlink(path=path)
380
 
 
381
 
    def cancel_creation(self, trans_id):
382
 
        """Cancel the creation of new file contents."""
383
 
        del self._new_contents[trans_id]
384
 
        children = self._limbo_children.get(trans_id)
385
 
        # if this is a limbo directory with children, move them before removing
386
 
        # the directory
387
 
        if children is not None:
388
 
            self._rename_in_limbo(children)
389
 
            del self._limbo_children[trans_id]
390
 
            del self._limbo_children_names[trans_id]
391
 
        delete_any(self._limbo_name(trans_id))
392
 
 
393
322
    def delete_contents(self, trans_id):
394
323
        """Schedule the contents of a path entry for deletion"""
395
 
        self.tree_kind(trans_id)
396
 
        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)
397
327
 
398
328
    def cancel_deletion(self, trans_id):
399
329
        """Cancel a scheduled deletion"""
423
353
 
424
354
    def version_file(self, file_id, trans_id):
425
355
        """Schedule a file to become versioned."""
426
 
        assert file_id is not None
 
356
        if file_id is None:
 
357
            raise ValueError()
427
358
        unique_add(self._new_id, trans_id, file_id)
428
359
        unique_add(self._r_new_id, file_id, trans_id)
429
360
 
433
364
        del self._new_id[trans_id]
434
365
        del self._r_new_id[file_id]
435
366
 
436
 
    def new_paths(self):
437
 
        """Determine the paths of all new and changed files"""
438
 
        new_ids = set()
439
 
        fp = FinalPaths(self)
440
 
        for id_set in (self._new_name, self._new_parent, self._new_contents,
441
 
                       self._new_id, self._new_executability):
442
 
            new_ids.update(id_set)
443
 
        new_paths = [(fp.get_path(t), t) for t in new_ids]
444
 
        new_paths.sort()
445
 
        return new_paths
446
 
 
447
 
    def tree_kind(self, trans_id):
448
 
        """Determine the file kind in the working tree.
449
 
 
450
 
        Raises NoSuchFile if the file does not exist
 
367
    def new_paths(self, filesystem_only=False):
 
368
        """Determine the paths of all new and changed files.
 
369
 
 
370
        :param filesystem_only: if True, only calculate values for files
 
371
            that require renames or execute bit changes.
451
372
        """
452
 
        path = self._tree_id_paths.get(trans_id)
453
 
        if path is None:
454
 
            raise NoSuchFile(None)
455
 
        try:
456
 
            return file_kind(self._tree.abspath(path))
457
 
        except OSError, e:
458
 
            if e.errno != errno.ENOENT:
459
 
                raise
460
 
            else:
461
 
                raise NoSuchFile(path)
 
373
        new_ids = set()
 
374
        if filesystem_only:
 
375
            stale_ids = self._needs_rename.difference(self._new_name)
 
376
            stale_ids.difference_update(self._new_parent)
 
377
            stale_ids.difference_update(self._new_contents)
 
378
            stale_ids.difference_update(self._new_id)
 
379
            needs_rename = self._needs_rename.difference(stale_ids)
 
380
            id_sets = (needs_rename, self._new_executability)
 
381
        else:
 
382
            id_sets = (self._new_name, self._new_parent, self._new_contents,
 
383
                       self._new_id, self._new_executability)
 
384
        for id_set in id_sets:
 
385
            new_ids.update(id_set)
 
386
        return sorted(FinalPaths(self).get_paths(new_ids))
 
387
 
 
388
    def _inventory_altered(self):
 
389
        """Get the trans_ids and paths of files needing new inv entries."""
 
390
        new_ids = set()
 
391
        for id_set in [self._new_name, self._new_parent, self._new_id,
 
392
                       self._new_executability]:
 
393
            new_ids.update(id_set)
 
394
        changed_kind = set(self._removed_contents)
 
395
        changed_kind.intersection_update(self._new_contents)
 
396
        changed_kind.difference_update(new_ids)
 
397
        changed_kind = (t for t in changed_kind
 
398
                        if self.tree_kind(t) != self.final_kind(t))
 
399
        new_ids.update(changed_kind)
 
400
        return sorted(FinalPaths(self).get_paths(new_ids))
462
401
 
463
402
    def final_kind(self, trans_id):
464
403
        """Determine the final file kind, after any changes applied.
465
 
        
466
 
        Raises NoSuchFile if the file does not exist/has no contents.
467
 
        (It is conceivable that a path would be created without the
468
 
        corresponding contents insertion command)
 
404
 
 
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)
469
408
        """
470
409
        if trans_id in self._new_contents:
471
410
            return self._new_contents[trans_id]
472
411
        elif trans_id in self._removed_contents:
473
 
            raise NoSuchFile(None)
 
412
            return None
474
413
        else:
475
414
            return self.tree_kind(trans_id)
476
415
 
484
423
        # the file is old; the old id is still valid
485
424
        if self._new_root == trans_id:
486
425
            return self._tree.get_root_id()
487
 
        return self._tree.inventory.path2id(path)
 
426
        return self._tree.path2id(path)
488
427
 
489
428
    def final_file_id(self, trans_id):
490
429
        """Determine the file id after any changes are applied, or None.
491
 
        
 
430
 
492
431
        None indicates that the file will not be versioned after changes are
493
432
        applied.
494
433
        """
495
434
        try:
496
 
            # there is a new id for this file
497
 
            assert self._new_id[trans_id] is not None
498
435
            return self._new_id[trans_id]
499
436
        except KeyError:
500
437
            if trans_id in self._removed_id:
535
472
 
536
473
    def by_parent(self):
537
474
        """Return a map of parent: children for known parents.
538
 
        
 
475
 
539
476
        Only new paths and parents of tree files with assigned ids are used.
540
477
        """
541
478
        by_parent = {}
542
479
        items = list(self._new_parent.iteritems())
543
 
        items.extend((t, self.final_parent(t)) for t in 
 
480
        items.extend((t, self.final_parent(t)) for t in
544
481
                      self._tree_id_paths.keys())
545
482
        for trans_id, parent_id in items:
546
483
            if parent_id not in by_parent:
574
511
        conflicts.extend(self._overwrite_conflicts())
575
512
        return conflicts
576
513
 
 
514
    def _check_malformed(self):
 
515
        conflicts = self.find_conflicts()
 
516
        if len(conflicts) != 0:
 
517
            raise MalformedTransform(conflicts=conflicts)
 
518
 
577
519
    def _add_tree_children(self):
578
520
        """Add all the children of all active parents to the known paths.
579
521
 
581
523
        removed.  This is a necessary first step in detecting conflicts.
582
524
        """
583
525
        parents = self.by_parent().keys()
584
 
        parents.extend([t for t in self._removed_contents if 
 
526
        parents.extend([t for t in self._removed_contents if
585
527
                        self.tree_kind(t) == 'directory'])
586
528
        for trans_id in self._removed_id:
587
529
            file_id = self.tree_file_id(trans_id)
595
537
            # ensure that all children are registered with the transaction
596
538
            list(self.iter_tree_children(parent_id))
597
539
 
598
 
    def iter_tree_children(self, parent_id):
599
 
        """Iterate through the entry's tree children, if any"""
600
 
        try:
601
 
            path = self._tree_id_paths[parent_id]
602
 
        except KeyError:
603
 
            return
604
 
        try:
605
 
            children = os.listdir(self._tree.abspath(path))
606
 
        except OSError, e:
607
 
            if e.errno not in (errno.ENOENT, errno.ESRCH, errno.ENOTDIR):
608
 
                raise
609
 
            return
610
 
            
611
 
        for child in children:
612
 
            childpath = joinpath(path, child)
613
 
            if self._tree.is_control_filename(childpath):
614
 
                continue
615
 
            yield self.trans_id_tree_path(childpath)
616
 
 
 
540
    @deprecated_method(deprecated_in((2, 3, 0)))
617
541
    def has_named_child(self, by_parent, parent_id, name):
618
 
        try:
619
 
            children = by_parent[parent_id]
620
 
        except KeyError:
621
 
            children = []
622
 
        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:
623
559
            if self.final_name(child) == name:
624
560
                return True
625
 
        try:
626
 
            path = self._tree_id_paths[parent_id]
627
 
        except KeyError:
 
561
        parent_path = self._tree_id_paths.get(parent_id, None)
 
562
        if parent_path is None:
 
563
            # No parent... no children
628
564
            return False
629
 
        childpath = joinpath(path, name)
630
 
        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)
631
567
        if child_id is None:
632
 
            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))
633
570
        else:
634
 
            if self.final_parent(child_id) != parent_id:
635
 
                return False
636
 
            if child_id in self._removed_contents:
637
 
                # XXX What about dangling file-ids?
638
 
                return False
639
 
            else:
640
 
                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))
641
587
 
642
588
    def _parent_loops(self):
643
589
        """No entry should be its own ancestor"""
673
619
 
674
620
    def _improper_versioning(self):
675
621
        """Cannot version a file with no contents, or a bad type.
676
 
        
 
622
 
677
623
        However, existing entries with no contents are okay.
678
624
        """
679
625
        conflicts = []
680
626
        for trans_id in self._new_id.iterkeys():
681
 
            try:
682
 
                kind = self.final_kind(trans_id)
683
 
            except NoSuchFile:
 
627
            kind = self.final_kind(trans_id)
 
628
            if kind is None:
684
629
                conflicts.append(('versioning no contents', trans_id))
685
630
                continue
686
631
            if not InventoryEntry.versionable_kind(kind):
689
634
 
690
635
    def _executability_conflicts(self):
691
636
        """Check for bad executability changes.
692
 
        
 
637
 
693
638
        Only versioned files may have their executability set, because
694
639
        1. only versioned entries can have executability under windows
695
640
        2. only files can be executable.  (The execute bit on a directory
700
645
            if self.final_file_id(trans_id) is None:
701
646
                conflicts.append(('unversioned executability', trans_id))
702
647
            else:
703
 
                try:
704
 
                    non_file = self.final_kind(trans_id) != "file"
705
 
                except NoSuchFile:
706
 
                    non_file = True
707
 
                if non_file is True:
 
648
                if self.final_kind(trans_id) != "file":
708
649
                    conflicts.append(('non-file executability', trans_id))
709
650
        return conflicts
710
651
 
712
653
        """Check for overwrites (not permitted on Win32)"""
713
654
        conflicts = []
714
655
        for trans_id in self._new_contents:
715
 
            try:
716
 
                self.tree_kind(trans_id)
717
 
            except NoSuchFile:
 
656
            if self.tree_kind(trans_id) is None:
718
657
                continue
719
658
            if trans_id not in self._removed_contents:
720
659
                conflicts.append(('overwrite', trans_id,
727
666
        if (self._new_name, self._new_parent) == ({}, {}):
728
667
            return conflicts
729
668
        for children in by_parent.itervalues():
730
 
            name_ids = [(self.final_name(t), t) for t in children]
731
 
            if not self._case_sensitive_target:
732
 
                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))
733
677
            name_ids.sort()
734
678
            last_name = None
735
679
            last_trans_id = None
736
680
            for name, trans_id in name_ids:
737
 
                try:
738
 
                    kind = self.final_kind(trans_id)
739
 
                except NoSuchFile:
740
 
                    kind = None
 
681
                kind = self.final_kind(trans_id)
741
682
                file_id = self.final_file_id(trans_id)
742
683
                if kind is None and file_id is None:
743
684
                    continue
753
694
        conflicts = []
754
695
        removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
755
696
                                self._removed_id))
756
 
        active_tree_ids = set((f for f in self._tree.inventory if
757
 
                               f not in removed_tree_ids))
 
697
        all_ids = self._tree.all_file_ids()
 
698
        active_tree_ids = all_ids.difference(removed_tree_ids)
758
699
        for trans_id, file_id in self._new_id.iteritems():
759
700
            if file_id in active_tree_ids:
760
701
                old_trans_id = self.trans_id_tree_file_id(file_id)
769
710
                continue
770
711
            if not self._any_contents(children):
771
712
                continue
772
 
            for child in children:
773
 
                try:
774
 
                    self.final_kind(child)
775
 
                except NoSuchFile:
776
 
                    continue
777
 
            try:
778
 
                kind = self.final_kind(parent_id)
779
 
            except NoSuchFile:
780
 
                kind = None
 
713
            kind = self.final_kind(parent_id)
781
714
            if kind is None:
782
715
                conflicts.append(('missing parent', parent_id))
783
716
            elif kind != "directory":
787
720
    def _any_contents(self, trans_ids):
788
721
        """Return true if any of the trans_ids, will have contents."""
789
722
        for trans_id in trans_ids:
790
 
            try:
791
 
                kind = self.final_kind(trans_id)
792
 
            except NoSuchFile:
793
 
                continue
794
 
            return True
 
723
            if self.final_kind(trans_id) is not None:
 
724
                return True
795
725
        return False
796
726
 
797
 
    def _limbo_name(self, trans_id):
798
 
        """Generate the limbo name of a file"""
799
 
        limbo_name = self._limbo_files.get(trans_id)
800
 
        if limbo_name is not None:
801
 
            return limbo_name
802
 
        parent = self._new_parent.get(trans_id)
803
 
        # if the parent directory is already in limbo (e.g. when building a
804
 
        # tree), choose a limbo name inside the parent, to reduce further
805
 
        # renames.
806
 
        use_direct_path = False
807
 
        if self._new_contents.get(parent) == 'directory':
808
 
            filename = self._new_name.get(trans_id)
809
 
            if filename is not None:
810
 
                if parent not in self._limbo_children:
811
 
                    self._limbo_children[parent] = set()
812
 
                    self._limbo_children_names[parent] = {}
813
 
                    use_direct_path = True
814
 
                # the direct path can only be used if no other file has
815
 
                # already taken this pathname, i.e. if the name is unused, or
816
 
                # if it is already associated with this trans_id.
817
 
                elif self._case_sensitive_target:
818
 
                    if (self._limbo_children_names[parent].get(filename)
819
 
                        in (trans_id, None)):
820
 
                        use_direct_path = True
821
 
                else:
822
 
                    for l_filename, l_trans_id in\
823
 
                        self._limbo_children_names[parent].iteritems():
824
 
                        if l_trans_id == trans_id:
825
 
                            continue
826
 
                        if l_filename.lower() == filename.lower():
827
 
                            break
828
 
                    else:
829
 
                        use_direct_path = True
830
 
 
831
 
        if use_direct_path:
832
 
            limbo_name = pathjoin(self._limbo_files[parent], filename)
833
 
            self._limbo_children[parent].add(trans_id)
834
 
            self._limbo_children_names[parent][filename] = trans_id
835
 
        else:
836
 
            limbo_name = pathjoin(self._limbodir, trans_id)
837
 
            self._needs_rename.add(trans_id)
838
 
        self._limbo_files[trans_id] = limbo_name
839
 
        return limbo_name
840
 
 
841
 
    def _set_executability(self, path, entry, trans_id):
 
727
    def _set_executability(self, path, trans_id):
842
728
        """Set the executability of versioned files """
843
 
        new_executability = self._new_executability[trans_id]
844
 
        entry.executable = new_executability
845
729
        if supports_executable():
 
730
            new_executability = self._new_executability[trans_id]
846
731
            abspath = self._tree.abspath(path)
847
732
            current_mode = os.stat(abspath).st_mode
848
733
            if new_executability:
865
750
            self.version_file(file_id, trans_id)
866
751
        return trans_id
867
752
 
868
 
    def new_file(self, name, parent_id, contents, file_id=None, 
 
753
    def new_file(self, name, parent_id, contents, file_id=None,
869
754
                 executable=None):
870
755
        """Convenience method to create files.
871
 
        
 
756
 
872
757
        name is the name of the file to create.
873
758
        parent_id is the transaction id of the parent directory of the file.
874
759
        contents is an iterator of bytestrings, which will be used to produce
894
779
        """
895
780
        trans_id = self._new_entry(name, parent_id, file_id)
896
781
        self.create_directory(trans_id)
897
 
        return trans_id 
 
782
        return trans_id
898
783
 
899
784
    def new_symlink(self, name, parent_id, target, file_id=None):
900
785
        """Convenience method to create symbolic link.
901
 
        
 
786
 
902
787
        name is the name of the symlink to create.
903
788
        parent_id is the transaction id of the parent directory of the symlink.
904
789
        target is a bytestring of the target of the symlink.
908
793
        self.create_symlink(target, trans_id)
909
794
        return trans_id
910
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
 
911
833
    def _affected_ids(self):
912
834
        """Return the set of transform ids affected by the transform"""
913
835
        trans_ids = set(self._removed_id)
943
865
        from_path = self._tree_id_paths.get(from_trans_id)
944
866
        if from_versioned:
945
867
            # get data from working tree if versioned
946
 
            from_entry = self._tree.inventory[file_id]
 
868
            from_entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
947
869
            from_name = from_entry.name
948
870
            from_parent = from_entry.parent_id
949
871
        else:
972
894
        Return a (name, parent, kind, executable) tuple
973
895
        """
974
896
        to_name = self.final_name(to_trans_id)
975
 
        try:
976
 
            to_kind = self.final_kind(to_trans_id)
977
 
        except NoSuchFile:
978
 
            to_kind = None
 
897
        to_kind = self.final_kind(to_trans_id)
979
898
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
980
899
        if to_trans_id in self._new_executability:
981
900
            to_executable = self._new_executability[to_trans_id]
985
904
            to_executable = False
986
905
        return to_name, to_parent, to_kind, to_executable
987
906
 
988
 
    def _iter_changes(self):
989
 
        """Produce output in the same format as Tree._iter_changes.
 
907
    def iter_changes(self):
 
908
        """Produce output in the same format as Tree.iter_changes.
990
909
 
991
910
        Will produce nonsensical results if invoked while inventory/filesystem
992
911
        conflicts (as reported by TreeTransform.find_conflicts()) are present.
1050
969
    def get_preview_tree(self):
1051
970
        """Return a tree representing the result of the transform.
1052
971
 
1053
 
        This tree only supports the subset of Tree functionality required
1054
 
        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.
1055
974
        """
1056
975
        return _PreviewTree(self)
1057
976
 
1058
 
 
1059
 
class TreeTransform(TreeTransformBase):
 
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
 
 
1035
    def _text_parent(self, trans_id):
 
1036
        file_id = self.tree_file_id(trans_id)
 
1037
        try:
 
1038
            if file_id is None or self._tree.kind(file_id) != 'file':
 
1039
                return None
 
1040
        except errors.NoSuchFile:
 
1041
            return None
 
1042
        return file_id
 
1043
 
 
1044
    def _get_parents_texts(self, trans_id):
 
1045
        """Get texts for compression parents of this file."""
 
1046
        file_id = self._text_parent(trans_id)
 
1047
        if file_id is None:
 
1048
            return ()
 
1049
        return (self._tree.get_file_text(file_id),)
 
1050
 
 
1051
    def _get_parents_lines(self, trans_id):
 
1052
        """Get lines for compression parents of this file."""
 
1053
        file_id = self._text_parent(trans_id)
 
1054
        if file_id is None:
 
1055
            return ()
 
1056
        return (self._tree.get_file_lines(file_id),)
 
1057
 
 
1058
    def serialize(self, serializer):
 
1059
        """Serialize this TreeTransform.
 
1060
 
 
1061
        :param serializer: A Serialiser like pack.ContainerSerializer.
 
1062
        """
 
1063
        new_name = dict((k, v.encode('utf-8')) for k, v in
 
1064
                        self._new_name.items())
 
1065
        new_executability = dict((k, int(v)) for k, v in
 
1066
                                 self._new_executability.items())
 
1067
        tree_path_ids = dict((k.encode('utf-8'), v)
 
1068
                             for k, v in self._tree_path_ids.items())
 
1069
        attribs = {
 
1070
            '_id_number': self._id_number,
 
1071
            '_new_name': new_name,
 
1072
            '_new_parent': self._new_parent,
 
1073
            '_new_executability': new_executability,
 
1074
            '_new_id': self._new_id,
 
1075
            '_tree_path_ids': tree_path_ids,
 
1076
            '_removed_id': list(self._removed_id),
 
1077
            '_removed_contents': list(self._removed_contents),
 
1078
            '_non_present_ids': self._non_present_ids,
 
1079
            }
 
1080
        yield serializer.bytes_record(bencode.bencode(attribs),
 
1081
                                      (('attribs',),))
 
1082
        for trans_id, kind in self._new_contents.items():
 
1083
            if kind == 'file':
 
1084
                lines = osutils.chunks_to_lines(
 
1085
                    self._read_file_chunks(trans_id))
 
1086
                parents = self._get_parents_lines(trans_id)
 
1087
                mpdiff = multiparent.MultiParent.from_lines(lines, parents)
 
1088
                content = ''.join(mpdiff.to_patch())
 
1089
            if kind == 'directory':
 
1090
                content = ''
 
1091
            if kind == 'symlink':
 
1092
                content = self._read_symlink_target(trans_id)
 
1093
            yield serializer.bytes_record(content, ((trans_id, kind),))
 
1094
 
 
1095
    def deserialize(self, records):
 
1096
        """Deserialize a stored TreeTransform.
 
1097
 
 
1098
        :param records: An iterable of (names, content) tuples, as per
 
1099
            pack.ContainerPushParser.
 
1100
        """
 
1101
        names, content = records.next()
 
1102
        attribs = bencode.bdecode(content)
 
1103
        self._id_number = attribs['_id_number']
 
1104
        self._new_name = dict((k, v.decode('utf-8'))
 
1105
                            for k, v in attribs['_new_name'].items())
 
1106
        self._new_parent = attribs['_new_parent']
 
1107
        self._new_executability = dict((k, bool(v)) for k, v in
 
1108
            attribs['_new_executability'].items())
 
1109
        self._new_id = attribs['_new_id']
 
1110
        self._r_new_id = dict((v, k) for k, v in self._new_id.items())
 
1111
        self._tree_path_ids = {}
 
1112
        self._tree_id_paths = {}
 
1113
        for bytepath, trans_id in attribs['_tree_path_ids'].items():
 
1114
            path = bytepath.decode('utf-8')
 
1115
            self._tree_path_ids[path] = trans_id
 
1116
            self._tree_id_paths[trans_id] = path
 
1117
        self._removed_id = set(attribs['_removed_id'])
 
1118
        self._removed_contents = set(attribs['_removed_contents'])
 
1119
        self._non_present_ids = attribs['_non_present_ids']
 
1120
        for ((trans_id, kind),), content in records:
 
1121
            if kind == 'file':
 
1122
                mpdiff = multiparent.MultiParent.from_patch(content)
 
1123
                lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
 
1124
                self.create_file(lines, trans_id)
 
1125
            if kind == 'directory':
 
1126
                self.create_directory(trans_id)
 
1127
            if kind == 'symlink':
 
1128
                self.create_symlink(content.decode('utf-8'), trans_id)
 
1129
 
 
1130
 
 
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):
1060
1430
    """Represent a tree transformation.
1061
1431
 
1062
1432
    This object is designed to support incremental generation of the transform,
1121
1491
    FileMover does not delete files until it is sure that a rollback will not
1122
1492
    happen.
1123
1493
    """
1124
 
    def __init__(self, tree, pb=DummyProgress()):
 
1494
    def __init__(self, tree, pb=None):
1125
1495
        """Note: a tree_write lock is taken on the tree.
1126
1496
 
1127
1497
        Use TreeTransform.finalize() to release the lock (can be omitted if
1130
1500
        tree.lock_tree_write()
1131
1501
 
1132
1502
        try:
1133
 
            control_files = tree._control_files
1134
1503
            limbodir = urlutils.local_path_from_url(
1135
 
                control_files.controlfilename('limbo'))
 
1504
                tree._transport.abspath('limbo'))
1136
1505
            try:
1137
1506
                os.mkdir(limbodir)
1138
1507
            except OSError, e:
1139
1508
                if e.errno == errno.EEXIST:
1140
1509
                    raise ExistingLimbo(limbodir)
1141
1510
            deletiondir = urlutils.local_path_from_url(
1142
 
                control_files.controlfilename('pending-deletion'))
 
1511
                tree._transport.abspath('pending-deletion'))
1143
1512
            try:
1144
1513
                os.mkdir(deletiondir)
1145
1514
            except OSError, e:
1149
1518
            tree.unlock()
1150
1519
            raise
1151
1520
 
1152
 
        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,
1153
1526
                                   tree.case_sensitive)
1154
1527
        self._deletiondir = deletiondir
1155
1528
 
1156
 
    def apply(self, no_conflicts=False, _mover=None):
 
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
 
 
1655
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
1157
1656
        """Apply all changes to the inventory and filesystem.
1158
1657
 
1159
1658
        If filesystem or inventory conflicts are present, MalformedTransform
1163
1662
 
1164
1663
        :param no_conflicts: if True, the caller guarantees there are no
1165
1664
            conflicts, so no check is made.
 
1665
        :param precomputed_delta: An inventory delta to use instead of
 
1666
            calculating one.
1166
1667
        :param _mover: Supply an alternate FileMover, for testing
1167
1668
        """
1168
1669
        if not no_conflicts:
1169
 
            conflicts = self.find_conflicts()
1170
 
            if len(conflicts) != 0:
1171
 
                raise MalformedTransform(conflicts=conflicts)
1172
 
        inv = self._tree.inventory
1173
 
        inventory_delta = []
 
1670
            self._check_malformed()
1174
1671
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1175
1672
        try:
 
1673
            if precomputed_delta is None:
 
1674
                child_pb.update('Apply phase', 0, 2)
 
1675
                inventory_delta = self._generate_inventory_delta()
 
1676
                offset = 1
 
1677
            else:
 
1678
                inventory_delta = precomputed_delta
 
1679
                offset = 0
1176
1680
            if _mover is None:
1177
1681
                mover = _FileMover()
1178
1682
            else:
1179
1683
                mover = _mover
1180
1684
            try:
1181
 
                child_pb.update('Apply phase', 0, 2)
1182
 
                self._apply_removals(inv, inventory_delta, mover)
1183
 
                child_pb.update('Apply phase', 1, 2)
1184
 
                modified_paths = self._apply_insertions(inv, inventory_delta,
1185
 
                                                        mover)
 
1685
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
 
1686
                self._apply_removals(mover)
 
1687
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
 
1688
                modified_paths = self._apply_insertions(mover)
1186
1689
            except:
1187
1690
                mover.rollback()
1188
1691
                raise
1195
1698
        self.finalize()
1196
1699
        return _TransformResults(modified_paths, self.rename_count)
1197
1700
 
1198
 
    def _apply_removals(self, inv, inventory_delta, mover):
 
1701
    def _generate_inventory_delta(self):
 
1702
        """Generate an inventory delta for the current transform."""
 
1703
        inventory_delta = []
 
1704
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1705
        new_paths = self._inventory_altered()
 
1706
        total_entries = len(new_paths) + len(self._removed_id)
 
1707
        try:
 
1708
            for num, trans_id in enumerate(self._removed_id):
 
1709
                if (num % 10) == 0:
 
1710
                    child_pb.update('removing file', num, total_entries)
 
1711
                if trans_id == self._new_root:
 
1712
                    file_id = self._tree.get_root_id()
 
1713
                else:
 
1714
                    file_id = self.tree_file_id(trans_id)
 
1715
                # File-id isn't really being deleted, just moved
 
1716
                if file_id in self._r_new_id:
 
1717
                    continue
 
1718
                path = self._tree_id_paths[trans_id]
 
1719
                inventory_delta.append((path, None, file_id, None))
 
1720
            new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
 
1721
                                     new_paths)
 
1722
            entries = self._tree.iter_entries_by_dir(
 
1723
                new_path_file_ids.values())
 
1724
            old_paths = dict((e.file_id, p) for p, e in entries)
 
1725
            final_kinds = {}
 
1726
            for num, (path, trans_id) in enumerate(new_paths):
 
1727
                if (num % 10) == 0:
 
1728
                    child_pb.update('adding file',
 
1729
                                    num + len(self._removed_id), total_entries)
 
1730
                file_id = new_path_file_ids[trans_id]
 
1731
                if file_id is None:
 
1732
                    continue
 
1733
                needs_entry = False
 
1734
                kind = self.final_kind(trans_id)
 
1735
                if kind is None:
 
1736
                    kind = self._tree.stored_kind(file_id)
 
1737
                parent_trans_id = self.final_parent(trans_id)
 
1738
                parent_file_id = new_path_file_ids.get(parent_trans_id)
 
1739
                if parent_file_id is None:
 
1740
                    parent_file_id = self.final_file_id(parent_trans_id)
 
1741
                if trans_id in self._new_reference_revision:
 
1742
                    new_entry = inventory.TreeReference(
 
1743
                        file_id,
 
1744
                        self._new_name[trans_id],
 
1745
                        self.final_file_id(self._new_parent[trans_id]),
 
1746
                        None, self._new_reference_revision[trans_id])
 
1747
                else:
 
1748
                    new_entry = inventory.make_entry(kind,
 
1749
                        self.final_name(trans_id),
 
1750
                        parent_file_id, file_id)
 
1751
                old_path = old_paths.get(new_entry.file_id)
 
1752
                new_executability = self._new_executability.get(trans_id)
 
1753
                if new_executability is not None:
 
1754
                    new_entry.executable = new_executability
 
1755
                inventory_delta.append(
 
1756
                    (old_path, path, new_entry.file_id, new_entry))
 
1757
        finally:
 
1758
            child_pb.finished()
 
1759
        return inventory_delta
 
1760
 
 
1761
    def _apply_removals(self, mover):
1199
1762
        """Perform tree operations that remove directory/inventory names.
1200
1763
 
1201
1764
        That is, delete files that are to be deleted, and put any files that
1202
1765
        need renaming into limbo.  This must be done in strict child-to-parent
1203
1766
        order.
 
1767
 
 
1768
        If inventory_delta is None, no inventory delta generation is performed.
1204
1769
        """
1205
1770
        tree_paths = list(self._tree_path_ids.iteritems())
1206
1771
        tree_paths.sort(reverse=True)
1211
1776
                child_pb.update('removing file', num, len(tree_paths))
1212
1777
                full_path = self._tree.abspath(path)
1213
1778
                if trans_id in self._removed_contents:
1214
 
                    mover.pre_delete(full_path, os.path.join(self._deletiondir,
1215
 
                                     trans_id))
1216
 
                elif trans_id in self._new_name or trans_id in \
1217
 
                    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):
1218
1783
                    try:
1219
1784
                        mover.rename(full_path, self._limbo_name(trans_id))
1220
 
                    except OSError, e:
 
1785
                    except errors.TransformRenameFailed, e:
1221
1786
                        if e.errno != errno.ENOENT:
1222
1787
                            raise
1223
1788
                    else:
1224
1789
                        self.rename_count += 1
1225
 
                if trans_id in self._removed_id:
1226
 
                    if trans_id == self._new_root:
1227
 
                        file_id = self._tree.get_root_id()
1228
 
                    else:
1229
 
                        file_id = self.tree_file_id(trans_id)
1230
 
                    if file_id is not None:
1231
 
                        inventory_delta.append((path, None, file_id, None))
1232
1790
        finally:
1233
1791
            child_pb.finished()
1234
1792
 
1235
 
    def _apply_insertions(self, inv, inventory_delta, mover):
 
1793
    def _apply_insertions(self, mover):
1236
1794
        """Perform tree operations that insert directory/inventory names.
1237
1795
 
1238
1796
        That is, create any files that need to be created, and restore from
1239
1797
        limbo any files that needed renaming.  This must be done in strict
1240
1798
        parent-to-child order.
 
1799
 
 
1800
        If inventory_delta is None, no inventory delta is calculated, and
 
1801
        no list of modified paths is returned.
1241
1802
        """
1242
 
        new_paths = self.new_paths()
 
1803
        new_paths = self.new_paths(filesystem_only=True)
1243
1804
        modified_paths = []
 
1805
        new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
 
1806
                                 new_paths)
1244
1807
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1245
 
        completed_new = []
1246
1808
        try:
1247
1809
            for num, (path, trans_id) in enumerate(new_paths):
1248
 
                new_entry = None
1249
 
                child_pb.update('adding file', num, len(new_paths))
1250
 
                try:
1251
 
                    kind = self._new_contents[trans_id]
1252
 
                except KeyError:
1253
 
                    kind = contents = None
1254
 
                if trans_id in self._new_contents or \
1255
 
                    self.path_changed(trans_id):
1256
 
                    full_path = self._tree.abspath(path)
1257
 
                    if trans_id in self._needs_rename:
1258
 
                        try:
1259
 
                            mover.rename(self._limbo_name(trans_id), full_path)
1260
 
                        except OSError, e:
1261
 
                            # We may be renaming a dangling inventory id
1262
 
                            if e.errno != errno.ENOENT:
1263
 
                                raise
1264
 
                        else:
1265
 
                            self.rename_count += 1
 
1810
                if (num % 10) == 0:
 
1811
                    child_pb.update('adding file', num, len(new_paths))
 
1812
                full_path = self._tree.abspath(path)
 
1813
                if trans_id in self._needs_rename:
 
1814
                    try:
 
1815
                        mover.rename(self._limbo_name(trans_id), full_path)
 
1816
                    except errors.TransformRenameFailed, e:
 
1817
                        # We may be renaming a dangling inventory id
 
1818
                        if e.errno != errno.ENOENT:
 
1819
                            raise
 
1820
                    else:
 
1821
                        self.rename_count += 1
 
1822
                if (trans_id in self._new_contents or
 
1823
                    self.path_changed(trans_id)):
1266
1824
                    if trans_id in self._new_contents:
1267
1825
                        modified_paths.append(full_path)
1268
 
                        completed_new.append(trans_id)
1269
 
 
1270
 
                if trans_id in self._new_id:
1271
 
                    if kind is None:
1272
 
                        kind = file_kind(self._tree.abspath(path))
1273
 
                    if trans_id in self._new_reference_revision:
1274
 
                        new_entry = inventory.TreeReference(
1275
 
                            self._new_id[trans_id],
1276
 
                            self._new_name[trans_id],
1277
 
                            self.final_file_id(self._new_parent[trans_id]),
1278
 
                            None, self._new_reference_revision[trans_id])
1279
 
                    else:
1280
 
                        new_entry = inventory.make_entry(kind,
1281
 
                            self.final_name(trans_id),
1282
 
                            self.final_file_id(self.final_parent(trans_id)),
1283
 
                            self._new_id[trans_id])
1284
 
                else:
1285
 
                    if trans_id in self._new_name or trans_id in\
1286
 
                        self._new_parent or\
1287
 
                        trans_id in self._new_executability:
1288
 
                        file_id = self.final_file_id(trans_id)
1289
 
                        if file_id is not None:
1290
 
                            entry = inv[file_id]
1291
 
                            new_entry = entry.copy()
1292
 
 
1293
 
                    if trans_id in self._new_name or trans_id in\
1294
 
                        self._new_parent:
1295
 
                            if new_entry is not None:
1296
 
                                new_entry.name = self.final_name(trans_id)
1297
 
                                parent = self.final_parent(trans_id)
1298
 
                                parent_id = self.final_file_id(parent)
1299
 
                                new_entry.parent_id = parent_id
1300
 
 
1301
1826
                if trans_id in self._new_executability:
1302
 
                    self._set_executability(path, new_entry, trans_id)
1303
 
                if new_entry is not None:
1304
 
                    if new_entry.file_id in inv:
1305
 
                        old_path = inv.id2path(new_entry.file_id)
1306
 
                    else:
1307
 
                        old_path = None
1308
 
                    inventory_delta.append((old_path, path,
1309
 
                                            new_entry.file_id,
1310
 
                                            new_entry))
 
1827
                    self._set_executability(path, trans_id)
1311
1828
        finally:
1312
1829
            child_pb.finished()
1313
 
        for trans_id in completed_new:
1314
 
            del self._new_contents[trans_id]
 
1830
        self._new_contents.clear()
1315
1831
        return modified_paths
1316
1832
 
1317
1833
 
1318
 
class TransformPreview(TreeTransformBase):
 
1834
class TransformPreview(DiskTreeTransform):
1319
1835
    """A TreeTransform for generating preview trees.
1320
1836
 
1321
1837
    Unlike TreeTransform, this version works when the input tree is a
1323
1839
    unversioned files in the input tree.
1324
1840
    """
1325
1841
 
1326
 
    def __init__(self, tree, pb=DummyProgress(), case_sensitive=True):
 
1842
    def __init__(self, tree, pb=None, case_sensitive=True):
1327
1843
        tree.lock_read()
1328
 
        limbodir = tempfile.mkdtemp(prefix='bzr-limbo-')
1329
 
        TreeTransformBase.__init__(self, tree, limbodir, pb, case_sensitive)
 
1844
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
 
1845
        DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
1330
1846
 
1331
1847
    def canonical_path(self, path):
1332
1848
        return path
1334
1850
    def tree_kind(self, trans_id):
1335
1851
        path = self._tree_id_paths.get(trans_id)
1336
1852
        if path is None:
1337
 
            raise NoSuchFile(None)
 
1853
            return None
1338
1854
        file_id = self._tree.path2id(path)
1339
 
        return self._tree.kind(file_id)
 
1855
        try:
 
1856
            return self._tree.kind(file_id)
 
1857
        except errors.NoSuchFile:
 
1858
            return None
1340
1859
 
1341
1860
    def _set_mode(self, trans_id, mode_id, typefunc):
1342
1861
        """Set the mode of new file contents.
1354
1873
        except KeyError:
1355
1874
            return
1356
1875
        file_id = self.tree_file_id(parent_id)
1357
 
        for child in self._tree.inventory[file_id].children.iterkeys():
 
1876
        if file_id is None:
 
1877
            return
 
1878
        entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
 
1879
        children = getattr(entry, 'children', {})
 
1880
        for child in children:
1358
1881
            childpath = joinpath(path, child)
1359
1882
            yield self.trans_id_tree_path(childpath)
1360
1883
 
1361
 
 
1362
 
class _PreviewTree(object):
 
1884
    def new_orphan(self, trans_id, parent_id):
 
1885
        raise NotImplementedError(self.new_orphan)
 
1886
 
 
1887
 
 
1888
class _PreviewTree(tree.Tree):
1363
1889
    """Partial implementation of Tree to support show_diff_trees"""
1364
1890
 
1365
1891
    def __init__(self, transform):
1366
1892
        self._transform = transform
 
1893
        self._final_paths = FinalPaths(transform)
 
1894
        self.__by_parent = None
 
1895
        self._parent_ids = []
 
1896
        self._all_children_cache = {}
 
1897
        self._path2trans_id_cache = {}
 
1898
        self._final_name_cache = {}
 
1899
        self._iter_changes_cache = dict((c[0], c) for c in
 
1900
                                        self._transform.iter_changes())
 
1901
 
 
1902
    def _content_change(self, file_id):
 
1903
        """Return True if the content of this file changed"""
 
1904
        changes = self._iter_changes_cache.get(file_id)
 
1905
        # changes[2] is true if the file content changed.  See
 
1906
        # InterTree.iter_changes.
 
1907
        return (changes is not None and changes[2])
 
1908
 
 
1909
    def _get_repository(self):
 
1910
        repo = getattr(self._transform._tree, '_repository', None)
 
1911
        if repo is None:
 
1912
            repo = self._transform._tree.branch.repository
 
1913
        return repo
 
1914
 
 
1915
    def _iter_parent_trees(self):
 
1916
        for revision_id in self.get_parent_ids():
 
1917
            try:
 
1918
                yield self.revision_tree(revision_id)
 
1919
            except errors.NoSuchRevisionInTree:
 
1920
                yield self._get_repository().revision_tree(revision_id)
 
1921
 
 
1922
    def _get_file_revision(self, file_id, vf, tree_revision):
 
1923
        parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
 
1924
                       self._iter_parent_trees()]
 
1925
        vf.add_lines((file_id, tree_revision), parent_keys,
 
1926
                     self.get_file_lines(file_id))
 
1927
        repo = self._get_repository()
 
1928
        base_vf = repo.texts
 
1929
        if base_vf not in vf.fallback_versionedfiles:
 
1930
            vf.fallback_versionedfiles.append(base_vf)
 
1931
        return tree_revision
 
1932
 
 
1933
    def _stat_limbo_file(self, file_id):
 
1934
        trans_id = self._transform.trans_id_file_id(file_id)
 
1935
        name = self._transform._limbo_name(trans_id)
 
1936
        return os.lstat(name)
 
1937
 
 
1938
    @property
 
1939
    def _by_parent(self):
 
1940
        if self.__by_parent is None:
 
1941
            self.__by_parent = self._transform.by_parent()
 
1942
        return self.__by_parent
 
1943
 
 
1944
    def _comparison_data(self, entry, path):
 
1945
        kind, size, executable, link_or_sha1 = self.path_content_summary(path)
 
1946
        if kind == 'missing':
 
1947
            kind = None
 
1948
            executable = False
 
1949
        else:
 
1950
            file_id = self._transform.final_file_id(self._path2trans_id(path))
 
1951
            executable = self.is_executable(file_id, path)
 
1952
        return kind, executable, None
 
1953
 
 
1954
    def is_locked(self):
 
1955
        return False
1367
1956
 
1368
1957
    def lock_read(self):
1369
1958
        # Perhaps in theory, this should lock the TreeTransform?
1370
 
        pass
 
1959
        return self
1371
1960
 
1372
1961
    def unlock(self):
1373
1962
        pass
1374
1963
 
1375
 
    def _iter_changes(self, from_tree, include_unchanged=False,
 
1964
    @property
 
1965
    def inventory(self):
 
1966
        """This Tree does not use inventory as its backing data."""
 
1967
        raise NotImplementedError(_PreviewTree.inventory)
 
1968
 
 
1969
    def get_root_id(self):
 
1970
        return self._transform.final_file_id(self._transform.root)
 
1971
 
 
1972
    def all_file_ids(self):
 
1973
        tree_ids = set(self._transform._tree.all_file_ids())
 
1974
        tree_ids.difference_update(self._transform.tree_file_id(t)
 
1975
                                   for t in self._transform._removed_id)
 
1976
        tree_ids.update(self._transform._new_id.values())
 
1977
        return tree_ids
 
1978
 
 
1979
    def __iter__(self):
 
1980
        return iter(self.all_file_ids())
 
1981
 
 
1982
    def _has_id(self, file_id, fallback_check):
 
1983
        if file_id in self._transform._r_new_id:
 
1984
            return True
 
1985
        elif file_id in set([self._transform.tree_file_id(trans_id) for
 
1986
            trans_id in self._transform._removed_id]):
 
1987
            return False
 
1988
        else:
 
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)
 
1996
 
 
1997
    def _path2trans_id(self, path):
 
1998
        # We must not use None here, because that is a valid value to store.
 
1999
        trans_id = self._path2trans_id_cache.get(path, object)
 
2000
        if trans_id is not object:
 
2001
            return trans_id
 
2002
        segments = splitpath(path)
 
2003
        cur_parent = self._transform.root
 
2004
        for cur_segment in segments:
 
2005
            for child in self._all_children(cur_parent):
 
2006
                final_name = self._final_name_cache.get(child)
 
2007
                if final_name is None:
 
2008
                    final_name = self._transform.final_name(child)
 
2009
                    self._final_name_cache[child] = final_name
 
2010
                if final_name == cur_segment:
 
2011
                    cur_parent = child
 
2012
                    break
 
2013
            else:
 
2014
                self._path2trans_id_cache[path] = None
 
2015
                return None
 
2016
        self._path2trans_id_cache[path] = cur_parent
 
2017
        return cur_parent
 
2018
 
 
2019
    def path2id(self, path):
 
2020
        return self._transform.final_file_id(self._path2trans_id(path))
 
2021
 
 
2022
    def id2path(self, file_id):
 
2023
        trans_id = self._transform.trans_id_file_id(file_id)
 
2024
        try:
 
2025
            return self._final_paths._determine_path(trans_id)
 
2026
        except NoFinalPath:
 
2027
            raise errors.NoSuchId(self, file_id)
 
2028
 
 
2029
    def _all_children(self, trans_id):
 
2030
        children = self._all_children_cache.get(trans_id)
 
2031
        if children is not None:
 
2032
            return children
 
2033
        children = set(self._transform.iter_tree_children(trans_id))
 
2034
        # children in the _new_parent set are provided by _by_parent.
 
2035
        children.difference_update(self._transform._new_parent.keys())
 
2036
        children.update(self._by_parent.get(trans_id, []))
 
2037
        self._all_children_cache[trans_id] = children
 
2038
        return children
 
2039
 
 
2040
    def iter_children(self, file_id):
 
2041
        trans_id = self._transform.trans_id_file_id(file_id)
 
2042
        for child_trans_id in self._all_children(trans_id):
 
2043
            yield self._transform.final_file_id(child_trans_id)
 
2044
 
 
2045
    def extras(self):
 
2046
        possible_extras = set(self._transform.trans_id_tree_path(p) for p
 
2047
                              in self._transform._tree.extras())
 
2048
        possible_extras.update(self._transform._new_contents)
 
2049
        possible_extras.update(self._transform._removed_id)
 
2050
        for trans_id in possible_extras:
 
2051
            if self._transform.final_file_id(trans_id) is None:
 
2052
                yield self._final_paths._determine_path(trans_id)
 
2053
 
 
2054
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
 
2055
        yield_parents=False):
 
2056
        for trans_id, parent_file_id in ordered_entries:
 
2057
            file_id = self._transform.final_file_id(trans_id)
 
2058
            if file_id is None:
 
2059
                continue
 
2060
            if (specific_file_ids is not None
 
2061
                and file_id not in specific_file_ids):
 
2062
                continue
 
2063
            kind = self._transform.final_kind(trans_id)
 
2064
            if kind is None:
 
2065
                kind = self._transform._tree.stored_kind(file_id)
 
2066
            new_entry = inventory.make_entry(
 
2067
                kind,
 
2068
                self._transform.final_name(trans_id),
 
2069
                parent_file_id, file_id)
 
2070
            yield new_entry, trans_id
 
2071
 
 
2072
    def _list_files_by_dir(self):
 
2073
        todo = [ROOT_PARENT]
 
2074
        ordered_ids = []
 
2075
        while len(todo) > 0:
 
2076
            parent = todo.pop()
 
2077
            parent_file_id = self._transform.final_file_id(parent)
 
2078
            children = list(self._all_children(parent))
 
2079
            paths = dict(zip(children, self._final_paths.get_paths(children)))
 
2080
            children.sort(key=paths.get)
 
2081
            todo.extend(reversed(children))
 
2082
            for trans_id in children:
 
2083
                ordered_ids.append((trans_id, parent_file_id))
 
2084
        return ordered_ids
 
2085
 
 
2086
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
 
2087
        # This may not be a maximally efficient implementation, but it is
 
2088
        # reasonably straightforward.  An implementation that grafts the
 
2089
        # TreeTransform changes onto the tree's iter_entries_by_dir results
 
2090
        # might be more efficient, but requires tricky inferences about stack
 
2091
        # position.
 
2092
        ordered_ids = self._list_files_by_dir()
 
2093
        for entry, trans_id in self._make_inv_entries(ordered_ids,
 
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."""
 
2109
        # XXX This should behave like WorkingTree.list_files, but is really
 
2110
        # more like RevisionTree.list_files.
 
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
 
2132
 
 
2133
    def kind(self, file_id):
 
2134
        trans_id = self._transform.trans_id_file_id(file_id)
 
2135
        return self._transform.final_kind(trans_id)
 
2136
 
 
2137
    def stored_kind(self, file_id):
 
2138
        trans_id = self._transform.trans_id_file_id(file_id)
 
2139
        try:
 
2140
            return self._transform._new_contents[trans_id]
 
2141
        except KeyError:
 
2142
            return self._transform._tree.stored_kind(file_id)
 
2143
 
 
2144
    def get_file_mtime(self, file_id, path=None):
 
2145
        """See Tree.get_file_mtime"""
 
2146
        if not self._content_change(file_id):
 
2147
            return self._transform._tree.get_file_mtime(file_id)
 
2148
        return self._stat_limbo_file(file_id).st_mtime
 
2149
 
 
2150
    def _file_size(self, entry, stat_value):
 
2151
        return self.get_file_size(entry.file_id)
 
2152
 
 
2153
    def get_file_size(self, file_id):
 
2154
        """See Tree.get_file_size"""
 
2155
        if self.kind(file_id) == 'file':
 
2156
            return self._transform._tree.get_file_size(file_id)
 
2157
        else:
 
2158
            return None
 
2159
 
 
2160
    def get_file_sha1(self, file_id, path=None, stat_value=None):
 
2161
        trans_id = self._transform.trans_id_file_id(file_id)
 
2162
        kind = self._transform._new_contents.get(trans_id)
 
2163
        if kind is None:
 
2164
            return self._transform._tree.get_file_sha1(file_id)
 
2165
        if kind == 'file':
 
2166
            fileobj = self.get_file(file_id)
 
2167
            try:
 
2168
                return sha_file(fileobj)
 
2169
            finally:
 
2170
                fileobj.close()
 
2171
 
 
2172
    def is_executable(self, file_id, path=None):
 
2173
        if file_id is None:
 
2174
            return False
 
2175
        trans_id = self._transform.trans_id_file_id(file_id)
 
2176
        try:
 
2177
            return self._transform._new_executability[trans_id]
 
2178
        except KeyError:
 
2179
            try:
 
2180
                return self._transform._tree.is_executable(file_id, path)
 
2181
            except OSError, e:
 
2182
                if e.errno == errno.ENOENT:
 
2183
                    return False
 
2184
                raise
 
2185
            except errors.NoSuchId:
 
2186
                return False
 
2187
 
 
2188
    def path_content_summary(self, path):
 
2189
        trans_id = self._path2trans_id(path)
 
2190
        tt = self._transform
 
2191
        tree_path = tt._tree_id_paths.get(trans_id)
 
2192
        kind = tt._new_contents.get(trans_id)
 
2193
        if kind is None:
 
2194
            if tree_path is None or trans_id in tt._removed_contents:
 
2195
                return 'missing', None, None, None
 
2196
            summary = tt._tree.path_content_summary(tree_path)
 
2197
            kind, size, executable, link_or_sha1 = summary
 
2198
        else:
 
2199
            link_or_sha1 = None
 
2200
            limbo_name = tt._limbo_name(trans_id)
 
2201
            if trans_id in tt._new_reference_revision:
 
2202
                kind = 'tree-reference'
 
2203
            if kind == 'file':
 
2204
                statval = os.lstat(limbo_name)
 
2205
                size = statval.st_size
 
2206
                if not supports_executable():
 
2207
                    executable = False
 
2208
                else:
 
2209
                    executable = statval.st_mode & S_IEXEC
 
2210
            else:
 
2211
                size = None
 
2212
                executable = None
 
2213
            if kind == 'symlink':
 
2214
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
 
2215
        executable = tt._new_executability.get(trans_id, executable)
 
2216
        return kind, size, executable, link_or_sha1
 
2217
 
 
2218
    def iter_changes(self, from_tree, include_unchanged=False,
1376
2219
                      specific_files=None, pb=None, extra_trees=None,
1377
2220
                      require_versioned=True, want_unversioned=False):
1378
 
        """See InterTree._iter_changes.
 
2221
        """See InterTree.iter_changes.
1379
2222
 
1380
 
        This implementation does not support include_unchanged, specific_files,
1381
 
        or want_unversioned.  extra_trees, require_versioned, and pb are
1382
 
        ignored.
 
2223
        This has a fast path that is only used when the from_tree matches
 
2224
        the transform tree, and no fancy options are supplied.
1383
2225
        """
1384
 
        if from_tree is not self._transform._tree:
1385
 
            raise ValueError('from_tree must be transform source tree.')
1386
 
        if include_unchanged:
1387
 
            raise ValueError('include_unchanged is not supported')
1388
 
        if specific_files is not None:
1389
 
            raise ValueError('specific_files is not supported')
 
2226
        if (from_tree is not self._transform._tree or include_unchanged or
 
2227
            specific_files or want_unversioned):
 
2228
            return tree.InterTree(from_tree, self).iter_changes(
 
2229
                include_unchanged=include_unchanged,
 
2230
                specific_files=specific_files,
 
2231
                pb=pb,
 
2232
                extra_trees=extra_trees,
 
2233
                require_versioned=require_versioned,
 
2234
                want_unversioned=want_unversioned)
1390
2235
        if want_unversioned:
1391
2236
            raise ValueError('want_unversioned is not supported')
1392
 
        return self._transform._iter_changes()
1393
 
 
1394
 
    def kind(self, file_id):
1395
 
        trans_id = self._transform.trans_id_file_id(file_id)
1396
 
        return self._transform.final_kind(trans_id)
1397
 
 
1398
 
    def get_file_mtime(self, file_id, path=None):
1399
 
        """See Tree.get_file_mtime"""
1400
 
        trans_id = self._transform.trans_id_file_id(file_id)
1401
 
        name = self._transform._limbo_name(trans_id)
1402
 
        return os.stat(name).st_mtime
1403
 
 
1404
 
    def get_file(self, file_id):
 
2237
        return self._transform.iter_changes()
 
2238
 
 
2239
    def get_file(self, file_id, path=None):
1405
2240
        """See Tree.get_file"""
 
2241
        if not self._content_change(file_id):
 
2242
            return self._transform._tree.get_file(file_id, path)
1406
2243
        trans_id = self._transform.trans_id_file_id(file_id)
1407
2244
        name = self._transform._limbo_name(trans_id)
1408
2245
        return open(name, 'rb')
1409
2246
 
1410
 
    def paths2ids(self, specific_files, trees=None, require_versioned=False):
1411
 
        """See Tree.paths2ids"""
1412
 
        return 'not_empty'
 
2247
    def get_file_with_stat(self, file_id, path=None):
 
2248
        return self.get_file(file_id, path), None
 
2249
 
 
2250
    def annotate_iter(self, file_id,
 
2251
                      default_revision=_mod_revision.CURRENT_REVISION):
 
2252
        changes = self._iter_changes_cache.get(file_id)
 
2253
        if changes is None:
 
2254
            get_old = True
 
2255
        else:
 
2256
            changed_content, versioned, kind = (changes[2], changes[3],
 
2257
                                                changes[6])
 
2258
            if kind[1] is None:
 
2259
                return None
 
2260
            get_old = (kind[0] == 'file' and versioned[0])
 
2261
        if get_old:
 
2262
            old_annotation = self._transform._tree.annotate_iter(file_id,
 
2263
                default_revision=default_revision)
 
2264
        else:
 
2265
            old_annotation = []
 
2266
        if changes is None:
 
2267
            return old_annotation
 
2268
        if not changed_content:
 
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.
 
2277
        return annotate.reannotate([old_annotation],
 
2278
                                   self.get_file(file_id).readlines(),
 
2279
                                   default_revision)
 
2280
 
 
2281
    def get_symlink_target(self, file_id):
 
2282
        """See Tree.get_symlink_target"""
 
2283
        if not self._content_change(file_id):
 
2284
            return self._transform._tree.get_symlink_target(file_id)
 
2285
        trans_id = self._transform.trans_id_file_id(file_id)
 
2286
        name = self._transform._limbo_name(trans_id)
 
2287
        return osutils.readlink(name)
 
2288
 
 
2289
    def walkdirs(self, prefix=''):
 
2290
        pending = [self._transform.root]
 
2291
        while len(pending) > 0:
 
2292
            parent_id = pending.pop()
 
2293
            children = []
 
2294
            subdirs = []
 
2295
            prefix = prefix.rstrip('/')
 
2296
            parent_path = self._final_paths.get_path(parent_id)
 
2297
            parent_file_id = self._transform.final_file_id(parent_id)
 
2298
            for child_id in self._all_children(parent_id):
 
2299
                path_from_root = self._final_paths.get_path(child_id)
 
2300
                basename = self._transform.final_name(child_id)
 
2301
                file_id = self._transform.final_file_id(child_id)
 
2302
                kind  = self._transform.final_kind(child_id)
 
2303
                if kind is not None:
 
2304
                    versioned_kind = kind
 
2305
                else:
 
2306
                    kind = 'unknown'
 
2307
                    versioned_kind = self._transform._tree.stored_kind(file_id)
 
2308
                if versioned_kind == 'directory':
 
2309
                    subdirs.append(child_id)
 
2310
                children.append((path_from_root, basename, kind, None,
 
2311
                                 file_id, versioned_kind))
 
2312
            children.sort()
 
2313
            if parent_path.startswith(prefix):
 
2314
                yield (parent_path, parent_file_id), children
 
2315
            pending.extend(sorted(subdirs, key=self._final_paths.get_path,
 
2316
                                  reverse=True))
 
2317
 
 
2318
    def get_parent_ids(self):
 
2319
        return self._parent_ids
 
2320
 
 
2321
    def set_parent_ids(self, parent_ids):
 
2322
        self._parent_ids = parent_ids
 
2323
 
 
2324
    def get_revision_tree(self, revision_id):
 
2325
        return self._transform._tree.get_revision_tree(revision_id)
1413
2326
 
1414
2327
 
1415
2328
def joinpath(parent, child):
1432
2345
        self.transform = transform
1433
2346
 
1434
2347
    def _determine_path(self, trans_id):
1435
 
        if trans_id == self.transform.root:
 
2348
        if (trans_id == self.transform.root or trans_id == ROOT_PARENT):
1436
2349
            return ""
1437
2350
        name = self.transform.final_name(trans_id)
1438
2351
        parent_id = self.transform.final_parent(trans_id)
1447
2360
            self._known_paths[trans_id] = self._determine_path(trans_id)
1448
2361
        return self._known_paths[trans_id]
1449
2362
 
 
2363
    def get_paths(self, trans_ids):
 
2364
        return [(self.get_path(t), t) for t in trans_ids]
 
2365
 
 
2366
 
1450
2367
 
1451
2368
def topology_sorted_ids(tree):
1452
2369
    """Determine the topological order of the ids in a tree"""
1455
2372
    return file_ids
1456
2373
 
1457
2374
 
1458
 
def build_tree(tree, wt, accelerator_tree=None):
 
2375
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
 
2376
               delta_from_tree=False):
1459
2377
    """Create working tree for a branch, using a TreeTransform.
1460
 
    
 
2378
 
1461
2379
    This function should be used on empty trees, having a tree root at most.
1462
2380
    (see merge and revert functionality for working with existing trees)
1463
2381
 
1464
2382
    Existing files are handled like so:
1465
 
    
 
2383
 
1466
2384
    - Existing bzrdirs take precedence over creating new items.  They are
1467
2385
      created as '%s.diverted' % name.
1468
2386
    - Otherwise, if the content on disk matches the content we are building,
1474
2392
    :param accelerator_tree: A tree which can be used for retrieving file
1475
2393
        contents more quickly than tree itself, i.e. a workingtree.  tree
1476
2394
        will be used for cases where accelerator_tree's content is different.
 
2395
    :param hardlink: If true, hard-link files to accelerator_tree, where
 
2396
        possible.  accelerator_tree must implement abspath, i.e. be a
 
2397
        working tree.
 
2398
    :param delta_from_tree: If true, build_tree may use the input Tree to
 
2399
        generate the inventory delta.
1477
2400
    """
1478
2401
    wt.lock_tree_write()
1479
2402
    try:
1482
2405
            if accelerator_tree is not None:
1483
2406
                accelerator_tree.lock_read()
1484
2407
            try:
1485
 
                return _build_tree(tree, wt, accelerator_tree)
 
2408
                return _build_tree(tree, wt, accelerator_tree, hardlink,
 
2409
                                   delta_from_tree)
1486
2410
            finally:
1487
2411
                if accelerator_tree is not None:
1488
2412
                    accelerator_tree.unlock()
1492
2416
        wt.unlock()
1493
2417
 
1494
2418
 
1495
 
def _build_tree(tree, wt, accelerator_tree):
 
2419
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
1496
2420
    """See build_tree."""
1497
 
    if len(wt.inventory) > 1:  # more than just a root
1498
 
        raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
 
2421
    for num, _unused in enumerate(wt.all_file_ids()):
 
2422
        if num > 0:  # more than just a root
 
2423
            raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
1499
2424
    file_trans_id = {}
1500
2425
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1501
2426
    pp = ProgressPhase("Build phase", 2, top_pb)
1519
2444
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
1520
2445
        try:
1521
2446
            deferred_contents = []
 
2447
            num = 0
 
2448
            total = len(tree.inventory)
 
2449
            if delta_from_tree:
 
2450
                precomputed_delta = []
 
2451
            else:
 
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)
1522
2462
            for num, (tree_path, entry) in \
1523
2463
                enumerate(tree.inventory.iter_entries_by_dir()):
1524
 
                pb.update("Building tree", num - len(deferred_contents),
1525
 
                          len(tree.inventory))
 
2464
                pb.update("Building tree", num - len(deferred_contents), total)
1526
2465
                if entry.parent_id is None:
1527
2466
                    continue
1528
2467
                reparent = False
1529
2468
                file_id = entry.file_id
1530
 
                target_path = wt.abspath(tree_path)
1531
 
                try:
 
2469
                if delta_from_tree:
 
2470
                    precomputed_delta.append((None, tree_path, file_id, entry))
 
2471
                if tree_path in existing_files:
 
2472
                    target_path = wt.abspath(tree_path)
1532
2473
                    kind = file_kind(target_path)
1533
 
                except NoSuchFile:
1534
 
                    pass
1535
 
                else:
1536
2474
                    if kind == "directory":
1537
2475
                        try:
1538
2476
                            bzrdir.BzrDir.open(target_path)
1546
2484
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
1547
2485
                        if kind == 'directory':
1548
2486
                            reparent = True
1549
 
                if entry.parent_id not in file_trans_id:
1550
 
                    raise AssertionError(
1551
 
                        'entry %s parent id %r is not in file_trans_id %r'
1552
 
                        % (entry, entry.parent_id, file_trans_id))
1553
2487
                parent_id = file_trans_id[entry.parent_id]
1554
2488
                if entry.kind == 'file':
1555
2489
                    # We *almost* replicate new_by_entry, so that we can defer
1556
2490
                    # getting the file text, and get them all at once.
1557
2491
                    trans_id = tt.create_path(entry.name, parent_id)
1558
2492
                    file_trans_id[file_id] = trans_id
1559
 
                    tt.version_file(entry.file_id, trans_id)
1560
 
                    executable = tree.is_executable(entry.file_id, tree_path)
1561
 
                    if executable is not None:
 
2493
                    tt.version_file(file_id, trans_id)
 
2494
                    executable = tree.is_executable(file_id, tree_path)
 
2495
                    if executable:
1562
2496
                        tt.set_executability(executable, trans_id)
1563
 
                    deferred_contents.append((entry.file_id, trans_id))
 
2497
                    trans_data = (trans_id, tree_path)
 
2498
                    deferred_contents.append((file_id, trans_data))
1564
2499
                else:
1565
2500
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1566
2501
                                                          tree)
1568
2503
                    new_trans_id = file_trans_id[file_id]
1569
2504
                    old_parent = tt.trans_id_tree_path(tree_path)
1570
2505
                    _reparent_children(tt, old_parent, new_trans_id)
1571
 
            for num, (trans_id, bytes) in enumerate(
1572
 
                _iter_files_bytes_accelerated(tree, accelerator_tree,
1573
 
                                              deferred_contents)):
1574
 
                tt.create_file(bytes, trans_id)
1575
 
                pb.update('Adding file contents',
1576
 
                          (num + len(tree.inventory) - len(deferred_contents)),
1577
 
                          len(tree.inventory))
 
2506
            offset = num + 1 - len(deferred_contents)
 
2507
            _create_files(tt, tree, deferred_contents, pb, offset,
 
2508
                          accelerator_tree, hardlink)
1578
2509
        finally:
1579
2510
            pb.finished()
1580
2511
        pp.next_phase()
1581
2512
        divert_trans = set(file_trans_id[f] for f in divert)
1582
2513
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1583
2514
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
 
2515
        if len(raw_conflicts) > 0:
 
2516
            precomputed_delta = None
1584
2517
        conflicts = cook_conflicts(raw_conflicts, tt)
1585
2518
        for conflict in conflicts:
1586
2519
            warning(conflict)
1588
2521
            wt.add_conflicts(conflicts)
1589
2522
        except errors.UnsupportedOperation:
1590
2523
            pass
1591
 
        result = tt.apply(no_conflicts=True)
 
2524
        result = tt.apply(no_conflicts=True,
 
2525
                          precomputed_delta=precomputed_delta)
1592
2526
    finally:
1593
2527
        tt.finalize()
1594
2528
        top_pb.finished()
1595
2529
    return result
1596
2530
 
1597
2531
 
1598
 
def _iter_files_bytes_accelerated(tree, accelerator_tree, desired_files):
 
2532
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
 
2533
                  hardlink):
 
2534
    total = len(desired_files) + offset
 
2535
    wt = tt._tree
1599
2536
    if accelerator_tree is None:
1600
2537
        new_desired_files = desired_files
1601
2538
    else:
1602
 
        iter = accelerator_tree._iter_changes(tree, include_unchanged=True)
1603
 
        unchanged = dict((f, p[1]) for (f, p, c, v, d, n, k, e)
1604
 
                         in iter if not c)
 
2539
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
 
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)
1605
2546
        new_desired_files = []
1606
 
        for file_id, identifier in desired_files:
 
2547
        count = 0
 
2548
        for file_id, (trans_id, tree_path) in desired_files:
1607
2549
            accelerator_path = unchanged.get(file_id)
1608
2550
            if accelerator_path is None:
1609
 
                new_desired_files.append((file_id, identifier))
 
2551
                new_desired_files.append((file_id, (trans_id, tree_path)))
1610
2552
                continue
1611
 
            contents = accelerator_tree.get_file(file_id, accelerator_path)
1612
 
            try:
1613
 
                want_new = False
1614
 
                contents_bytes = (contents.read(),)
1615
 
            finally:
1616
 
                contents.close()
1617
 
            yield identifier, contents_bytes
1618
 
    for result in tree.iter_files_bytes(new_desired_files):
1619
 
        yield result
 
2553
            pb.update('Adding file contents', count + offset, total)
 
2554
            if hardlink:
 
2555
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
 
2556
                                   trans_id)
 
2557
            else:
 
2558
                contents = accelerator_tree.get_file(file_id, accelerator_path)
 
2559
                if wt.supports_content_filtering():
 
2560
                    filters = wt._content_filter_stack(tree_path)
 
2561
                    contents = filtered_output_bytes(contents, filters,
 
2562
                        ContentFilterContext(tree_path, tree))
 
2563
                try:
 
2564
                    tt.create_file(contents, trans_id)
 
2565
                finally:
 
2566
                    try:
 
2567
                        contents.close()
 
2568
                    except AttributeError:
 
2569
                        # after filtering, contents may no longer be file-like
 
2570
                        pass
 
2571
            count += 1
 
2572
        offset += count
 
2573
    for count, ((trans_id, tree_path), contents) in enumerate(
 
2574
            tree.iter_files_bytes(new_desired_files)):
 
2575
        if wt.supports_content_filtering():
 
2576
            filters = wt._content_filter_stack(tree_path)
 
2577
            contents = filtered_output_bytes(contents, filters,
 
2578
                ContentFilterContext(tree_path, tree))
 
2579
        tt.create_file(contents, trans_id)
 
2580
        pb.update('Adding file contents', count + offset, total)
1620
2581
 
1621
2582
 
1622
2583
def _reparent_children(tt, old_parent, new_parent):
1623
2584
    for child in tt.iter_tree_children(old_parent):
1624
2585
        tt.adjust_path(tt.final_name(child), new_parent, child)
1625
2586
 
 
2587
 
1626
2588
def _reparent_transform_children(tt, old_parent, new_parent):
1627
2589
    by_parent = tt.by_parent()
1628
2590
    for child in by_parent[old_parent]:
1629
2591
        tt.adjust_path(tt.final_name(child), new_parent, child)
 
2592
    return by_parent[old_parent]
 
2593
 
1630
2594
 
1631
2595
def _content_match(tree, entry, file_id, kind, target_path):
1632
2596
    if entry.kind != kind:
1634
2598
    if entry.kind == "directory":
1635
2599
        return True
1636
2600
    if entry.kind == "file":
1637
 
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1638
 
            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()
1639
2607
    elif entry.kind == "symlink":
1640
2608
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
1641
2609
            return True
1646
2614
    new_conflicts = set()
1647
2615
    for c_type, conflict in ((c[0], c) for c in conflicts):
1648
2616
        # Anything but a 'duplicate' would indicate programmer error
1649
 
        assert c_type == 'duplicate', c_type
 
2617
        if c_type != 'duplicate':
 
2618
            raise AssertionError(c_type)
1650
2619
        # Now figure out which is new and which is old
1651
2620
        if tt.new_contents(conflict[1]):
1652
2621
            new_file = conflict[1]
1678
2647
    if kind == 'file':
1679
2648
        contents = tree.get_file(entry.file_id).readlines()
1680
2649
        executable = tree.is_executable(entry.file_id)
1681
 
        return tt.new_file(name, parent_id, contents, entry.file_id, 
 
2650
        return tt.new_file(name, parent_id, contents, entry.file_id,
1682
2651
                           executable)
1683
2652
    elif kind in ('directory', 'tree-reference'):
1684
2653
        trans_id = tt.new_directory(name, parent_id, entry.file_id)
1685
2654
        if kind == 'tree-reference':
1686
2655
            tt.set_tree_reference(entry.reference_revision, trans_id)
1687
 
        return trans_id 
 
2656
        return trans_id
1688
2657
    elif kind == 'symlink':
1689
2658
        target = tree.get_symlink_target(entry.file_id)
1690
2659
        return tt.new_symlink(name, parent_id, target, entry.file_id)
1692
2661
        raise errors.BadFileKindError(name, kind)
1693
2662
 
1694
2663
 
1695
 
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1696
 
    """Create new file contents according to an inventory entry."""
1697
 
    if entry.kind == "file":
1698
 
        if lines is None:
1699
 
            lines = tree.get_file(entry.file_id).readlines()
1700
 
        tt.create_file(lines, trans_id, mode_id=mode_id)
1701
 
    elif entry.kind == "symlink":
1702
 
        tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
1703
 
    elif entry.kind == "directory":
 
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.
 
2671
    """
 
2672
    kind = tree.kind(file_id)
 
2673
    if kind == 'directory':
1704
2674
        tt.create_directory(trans_id)
 
2675
    elif kind == "file":
 
2676
        if bytes is None:
 
2677
            tree_file = tree.get_file(file_id)
 
2678
            try:
 
2679
                bytes = tree_file.readlines()
 
2680
            finally:
 
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))
 
2687
        tt.create_file(bytes, trans_id)
 
2688
    elif kind == "symlink":
 
2689
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
 
2690
    else:
 
2691
        raise AssertionError('Unknown kind %r' % kind)
1705
2692
 
1706
2693
 
1707
2694
def create_entry_executability(tt, entry, trans_id):
1710
2697
        tt.set_executability(entry.executable, trans_id)
1711
2698
 
1712
2699
 
1713
 
@deprecated_function(zero_fifteen)
1714
 
def find_interesting(working_tree, target_tree, filenames):
1715
 
    """Find the ids corresponding to specified filenames.
1716
 
    
1717
 
    Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
1718
 
    """
1719
 
    working_tree.lock_read()
1720
 
    try:
1721
 
        target_tree.lock_read()
1722
 
        try:
1723
 
            return working_tree.paths2ids(filenames, [target_tree])
1724
 
        finally:
1725
 
            target_tree.unlock()
1726
 
    finally:
1727
 
        working_tree.unlock()
1728
 
 
1729
 
 
1730
 
@deprecated_function(zero_ninety)
1731
 
def change_entry(tt, file_id, working_tree, target_tree, 
1732
 
                 trans_id_file_id, backups, trans_id, by_parent):
1733
 
    """Replace a file_id's contents with those from a target tree."""
1734
 
    if file_id is None and target_tree is None:
1735
 
        # skip the logic altogether in the deprecation test
1736
 
        return
1737
 
    e_trans_id = trans_id_file_id(file_id)
1738
 
    entry = target_tree.inventory[file_id]
1739
 
    has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry, 
1740
 
                                                           working_tree)
1741
 
    if contents_mod:
1742
 
        mode_id = e_trans_id
1743
 
        if has_contents:
1744
 
            if not backups:
1745
 
                tt.delete_contents(e_trans_id)
1746
 
            else:
1747
 
                parent_trans_id = trans_id_file_id(entry.parent_id)
1748
 
                backup_name = get_backup_name(entry, by_parent,
1749
 
                                              parent_trans_id, tt)
1750
 
                tt.adjust_path(backup_name, parent_trans_id, e_trans_id)
1751
 
                tt.unversion_file(e_trans_id)
1752
 
                e_trans_id = tt.create_path(entry.name, parent_trans_id)
1753
 
                tt.version_file(file_id, e_trans_id)
1754
 
                trans_id[file_id] = e_trans_id
1755
 
        create_by_entry(tt, entry, target_tree, e_trans_id, mode_id=mode_id)
1756
 
        create_entry_executability(tt, entry, e_trans_id)
1757
 
 
1758
 
    elif meta_mod:
1759
 
        tt.set_executability(entry.executable, e_trans_id)
1760
 
    if tt.final_name(e_trans_id) != entry.name:
1761
 
        adjust_path  = True
1762
 
    else:
1763
 
        parent_id = tt.final_parent(e_trans_id)
1764
 
        parent_file_id = tt.final_file_id(parent_id)
1765
 
        if parent_file_id != entry.parent_id:
1766
 
            adjust_path = True
1767
 
        else:
1768
 
            adjust_path = False
1769
 
    if adjust_path:
1770
 
        parent_trans_id = trans_id_file_id(entry.parent_id)
1771
 
        tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
1772
 
 
1773
 
 
 
2700
@deprecated_function(deprecated_in((2, 3, 0)))
1774
2701
def get_backup_name(entry, by_parent, parent_trans_id, tt):
1775
2702
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1776
2703
 
1777
2704
 
 
2705
@deprecated_function(deprecated_in((2, 3, 0)))
1778
2706
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1779
2707
    """Produce a backup-style name that appears to be available"""
1780
2708
    def name_gen():
1807
2735
        if entry.kind != working_kind:
1808
2736
            contents_mod, meta_mod = True, False
1809
2737
        else:
1810
 
            cur_entry._read_tree_state(working_tree.id2path(file_id), 
 
2738
            cur_entry._read_tree_state(working_tree.id2path(file_id),
1811
2739
                                       working_tree)
1812
2740
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
1813
2741
            cur_entry._forget_tree_state()
1815
2743
 
1816
2744
 
1817
2745
def revert(working_tree, target_tree, filenames, backups=False,
1818
 
           pb=DummyProgress(), change_reporter=None):
 
2746
           pb=None, change_reporter=None):
1819
2747
    """Revert a working tree's contents to those of a target tree."""
1820
2748
    target_tree.lock_read()
 
2749
    pb = ui.ui_factory.nested_progress_bar()
1821
2750
    tt = TreeTransform(working_tree, pb)
1822
2751
    try:
1823
2752
        pp = ProgressPhase("Revert phase", 3, pb)
1824
 
        pp.next_phase()
1825
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1826
 
        try:
1827
 
            merge_modified = _alter_files(working_tree, target_tree, tt,
1828
 
                                          child_pb, filenames, backups)
1829
 
        finally:
1830
 
            child_pb.finished()
1831
 
        pp.next_phase()
1832
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1833
 
        try:
1834
 
            raw_conflicts = resolve_conflicts(tt, child_pb,
1835
 
                lambda t, c: conflict_pass(t, c, target_tree))
1836
 
        finally:
1837
 
            child_pb.finished()
1838
 
        conflicts = cook_conflicts(raw_conflicts, tt)
 
2753
        conflicts, merge_modified = _prepare_revert_transform(
 
2754
            working_tree, target_tree, tt, filenames, backups, pp)
1839
2755
        if change_reporter:
1840
2756
            change_reporter = delta._ChangeReporter(
1841
2757
                unversioned_filter=working_tree.is_ignored)
1842
 
            delta.report_changes(tt._iter_changes(), change_reporter)
 
2758
            delta.report_changes(tt.iter_changes(), change_reporter)
1843
2759
        for conflict in conflicts:
1844
2760
            warning(conflict)
1845
2761
        pp.next_phase()
1852
2768
    return conflicts
1853
2769
 
1854
2770
 
 
2771
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
 
2772
                              backups, pp, basis_tree=None,
 
2773
                              merge_modified=None):
 
2774
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2775
    try:
 
2776
        if merge_modified is None:
 
2777
            merge_modified = working_tree.merge_modified()
 
2778
        merge_modified = _alter_files(working_tree, target_tree, tt,
 
2779
                                      child_pb, filenames, backups,
 
2780
                                      merge_modified, basis_tree)
 
2781
    finally:
 
2782
        child_pb.finished()
 
2783
    child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2784
    try:
 
2785
        raw_conflicts = resolve_conflicts(tt, child_pb,
 
2786
            lambda t, c: conflict_pass(t, c, target_tree))
 
2787
    finally:
 
2788
        child_pb.finished()
 
2789
    conflicts = cook_conflicts(raw_conflicts, tt)
 
2790
    return conflicts, merge_modified
 
2791
 
 
2792
 
1855
2793
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1856
 
                 backups):
1857
 
    merge_modified = working_tree.merge_modified()
1858
 
    change_list = target_tree._iter_changes(working_tree,
 
2794
                 backups, merge_modified, basis_tree=None):
 
2795
    if basis_tree is not None:
 
2796
        basis_tree.lock_read()
 
2797
    change_list = target_tree.iter_changes(working_tree,
1859
2798
        specific_files=specific_files, pb=pb)
1860
 
    if target_tree.inventory.root is None:
 
2799
    if target_tree.get_root_id() is None:
1861
2800
        skip_root = True
1862
2801
    else:
1863
2802
        skip_root = False
1864
 
    basis_tree = None
1865
2803
    try:
1866
2804
        deferred_files = []
1867
2805
        for id_num, (file_id, path, changed_content, versioned, parent, name,
1891
2829
                        tt.delete_contents(trans_id)
1892
2830
                    elif kind[1] is not None:
1893
2831
                        parent_trans_id = tt.trans_id_file_id(parent[0])
1894
 
                        by_parent = tt.by_parent()
1895
 
                        backup_name = _get_backup_name(name[0], by_parent,
1896
 
                                                       parent_trans_id, tt)
 
2832
                        backup_name = tt._available_backup_name(
 
2833
                            name[0], parent_trans_id)
1897
2834
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
1898
2835
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
1899
2836
                        if versioned == (True, True):
1903
2840
                        # contents
1904
2841
                        mode_id = trans_id
1905
2842
                        trans_id = new_trans_id
1906
 
                if kind[1] == 'directory':
 
2843
                if kind[1] in ('directory', 'tree-reference'):
1907
2844
                    tt.create_directory(trans_id)
 
2845
                    if kind[1] == 'tree-reference':
 
2846
                        revision = target_tree.get_reference_revision(file_id,
 
2847
                                                                      path[1])
 
2848
                        tt.set_tree_reference(revision, trans_id)
1908
2849
                elif kind[1] == 'symlink':
1909
2850
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
1910
2851
                                      trans_id)
1924
2865
                    # preserve the execute bit when backing up
1925
2866
                    if keep_content and executable[0] == executable[1]:
1926
2867
                        tt.set_executability(executable[1], trans_id)
1927
 
                else:
1928
 
                    assert kind[1] is None
 
2868
                elif kind[1] is not None:
 
2869
                    raise AssertionError(kind[1])
1929
2870
            if versioned == (False, True):
1930
2871
                tt.version_file(file_id, trans_id)
1931
2872
            if versioned == (True, False):
1932
2873
                tt.unversion_file(trans_id)
1933
 
            if (name[1] is not None and 
 
2874
            if (name[1] is not None and
1934
2875
                (name[0] != name[1] or parent[0] != parent[1])):
1935
 
                tt.adjust_path(
1936
 
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
 
2876
                if name[1] == '' and parent[1] is None:
 
2877
                    parent_trans = ROOT_PARENT
 
2878
                else:
 
2879
                    parent_trans = tt.trans_id_file_id(parent[1])
 
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)
1937
2884
            if executable[0] != executable[1] and kind[1] == "file":
1938
2885
                tt.set_executability(executable[1], trans_id)
1939
 
        for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
1940
 
            deferred_files):
1941
 
            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()
1942
2903
    finally:
1943
2904
        if basis_tree is not None:
1944
2905
            basis_tree.unlock()
1945
2906
    return merge_modified
1946
2907
 
1947
2908
 
1948
 
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
 
2909
def resolve_conflicts(tt, pb=None, pass_func=None):
1949
2910
    """Make many conflict-resolution attempts, but die if they fail"""
1950
2911
    if pass_func is None:
1951
2912
        pass_func = conflict_pass
1952
2913
    new_conflicts = set()
 
2914
    pb = ui.ui_factory.nested_progress_bar()
1953
2915
    try:
1954
2916
        for n in range(10):
1955
2917
            pb.update('Resolution pass', n+1, 10)
1959
2921
            new_conflicts.update(pass_func(tt, conflicts))
1960
2922
        raise MalformedTransform(conflicts=conflicts)
1961
2923
    finally:
1962
 
        pb.clear()
 
2924
        pb.finished()
1963
2925
 
1964
2926
 
1965
2927
def conflict_pass(tt, conflicts, path_tree=None):
1984
2946
                existing_file, new_file = conflict[1], conflict[2]
1985
2947
            new_name = tt.final_name(existing_file)+'.moved'
1986
2948
            tt.adjust_path(new_name, final_parent, existing_file)
1987
 
            new_conflicts.add((c_type, 'Moved existing file to', 
 
2949
            new_conflicts.add((c_type, 'Moved existing file to',
1988
2950
                               existing_file, new_file))
1989
2951
        elif c_type == 'parent loop':
1990
2952
            # break the loop by undoing one of the ops that caused the loop
1994
2956
            new_conflicts.add((c_type, 'Cancelled move', cur,
1995
2957
                               tt.final_parent(cur),))
1996
2958
            tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
1997
 
            
 
2959
 
1998
2960
        elif c_type == 'missing parent':
1999
2961
            trans_id = conflict[1]
2000
 
            try:
2001
 
                tt.cancel_deletion(trans_id)
2002
 
                new_conflicts.add(('deleting parent', 'Not deleting', 
2003
 
                                   trans_id))
2004
 
            except KeyError:
2005
 
                tt.create_directory(trans_id)
2006
 
                new_conflicts.add((c_type, 'Created directory', trans_id))
 
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:
 
2986
                create = True
2007
2987
                try:
2008
2988
                    tt.final_name(trans_id)
2009
2989
                except NoFinalPath:
2010
2990
                    if path_tree is not None:
2011
2991
                        file_id = tt.final_file_id(trans_id)
 
2992
                        if file_id is None:
 
2993
                            file_id = tt.inactive_file_id(trans_id)
2012
2994
                        entry = path_tree.inventory[file_id]
2013
 
                        parent_trans_id = tt.trans_id_file_id(entry.parent_id)
2014
 
                        tt.adjust_path(entry.name, parent_trans_id, trans_id)
 
2995
                        # special-case the other tree root (move its
 
2996
                        # children to current root)
 
2997
                        if entry.parent_id is None:
 
2998
                            create = False
 
2999
                            moved = _reparent_transform_children(
 
3000
                                tt, trans_id, tt.root)
 
3001
                            for child in moved:
 
3002
                                new_conflicts.add((c_type, 'Moved to root',
 
3003
                                                   child))
 
3004
                        else:
 
3005
                            parent_trans_id = tt.trans_id_file_id(
 
3006
                                entry.parent_id)
 
3007
                            tt.adjust_path(entry.name, parent_trans_id,
 
3008
                                           trans_id)
 
3009
                if create:
 
3010
                    tt.create_directory(trans_id)
 
3011
                    new_conflicts.add((c_type, 'Created directory', trans_id))
2015
3012
        elif c_type == 'unversioned parent':
2016
 
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
 
3013
            file_id = tt.inactive_file_id(conflict[1])
 
3014
            # special-case the other tree root (move its children instead)
 
3015
            if path_tree and file_id in path_tree:
 
3016
                if path_tree.inventory[file_id].parent_id is None:
 
3017
                    continue
 
3018
            tt.version_file(file_id, conflict[1])
2017
3019
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
2018
3020
        elif c_type == 'non-directory parent':
2019
3021
            parent_id = conflict[1]
2023
3025
            new_parent_id = tt.new_directory(parent_name + '.new',
2024
3026
                parent_parent, parent_file_id)
2025
3027
            _reparent_transform_children(tt, parent_id, new_parent_id)
2026
 
            tt.unversion_file(parent_id)
 
3028
            if parent_file_id is not None:
 
3029
                tt.unversion_file(parent_id)
2027
3030
            new_conflicts.add((c_type, 'Created directory', new_parent_id))
 
3031
        elif c_type == 'versioning no contents':
 
3032
            tt.cancel_versioning(conflict[1])
2028
3033
    return new_conflicts
2029
3034
 
2030
3035
 
2046
3051
        if len(conflict) == 3:
2047
3052
            yield Conflict.factory(c_type, action=action, path=modified_path,
2048
3053
                                     file_id=modified_id)
2049
 
             
 
3054
 
2050
3055
        else:
2051
3056
            conflicting_path = fp.get_path(conflict[3])
2052
3057
            conflicting_id = tt.final_file_id(conflict[3])
2053
3058
            yield Conflict.factory(c_type, action=action, path=modified_path,
2054
 
                                   file_id=modified_id, 
 
3059
                                   file_id=modified_id,
2055
3060
                                   conflict_path=conflicting_path,
2056
3061
                                   conflict_file_id=conflicting_id)
2057
3062
 
2064
3069
        self.pending_deletions = []
2065
3070
 
2066
3071
    def rename(self, from_, to):
2067
 
        """Rename a file from one path to another.  Functions like os.rename"""
 
3072
        """Rename a file from one path to another."""
2068
3073
        try:
2069
3074
            os.rename(from_, to)
2070
3075
        except OSError, e:
2071
3076
            if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
2072
3077
                raise errors.FileExists(to, str(e))
2073
 
            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)
2074
3081
        self.past_renames.append((from_, to))
2075
3082
 
2076
3083
    def pre_delete(self, from_, to):
2086
3093
    def rollback(self):
2087
3094
        """Reverse all renames that have been performed"""
2088
3095
        for from_, to in reversed(self.past_renames):
2089
 
            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)
2090
3100
        # after rollback, don't reuse _FileMover
2091
3101
        past_renames = None
2092
3102
        pending_deletions = None