/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-06-29 17:19:58 UTC
  • mfrom: (5967.8.4 rm-del-methods)
  • Revision ID: pqm@pqm.ubuntu.com-20110629171958-u8a6lkpw5kdnfm55
(mbp) remove most __del__ methods (Martin Pool)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
2
 
 
 
1
# Copyright (C) 2006-2011 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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
 
19
from stat import S_ISREG, S_IEXEC
 
20
import time
20
21
 
 
22
from bzrlib import (
 
23
    errors,
 
24
    lazy_import,
 
25
    registry,
 
26
    trace,
 
27
    tree,
 
28
    )
 
29
lazy_import.lazy_import(globals(), """
 
30
from bzrlib import (
 
31
    annotate,
 
32
    bencode,
 
33
    bzrdir,
 
34
    commit,
 
35
    conflicts,
 
36
    delta,
 
37
    errors,
 
38
    inventory,
 
39
    multiparent,
 
40
    osutils,
 
41
    revision as _mod_revision,
 
42
    ui,
 
43
    urlutils,
 
44
    )
 
45
""")
21
46
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
22
 
                           ReusingTransform, NotVersionedError, CantMoveRoot,
23
 
                           ExistingLimbo, ImmortalLimbo)
24
 
from bzrlib.inventory import InventoryEntry
25
 
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
26
 
                            delete_any)
27
 
from bzrlib.progress import DummyProgress, ProgressPhase
28
 
from bzrlib.trace import mutter, warning
29
 
import bzrlib.ui 
 
47
                           ReusingTransform, CantMoveRoot,
 
48
                           ExistingLimbo, ImmortalLimbo, NoFinalPath,
 
49
                           UnableCreateSymlink)
 
50
from bzrlib.filters import filtered_output_bytes, ContentFilterContext
 
51
from bzrlib.osutils import (
 
52
    delete_any,
 
53
    file_kind,
 
54
    has_symlinks,
 
55
    pathjoin,
 
56
    sha_file,
 
57
    splitpath,
 
58
    supports_executable,
 
59
    )
 
60
from bzrlib.progress import ProgressPhase
 
61
from bzrlib.symbol_versioning import (
 
62
    deprecated_function,
 
63
    deprecated_in,
 
64
    deprecated_method,
 
65
    )
30
66
 
31
67
 
32
68
ROOT_PARENT = "root-parent"
33
69
 
34
 
 
35
70
def unique_add(map, key, value):
36
71
    if key in map:
37
72
        raise DuplicateKey(key=key)
38
73
    map[key] = value
39
74
 
40
75
 
 
76
 
41
77
class _TransformResults(object):
42
 
    def __init__(self, modified_paths):
 
78
    def __init__(self, modified_paths, rename_count):
43
79
        object.__init__(self)
44
80
        self.modified_paths = modified_paths
45
 
 
46
 
 
47
 
class TreeTransform(object):
48
 
    """Represent a tree transformation.
49
 
    
50
 
    This object is designed to support incremental generation of the transform,
51
 
    in any order.  
52
 
    
53
 
    It is easy to produce malformed transforms, but they are generally
54
 
    harmless.  Attempting to apply a malformed transform will cause an
55
 
    exception to be raised before any modifications are made to the tree.  
56
 
 
57
 
    Many kinds of malformed transforms can be corrected with the 
58
 
    resolve_conflicts function.  The remaining ones indicate programming error,
59
 
    such as trying to create a file with no path.
60
 
 
61
 
    Two sets of file creation methods are supplied.  Convenience methods are:
62
 
     * new_file
63
 
     * new_directory
64
 
     * new_symlink
65
 
 
66
 
    These are composed of the low-level methods:
67
 
     * create_path
68
 
     * create_file or create_directory or create_symlink
69
 
     * version_file
70
 
     * set_executability
71
 
    """
72
 
    def __init__(self, tree, pb=DummyProgress()):
73
 
        """Note: a write lock is taken on the tree.
74
 
        
75
 
        Use TreeTransform.finalize() to release the lock
 
81
        self.rename_count = rename_count
 
82
 
 
83
 
 
84
class TreeTransformBase(object):
 
85
    """The base class for TreeTransform and its kin."""
 
86
 
 
87
    def __init__(self, tree, pb=None,
 
88
                 case_sensitive=True):
 
89
        """Constructor.
 
90
 
 
91
        :param tree: The tree that will be transformed, but not necessarily
 
92
            the output tree.
 
93
        :param pb: ignored
 
94
        :param case_sensitive: If True, the target of the transform is
 
95
            case sensitive, not just case preserving.
76
96
        """
77
97
        object.__init__(self)
78
98
        self._tree = tree
79
 
        self._tree.lock_write()
80
 
        try:
81
 
            control_files = self._tree._control_files
82
 
            self._limbodir = control_files.controlfilename('limbo')
83
 
            try:
84
 
                os.mkdir(self._limbodir)
85
 
            except OSError, e:
86
 
                if e.errno == errno.EEXIST:
87
 
                    raise ExistingLimbo(self._limbodir)
88
 
        except: 
89
 
            self._tree.unlock()
90
 
            raise
91
 
 
92
99
        self._id_number = 0
 
100
        # mapping of trans_id -> new basename
93
101
        self._new_name = {}
 
102
        # mapping of trans_id -> new parent trans_id
94
103
        self._new_parent = {}
 
104
        # mapping of trans_id with new contents -> new file_kind
95
105
        self._new_contents = {}
 
106
        # mapping of trans_id => (sha1 of content, stat_value)
 
107
        self._observed_sha1s = {}
 
108
        # Set of trans_ids whose contents will be removed
96
109
        self._removed_contents = set()
 
110
        # Mapping of trans_id -> new execute-bit value
97
111
        self._new_executability = {}
 
112
        # Mapping of trans_id -> new tree-reference value
 
113
        self._new_reference_revision = {}
 
114
        # Mapping of trans_id -> new file_id
98
115
        self._new_id = {}
 
116
        # Mapping of old file-id -> trans_id
99
117
        self._non_present_ids = {}
 
118
        # Mapping of new file_id -> trans_id
100
119
        self._r_new_id = {}
 
120
        # Set of trans_ids that will be removed
101
121
        self._removed_id = set()
 
122
        # Mapping of path in old tree -> trans_id
102
123
        self._tree_path_ids = {}
 
124
        # Mapping trans_id -> path in old tree
103
125
        self._tree_id_paths = {}
104
 
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
105
 
        self.__done = False
 
126
        # The trans_id that will be used as the tree root
 
127
        root_id = tree.get_root_id()
 
128
        if root_id is not None:
 
129
            self._new_root = self.trans_id_tree_file_id(root_id)
 
130
        else:
 
131
            self._new_root = None
 
132
        # Indicator of whether the transform has been applied
 
133
        self._done = False
 
134
        # A progress bar
106
135
        self._pb = pb
 
136
        # Whether the target is case sensitive
 
137
        self._case_sensitive_target = case_sensitive
 
138
        # A counter of how many files have been renamed
 
139
        self.rename_count = 0
 
140
 
 
141
    def __enter__(self):
 
142
        """Support Context Manager API."""
 
143
        return self
 
144
 
 
145
    def __exit__(self, exc_type, exc_val, exc_tb):
 
146
        """Support Context Manager API."""
 
147
        self.finalize()
 
148
 
 
149
    def finalize(self):
 
150
        """Release the working tree lock, if held.
 
151
 
 
152
        This is required if apply has not been invoked, but can be invoked
 
153
        even after apply.
 
154
        """
 
155
        if self._tree is None:
 
156
            return
 
157
        self._tree.unlock()
 
158
        self._tree = None
107
159
 
108
160
    def __get_root(self):
109
161
        return self._new_root
110
162
 
111
163
    root = property(__get_root)
112
164
 
113
 
    def finalize(self):
114
 
        """Release the working tree lock, if held, clean up limbo dir."""
115
 
        if self._tree is None:
116
 
            return
117
 
        try:
118
 
            for trans_id, kind in self._new_contents.iteritems():
119
 
                path = self._limbo_name(trans_id)
120
 
                if kind == "directory":
121
 
                    os.rmdir(path)
122
 
                else:
123
 
                    os.unlink(path)
124
 
            try:
125
 
                os.rmdir(self._limbodir)
126
 
            except OSError:
127
 
                # We don't especially care *why* the dir is immortal.
128
 
                raise ImmortalLimbo(self._limbodir)
129
 
        finally:
130
 
            self._tree.unlock()
131
 
            self._tree = None
132
 
 
133
165
    def _assign_id(self):
134
166
        """Produce a new tranform id"""
135
167
        new_id = "new-%s" % self._id_number
145
177
 
146
178
    def adjust_path(self, name, parent, trans_id):
147
179
        """Change the path that is assigned to a transaction id."""
 
180
        if parent is None:
 
181
            raise ValueError("Parent trans-id may not be None")
148
182
        if trans_id == self._new_root:
149
183
            raise CantMoveRoot
150
184
        self._new_name[trans_id] = name
152
186
 
153
187
    def adjust_root_path(self, name, parent):
154
188
        """Emulate moving the root by moving all children, instead.
155
 
        
 
189
 
156
190
        We do this by undoing the association of root's transaction id with the
157
191
        current tree.  This allows us to create a new directory with that
158
 
        transaction id.  We unversion the root directory and version the 
 
192
        transaction id.  We unversion the root directory and version the
159
193
        physically new directory, and hope someone versions the tree root
160
194
        later.
161
195
        """
164
198
        # force moving all children of root
165
199
        for child_id in self.iter_tree_children(old_root):
166
200
            if child_id != parent:
167
 
                self.adjust_path(self.final_name(child_id), 
 
201
                self.adjust_path(self.final_name(child_id),
168
202
                                 self.final_parent(child_id), child_id)
169
203
            file_id = self.final_file_id(child_id)
170
204
            if file_id is not None:
171
205
                self.unversion_file(child_id)
172
206
            self.version_file(file_id, child_id)
173
 
        
 
207
 
174
208
        # the physical root needs a new transaction id
175
209
        self._tree_path_ids.pop("")
176
210
        self._tree_id_paths.pop(old_root)
182
216
        self.version_file(old_root_file_id, old_root)
183
217
        self.unversion_file(self._new_root)
184
218
 
 
219
    def fixup_new_roots(self):
 
220
        """Reinterpret requests to change the root directory
 
221
 
 
222
        Instead of creating a root directory, or moving an existing directory,
 
223
        all the attributes and children of the new root are applied to the
 
224
        existing root directory.
 
225
 
 
226
        This means that the old root trans-id becomes obsolete, so it is
 
227
        recommended only to invoke this after the root trans-id has become
 
228
        irrelevant.
 
229
 
 
230
        """
 
231
        new_roots = [k for k, v in self._new_parent.iteritems() if v is
 
232
                     ROOT_PARENT]
 
233
        if len(new_roots) < 1:
 
234
            if self.final_kind(self.root) is None:
 
235
                self.cancel_deletion(self.root)
 
236
            if self.final_file_id(self.root) is None:
 
237
                self.version_file(self.tree_file_id(self.root),
 
238
                                     self.root)
 
239
            return
 
240
        if len(new_roots) != 1:
 
241
            raise ValueError('A tree cannot have two roots!')
 
242
        if self._new_root is None:
 
243
            self._new_root = new_roots[0]
 
244
            return
 
245
        old_new_root = new_roots[0]
 
246
        # TODO: What to do if a old_new_root is present, but self._new_root is
 
247
        #       not listed as being removed? This code explicitly unversions
 
248
        #       the old root and versions it with the new file_id. Though that
 
249
        #       seems like an incomplete delta
 
250
 
 
251
        # unversion the new root's directory.
 
252
        file_id = self.final_file_id(old_new_root)
 
253
        if old_new_root in self._new_id:
 
254
            self.cancel_versioning(old_new_root)
 
255
        else:
 
256
            self.unversion_file(old_new_root)
 
257
        # if, at this stage, root still has an old file_id, zap it so we can
 
258
        # stick a new one in.
 
259
        if (self.tree_file_id(self._new_root) is not None and
 
260
            self._new_root not in self._removed_id):
 
261
            self.unversion_file(self._new_root)
 
262
        if file_id is not None:
 
263
            self.version_file(file_id, self._new_root)
 
264
 
 
265
        # Now move children of new root into old root directory.
 
266
        # Ensure all children are registered with the transaction, but don't
 
267
        # use directly-- some tree children have new parents
 
268
        list(self.iter_tree_children(old_new_root))
 
269
        # Move all children of new root into old root directory.
 
270
        for child in self.by_parent().get(old_new_root, []):
 
271
            self.adjust_path(self.final_name(child), self._new_root, child)
 
272
 
 
273
        # Ensure old_new_root has no directory.
 
274
        if old_new_root in self._new_contents:
 
275
            self.cancel_creation(old_new_root)
 
276
        else:
 
277
            self.delete_contents(old_new_root)
 
278
 
 
279
        # prevent deletion of root directory.
 
280
        if self._new_root in self._removed_contents:
 
281
            self.cancel_deletion(self._new_root)
 
282
 
 
283
        # destroy path info for old_new_root.
 
284
        del self._new_parent[old_new_root]
 
285
        del self._new_name[old_new_root]
 
286
 
185
287
    def trans_id_tree_file_id(self, inventory_id):
186
288
        """Determine the transaction id of a working tree file.
187
 
        
 
289
 
188
290
        This reflects only files that already exist, not ones that will be
189
291
        added by transactions.
190
292
        """
191
 
        path = self._tree.inventory.id2path(inventory_id)
 
293
        if inventory_id is None:
 
294
            raise ValueError('None is not a valid file id')
 
295
        path = self._tree.id2path(inventory_id)
192
296
        return self.trans_id_tree_path(path)
193
297
 
194
298
    def trans_id_file_id(self, file_id):
197
301
        a transaction has been unversioned, it is deliberately still returned.
198
302
        (this will likely lead to an unversioned parent conflict.)
199
303
        """
 
304
        if file_id is None:
 
305
            raise ValueError('None is not a valid file id')
200
306
        if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
201
307
            return self._r_new_id[file_id]
202
 
        elif file_id in self._tree.inventory:
203
 
            return self.trans_id_tree_file_id(file_id)
204
 
        elif file_id in self._non_present_ids:
205
 
            return self._non_present_ids[file_id]
206
308
        else:
207
 
            trans_id = self._assign_id()
208
 
            self._non_present_ids[file_id] = trans_id
209
 
            return trans_id
210
 
 
211
 
    def canonical_path(self, path):
212
 
        """Get the canonical tree-relative path"""
213
 
        # don't follow final symlinks
214
 
        dirname, basename = os.path.split(self._tree.abspath(path))
215
 
        dirname = os.path.realpath(dirname)
216
 
        return self._tree.relpath(pathjoin(dirname, basename))
 
309
            try:
 
310
                self._tree.iter_entries_by_dir([file_id]).next()
 
311
            except StopIteration:
 
312
                if file_id in self._non_present_ids:
 
313
                    return self._non_present_ids[file_id]
 
314
                else:
 
315
                    trans_id = self._assign_id()
 
316
                    self._non_present_ids[file_id] = trans_id
 
317
                    return trans_id
 
318
            else:
 
319
                return self.trans_id_tree_file_id(file_id)
217
320
 
218
321
    def trans_id_tree_path(self, path):
219
322
        """Determine (and maybe set) the transaction ID for a tree path."""
230
333
            return ROOT_PARENT
231
334
        return self.trans_id_tree_path(os.path.dirname(path))
232
335
 
233
 
    def create_file(self, contents, trans_id, mode_id=None):
234
 
        """Schedule creation of a new file.
235
 
 
236
 
        See also new_file.
237
 
        
238
 
        Contents is an iterator of strings, all of which will be written
239
 
        to the target destination.
240
 
 
241
 
        New file takes the permissions of any existing file with that id,
242
 
        unless mode_id is specified.
243
 
        """
244
 
        f = file(self._limbo_name(trans_id), 'wb')
245
 
        unique_add(self._new_contents, trans_id, 'file')
246
 
        for segment in contents:
247
 
            f.write(segment)
248
 
        f.close()
249
 
        self._set_mode(trans_id, mode_id, S_ISREG)
250
 
 
251
 
    def _set_mode(self, trans_id, mode_id, typefunc):
252
 
        """Set the mode of new file contents.
253
 
        The mode_id is the existing file to get the mode from (often the same
254
 
        as trans_id).  The operation is only performed if there's a mode match
255
 
        according to typefunc.
256
 
        """
257
 
        if mode_id is None:
258
 
            mode_id = trans_id
259
 
        try:
260
 
            old_path = self._tree_id_paths[mode_id]
261
 
        except KeyError:
262
 
            return
263
 
        try:
264
 
            mode = os.stat(old_path).st_mode
265
 
        except OSError, e:
266
 
            if e.errno == errno.ENOENT:
267
 
                return
268
 
            else:
269
 
                raise
270
 
        if typefunc(mode):
271
 
            os.chmod(self._limbo_name(trans_id), mode)
272
 
 
273
 
    def create_directory(self, trans_id):
274
 
        """Schedule creation of a new directory.
275
 
        
276
 
        See also new_directory.
277
 
        """
278
 
        os.mkdir(self._limbo_name(trans_id))
279
 
        unique_add(self._new_contents, trans_id, 'directory')
280
 
 
281
 
    def create_symlink(self, target, trans_id):
282
 
        """Schedule creation of a new symbolic link.
283
 
 
284
 
        target is a bytestring.
285
 
        See also new_symlink.
286
 
        """
287
 
        os.symlink(target, self._limbo_name(trans_id))
288
 
        unique_add(self._new_contents, trans_id, 'symlink')
289
 
 
290
 
    def cancel_creation(self, trans_id):
291
 
        """Cancel the creation of new file contents."""
292
 
        del self._new_contents[trans_id]
293
 
        delete_any(self._limbo_name(trans_id))
294
 
 
295
336
    def delete_contents(self, trans_id):
296
337
        """Schedule the contents of a path entry for deletion"""
297
 
        self.tree_kind(trans_id)
298
 
        self._removed_contents.add(trans_id)
 
338
        kind = self.tree_kind(trans_id)
 
339
        if kind is not None:
 
340
            self._removed_contents.add(trans_id)
299
341
 
300
342
    def cancel_deletion(self, trans_id):
301
343
        """Cancel a scheduled deletion"""
319
361
        else:
320
362
            unique_add(self._new_executability, trans_id, executability)
321
363
 
 
364
    def set_tree_reference(self, revision_id, trans_id):
 
365
        """Set the reference associated with a directory"""
 
366
        unique_add(self._new_reference_revision, trans_id, revision_id)
 
367
 
322
368
    def version_file(self, file_id, trans_id):
323
369
        """Schedule a file to become versioned."""
324
 
        assert file_id is not None
 
370
        if file_id is None:
 
371
            raise ValueError()
325
372
        unique_add(self._new_id, trans_id, file_id)
326
373
        unique_add(self._r_new_id, file_id, trans_id)
327
374
 
331
378
        del self._new_id[trans_id]
332
379
        del self._r_new_id[file_id]
333
380
 
334
 
    def new_paths(self):
335
 
        """Determine the paths of all new and changed files"""
 
381
    def new_paths(self, filesystem_only=False):
 
382
        """Determine the paths of all new and changed files.
 
383
 
 
384
        :param filesystem_only: if True, only calculate values for files
 
385
            that require renames or execute bit changes.
 
386
        """
336
387
        new_ids = set()
337
 
        fp = FinalPaths(self)
338
 
        for id_set in (self._new_name, self._new_parent, self._new_contents,
339
 
                       self._new_id, self._new_executability):
 
388
        if filesystem_only:
 
389
            stale_ids = self._needs_rename.difference(self._new_name)
 
390
            stale_ids.difference_update(self._new_parent)
 
391
            stale_ids.difference_update(self._new_contents)
 
392
            stale_ids.difference_update(self._new_id)
 
393
            needs_rename = self._needs_rename.difference(stale_ids)
 
394
            id_sets = (needs_rename, self._new_executability)
 
395
        else:
 
396
            id_sets = (self._new_name, self._new_parent, self._new_contents,
 
397
                       self._new_id, self._new_executability)
 
398
        for id_set in id_sets:
340
399
            new_ids.update(id_set)
341
 
        new_paths = [(fp.get_path(t), t) for t in new_ids]
342
 
        new_paths.sort()
343
 
        return new_paths
344
 
 
345
 
    def tree_kind(self, trans_id):
346
 
        """Determine the file kind in the working tree.
347
 
 
348
 
        Raises NoSuchFile if the file does not exist
 
400
        return sorted(FinalPaths(self).get_paths(new_ids))
 
401
 
 
402
    def _inventory_altered(self):
 
403
        """Determine which trans_ids need new Inventory entries.
 
404
 
 
405
        An new entry is needed when anything that would be reflected by an
 
406
        inventory entry changes, including file name, file_id, parent file_id,
 
407
        file kind, and the execute bit.
 
408
 
 
409
        Some care is taken to return entries with real changes, not cases
 
410
        where the value is deleted and then restored to its original value,
 
411
        but some actually unchanged values may be returned.
 
412
 
 
413
        :returns: A list of (path, trans_id) for all items requiring an
 
414
            inventory change. Ordered by path.
349
415
        """
350
 
        path = self._tree_id_paths.get(trans_id)
351
 
        if path is None:
352
 
            raise NoSuchFile(None)
353
 
        try:
354
 
            return file_kind(self._tree.abspath(path))
355
 
        except OSError, e:
356
 
            if e.errno != errno.ENOENT:
357
 
                raise
358
 
            else:
359
 
                raise NoSuchFile(path)
 
416
        changed_ids = set()
 
417
        # Find entries whose file_ids are new (or changed).
 
418
        new_file_id = set(t for t in self._new_id
 
419
                          if self._new_id[t] != self.tree_file_id(t))
 
420
        for id_set in [self._new_name, self._new_parent, new_file_id,
 
421
                       self._new_executability]:
 
422
            changed_ids.update(id_set)
 
423
        # removing implies a kind change
 
424
        changed_kind = set(self._removed_contents)
 
425
        # so does adding
 
426
        changed_kind.intersection_update(self._new_contents)
 
427
        # Ignore entries that are already known to have changed.
 
428
        changed_kind.difference_update(changed_ids)
 
429
        #  to keep only the truly changed ones
 
430
        changed_kind = (t for t in changed_kind
 
431
                        if self.tree_kind(t) != self.final_kind(t))
 
432
        # all kind changes will alter the inventory
 
433
        changed_ids.update(changed_kind)
 
434
        # To find entries with changed parent_ids, find parents which existed,
 
435
        # but changed file_id.
 
436
        changed_file_id = set(t for t in new_file_id if t in self._removed_id)
 
437
        # Now add all their children to the set.
 
438
        for parent_trans_id in new_file_id:
 
439
            changed_ids.update(self.iter_tree_children(parent_trans_id))
 
440
        return sorted(FinalPaths(self).get_paths(changed_ids))
360
441
 
361
442
    def final_kind(self, trans_id):
362
443
        """Determine the final file kind, after any changes applied.
363
 
        
364
 
        Raises NoSuchFile if the file does not exist/has no contents.
365
 
        (It is conceivable that a path would be created without the
366
 
        corresponding contents insertion command)
 
444
 
 
445
        :return: None if the file does not exist/has no contents.  (It is
 
446
            conceivable that a path would be created without the corresponding
 
447
            contents insertion command)
367
448
        """
368
449
        if trans_id in self._new_contents:
369
450
            return self._new_contents[trans_id]
370
451
        elif trans_id in self._removed_contents:
371
 
            raise NoSuchFile(None)
 
452
            return None
372
453
        else:
373
454
            return self.tree_kind(trans_id)
374
455
 
381
462
            return None
382
463
        # the file is old; the old id is still valid
383
464
        if self._new_root == trans_id:
384
 
            return self._tree.inventory.root.file_id
385
 
        return self._tree.inventory.path2id(path)
 
465
            return self._tree.get_root_id()
 
466
        return self._tree.path2id(path)
386
467
 
387
468
    def final_file_id(self, trans_id):
388
469
        """Determine the file id after any changes are applied, or None.
389
 
        
 
470
 
390
471
        None indicates that the file will not be versioned after changes are
391
472
        applied.
392
473
        """
393
474
        try:
394
 
            # there is a new id for this file
395
 
            assert self._new_id[trans_id] is not None
396
475
            return self._new_id[trans_id]
397
476
        except KeyError:
398
477
            if trans_id in self._removed_id:
426
505
        try:
427
506
            return self._new_name[trans_id]
428
507
        except KeyError:
429
 
            return os.path.basename(self._tree_id_paths[trans_id])
 
508
            try:
 
509
                return os.path.basename(self._tree_id_paths[trans_id])
 
510
            except KeyError:
 
511
                raise NoFinalPath(trans_id, self)
430
512
 
431
513
    def by_parent(self):
432
514
        """Return a map of parent: children for known parents.
433
 
        
 
515
 
434
516
        Only new paths and parents of tree files with assigned ids are used.
435
517
        """
436
518
        by_parent = {}
437
519
        items = list(self._new_parent.iteritems())
438
 
        items.extend((t, self.final_parent(t)) for t in 
 
520
        items.extend((t, self.final_parent(t)) for t in
439
521
                      self._tree_id_paths.keys())
440
522
        for trans_id, parent_id in items:
441
523
            if parent_id not in by_parent:
445
527
 
446
528
    def path_changed(self, trans_id):
447
529
        """Return True if a trans_id's path has changed."""
448
 
        return trans_id in self._new_name or trans_id in self._new_parent
 
530
        return (trans_id in self._new_name) or (trans_id in self._new_parent)
 
531
 
 
532
    def new_contents(self, trans_id):
 
533
        return (trans_id in self._new_contents)
449
534
 
450
535
    def find_conflicts(self):
451
536
        """Find any violations of inventory or filesystem invariants"""
452
 
        if self.__done is True:
 
537
        if self._done is True:
453
538
            raise ReusingTransform()
454
539
        conflicts = []
455
540
        # ensure all children of all existent parents are known
466
551
        conflicts.extend(self._overwrite_conflicts())
467
552
        return conflicts
468
553
 
 
554
    def _check_malformed(self):
 
555
        conflicts = self.find_conflicts()
 
556
        if len(conflicts) != 0:
 
557
            raise MalformedTransform(conflicts=conflicts)
 
558
 
469
559
    def _add_tree_children(self):
470
560
        """Add all the children of all active parents to the known paths.
471
561
 
473
563
        removed.  This is a necessary first step in detecting conflicts.
474
564
        """
475
565
        parents = self.by_parent().keys()
476
 
        parents.extend([t for t in self._removed_contents if 
 
566
        parents.extend([t for t in self._removed_contents if
477
567
                        self.tree_kind(t) == 'directory'])
478
568
        for trans_id in self._removed_id:
479
569
            file_id = self.tree_file_id(trans_id)
480
 
            if self._tree.inventory[file_id].kind in ('directory', 
481
 
                                                      'root_directory'):
 
570
            if file_id is not None:
 
571
                # XXX: This seems like something that should go via a different
 
572
                #      indirection.
 
573
                if self._tree.inventory[file_id].kind == 'directory':
 
574
                    parents.append(trans_id)
 
575
            elif self.tree_kind(trans_id) == 'directory':
482
576
                parents.append(trans_id)
483
577
 
484
578
        for parent_id in parents:
485
579
            # ensure that all children are registered with the transaction
486
580
            list(self.iter_tree_children(parent_id))
487
581
 
488
 
    def iter_tree_children(self, parent_id):
489
 
        """Iterate through the entry's tree children, if any"""
490
 
        try:
491
 
            path = self._tree_id_paths[parent_id]
492
 
        except KeyError:
493
 
            return
494
 
        try:
495
 
            children = os.listdir(self._tree.abspath(path))
496
 
        except OSError, e:
497
 
            if e.errno != errno.ENOENT and e.errno != errno.ESRCH:
498
 
                raise
499
 
            return
500
 
            
501
 
        for child in children:
502
 
            childpath = joinpath(path, child)
503
 
            if self._tree.is_control_filename(childpath):
504
 
                continue
505
 
            yield self.trans_id_tree_path(childpath)
506
 
 
 
582
    @deprecated_method(deprecated_in((2, 3, 0)))
507
583
    def has_named_child(self, by_parent, parent_id, name):
508
 
        try:
509
 
            children = by_parent[parent_id]
510
 
        except KeyError:
511
 
            children = []
512
 
        for child in children:
 
584
        return self._has_named_child(
 
585
            name, parent_id, known_children=by_parent.get(parent_id, []))
 
586
 
 
587
    def _has_named_child(self, name, parent_id, known_children):
 
588
        """Does a parent already have a name child.
 
589
 
 
590
        :param name: The searched for name.
 
591
 
 
592
        :param parent_id: The parent for which the check is made.
 
593
 
 
594
        :param known_children: The already known children. This should have
 
595
            been recently obtained from `self.by_parent.get(parent_id)`
 
596
            (or will be if None is passed).
 
597
        """
 
598
        if known_children is None:
 
599
            known_children = self.by_parent().get(parent_id, [])
 
600
        for child in known_children:
513
601
            if self.final_name(child) == name:
514
602
                return True
515
 
        try:
516
 
            path = self._tree_id_paths[parent_id]
517
 
        except KeyError:
 
603
        parent_path = self._tree_id_paths.get(parent_id, None)
 
604
        if parent_path is None:
 
605
            # No parent... no children
518
606
            return False
519
 
        childpath = joinpath(path, name)
520
 
        child_id = self._tree_path_ids.get(childpath)
 
607
        child_path = joinpath(parent_path, name)
 
608
        child_id = self._tree_path_ids.get(child_path, None)
521
609
        if child_id is None:
522
 
            return lexists(self._tree.abspath(childpath))
 
610
            # Not known by the tree transform yet, check the filesystem
 
611
            return osutils.lexists(self._tree.abspath(child_path))
523
612
        else:
524
 
            if tt.final_parent(child_id) != parent_id:
525
 
                return False
526
 
            if child_id in tt._removed_contents:
527
 
                # XXX What about dangling file-ids?
528
 
                return False
529
 
            else:
530
 
                return True
 
613
            raise AssertionError('child_id is missing: %s, %s, %s'
 
614
                                 % (name, parent_id, child_id))
 
615
 
 
616
    def _available_backup_name(self, name, target_id):
 
617
        """Find an available backup name.
 
618
 
 
619
        :param name: The basename of the file.
 
620
 
 
621
        :param target_id: The directory trans_id where the backup should 
 
622
            be placed.
 
623
        """
 
624
        known_children = self.by_parent().get(target_id, [])
 
625
        return osutils.available_backup_name(
 
626
            name,
 
627
            lambda base: self._has_named_child(
 
628
                base, target_id, known_children))
531
629
 
532
630
    def _parent_loops(self):
533
631
        """No entry should be its own ancestor"""
537
635
            parent_id = trans_id
538
636
            while parent_id is not ROOT_PARENT:
539
637
                seen.add(parent_id)
540
 
                parent_id = self.final_parent(parent_id)
 
638
                try:
 
639
                    parent_id = self.final_parent(parent_id)
 
640
                except KeyError:
 
641
                    break
541
642
                if parent_id == trans_id:
542
643
                    conflicts.append(('parent loop', trans_id))
543
644
                if parent_id in seen:
560
661
 
561
662
    def _improper_versioning(self):
562
663
        """Cannot version a file with no contents, or a bad type.
563
 
        
 
664
 
564
665
        However, existing entries with no contents are okay.
565
666
        """
566
667
        conflicts = []
567
668
        for trans_id in self._new_id.iterkeys():
568
 
            try:
569
 
                kind = self.final_kind(trans_id)
570
 
            except NoSuchFile:
 
669
            kind = self.final_kind(trans_id)
 
670
            if kind is None:
571
671
                conflicts.append(('versioning no contents', trans_id))
572
672
                continue
573
 
            if not InventoryEntry.versionable_kind(kind):
 
673
            if not inventory.InventoryEntry.versionable_kind(kind):
574
674
                conflicts.append(('versioning bad kind', trans_id, kind))
575
675
        return conflicts
576
676
 
577
677
    def _executability_conflicts(self):
578
678
        """Check for bad executability changes.
579
 
        
 
679
 
580
680
        Only versioned files may have their executability set, because
581
681
        1. only versioned entries can have executability under windows
582
682
        2. only files can be executable.  (The execute bit on a directory
587
687
            if self.final_file_id(trans_id) is None:
588
688
                conflicts.append(('unversioned executability', trans_id))
589
689
            else:
590
 
                try:
591
 
                    non_file = self.final_kind(trans_id) != "file"
592
 
                except NoSuchFile:
593
 
                    non_file = True
594
 
                if non_file is True:
 
690
                if self.final_kind(trans_id) != "file":
595
691
                    conflicts.append(('non-file executability', trans_id))
596
692
        return conflicts
597
693
 
599
695
        """Check for overwrites (not permitted on Win32)"""
600
696
        conflicts = []
601
697
        for trans_id in self._new_contents:
602
 
            try:
603
 
                self.tree_kind(trans_id)
604
 
            except NoSuchFile:
 
698
            if self.tree_kind(trans_id) is None:
605
699
                continue
606
700
            if trans_id not in self._removed_contents:
607
701
                conflicts.append(('overwrite', trans_id,
611
705
    def _duplicate_entries(self, by_parent):
612
706
        """No directory may have two entries with the same name."""
613
707
        conflicts = []
 
708
        if (self._new_name, self._new_parent) == ({}, {}):
 
709
            return conflicts
614
710
        for children in by_parent.itervalues():
615
 
            name_ids = [(self.final_name(t), t) for t in children]
 
711
            name_ids = []
 
712
            for child_tid in children:
 
713
                name = self.final_name(child_tid)
 
714
                if name is not None:
 
715
                    # Keep children only if they still exist in the end
 
716
                    if not self._case_sensitive_target:
 
717
                        name = name.lower()
 
718
                    name_ids.append((name, child_tid))
616
719
            name_ids.sort()
617
720
            last_name = None
618
721
            last_trans_id = None
619
722
            for name, trans_id in name_ids:
 
723
                kind = self.final_kind(trans_id)
 
724
                file_id = self.final_file_id(trans_id)
 
725
                if kind is None and file_id is None:
 
726
                    continue
620
727
                if name == last_name:
621
728
                    conflicts.append(('duplicate', last_trans_id, trans_id,
622
729
                    name))
623
 
                try:
624
 
                    kind = self.final_kind(trans_id)
625
 
                except NoSuchFile:
626
 
                    kind = None
627
 
                file_id = self.final_file_id(trans_id)
628
 
                if kind is not None or file_id is not None:
629
 
                    last_name = name
630
 
                    last_trans_id = trans_id
 
730
                last_name = name
 
731
                last_trans_id = trans_id
631
732
        return conflicts
632
733
 
633
734
    def _duplicate_ids(self):
635
736
        conflicts = []
636
737
        removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
637
738
                                self._removed_id))
638
 
        active_tree_ids = set((f for f in self._tree.inventory if
639
 
                               f not in removed_tree_ids))
 
739
        all_ids = self._tree.all_file_ids()
 
740
        active_tree_ids = all_ids.difference(removed_tree_ids)
640
741
        for trans_id, file_id in self._new_id.iteritems():
641
742
            if file_id in active_tree_ids:
642
743
                old_trans_id = self.trans_id_tree_file_id(file_id)
644
745
        return conflicts
645
746
 
646
747
    def _parent_type_conflicts(self, by_parent):
647
 
        """parents must have directory 'contents'."""
 
748
        """Children must have a directory parent"""
648
749
        conflicts = []
649
750
        for parent_id, children in by_parent.iteritems():
650
751
            if parent_id is ROOT_PARENT:
651
752
                continue
652
 
            if not self._any_contents(children):
 
753
            no_children = True
 
754
            for child_id in children:
 
755
                if self.final_kind(child_id) is not None:
 
756
                    no_children = False
 
757
                    break
 
758
            if no_children:
653
759
                continue
654
 
            for child in children:
655
 
                try:
656
 
                    self.final_kind(child)
657
 
                except NoSuchFile:
658
 
                    continue
659
 
            try:
660
 
                kind = self.final_kind(parent_id)
661
 
            except NoSuchFile:
662
 
                kind = None
 
760
            # There is at least a child, so we need an existing directory to
 
761
            # contain it.
 
762
            kind = self.final_kind(parent_id)
663
763
            if kind is None:
 
764
                # The directory will be deleted
664
765
                conflicts.append(('missing parent', parent_id))
665
766
            elif kind != "directory":
 
767
                # Meh, we need a *directory* to put something in it
666
768
                conflicts.append(('non-directory parent', parent_id))
667
769
        return conflicts
668
770
 
669
 
    def _any_contents(self, trans_ids):
670
 
        """Return true if any of the trans_ids, will have contents."""
671
 
        for trans_id in trans_ids:
672
 
            try:
673
 
                kind = self.final_kind(trans_id)
674
 
            except NoSuchFile:
675
 
                continue
676
 
            return True
677
 
        return False
678
 
            
679
 
    def apply(self):
680
 
        """Apply all changes to the inventory and filesystem.
681
 
        
682
 
        If filesystem or inventory conflicts are present, MalformedTransform
683
 
        will be thrown.
684
 
        """
685
 
        conflicts = self.find_conflicts()
686
 
        if len(conflicts) != 0:
687
 
            raise MalformedTransform(conflicts=conflicts)
688
 
        limbo_inv = {}
689
 
        inv = self._tree.inventory
690
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
691
 
        try:
692
 
            child_pb.update('Apply phase', 0, 2)
693
 
            self._apply_removals(inv, limbo_inv)
694
 
            child_pb.update('Apply phase', 1, 2)
695
 
            modified_paths = self._apply_insertions(inv, limbo_inv)
696
 
        finally:
697
 
            child_pb.finished()
698
 
        self._tree._write_inventory(inv)
699
 
        self.__done = True
700
 
        self.finalize()
701
 
        return _TransformResults(modified_paths)
702
 
 
703
 
    def _limbo_name(self, trans_id):
704
 
        """Generate the limbo name of a file"""
705
 
        return pathjoin(self._limbodir, trans_id)
706
 
 
707
 
    def _apply_removals(self, inv, limbo_inv):
708
 
        """Perform tree operations that remove directory/inventory names.
709
 
        
710
 
        That is, delete files that are to be deleted, and put any files that
711
 
        need renaming into limbo.  This must be done in strict child-to-parent
712
 
        order.
713
 
        """
714
 
        tree_paths = list(self._tree_path_ids.iteritems())
715
 
        tree_paths.sort(reverse=True)
716
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
717
 
        try:
718
 
            for num, data in enumerate(tree_paths):
719
 
                path, trans_id = data
720
 
                child_pb.update('removing file', num, len(tree_paths))
721
 
                full_path = self._tree.abspath(path)
722
 
                if trans_id in self._removed_contents:
723
 
                    delete_any(full_path)
724
 
                elif trans_id in self._new_name or trans_id in \
725
 
                    self._new_parent:
726
 
                    try:
727
 
                        os.rename(full_path, self._limbo_name(trans_id))
728
 
                    except OSError, e:
729
 
                        if e.errno != errno.ENOENT:
730
 
                            raise
731
 
                if trans_id in self._removed_id:
732
 
                    if trans_id == self._new_root:
733
 
                        file_id = self._tree.inventory.root.file_id
734
 
                    else:
735
 
                        file_id = self.tree_file_id(trans_id)
736
 
                    del inv[file_id]
737
 
                elif trans_id in self._new_name or trans_id in self._new_parent:
738
 
                    file_id = self.tree_file_id(trans_id)
739
 
                    if file_id is not None:
740
 
                        limbo_inv[trans_id] = inv[file_id]
741
 
                        del inv[file_id]
742
 
        finally:
743
 
            child_pb.finished()
744
 
 
745
 
    def _apply_insertions(self, inv, limbo_inv):
746
 
        """Perform tree operations that insert directory/inventory names.
747
 
        
748
 
        That is, create any files that need to be created, and restore from
749
 
        limbo any files that needed renaming.  This must be done in strict
750
 
        parent-to-child order.
751
 
        """
752
 
        new_paths = self.new_paths()
753
 
        modified_paths = []
754
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
755
 
        try:
756
 
            for num, (path, trans_id) in enumerate(new_paths):
757
 
                child_pb.update('adding file', num, len(new_paths))
758
 
                try:
759
 
                    kind = self._new_contents[trans_id]
760
 
                except KeyError:
761
 
                    kind = contents = None
762
 
                if trans_id in self._new_contents or \
763
 
                    self.path_changed(trans_id):
764
 
                    full_path = self._tree.abspath(path)
765
 
                    try:
766
 
                        os.rename(self._limbo_name(trans_id), full_path)
767
 
                    except OSError, e:
768
 
                        # We may be renaming a dangling inventory id
769
 
                        if e.errno != errno.ENOENT:
770
 
                            raise
771
 
                    if trans_id in self._new_contents:
772
 
                        modified_paths.append(full_path)
773
 
                        del self._new_contents[trans_id]
774
 
 
775
 
                if trans_id in self._new_id:
776
 
                    if kind is None:
777
 
                        kind = file_kind(self._tree.abspath(path))
778
 
                    inv.add_path(path, kind, self._new_id[trans_id])
779
 
                elif trans_id in self._new_name or trans_id in\
780
 
                    self._new_parent:
781
 
                    entry = limbo_inv.get(trans_id)
782
 
                    if entry is not None:
783
 
                        entry.name = self.final_name(trans_id)
784
 
                        parent_path = os.path.dirname(path)
785
 
                        entry.parent_id = \
786
 
                            self._tree.inventory.path2id(parent_path)
787
 
                        inv.add(entry)
788
 
 
789
 
                # requires files and inventory entries to be in place
790
 
                if trans_id in self._new_executability:
791
 
                    self._set_executability(path, inv, trans_id)
792
 
        finally:
793
 
            child_pb.finished()
794
 
        return modified_paths
795
 
 
796
 
    def _set_executability(self, path, inv, trans_id):
 
771
    def _set_executability(self, path, trans_id):
797
772
        """Set the executability of versioned files """
798
 
        file_id = inv.path2id(path)
799
 
        new_executability = self._new_executability[trans_id]
800
 
        inv[file_id].executable = new_executability
801
773
        if supports_executable():
 
774
            new_executability = self._new_executability[trans_id]
802
775
            abspath = self._tree.abspath(path)
803
776
            current_mode = os.stat(abspath).st_mode
804
777
            if new_executability:
821
794
            self.version_file(file_id, trans_id)
822
795
        return trans_id
823
796
 
824
 
    def new_file(self, name, parent_id, contents, file_id=None, 
825
 
                 executable=None):
 
797
    def new_file(self, name, parent_id, contents, file_id=None,
 
798
                 executable=None, sha1=None):
826
799
        """Convenience method to create files.
827
 
        
 
800
 
828
801
        name is the name of the file to create.
829
802
        parent_id is the transaction id of the parent directory of the file.
830
803
        contents is an iterator of bytestrings, which will be used to produce
831
804
        the file.
832
 
        file_id is the inventory ID of the file, if it is to be versioned.
 
805
        :param file_id: The inventory ID of the file, if it is to be versioned.
 
806
        :param executable: Only valid when a file_id has been supplied.
833
807
        """
834
808
        trans_id = self._new_entry(name, parent_id, file_id)
835
 
        self.create_file(contents, trans_id)
 
809
        # TODO: rather than scheduling a set_executable call,
 
810
        # have create_file create the file with the right mode.
 
811
        self.create_file(contents, trans_id, sha1=sha1)
836
812
        if executable is not None:
837
813
            self.set_executability(executable, trans_id)
838
814
        return trans_id
847
823
        """
848
824
        trans_id = self._new_entry(name, parent_id, file_id)
849
825
        self.create_directory(trans_id)
850
 
        return trans_id 
 
826
        return trans_id
851
827
 
852
828
    def new_symlink(self, name, parent_id, target, file_id=None):
853
829
        """Convenience method to create symbolic link.
854
 
        
 
830
 
855
831
        name is the name of the symlink to create.
856
832
        parent_id is the transaction id of the parent directory of the symlink.
857
833
        target is a bytestring of the target of the symlink.
861
837
        self.create_symlink(target, trans_id)
862
838
        return trans_id
863
839
 
 
840
    def new_orphan(self, trans_id, parent_id):
 
841
        """Schedule an item to be orphaned.
 
842
 
 
843
        When a directory is about to be removed, its children, if they are not
 
844
        versioned are moved out of the way: they don't have a parent anymore.
 
845
 
 
846
        :param trans_id: The trans_id of the existing item.
 
847
        :param parent_id: The parent trans_id of the item.
 
848
        """
 
849
        raise NotImplementedError(self.new_orphan)
 
850
 
 
851
    def _get_potential_orphans(self, dir_id):
 
852
        """Find the potential orphans in a directory.
 
853
 
 
854
        A directory can't be safely deleted if there are versioned files in it.
 
855
        If all the contained files are unversioned then they can be orphaned.
 
856
 
 
857
        The 'None' return value means that the directory contains at least one
 
858
        versioned file and should not be deleted.
 
859
 
 
860
        :param dir_id: The directory trans id.
 
861
 
 
862
        :return: A list of the orphan trans ids or None if at least one
 
863
             versioned file is present.
 
864
        """
 
865
        orphans = []
 
866
        # Find the potential orphans, stop if one item should be kept
 
867
        for child_tid in self.by_parent()[dir_id]:
 
868
            if child_tid in self._removed_contents:
 
869
                # The child is removed as part of the transform. Since it was
 
870
                # versioned before, it's not an orphan
 
871
                continue
 
872
            elif self.final_file_id(child_tid) is None:
 
873
                # The child is not versioned
 
874
                orphans.append(child_tid)
 
875
            else:
 
876
                # We have a versioned file here, searching for orphans is
 
877
                # meaningless.
 
878
                orphans = None
 
879
                break
 
880
        return orphans
 
881
 
 
882
    def _affected_ids(self):
 
883
        """Return the set of transform ids affected by the transform"""
 
884
        trans_ids = set(self._removed_id)
 
885
        trans_ids.update(self._new_id.keys())
 
886
        trans_ids.update(self._removed_contents)
 
887
        trans_ids.update(self._new_contents.keys())
 
888
        trans_ids.update(self._new_executability.keys())
 
889
        trans_ids.update(self._new_name.keys())
 
890
        trans_ids.update(self._new_parent.keys())
 
891
        return trans_ids
 
892
 
 
893
    def _get_file_id_maps(self):
 
894
        """Return mapping of file_ids to trans_ids in the to and from states"""
 
895
        trans_ids = self._affected_ids()
 
896
        from_trans_ids = {}
 
897
        to_trans_ids = {}
 
898
        # Build up two dicts: trans_ids associated with file ids in the
 
899
        # FROM state, vs the TO state.
 
900
        for trans_id in trans_ids:
 
901
            from_file_id = self.tree_file_id(trans_id)
 
902
            if from_file_id is not None:
 
903
                from_trans_ids[from_file_id] = trans_id
 
904
            to_file_id = self.final_file_id(trans_id)
 
905
            if to_file_id is not None:
 
906
                to_trans_ids[to_file_id] = trans_id
 
907
        return from_trans_ids, to_trans_ids
 
908
 
 
909
    def _from_file_data(self, from_trans_id, from_versioned, file_id):
 
910
        """Get data about a file in the from (tree) state
 
911
 
 
912
        Return a (name, parent, kind, executable) tuple
 
913
        """
 
914
        from_path = self._tree_id_paths.get(from_trans_id)
 
915
        if from_versioned:
 
916
            # get data from working tree if versioned
 
917
            from_entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
 
918
            from_name = from_entry.name
 
919
            from_parent = from_entry.parent_id
 
920
        else:
 
921
            from_entry = None
 
922
            if from_path is None:
 
923
                # File does not exist in FROM state
 
924
                from_name = None
 
925
                from_parent = None
 
926
            else:
 
927
                # File exists, but is not versioned.  Have to use path-
 
928
                # splitting stuff
 
929
                from_name = os.path.basename(from_path)
 
930
                tree_parent = self.get_tree_parent(from_trans_id)
 
931
                from_parent = self.tree_file_id(tree_parent)
 
932
        if from_path is not None:
 
933
            from_kind, from_executable, from_stats = \
 
934
                self._tree._comparison_data(from_entry, from_path)
 
935
        else:
 
936
            from_kind = None
 
937
            from_executable = False
 
938
        return from_name, from_parent, from_kind, from_executable
 
939
 
 
940
    def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
 
941
        """Get data about a file in the to (target) state
 
942
 
 
943
        Return a (name, parent, kind, executable) tuple
 
944
        """
 
945
        to_name = self.final_name(to_trans_id)
 
946
        to_kind = self.final_kind(to_trans_id)
 
947
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
 
948
        if to_trans_id in self._new_executability:
 
949
            to_executable = self._new_executability[to_trans_id]
 
950
        elif to_trans_id == from_trans_id:
 
951
            to_executable = from_executable
 
952
        else:
 
953
            to_executable = False
 
954
        return to_name, to_parent, to_kind, to_executable
 
955
 
 
956
    def iter_changes(self):
 
957
        """Produce output in the same format as Tree.iter_changes.
 
958
 
 
959
        Will produce nonsensical results if invoked while inventory/filesystem
 
960
        conflicts (as reported by TreeTransform.find_conflicts()) are present.
 
961
 
 
962
        This reads the Transform, but only reproduces changes involving a
 
963
        file_id.  Files that are not versioned in either of the FROM or TO
 
964
        states are not reflected.
 
965
        """
 
966
        final_paths = FinalPaths(self)
 
967
        from_trans_ids, to_trans_ids = self._get_file_id_maps()
 
968
        results = []
 
969
        # Now iterate through all active file_ids
 
970
        for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
 
971
            modified = False
 
972
            from_trans_id = from_trans_ids.get(file_id)
 
973
            # find file ids, and determine versioning state
 
974
            if from_trans_id is None:
 
975
                from_versioned = False
 
976
                from_trans_id = to_trans_ids[file_id]
 
977
            else:
 
978
                from_versioned = True
 
979
            to_trans_id = to_trans_ids.get(file_id)
 
980
            if to_trans_id is None:
 
981
                to_versioned = False
 
982
                to_trans_id = from_trans_id
 
983
            else:
 
984
                to_versioned = True
 
985
 
 
986
            from_name, from_parent, from_kind, from_executable = \
 
987
                self._from_file_data(from_trans_id, from_versioned, file_id)
 
988
 
 
989
            to_name, to_parent, to_kind, to_executable = \
 
990
                self._to_file_data(to_trans_id, from_trans_id, from_executable)
 
991
 
 
992
            if not from_versioned:
 
993
                from_path = None
 
994
            else:
 
995
                from_path = self._tree_id_paths.get(from_trans_id)
 
996
            if not to_versioned:
 
997
                to_path = None
 
998
            else:
 
999
                to_path = final_paths.get_path(to_trans_id)
 
1000
            if from_kind != to_kind:
 
1001
                modified = True
 
1002
            elif to_kind in ('file', 'symlink') and (
 
1003
                to_trans_id != from_trans_id or
 
1004
                to_trans_id in self._new_contents):
 
1005
                modified = True
 
1006
            if (not modified and from_versioned == to_versioned and
 
1007
                from_parent==to_parent and from_name == to_name and
 
1008
                from_executable == to_executable):
 
1009
                continue
 
1010
            results.append((file_id, (from_path, to_path), modified,
 
1011
                   (from_versioned, to_versioned),
 
1012
                   (from_parent, to_parent),
 
1013
                   (from_name, to_name),
 
1014
                   (from_kind, to_kind),
 
1015
                   (from_executable, to_executable)))
 
1016
        return iter(sorted(results, key=lambda x:x[1]))
 
1017
 
 
1018
    def get_preview_tree(self):
 
1019
        """Return a tree representing the result of the transform.
 
1020
 
 
1021
        The tree is a snapshot, and altering the TreeTransform will invalidate
 
1022
        it.
 
1023
        """
 
1024
        return _PreviewTree(self)
 
1025
 
 
1026
    def commit(self, branch, message, merge_parents=None, strict=False,
 
1027
               timestamp=None, timezone=None, committer=None, authors=None,
 
1028
               revprops=None, revision_id=None):
 
1029
        """Commit the result of this TreeTransform to a branch.
 
1030
 
 
1031
        :param branch: The branch to commit to.
 
1032
        :param message: The message to attach to the commit.
 
1033
        :param merge_parents: Additional parent revision-ids specified by
 
1034
            pending merges.
 
1035
        :param strict: If True, abort the commit if there are unversioned
 
1036
            files.
 
1037
        :param timestamp: if not None, seconds-since-epoch for the time and
 
1038
            date.  (May be a float.)
 
1039
        :param timezone: Optional timezone for timestamp, as an offset in
 
1040
            seconds.
 
1041
        :param committer: Optional committer in email-id format.
 
1042
            (e.g. "J Random Hacker <jrandom@example.com>")
 
1043
        :param authors: Optional list of authors in email-id format.
 
1044
        :param revprops: Optional dictionary of revision properties.
 
1045
        :param revision_id: Optional revision id.  (Specifying a revision-id
 
1046
            may reduce performance for some non-native formats.)
 
1047
        :return: The revision_id of the revision committed.
 
1048
        """
 
1049
        self._check_malformed()
 
1050
        if strict:
 
1051
            unversioned = set(self._new_contents).difference(set(self._new_id))
 
1052
            for trans_id in unversioned:
 
1053
                if self.final_file_id(trans_id) is None:
 
1054
                    raise errors.StrictCommitFailed()
 
1055
 
 
1056
        revno, last_rev_id = branch.last_revision_info()
 
1057
        if last_rev_id == _mod_revision.NULL_REVISION:
 
1058
            if merge_parents is not None:
 
1059
                raise ValueError('Cannot supply merge parents for first'
 
1060
                                 ' commit.')
 
1061
            parent_ids = []
 
1062
        else:
 
1063
            parent_ids = [last_rev_id]
 
1064
            if merge_parents is not None:
 
1065
                parent_ids.extend(merge_parents)
 
1066
        if self._tree.get_revision_id() != last_rev_id:
 
1067
            raise ValueError('TreeTransform not based on branch basis: %s' %
 
1068
                             self._tree.get_revision_id())
 
1069
        revprops = commit.Commit.update_revprops(revprops, branch, authors)
 
1070
        builder = branch.get_commit_builder(parent_ids,
 
1071
                                            timestamp=timestamp,
 
1072
                                            timezone=timezone,
 
1073
                                            committer=committer,
 
1074
                                            revprops=revprops,
 
1075
                                            revision_id=revision_id)
 
1076
        preview = self.get_preview_tree()
 
1077
        list(builder.record_iter_changes(preview, last_rev_id,
 
1078
                                         self.iter_changes()))
 
1079
        builder.finish_inventory()
 
1080
        revision_id = builder.commit(message)
 
1081
        branch.set_last_revision_info(revno + 1, revision_id)
 
1082
        return revision_id
 
1083
 
 
1084
    def _text_parent(self, trans_id):
 
1085
        file_id = self.tree_file_id(trans_id)
 
1086
        try:
 
1087
            if file_id is None or self._tree.kind(file_id) != 'file':
 
1088
                return None
 
1089
        except errors.NoSuchFile:
 
1090
            return None
 
1091
        return file_id
 
1092
 
 
1093
    def _get_parents_texts(self, trans_id):
 
1094
        """Get texts for compression parents of this file."""
 
1095
        file_id = self._text_parent(trans_id)
 
1096
        if file_id is None:
 
1097
            return ()
 
1098
        return (self._tree.get_file_text(file_id),)
 
1099
 
 
1100
    def _get_parents_lines(self, trans_id):
 
1101
        """Get lines for compression parents of this file."""
 
1102
        file_id = self._text_parent(trans_id)
 
1103
        if file_id is None:
 
1104
            return ()
 
1105
        return (self._tree.get_file_lines(file_id),)
 
1106
 
 
1107
    def serialize(self, serializer):
 
1108
        """Serialize this TreeTransform.
 
1109
 
 
1110
        :param serializer: A Serialiser like pack.ContainerSerializer.
 
1111
        """
 
1112
        new_name = dict((k, v.encode('utf-8')) for k, v in
 
1113
                        self._new_name.items())
 
1114
        new_executability = dict((k, int(v)) for k, v in
 
1115
                                 self._new_executability.items())
 
1116
        tree_path_ids = dict((k.encode('utf-8'), v)
 
1117
                             for k, v in self._tree_path_ids.items())
 
1118
        attribs = {
 
1119
            '_id_number': self._id_number,
 
1120
            '_new_name': new_name,
 
1121
            '_new_parent': self._new_parent,
 
1122
            '_new_executability': new_executability,
 
1123
            '_new_id': self._new_id,
 
1124
            '_tree_path_ids': tree_path_ids,
 
1125
            '_removed_id': list(self._removed_id),
 
1126
            '_removed_contents': list(self._removed_contents),
 
1127
            '_non_present_ids': self._non_present_ids,
 
1128
            }
 
1129
        yield serializer.bytes_record(bencode.bencode(attribs),
 
1130
                                      (('attribs',),))
 
1131
        for trans_id, kind in self._new_contents.items():
 
1132
            if kind == 'file':
 
1133
                lines = osutils.chunks_to_lines(
 
1134
                    self._read_file_chunks(trans_id))
 
1135
                parents = self._get_parents_lines(trans_id)
 
1136
                mpdiff = multiparent.MultiParent.from_lines(lines, parents)
 
1137
                content = ''.join(mpdiff.to_patch())
 
1138
            if kind == 'directory':
 
1139
                content = ''
 
1140
            if kind == 'symlink':
 
1141
                content = self._read_symlink_target(trans_id)
 
1142
            yield serializer.bytes_record(content, ((trans_id, kind),))
 
1143
 
 
1144
    def deserialize(self, records):
 
1145
        """Deserialize a stored TreeTransform.
 
1146
 
 
1147
        :param records: An iterable of (names, content) tuples, as per
 
1148
            pack.ContainerPushParser.
 
1149
        """
 
1150
        names, content = records.next()
 
1151
        attribs = bencode.bdecode(content)
 
1152
        self._id_number = attribs['_id_number']
 
1153
        self._new_name = dict((k, v.decode('utf-8'))
 
1154
                            for k, v in attribs['_new_name'].items())
 
1155
        self._new_parent = attribs['_new_parent']
 
1156
        self._new_executability = dict((k, bool(v)) for k, v in
 
1157
            attribs['_new_executability'].items())
 
1158
        self._new_id = attribs['_new_id']
 
1159
        self._r_new_id = dict((v, k) for k, v in self._new_id.items())
 
1160
        self._tree_path_ids = {}
 
1161
        self._tree_id_paths = {}
 
1162
        for bytepath, trans_id in attribs['_tree_path_ids'].items():
 
1163
            path = bytepath.decode('utf-8')
 
1164
            self._tree_path_ids[path] = trans_id
 
1165
            self._tree_id_paths[trans_id] = path
 
1166
        self._removed_id = set(attribs['_removed_id'])
 
1167
        self._removed_contents = set(attribs['_removed_contents'])
 
1168
        self._non_present_ids = attribs['_non_present_ids']
 
1169
        for ((trans_id, kind),), content in records:
 
1170
            if kind == 'file':
 
1171
                mpdiff = multiparent.MultiParent.from_patch(content)
 
1172
                lines = mpdiff.to_lines(self._get_parents_texts(trans_id))
 
1173
                self.create_file(lines, trans_id)
 
1174
            if kind == 'directory':
 
1175
                self.create_directory(trans_id)
 
1176
            if kind == 'symlink':
 
1177
                self.create_symlink(content.decode('utf-8'), trans_id)
 
1178
 
 
1179
 
 
1180
class DiskTreeTransform(TreeTransformBase):
 
1181
    """Tree transform storing its contents on disk."""
 
1182
 
 
1183
    def __init__(self, tree, limbodir, pb=None,
 
1184
                 case_sensitive=True):
 
1185
        """Constructor.
 
1186
        :param tree: The tree that will be transformed, but not necessarily
 
1187
            the output tree.
 
1188
        :param limbodir: A directory where new files can be stored until
 
1189
            they are installed in their proper places
 
1190
        :param pb: ignored
 
1191
        :param case_sensitive: If True, the target of the transform is
 
1192
            case sensitive, not just case preserving.
 
1193
        """
 
1194
        TreeTransformBase.__init__(self, tree, pb, case_sensitive)
 
1195
        self._limbodir = limbodir
 
1196
        self._deletiondir = None
 
1197
        # A mapping of transform ids to their limbo filename
 
1198
        self._limbo_files = {}
 
1199
        self._possibly_stale_limbo_files = set()
 
1200
        # A mapping of transform ids to a set of the transform ids of children
 
1201
        # that their limbo directory has
 
1202
        self._limbo_children = {}
 
1203
        # Map transform ids to maps of child filename to child transform id
 
1204
        self._limbo_children_names = {}
 
1205
        # List of transform ids that need to be renamed from limbo into place
 
1206
        self._needs_rename = set()
 
1207
        self._creation_mtime = None
 
1208
 
 
1209
    def finalize(self):
 
1210
        """Release the working tree lock, if held, clean up limbo dir.
 
1211
 
 
1212
        This is required if apply has not been invoked, but can be invoked
 
1213
        even after apply.
 
1214
        """
 
1215
        if self._tree is None:
 
1216
            return
 
1217
        try:
 
1218
            limbo_paths = self._limbo_files.values() + list(
 
1219
                self._possibly_stale_limbo_files)
 
1220
            limbo_paths = sorted(limbo_paths, reverse=True)
 
1221
            for path in limbo_paths:
 
1222
                try:
 
1223
                    delete_any(path)
 
1224
                except OSError, e:
 
1225
                    if e.errno != errno.ENOENT:
 
1226
                        raise
 
1227
                    # XXX: warn? perhaps we just got interrupted at an
 
1228
                    # inconvenient moment, but perhaps files are disappearing
 
1229
                    # from under us?
 
1230
            try:
 
1231
                delete_any(self._limbodir)
 
1232
            except OSError:
 
1233
                # We don't especially care *why* the dir is immortal.
 
1234
                raise ImmortalLimbo(self._limbodir)
 
1235
            try:
 
1236
                if self._deletiondir is not None:
 
1237
                    delete_any(self._deletiondir)
 
1238
            except OSError:
 
1239
                raise errors.ImmortalPendingDeletion(self._deletiondir)
 
1240
        finally:
 
1241
            TreeTransformBase.finalize(self)
 
1242
 
 
1243
    def _limbo_name(self, trans_id):
 
1244
        """Generate the limbo name of a file"""
 
1245
        limbo_name = self._limbo_files.get(trans_id)
 
1246
        if limbo_name is None:
 
1247
            limbo_name = self._generate_limbo_path(trans_id)
 
1248
            self._limbo_files[trans_id] = limbo_name
 
1249
        return limbo_name
 
1250
 
 
1251
    def _generate_limbo_path(self, trans_id):
 
1252
        """Generate a limbo path using the trans_id as the relative path.
 
1253
 
 
1254
        This is suitable as a fallback, and when the transform should not be
 
1255
        sensitive to the path encoding of the limbo directory.
 
1256
        """
 
1257
        self._needs_rename.add(trans_id)
 
1258
        return pathjoin(self._limbodir, trans_id)
 
1259
 
 
1260
    def adjust_path(self, name, parent, trans_id):
 
1261
        previous_parent = self._new_parent.get(trans_id)
 
1262
        previous_name = self._new_name.get(trans_id)
 
1263
        TreeTransformBase.adjust_path(self, name, parent, trans_id)
 
1264
        if (trans_id in self._limbo_files and
 
1265
            trans_id not in self._needs_rename):
 
1266
            self._rename_in_limbo([trans_id])
 
1267
            if previous_parent != parent:
 
1268
                self._limbo_children[previous_parent].remove(trans_id)
 
1269
            if previous_parent != parent or previous_name != name:
 
1270
                del self._limbo_children_names[previous_parent][previous_name]
 
1271
 
 
1272
    def _rename_in_limbo(self, trans_ids):
 
1273
        """Fix limbo names so that the right final path is produced.
 
1274
 
 
1275
        This means we outsmarted ourselves-- we tried to avoid renaming
 
1276
        these files later by creating them with their final names in their
 
1277
        final parents.  But now the previous name or parent is no longer
 
1278
        suitable, so we have to rename them.
 
1279
 
 
1280
        Even for trans_ids that have no new contents, we must remove their
 
1281
        entries from _limbo_files, because they are now stale.
 
1282
        """
 
1283
        for trans_id in trans_ids:
 
1284
            old_path = self._limbo_files[trans_id]
 
1285
            self._possibly_stale_limbo_files.add(old_path)
 
1286
            del self._limbo_files[trans_id]
 
1287
            if trans_id not in self._new_contents:
 
1288
                continue
 
1289
            new_path = self._limbo_name(trans_id)
 
1290
            os.rename(old_path, new_path)
 
1291
            self._possibly_stale_limbo_files.remove(old_path)
 
1292
            for descendant in self._limbo_descendants(trans_id):
 
1293
                desc_path = self._limbo_files[descendant]
 
1294
                desc_path = new_path + desc_path[len(old_path):]
 
1295
                self._limbo_files[descendant] = desc_path
 
1296
 
 
1297
    def _limbo_descendants(self, trans_id):
 
1298
        """Return the set of trans_ids whose limbo paths descend from this."""
 
1299
        descendants = set(self._limbo_children.get(trans_id, []))
 
1300
        for descendant in list(descendants):
 
1301
            descendants.update(self._limbo_descendants(descendant))
 
1302
        return descendants
 
1303
 
 
1304
    def create_file(self, contents, trans_id, mode_id=None, sha1=None):
 
1305
        """Schedule creation of a new file.
 
1306
 
 
1307
        :seealso: new_file.
 
1308
 
 
1309
        :param contents: an iterator of strings, all of which will be written
 
1310
            to the target destination.
 
1311
        :param trans_id: TreeTransform handle
 
1312
        :param mode_id: If not None, force the mode of the target file to match
 
1313
            the mode of the object referenced by mode_id.
 
1314
            Otherwise, we will try to preserve mode bits of an existing file.
 
1315
        :param sha1: If the sha1 of this content is already known, pass it in.
 
1316
            We can use it to prevent future sha1 computations.
 
1317
        """
 
1318
        name = self._limbo_name(trans_id)
 
1319
        f = open(name, 'wb')
 
1320
        try:
 
1321
            unique_add(self._new_contents, trans_id, 'file')
 
1322
            f.writelines(contents)
 
1323
        finally:
 
1324
            f.close()
 
1325
        self._set_mtime(name)
 
1326
        self._set_mode(trans_id, mode_id, S_ISREG)
 
1327
        # It is unfortunate we have to use lstat instead of fstat, but we just
 
1328
        # used utime and chmod on the file, so we need the accurate final
 
1329
        # details.
 
1330
        if sha1 is not None:
 
1331
            self._observed_sha1s[trans_id] = (sha1, osutils.lstat(name))
 
1332
 
 
1333
    def _read_file_chunks(self, trans_id):
 
1334
        cur_file = open(self._limbo_name(trans_id), 'rb')
 
1335
        try:
 
1336
            return cur_file.readlines()
 
1337
        finally:
 
1338
            cur_file.close()
 
1339
 
 
1340
    def _read_symlink_target(self, trans_id):
 
1341
        return os.readlink(self._limbo_name(trans_id))
 
1342
 
 
1343
    def _set_mtime(self, path):
 
1344
        """All files that are created get the same mtime.
 
1345
 
 
1346
        This time is set by the first object to be created.
 
1347
        """
 
1348
        if self._creation_mtime is None:
 
1349
            self._creation_mtime = time.time()
 
1350
        os.utime(path, (self._creation_mtime, self._creation_mtime))
 
1351
 
 
1352
    def create_hardlink(self, path, trans_id):
 
1353
        """Schedule creation of a hard link"""
 
1354
        name = self._limbo_name(trans_id)
 
1355
        try:
 
1356
            os.link(path, name)
 
1357
        except OSError, e:
 
1358
            if e.errno != errno.EPERM:
 
1359
                raise
 
1360
            raise errors.HardLinkNotSupported(path)
 
1361
        try:
 
1362
            unique_add(self._new_contents, trans_id, 'file')
 
1363
        except:
 
1364
            # Clean up the file, it never got registered so
 
1365
            # TreeTransform.finalize() won't clean it up.
 
1366
            os.unlink(name)
 
1367
            raise
 
1368
 
 
1369
    def create_directory(self, trans_id):
 
1370
        """Schedule creation of a new directory.
 
1371
 
 
1372
        See also new_directory.
 
1373
        """
 
1374
        os.mkdir(self._limbo_name(trans_id))
 
1375
        unique_add(self._new_contents, trans_id, 'directory')
 
1376
 
 
1377
    def create_symlink(self, target, trans_id):
 
1378
        """Schedule creation of a new symbolic link.
 
1379
 
 
1380
        target is a bytestring.
 
1381
        See also new_symlink.
 
1382
        """
 
1383
        if has_symlinks():
 
1384
            os.symlink(target, self._limbo_name(trans_id))
 
1385
            unique_add(self._new_contents, trans_id, 'symlink')
 
1386
        else:
 
1387
            try:
 
1388
                path = FinalPaths(self).get_path(trans_id)
 
1389
            except KeyError:
 
1390
                path = None
 
1391
            raise UnableCreateSymlink(path=path)
 
1392
 
 
1393
    def cancel_creation(self, trans_id):
 
1394
        """Cancel the creation of new file contents."""
 
1395
        del self._new_contents[trans_id]
 
1396
        if trans_id in self._observed_sha1s:
 
1397
            del self._observed_sha1s[trans_id]
 
1398
        children = self._limbo_children.get(trans_id)
 
1399
        # if this is a limbo directory with children, move them before removing
 
1400
        # the directory
 
1401
        if children is not None:
 
1402
            self._rename_in_limbo(children)
 
1403
            del self._limbo_children[trans_id]
 
1404
            del self._limbo_children_names[trans_id]
 
1405
        delete_any(self._limbo_name(trans_id))
 
1406
 
 
1407
    def new_orphan(self, trans_id, parent_id):
 
1408
        # FIXME: There is no tree config, so we use the branch one (it's weird
 
1409
        # to define it this way as orphaning can only occur in a working tree,
 
1410
        # but that's all we have (for now). It will find the option in
 
1411
        # locations.conf or bazaar.conf though) -- vila 20100916
 
1412
        conf = self._tree.branch.get_config()
 
1413
        conf_var_name = 'bzr.transform.orphan_policy'
 
1414
        orphan_policy = conf.get_user_option(conf_var_name)
 
1415
        default_policy = orphaning_registry.default_key
 
1416
        if orphan_policy is None:
 
1417
            orphan_policy = default_policy
 
1418
        if orphan_policy not in orphaning_registry:
 
1419
            trace.warning('%s (from %s) is not a known policy, defaulting '
 
1420
                'to %s' % (orphan_policy, conf_var_name, default_policy))
 
1421
            orphan_policy = default_policy
 
1422
        handle_orphan = orphaning_registry.get(orphan_policy)
 
1423
        handle_orphan(self, trans_id, parent_id)
 
1424
 
 
1425
 
 
1426
class OrphaningError(errors.BzrError):
 
1427
 
 
1428
    # Only bugs could lead to such exception being seen by the user
 
1429
    internal_error = True
 
1430
    _fmt = "Error while orphaning %s in %s directory"
 
1431
 
 
1432
    def __init__(self, orphan, parent):
 
1433
        errors.BzrError.__init__(self)
 
1434
        self.orphan = orphan
 
1435
        self.parent = parent
 
1436
 
 
1437
 
 
1438
class OrphaningForbidden(OrphaningError):
 
1439
 
 
1440
    _fmt = "Policy: %s doesn't allow creating orphans."
 
1441
 
 
1442
    def __init__(self, policy):
 
1443
        errors.BzrError.__init__(self)
 
1444
        self.policy = policy
 
1445
 
 
1446
 
 
1447
def move_orphan(tt, orphan_id, parent_id):
 
1448
    """See TreeTransformBase.new_orphan.
 
1449
 
 
1450
    This creates a new orphan in the `bzr-orphans` dir at the root of the
 
1451
    `TreeTransform`.
 
1452
 
 
1453
    :param tt: The TreeTransform orphaning `trans_id`.
 
1454
 
 
1455
    :param orphan_id: The trans id that should be orphaned.
 
1456
 
 
1457
    :param parent_id: The orphan parent trans id.
 
1458
    """
 
1459
    # Add the orphan dir if it doesn't exist
 
1460
    orphan_dir_basename = 'bzr-orphans'
 
1461
    od_id = tt.trans_id_tree_path(orphan_dir_basename)
 
1462
    if tt.final_kind(od_id) is None:
 
1463
        tt.create_directory(od_id)
 
1464
    parent_path = tt._tree_id_paths[parent_id]
 
1465
    # Find a name that doesn't exist yet in the orphan dir
 
1466
    actual_name = tt.final_name(orphan_id)
 
1467
    new_name = tt._available_backup_name(actual_name, od_id)
 
1468
    tt.adjust_path(new_name, od_id, orphan_id)
 
1469
    trace.warning('%s has been orphaned in %s'
 
1470
                  % (joinpath(parent_path, actual_name), orphan_dir_basename))
 
1471
 
 
1472
 
 
1473
def refuse_orphan(tt, orphan_id, parent_id):
 
1474
    """See TreeTransformBase.new_orphan.
 
1475
 
 
1476
    This refuses to create orphan, letting the caller handle the conflict.
 
1477
    """
 
1478
    raise OrphaningForbidden('never')
 
1479
 
 
1480
 
 
1481
orphaning_registry = registry.Registry()
 
1482
orphaning_registry.register(
 
1483
    'conflict', refuse_orphan,
 
1484
    'Leave orphans in place and create a conflict on the directory.')
 
1485
orphaning_registry.register(
 
1486
    'move', move_orphan,
 
1487
    'Move orphans into the bzr-orphans directory.')
 
1488
orphaning_registry._set_default_key('conflict')
 
1489
 
 
1490
 
 
1491
class TreeTransform(DiskTreeTransform):
 
1492
    """Represent a tree transformation.
 
1493
 
 
1494
    This object is designed to support incremental generation of the transform,
 
1495
    in any order.
 
1496
 
 
1497
    However, it gives optimum performance when parent directories are created
 
1498
    before their contents.  The transform is then able to put child files
 
1499
    directly in their parent directory, avoiding later renames.
 
1500
 
 
1501
    It is easy to produce malformed transforms, but they are generally
 
1502
    harmless.  Attempting to apply a malformed transform will cause an
 
1503
    exception to be raised before any modifications are made to the tree.
 
1504
 
 
1505
    Many kinds of malformed transforms can be corrected with the
 
1506
    resolve_conflicts function.  The remaining ones indicate programming error,
 
1507
    such as trying to create a file with no path.
 
1508
 
 
1509
    Two sets of file creation methods are supplied.  Convenience methods are:
 
1510
     * new_file
 
1511
     * new_directory
 
1512
     * new_symlink
 
1513
 
 
1514
    These are composed of the low-level methods:
 
1515
     * create_path
 
1516
     * create_file or create_directory or create_symlink
 
1517
     * version_file
 
1518
     * set_executability
 
1519
 
 
1520
    Transform/Transaction ids
 
1521
    -------------------------
 
1522
    trans_ids are temporary ids assigned to all files involved in a transform.
 
1523
    It's possible, even common, that not all files in the Tree have trans_ids.
 
1524
 
 
1525
    trans_ids are used because filenames and file_ids are not good enough
 
1526
    identifiers; filenames change, and not all files have file_ids.  File-ids
 
1527
    are also associated with trans-ids, so that moving a file moves its
 
1528
    file-id.
 
1529
 
 
1530
    trans_ids are only valid for the TreeTransform that generated them.
 
1531
 
 
1532
    Limbo
 
1533
    -----
 
1534
    Limbo is a temporary directory use to hold new versions of files.
 
1535
    Files are added to limbo by create_file, create_directory, create_symlink,
 
1536
    and their convenience variants (new_*).  Files may be removed from limbo
 
1537
    using cancel_creation.  Files are renamed from limbo into their final
 
1538
    location as part of TreeTransform.apply
 
1539
 
 
1540
    Limbo must be cleaned up, by either calling TreeTransform.apply or
 
1541
    calling TreeTransform.finalize.
 
1542
 
 
1543
    Files are placed into limbo inside their parent directories, where
 
1544
    possible.  This reduces subsequent renames, and makes operations involving
 
1545
    lots of files faster.  This optimization is only possible if the parent
 
1546
    directory is created *before* creating any of its children, so avoid
 
1547
    creating children before parents, where possible.
 
1548
 
 
1549
    Pending-deletion
 
1550
    ----------------
 
1551
    This temporary directory is used by _FileMover for storing files that are
 
1552
    about to be deleted.  In case of rollback, the files will be restored.
 
1553
    FileMover does not delete files until it is sure that a rollback will not
 
1554
    happen.
 
1555
    """
 
1556
    def __init__(self, tree, pb=None):
 
1557
        """Note: a tree_write lock is taken on the tree.
 
1558
 
 
1559
        Use TreeTransform.finalize() to release the lock (can be omitted if
 
1560
        TreeTransform.apply() called).
 
1561
        """
 
1562
        tree.lock_tree_write()
 
1563
 
 
1564
        try:
 
1565
            limbodir = urlutils.local_path_from_url(
 
1566
                tree._transport.abspath('limbo'))
 
1567
            try:
 
1568
                os.mkdir(limbodir)
 
1569
            except OSError, e:
 
1570
                if e.errno == errno.EEXIST:
 
1571
                    raise ExistingLimbo(limbodir)
 
1572
            deletiondir = urlutils.local_path_from_url(
 
1573
                tree._transport.abspath('pending-deletion'))
 
1574
            try:
 
1575
                os.mkdir(deletiondir)
 
1576
            except OSError, e:
 
1577
                if e.errno == errno.EEXIST:
 
1578
                    raise errors.ExistingPendingDeletion(deletiondir)
 
1579
        except:
 
1580
            tree.unlock()
 
1581
            raise
 
1582
 
 
1583
        # Cache of realpath results, to speed up canonical_path
 
1584
        self._realpaths = {}
 
1585
        # Cache of relpath results, to speed up canonical_path
 
1586
        self._relpaths = {}
 
1587
        DiskTreeTransform.__init__(self, tree, limbodir, pb,
 
1588
                                   tree.case_sensitive)
 
1589
        self._deletiondir = deletiondir
 
1590
 
 
1591
    def canonical_path(self, path):
 
1592
        """Get the canonical tree-relative path"""
 
1593
        # don't follow final symlinks
 
1594
        abs = self._tree.abspath(path)
 
1595
        if abs in self._relpaths:
 
1596
            return self._relpaths[abs]
 
1597
        dirname, basename = os.path.split(abs)
 
1598
        if dirname not in self._realpaths:
 
1599
            self._realpaths[dirname] = os.path.realpath(dirname)
 
1600
        dirname = self._realpaths[dirname]
 
1601
        abs = pathjoin(dirname, basename)
 
1602
        if dirname in self._relpaths:
 
1603
            relpath = pathjoin(self._relpaths[dirname], basename)
 
1604
            relpath = relpath.rstrip('/\\')
 
1605
        else:
 
1606
            relpath = self._tree.relpath(abs)
 
1607
        self._relpaths[abs] = relpath
 
1608
        return relpath
 
1609
 
 
1610
    def tree_kind(self, trans_id):
 
1611
        """Determine the file kind in the working tree.
 
1612
 
 
1613
        :returns: The file kind or None if the file does not exist
 
1614
        """
 
1615
        path = self._tree_id_paths.get(trans_id)
 
1616
        if path is None:
 
1617
            return None
 
1618
        try:
 
1619
            return file_kind(self._tree.abspath(path))
 
1620
        except errors.NoSuchFile:
 
1621
            return None
 
1622
 
 
1623
    def _set_mode(self, trans_id, mode_id, typefunc):
 
1624
        """Set the mode of new file contents.
 
1625
        The mode_id is the existing file to get the mode from (often the same
 
1626
        as trans_id).  The operation is only performed if there's a mode match
 
1627
        according to typefunc.
 
1628
        """
 
1629
        if mode_id is None:
 
1630
            mode_id = trans_id
 
1631
        try:
 
1632
            old_path = self._tree_id_paths[mode_id]
 
1633
        except KeyError:
 
1634
            return
 
1635
        try:
 
1636
            mode = os.stat(self._tree.abspath(old_path)).st_mode
 
1637
        except OSError, e:
 
1638
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
 
1639
                # Either old_path doesn't exist, or the parent of the
 
1640
                # target is not a directory (but will be one eventually)
 
1641
                # Either way, we know it doesn't exist *right now*
 
1642
                # See also bug #248448
 
1643
                return
 
1644
            else:
 
1645
                raise
 
1646
        if typefunc(mode):
 
1647
            os.chmod(self._limbo_name(trans_id), mode)
 
1648
 
 
1649
    def iter_tree_children(self, parent_id):
 
1650
        """Iterate through the entry's tree children, if any"""
 
1651
        try:
 
1652
            path = self._tree_id_paths[parent_id]
 
1653
        except KeyError:
 
1654
            return
 
1655
        try:
 
1656
            children = os.listdir(self._tree.abspath(path))
 
1657
        except OSError, e:
 
1658
            if not (osutils._is_error_enotdir(e)
 
1659
                    or e.errno in (errno.ENOENT, errno.ESRCH)):
 
1660
                raise
 
1661
            return
 
1662
 
 
1663
        for child in children:
 
1664
            childpath = joinpath(path, child)
 
1665
            if self._tree.is_control_filename(childpath):
 
1666
                continue
 
1667
            yield self.trans_id_tree_path(childpath)
 
1668
 
 
1669
    def _generate_limbo_path(self, trans_id):
 
1670
        """Generate a limbo path using the final path if possible.
 
1671
 
 
1672
        This optimizes the performance of applying the tree transform by
 
1673
        avoiding renames.  These renames can be avoided only when the parent
 
1674
        directory is already scheduled for creation.
 
1675
 
 
1676
        If the final path cannot be used, falls back to using the trans_id as
 
1677
        the relpath.
 
1678
        """
 
1679
        parent = self._new_parent.get(trans_id)
 
1680
        # if the parent directory is already in limbo (e.g. when building a
 
1681
        # tree), choose a limbo name inside the parent, to reduce further
 
1682
        # renames.
 
1683
        use_direct_path = False
 
1684
        if self._new_contents.get(parent) == 'directory':
 
1685
            filename = self._new_name.get(trans_id)
 
1686
            if filename is not None:
 
1687
                if parent not in self._limbo_children:
 
1688
                    self._limbo_children[parent] = set()
 
1689
                    self._limbo_children_names[parent] = {}
 
1690
                    use_direct_path = True
 
1691
                # the direct path can only be used if no other file has
 
1692
                # already taken this pathname, i.e. if the name is unused, or
 
1693
                # if it is already associated with this trans_id.
 
1694
                elif self._case_sensitive_target:
 
1695
                    if (self._limbo_children_names[parent].get(filename)
 
1696
                        in (trans_id, None)):
 
1697
                        use_direct_path = True
 
1698
                else:
 
1699
                    for l_filename, l_trans_id in\
 
1700
                        self._limbo_children_names[parent].iteritems():
 
1701
                        if l_trans_id == trans_id:
 
1702
                            continue
 
1703
                        if l_filename.lower() == filename.lower():
 
1704
                            break
 
1705
                    else:
 
1706
                        use_direct_path = True
 
1707
 
 
1708
        if not use_direct_path:
 
1709
            return DiskTreeTransform._generate_limbo_path(self, trans_id)
 
1710
 
 
1711
        limbo_name = pathjoin(self._limbo_files[parent], filename)
 
1712
        self._limbo_children[parent].add(trans_id)
 
1713
        self._limbo_children_names[parent][filename] = trans_id
 
1714
        return limbo_name
 
1715
 
 
1716
 
 
1717
    def apply(self, no_conflicts=False, precomputed_delta=None, _mover=None):
 
1718
        """Apply all changes to the inventory and filesystem.
 
1719
 
 
1720
        If filesystem or inventory conflicts are present, MalformedTransform
 
1721
        will be thrown.
 
1722
 
 
1723
        If apply succeeds, finalize is not necessary.
 
1724
 
 
1725
        :param no_conflicts: if True, the caller guarantees there are no
 
1726
            conflicts, so no check is made.
 
1727
        :param precomputed_delta: An inventory delta to use instead of
 
1728
            calculating one.
 
1729
        :param _mover: Supply an alternate FileMover, for testing
 
1730
        """
 
1731
        if not no_conflicts:
 
1732
            self._check_malformed()
 
1733
        child_pb = ui.ui_factory.nested_progress_bar()
 
1734
        try:
 
1735
            if precomputed_delta is None:
 
1736
                child_pb.update('Apply phase', 0, 2)
 
1737
                inventory_delta = self._generate_inventory_delta()
 
1738
                offset = 1
 
1739
            else:
 
1740
                inventory_delta = precomputed_delta
 
1741
                offset = 0
 
1742
            if _mover is None:
 
1743
                mover = _FileMover()
 
1744
            else:
 
1745
                mover = _mover
 
1746
            try:
 
1747
                child_pb.update('Apply phase', 0 + offset, 2 + offset)
 
1748
                self._apply_removals(mover)
 
1749
                child_pb.update('Apply phase', 1 + offset, 2 + offset)
 
1750
                modified_paths = self._apply_insertions(mover)
 
1751
            except:
 
1752
                mover.rollback()
 
1753
                raise
 
1754
            else:
 
1755
                mover.apply_deletions()
 
1756
        finally:
 
1757
            child_pb.finished()
 
1758
        self._tree.apply_inventory_delta(inventory_delta)
 
1759
        self._apply_observed_sha1s()
 
1760
        self._done = True
 
1761
        self.finalize()
 
1762
        return _TransformResults(modified_paths, self.rename_count)
 
1763
 
 
1764
    def _generate_inventory_delta(self):
 
1765
        """Generate an inventory delta for the current transform."""
 
1766
        inventory_delta = []
 
1767
        child_pb = ui.ui_factory.nested_progress_bar()
 
1768
        new_paths = self._inventory_altered()
 
1769
        total_entries = len(new_paths) + len(self._removed_id)
 
1770
        try:
 
1771
            for num, trans_id in enumerate(self._removed_id):
 
1772
                if (num % 10) == 0:
 
1773
                    child_pb.update('removing file', num, total_entries)
 
1774
                if trans_id == self._new_root:
 
1775
                    file_id = self._tree.get_root_id()
 
1776
                else:
 
1777
                    file_id = self.tree_file_id(trans_id)
 
1778
                # File-id isn't really being deleted, just moved
 
1779
                if file_id in self._r_new_id:
 
1780
                    continue
 
1781
                path = self._tree_id_paths[trans_id]
 
1782
                inventory_delta.append((path, None, file_id, None))
 
1783
            new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
 
1784
                                     new_paths)
 
1785
            entries = self._tree.iter_entries_by_dir(
 
1786
                new_path_file_ids.values())
 
1787
            old_paths = dict((e.file_id, p) for p, e in entries)
 
1788
            final_kinds = {}
 
1789
            for num, (path, trans_id) in enumerate(new_paths):
 
1790
                if (num % 10) == 0:
 
1791
                    child_pb.update('adding file',
 
1792
                                    num + len(self._removed_id), total_entries)
 
1793
                file_id = new_path_file_ids[trans_id]
 
1794
                if file_id is None:
 
1795
                    continue
 
1796
                needs_entry = False
 
1797
                kind = self.final_kind(trans_id)
 
1798
                if kind is None:
 
1799
                    kind = self._tree.stored_kind(file_id)
 
1800
                parent_trans_id = self.final_parent(trans_id)
 
1801
                parent_file_id = new_path_file_ids.get(parent_trans_id)
 
1802
                if parent_file_id is None:
 
1803
                    parent_file_id = self.final_file_id(parent_trans_id)
 
1804
                if trans_id in self._new_reference_revision:
 
1805
                    new_entry = inventory.TreeReference(
 
1806
                        file_id,
 
1807
                        self._new_name[trans_id],
 
1808
                        self.final_file_id(self._new_parent[trans_id]),
 
1809
                        None, self._new_reference_revision[trans_id])
 
1810
                else:
 
1811
                    new_entry = inventory.make_entry(kind,
 
1812
                        self.final_name(trans_id),
 
1813
                        parent_file_id, file_id)
 
1814
                old_path = old_paths.get(new_entry.file_id)
 
1815
                new_executability = self._new_executability.get(trans_id)
 
1816
                if new_executability is not None:
 
1817
                    new_entry.executable = new_executability
 
1818
                inventory_delta.append(
 
1819
                    (old_path, path, new_entry.file_id, new_entry))
 
1820
        finally:
 
1821
            child_pb.finished()
 
1822
        return inventory_delta
 
1823
 
 
1824
    def _apply_removals(self, mover):
 
1825
        """Perform tree operations that remove directory/inventory names.
 
1826
 
 
1827
        That is, delete files that are to be deleted, and put any files that
 
1828
        need renaming into limbo.  This must be done in strict child-to-parent
 
1829
        order.
 
1830
 
 
1831
        If inventory_delta is None, no inventory delta generation is performed.
 
1832
        """
 
1833
        tree_paths = list(self._tree_path_ids.iteritems())
 
1834
        tree_paths.sort(reverse=True)
 
1835
        child_pb = ui.ui_factory.nested_progress_bar()
 
1836
        try:
 
1837
            for num, (path, trans_id) in enumerate(tree_paths):
 
1838
                # do not attempt to move root into a subdirectory of itself.
 
1839
                if path == '':
 
1840
                    continue
 
1841
                child_pb.update('removing file', num, len(tree_paths))
 
1842
                full_path = self._tree.abspath(path)
 
1843
                if trans_id in self._removed_contents:
 
1844
                    delete_path = os.path.join(self._deletiondir, trans_id)
 
1845
                    mover.pre_delete(full_path, delete_path)
 
1846
                elif (trans_id in self._new_name
 
1847
                      or trans_id in self._new_parent):
 
1848
                    try:
 
1849
                        mover.rename(full_path, self._limbo_name(trans_id))
 
1850
                    except errors.TransformRenameFailed, e:
 
1851
                        if e.errno != errno.ENOENT:
 
1852
                            raise
 
1853
                    else:
 
1854
                        self.rename_count += 1
 
1855
        finally:
 
1856
            child_pb.finished()
 
1857
 
 
1858
    def _apply_insertions(self, mover):
 
1859
        """Perform tree operations that insert directory/inventory names.
 
1860
 
 
1861
        That is, create any files that need to be created, and restore from
 
1862
        limbo any files that needed renaming.  This must be done in strict
 
1863
        parent-to-child order.
 
1864
 
 
1865
        If inventory_delta is None, no inventory delta is calculated, and
 
1866
        no list of modified paths is returned.
 
1867
        """
 
1868
        new_paths = self.new_paths(filesystem_only=True)
 
1869
        modified_paths = []
 
1870
        new_path_file_ids = dict((t, self.final_file_id(t)) for p, t in
 
1871
                                 new_paths)
 
1872
        child_pb = ui.ui_factory.nested_progress_bar()
 
1873
        try:
 
1874
            for num, (path, trans_id) in enumerate(new_paths):
 
1875
                if (num % 10) == 0:
 
1876
                    child_pb.update('adding file', num, len(new_paths))
 
1877
                full_path = self._tree.abspath(path)
 
1878
                if trans_id in self._needs_rename:
 
1879
                    try:
 
1880
                        mover.rename(self._limbo_name(trans_id), full_path)
 
1881
                    except errors.TransformRenameFailed, e:
 
1882
                        # We may be renaming a dangling inventory id
 
1883
                        if e.errno != errno.ENOENT:
 
1884
                            raise
 
1885
                    else:
 
1886
                        self.rename_count += 1
 
1887
                    # TODO: if trans_id in self._observed_sha1s, we should
 
1888
                    #       re-stat the final target, since ctime will be
 
1889
                    #       updated by the change.
 
1890
                if (trans_id in self._new_contents or
 
1891
                    self.path_changed(trans_id)):
 
1892
                    if trans_id in self._new_contents:
 
1893
                        modified_paths.append(full_path)
 
1894
                if trans_id in self._new_executability:
 
1895
                    self._set_executability(path, trans_id)
 
1896
                if trans_id in self._observed_sha1s:
 
1897
                    o_sha1, o_st_val = self._observed_sha1s[trans_id]
 
1898
                    st = osutils.lstat(full_path)
 
1899
                    self._observed_sha1s[trans_id] = (o_sha1, st)
 
1900
        finally:
 
1901
            child_pb.finished()
 
1902
        for path, trans_id in new_paths:
 
1903
            # new_paths includes stuff like workingtree conflicts. Only the
 
1904
            # stuff in new_contents actually comes from limbo.
 
1905
            if trans_id in self._limbo_files:
 
1906
                del self._limbo_files[trans_id]
 
1907
        self._new_contents.clear()
 
1908
        return modified_paths
 
1909
 
 
1910
    def _apply_observed_sha1s(self):
 
1911
        """After we have finished renaming everything, update observed sha1s
 
1912
 
 
1913
        This has to be done after self._tree.apply_inventory_delta, otherwise
 
1914
        it doesn't know anything about the files we are updating. Also, we want
 
1915
        to do this as late as possible, so that most entries end up cached.
 
1916
        """
 
1917
        # TODO: this doesn't update the stat information for directories. So
 
1918
        #       the first 'bzr status' will still need to rewrite
 
1919
        #       .bzr/checkout/dirstate. However, we at least don't need to
 
1920
        #       re-read all of the files.
 
1921
        # TODO: If the operation took a while, we could do a time.sleep(3) here
 
1922
        #       to allow the clock to tick over and ensure we won't have any
 
1923
        #       problems. (we could observe start time, and finish time, and if
 
1924
        #       it is less than eg 10% overhead, add a sleep call.)
 
1925
        paths = FinalPaths(self)
 
1926
        for trans_id, observed in self._observed_sha1s.iteritems():
 
1927
            path = paths.get_path(trans_id)
 
1928
            # We could get the file_id, but dirstate prefers to use the path
 
1929
            # anyway, and it is 'cheaper' to determine.
 
1930
            # file_id = self._new_id[trans_id]
 
1931
            self._tree._observed_sha1(None, path, observed)
 
1932
 
 
1933
 
 
1934
class TransformPreview(DiskTreeTransform):
 
1935
    """A TreeTransform for generating preview trees.
 
1936
 
 
1937
    Unlike TreeTransform, this version works when the input tree is a
 
1938
    RevisionTree, rather than a WorkingTree.  As a result, it tends to ignore
 
1939
    unversioned files in the input tree.
 
1940
    """
 
1941
 
 
1942
    def __init__(self, tree, pb=None, case_sensitive=True):
 
1943
        tree.lock_read()
 
1944
        limbodir = osutils.mkdtemp(prefix='bzr-limbo-')
 
1945
        DiskTreeTransform.__init__(self, tree, limbodir, pb, case_sensitive)
 
1946
 
 
1947
    def canonical_path(self, path):
 
1948
        return path
 
1949
 
 
1950
    def tree_kind(self, trans_id):
 
1951
        path = self._tree_id_paths.get(trans_id)
 
1952
        if path is None:
 
1953
            return None
 
1954
        kind = self._tree.path_content_summary(path)[0]
 
1955
        if kind == 'missing':
 
1956
            kind = None
 
1957
        return kind
 
1958
 
 
1959
    def _set_mode(self, trans_id, mode_id, typefunc):
 
1960
        """Set the mode of new file contents.
 
1961
        The mode_id is the existing file to get the mode from (often the same
 
1962
        as trans_id).  The operation is only performed if there's a mode match
 
1963
        according to typefunc.
 
1964
        """
 
1965
        # is it ok to ignore this?  probably
 
1966
        pass
 
1967
 
 
1968
    def iter_tree_children(self, parent_id):
 
1969
        """Iterate through the entry's tree children, if any"""
 
1970
        try:
 
1971
            path = self._tree_id_paths[parent_id]
 
1972
        except KeyError:
 
1973
            return
 
1974
        file_id = self.tree_file_id(parent_id)
 
1975
        if file_id is None:
 
1976
            return
 
1977
        entry = self._tree.iter_entries_by_dir([file_id]).next()[1]
 
1978
        children = getattr(entry, 'children', {})
 
1979
        for child in children:
 
1980
            childpath = joinpath(path, child)
 
1981
            yield self.trans_id_tree_path(childpath)
 
1982
 
 
1983
    def new_orphan(self, trans_id, parent_id):
 
1984
        raise NotImplementedError(self.new_orphan)
 
1985
 
 
1986
 
 
1987
class _PreviewTree(tree.InventoryTree):
 
1988
    """Partial implementation of Tree to support show_diff_trees"""
 
1989
 
 
1990
    def __init__(self, transform):
 
1991
        self._transform = transform
 
1992
        self._final_paths = FinalPaths(transform)
 
1993
        self.__by_parent = None
 
1994
        self._parent_ids = []
 
1995
        self._all_children_cache = {}
 
1996
        self._path2trans_id_cache = {}
 
1997
        self._final_name_cache = {}
 
1998
        self._iter_changes_cache = dict((c[0], c) for c in
 
1999
                                        self._transform.iter_changes())
 
2000
 
 
2001
    def _content_change(self, file_id):
 
2002
        """Return True if the content of this file changed"""
 
2003
        changes = self._iter_changes_cache.get(file_id)
 
2004
        # changes[2] is true if the file content changed.  See
 
2005
        # InterTree.iter_changes.
 
2006
        return (changes is not None and changes[2])
 
2007
 
 
2008
    def _get_repository(self):
 
2009
        repo = getattr(self._transform._tree, '_repository', None)
 
2010
        if repo is None:
 
2011
            repo = self._transform._tree.branch.repository
 
2012
        return repo
 
2013
 
 
2014
    def _iter_parent_trees(self):
 
2015
        for revision_id in self.get_parent_ids():
 
2016
            try:
 
2017
                yield self.revision_tree(revision_id)
 
2018
            except errors.NoSuchRevisionInTree:
 
2019
                yield self._get_repository().revision_tree(revision_id)
 
2020
 
 
2021
    def _get_file_revision(self, file_id, vf, tree_revision):
 
2022
        parent_keys = [(file_id, t.get_file_revision(file_id)) for t in
 
2023
                       self._iter_parent_trees()]
 
2024
        vf.add_lines((file_id, tree_revision), parent_keys,
 
2025
                     self.get_file_lines(file_id))
 
2026
        repo = self._get_repository()
 
2027
        base_vf = repo.texts
 
2028
        if base_vf not in vf.fallback_versionedfiles:
 
2029
            vf.fallback_versionedfiles.append(base_vf)
 
2030
        return tree_revision
 
2031
 
 
2032
    def _stat_limbo_file(self, file_id=None, trans_id=None):
 
2033
        if trans_id is None:
 
2034
            trans_id = self._transform.trans_id_file_id(file_id)
 
2035
        name = self._transform._limbo_name(trans_id)
 
2036
        return os.lstat(name)
 
2037
 
 
2038
    @property
 
2039
    def _by_parent(self):
 
2040
        if self.__by_parent is None:
 
2041
            self.__by_parent = self._transform.by_parent()
 
2042
        return self.__by_parent
 
2043
 
 
2044
    def _comparison_data(self, entry, path):
 
2045
        kind, size, executable, link_or_sha1 = self.path_content_summary(path)
 
2046
        if kind == 'missing':
 
2047
            kind = None
 
2048
            executable = False
 
2049
        else:
 
2050
            file_id = self._transform.final_file_id(self._path2trans_id(path))
 
2051
            executable = self.is_executable(file_id, path)
 
2052
        return kind, executable, None
 
2053
 
 
2054
    def is_locked(self):
 
2055
        return False
 
2056
 
 
2057
    def lock_read(self):
 
2058
        # Perhaps in theory, this should lock the TreeTransform?
 
2059
        return self
 
2060
 
 
2061
    def unlock(self):
 
2062
        pass
 
2063
 
 
2064
    @property
 
2065
    def inventory(self):
 
2066
        """This Tree does not use inventory as its backing data."""
 
2067
        raise NotImplementedError(_PreviewTree.inventory)
 
2068
 
 
2069
    def get_root_id(self):
 
2070
        return self._transform.final_file_id(self._transform.root)
 
2071
 
 
2072
    def all_file_ids(self):
 
2073
        tree_ids = set(self._transform._tree.all_file_ids())
 
2074
        tree_ids.difference_update(self._transform.tree_file_id(t)
 
2075
                                   for t in self._transform._removed_id)
 
2076
        tree_ids.update(self._transform._new_id.values())
 
2077
        return tree_ids
 
2078
 
 
2079
    def __iter__(self):
 
2080
        return iter(self.all_file_ids())
 
2081
 
 
2082
    def _has_id(self, file_id, fallback_check):
 
2083
        if file_id in self._transform._r_new_id:
 
2084
            return True
 
2085
        elif file_id in set([self._transform.tree_file_id(trans_id) for
 
2086
            trans_id in self._transform._removed_id]):
 
2087
            return False
 
2088
        else:
 
2089
            return fallback_check(file_id)
 
2090
 
 
2091
    def has_id(self, file_id):
 
2092
        return self._has_id(file_id, self._transform._tree.has_id)
 
2093
 
 
2094
    def has_or_had_id(self, file_id):
 
2095
        return self._has_id(file_id, self._transform._tree.has_or_had_id)
 
2096
 
 
2097
    def _path2trans_id(self, path):
 
2098
        # We must not use None here, because that is a valid value to store.
 
2099
        trans_id = self._path2trans_id_cache.get(path, object)
 
2100
        if trans_id is not object:
 
2101
            return trans_id
 
2102
        segments = splitpath(path)
 
2103
        cur_parent = self._transform.root
 
2104
        for cur_segment in segments:
 
2105
            for child in self._all_children(cur_parent):
 
2106
                final_name = self._final_name_cache.get(child)
 
2107
                if final_name is None:
 
2108
                    final_name = self._transform.final_name(child)
 
2109
                    self._final_name_cache[child] = final_name
 
2110
                if final_name == cur_segment:
 
2111
                    cur_parent = child
 
2112
                    break
 
2113
            else:
 
2114
                self._path2trans_id_cache[path] = None
 
2115
                return None
 
2116
        self._path2trans_id_cache[path] = cur_parent
 
2117
        return cur_parent
 
2118
 
 
2119
    def path2id(self, path):
 
2120
        return self._transform.final_file_id(self._path2trans_id(path))
 
2121
 
 
2122
    def id2path(self, file_id):
 
2123
        trans_id = self._transform.trans_id_file_id(file_id)
 
2124
        try:
 
2125
            return self._final_paths._determine_path(trans_id)
 
2126
        except NoFinalPath:
 
2127
            raise errors.NoSuchId(self, file_id)
 
2128
 
 
2129
    def _all_children(self, trans_id):
 
2130
        children = self._all_children_cache.get(trans_id)
 
2131
        if children is not None:
 
2132
            return children
 
2133
        children = set(self._transform.iter_tree_children(trans_id))
 
2134
        # children in the _new_parent set are provided by _by_parent.
 
2135
        children.difference_update(self._transform._new_parent.keys())
 
2136
        children.update(self._by_parent.get(trans_id, []))
 
2137
        self._all_children_cache[trans_id] = children
 
2138
        return children
 
2139
 
 
2140
    def iter_children(self, file_id):
 
2141
        trans_id = self._transform.trans_id_file_id(file_id)
 
2142
        for child_trans_id in self._all_children(trans_id):
 
2143
            yield self._transform.final_file_id(child_trans_id)
 
2144
 
 
2145
    def extras(self):
 
2146
        possible_extras = set(self._transform.trans_id_tree_path(p) for p
 
2147
                              in self._transform._tree.extras())
 
2148
        possible_extras.update(self._transform._new_contents)
 
2149
        possible_extras.update(self._transform._removed_id)
 
2150
        for trans_id in possible_extras:
 
2151
            if self._transform.final_file_id(trans_id) is None:
 
2152
                yield self._final_paths._determine_path(trans_id)
 
2153
 
 
2154
    def _make_inv_entries(self, ordered_entries, specific_file_ids=None,
 
2155
        yield_parents=False):
 
2156
        for trans_id, parent_file_id in ordered_entries:
 
2157
            file_id = self._transform.final_file_id(trans_id)
 
2158
            if file_id is None:
 
2159
                continue
 
2160
            if (specific_file_ids is not None
 
2161
                and file_id not in specific_file_ids):
 
2162
                continue
 
2163
            kind = self._transform.final_kind(trans_id)
 
2164
            if kind is None:
 
2165
                kind = self._transform._tree.stored_kind(file_id)
 
2166
            new_entry = inventory.make_entry(
 
2167
                kind,
 
2168
                self._transform.final_name(trans_id),
 
2169
                parent_file_id, file_id)
 
2170
            yield new_entry, trans_id
 
2171
 
 
2172
    def _list_files_by_dir(self):
 
2173
        todo = [ROOT_PARENT]
 
2174
        ordered_ids = []
 
2175
        while len(todo) > 0:
 
2176
            parent = todo.pop()
 
2177
            parent_file_id = self._transform.final_file_id(parent)
 
2178
            children = list(self._all_children(parent))
 
2179
            paths = dict(zip(children, self._final_paths.get_paths(children)))
 
2180
            children.sort(key=paths.get)
 
2181
            todo.extend(reversed(children))
 
2182
            for trans_id in children:
 
2183
                ordered_ids.append((trans_id, parent_file_id))
 
2184
        return ordered_ids
 
2185
 
 
2186
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
 
2187
        # This may not be a maximally efficient implementation, but it is
 
2188
        # reasonably straightforward.  An implementation that grafts the
 
2189
        # TreeTransform changes onto the tree's iter_entries_by_dir results
 
2190
        # might be more efficient, but requires tricky inferences about stack
 
2191
        # position.
 
2192
        ordered_ids = self._list_files_by_dir()
 
2193
        for entry, trans_id in self._make_inv_entries(ordered_ids,
 
2194
            specific_file_ids, yield_parents=yield_parents):
 
2195
            yield unicode(self._final_paths.get_path(trans_id)), entry
 
2196
 
 
2197
    def _iter_entries_for_dir(self, dir_path):
 
2198
        """Return path, entry for items in a directory without recursing down."""
 
2199
        dir_file_id = self.path2id(dir_path)
 
2200
        ordered_ids = []
 
2201
        for file_id in self.iter_children(dir_file_id):
 
2202
            trans_id = self._transform.trans_id_file_id(file_id)
 
2203
            ordered_ids.append((trans_id, file_id))
 
2204
        for entry, trans_id in self._make_inv_entries(ordered_ids):
 
2205
            yield unicode(self._final_paths.get_path(trans_id)), entry
 
2206
 
 
2207
    def list_files(self, include_root=False, from_dir=None, recursive=True):
 
2208
        """See WorkingTree.list_files."""
 
2209
        # XXX This should behave like WorkingTree.list_files, but is really
 
2210
        # more like RevisionTree.list_files.
 
2211
        if recursive:
 
2212
            prefix = None
 
2213
            if from_dir:
 
2214
                prefix = from_dir + '/'
 
2215
            entries = self.iter_entries_by_dir()
 
2216
            for path, entry in entries:
 
2217
                if entry.name == '' and not include_root:
 
2218
                    continue
 
2219
                if prefix:
 
2220
                    if not path.startswith(prefix):
 
2221
                        continue
 
2222
                    path = path[len(prefix):]
 
2223
                yield path, 'V', entry.kind, entry.file_id, entry
 
2224
        else:
 
2225
            if from_dir is None and include_root is True:
 
2226
                root_entry = inventory.make_entry('directory', '',
 
2227
                    ROOT_PARENT, self.get_root_id())
 
2228
                yield '', 'V', 'directory', root_entry.file_id, root_entry
 
2229
            entries = self._iter_entries_for_dir(from_dir or '')
 
2230
            for path, entry in entries:
 
2231
                yield path, 'V', entry.kind, entry.file_id, entry
 
2232
 
 
2233
    def kind(self, file_id):
 
2234
        trans_id = self._transform.trans_id_file_id(file_id)
 
2235
        return self._transform.final_kind(trans_id)
 
2236
 
 
2237
    def stored_kind(self, file_id):
 
2238
        trans_id = self._transform.trans_id_file_id(file_id)
 
2239
        try:
 
2240
            return self._transform._new_contents[trans_id]
 
2241
        except KeyError:
 
2242
            return self._transform._tree.stored_kind(file_id)
 
2243
 
 
2244
    def get_file_mtime(self, file_id, path=None):
 
2245
        """See Tree.get_file_mtime"""
 
2246
        if not self._content_change(file_id):
 
2247
            return self._transform._tree.get_file_mtime(file_id)
 
2248
        return self._stat_limbo_file(file_id).st_mtime
 
2249
 
 
2250
    def _file_size(self, entry, stat_value):
 
2251
        return self.get_file_size(entry.file_id)
 
2252
 
 
2253
    def get_file_size(self, file_id):
 
2254
        """See Tree.get_file_size"""
 
2255
        trans_id = self._transform.trans_id_file_id(file_id)
 
2256
        kind = self._transform.final_kind(trans_id)
 
2257
        if kind != 'file':
 
2258
            return None
 
2259
        if trans_id in self._transform._new_contents:
 
2260
            return self._stat_limbo_file(trans_id=trans_id).st_size
 
2261
        if self.kind(file_id) == 'file':
 
2262
            return self._transform._tree.get_file_size(file_id)
 
2263
        else:
 
2264
            return None
 
2265
 
 
2266
    def get_file_sha1(self, file_id, path=None, stat_value=None):
 
2267
        trans_id = self._transform.trans_id_file_id(file_id)
 
2268
        kind = self._transform._new_contents.get(trans_id)
 
2269
        if kind is None:
 
2270
            return self._transform._tree.get_file_sha1(file_id)
 
2271
        if kind == 'file':
 
2272
            fileobj = self.get_file(file_id)
 
2273
            try:
 
2274
                return sha_file(fileobj)
 
2275
            finally:
 
2276
                fileobj.close()
 
2277
 
 
2278
    def is_executable(self, file_id, path=None):
 
2279
        if file_id is None:
 
2280
            return False
 
2281
        trans_id = self._transform.trans_id_file_id(file_id)
 
2282
        try:
 
2283
            return self._transform._new_executability[trans_id]
 
2284
        except KeyError:
 
2285
            try:
 
2286
                return self._transform._tree.is_executable(file_id, path)
 
2287
            except OSError, e:
 
2288
                if e.errno == errno.ENOENT:
 
2289
                    return False
 
2290
                raise
 
2291
            except errors.NoSuchId:
 
2292
                return False
 
2293
 
 
2294
    def has_filename(self, path):
 
2295
        trans_id = self._path2trans_id(path)
 
2296
        if trans_id in self._transform._new_contents:
 
2297
            return True
 
2298
        elif trans_id in self._transform._removed_contents:
 
2299
            return False
 
2300
        else:
 
2301
            return self._transform._tree.has_filename(path)
 
2302
 
 
2303
    def path_content_summary(self, path):
 
2304
        trans_id = self._path2trans_id(path)
 
2305
        tt = self._transform
 
2306
        tree_path = tt._tree_id_paths.get(trans_id)
 
2307
        kind = tt._new_contents.get(trans_id)
 
2308
        if kind is None:
 
2309
            if tree_path is None or trans_id in tt._removed_contents:
 
2310
                return 'missing', None, None, None
 
2311
            summary = tt._tree.path_content_summary(tree_path)
 
2312
            kind, size, executable, link_or_sha1 = summary
 
2313
        else:
 
2314
            link_or_sha1 = None
 
2315
            limbo_name = tt._limbo_name(trans_id)
 
2316
            if trans_id in tt._new_reference_revision:
 
2317
                kind = 'tree-reference'
 
2318
            if kind == 'file':
 
2319
                statval = os.lstat(limbo_name)
 
2320
                size = statval.st_size
 
2321
                if not supports_executable():
 
2322
                    executable = False
 
2323
                else:
 
2324
                    executable = statval.st_mode & S_IEXEC
 
2325
            else:
 
2326
                size = None
 
2327
                executable = None
 
2328
            if kind == 'symlink':
 
2329
                link_or_sha1 = os.readlink(limbo_name).decode(osutils._fs_enc)
 
2330
        executable = tt._new_executability.get(trans_id, executable)
 
2331
        return kind, size, executable, link_or_sha1
 
2332
 
 
2333
    def iter_changes(self, from_tree, include_unchanged=False,
 
2334
                      specific_files=None, pb=None, extra_trees=None,
 
2335
                      require_versioned=True, want_unversioned=False):
 
2336
        """See InterTree.iter_changes.
 
2337
 
 
2338
        This has a fast path that is only used when the from_tree matches
 
2339
        the transform tree, and no fancy options are supplied.
 
2340
        """
 
2341
        if (from_tree is not self._transform._tree or include_unchanged or
 
2342
            specific_files or want_unversioned):
 
2343
            return tree.InterTree(from_tree, self).iter_changes(
 
2344
                include_unchanged=include_unchanged,
 
2345
                specific_files=specific_files,
 
2346
                pb=pb,
 
2347
                extra_trees=extra_trees,
 
2348
                require_versioned=require_versioned,
 
2349
                want_unversioned=want_unversioned)
 
2350
        if want_unversioned:
 
2351
            raise ValueError('want_unversioned is not supported')
 
2352
        return self._transform.iter_changes()
 
2353
 
 
2354
    def get_file(self, file_id, path=None):
 
2355
        """See Tree.get_file"""
 
2356
        if not self._content_change(file_id):
 
2357
            return self._transform._tree.get_file(file_id, path)
 
2358
        trans_id = self._transform.trans_id_file_id(file_id)
 
2359
        name = self._transform._limbo_name(trans_id)
 
2360
        return open(name, 'rb')
 
2361
 
 
2362
    def get_file_with_stat(self, file_id, path=None):
 
2363
        return self.get_file(file_id, path), None
 
2364
 
 
2365
    def annotate_iter(self, file_id,
 
2366
                      default_revision=_mod_revision.CURRENT_REVISION):
 
2367
        changes = self._iter_changes_cache.get(file_id)
 
2368
        if changes is None:
 
2369
            get_old = True
 
2370
        else:
 
2371
            changed_content, versioned, kind = (changes[2], changes[3],
 
2372
                                                changes[6])
 
2373
            if kind[1] is None:
 
2374
                return None
 
2375
            get_old = (kind[0] == 'file' and versioned[0])
 
2376
        if get_old:
 
2377
            old_annotation = self._transform._tree.annotate_iter(file_id,
 
2378
                default_revision=default_revision)
 
2379
        else:
 
2380
            old_annotation = []
 
2381
        if changes is None:
 
2382
            return old_annotation
 
2383
        if not changed_content:
 
2384
            return old_annotation
 
2385
        # TODO: This is doing something similar to what WT.annotate_iter is
 
2386
        #       doing, however it fails slightly because it doesn't know what
 
2387
        #       the *other* revision_id is, so it doesn't know how to give the
 
2388
        #       other as the origin for some lines, they all get
 
2389
        #       'default_revision'
 
2390
        #       It would be nice to be able to use the new Annotator based
 
2391
        #       approach, as well.
 
2392
        return annotate.reannotate([old_annotation],
 
2393
                                   self.get_file(file_id).readlines(),
 
2394
                                   default_revision)
 
2395
 
 
2396
    def get_symlink_target(self, file_id, path=None):
 
2397
        """See Tree.get_symlink_target"""
 
2398
        if not self._content_change(file_id):
 
2399
            return self._transform._tree.get_symlink_target(file_id)
 
2400
        trans_id = self._transform.trans_id_file_id(file_id)
 
2401
        name = self._transform._limbo_name(trans_id)
 
2402
        return osutils.readlink(name)
 
2403
 
 
2404
    def walkdirs(self, prefix=''):
 
2405
        pending = [self._transform.root]
 
2406
        while len(pending) > 0:
 
2407
            parent_id = pending.pop()
 
2408
            children = []
 
2409
            subdirs = []
 
2410
            prefix = prefix.rstrip('/')
 
2411
            parent_path = self._final_paths.get_path(parent_id)
 
2412
            parent_file_id = self._transform.final_file_id(parent_id)
 
2413
            for child_id in self._all_children(parent_id):
 
2414
                path_from_root = self._final_paths.get_path(child_id)
 
2415
                basename = self._transform.final_name(child_id)
 
2416
                file_id = self._transform.final_file_id(child_id)
 
2417
                kind  = self._transform.final_kind(child_id)
 
2418
                if kind is not None:
 
2419
                    versioned_kind = kind
 
2420
                else:
 
2421
                    kind = 'unknown'
 
2422
                    versioned_kind = self._transform._tree.stored_kind(file_id)
 
2423
                if versioned_kind == 'directory':
 
2424
                    subdirs.append(child_id)
 
2425
                children.append((path_from_root, basename, kind, None,
 
2426
                                 file_id, versioned_kind))
 
2427
            children.sort()
 
2428
            if parent_path.startswith(prefix):
 
2429
                yield (parent_path, parent_file_id), children
 
2430
            pending.extend(sorted(subdirs, key=self._final_paths.get_path,
 
2431
                                  reverse=True))
 
2432
 
 
2433
    def get_parent_ids(self):
 
2434
        return self._parent_ids
 
2435
 
 
2436
    def set_parent_ids(self, parent_ids):
 
2437
        self._parent_ids = parent_ids
 
2438
 
 
2439
    def get_revision_tree(self, revision_id):
 
2440
        return self._transform._tree.get_revision_tree(revision_id)
 
2441
 
 
2442
 
864
2443
def joinpath(parent, child):
865
2444
    """Join tree-relative paths, handling the tree root specially"""
866
2445
    if parent is None or parent == "":
881
2460
        self.transform = transform
882
2461
 
883
2462
    def _determine_path(self, trans_id):
884
 
        if trans_id == self.transform.root:
 
2463
        if (trans_id == self.transform.root or trans_id == ROOT_PARENT):
885
2464
            return ""
886
2465
        name = self.transform.final_name(trans_id)
887
2466
        parent_id = self.transform.final_parent(trans_id)
896
2475
            self._known_paths[trans_id] = self._determine_path(trans_id)
897
2476
        return self._known_paths[trans_id]
898
2477
 
 
2478
    def get_paths(self, trans_ids):
 
2479
        return [(self.get_path(t), t) for t in trans_ids]
 
2480
 
 
2481
 
 
2482
 
899
2483
def topology_sorted_ids(tree):
900
2484
    """Determine the topological order of the ids in a tree"""
901
2485
    file_ids = list(tree)
902
2486
    file_ids.sort(key=tree.id2path)
903
2487
    return file_ids
904
2488
 
905
 
def build_tree(tree, wt):
906
 
    """Create working tree for a branch, using a Transaction."""
 
2489
 
 
2490
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
 
2491
               delta_from_tree=False):
 
2492
    """Create working tree for a branch, using a TreeTransform.
 
2493
 
 
2494
    This function should be used on empty trees, having a tree root at most.
 
2495
    (see merge and revert functionality for working with existing trees)
 
2496
 
 
2497
    Existing files are handled like so:
 
2498
 
 
2499
    - Existing bzrdirs take precedence over creating new items.  They are
 
2500
      created as '%s.diverted' % name.
 
2501
    - Otherwise, if the content on disk matches the content we are building,
 
2502
      it is silently replaced.
 
2503
    - Otherwise, conflict resolution will move the old file to 'oldname.moved'.
 
2504
 
 
2505
    :param tree: The tree to convert wt into a copy of
 
2506
    :param wt: The working tree that files will be placed into
 
2507
    :param accelerator_tree: A tree which can be used for retrieving file
 
2508
        contents more quickly than tree itself, i.e. a workingtree.  tree
 
2509
        will be used for cases where accelerator_tree's content is different.
 
2510
    :param hardlink: If true, hard-link files to accelerator_tree, where
 
2511
        possible.  accelerator_tree must implement abspath, i.e. be a
 
2512
        working tree.
 
2513
    :param delta_from_tree: If true, build_tree may use the input Tree to
 
2514
        generate the inventory delta.
 
2515
    """
 
2516
    wt.lock_tree_write()
 
2517
    try:
 
2518
        tree.lock_read()
 
2519
        try:
 
2520
            if accelerator_tree is not None:
 
2521
                accelerator_tree.lock_read()
 
2522
            try:
 
2523
                return _build_tree(tree, wt, accelerator_tree, hardlink,
 
2524
                                   delta_from_tree)
 
2525
            finally:
 
2526
                if accelerator_tree is not None:
 
2527
                    accelerator_tree.unlock()
 
2528
        finally:
 
2529
            tree.unlock()
 
2530
    finally:
 
2531
        wt.unlock()
 
2532
 
 
2533
 
 
2534
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
 
2535
    """See build_tree."""
 
2536
    for num, _unused in enumerate(wt.all_file_ids()):
 
2537
        if num > 0:  # more than just a root
 
2538
            raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
907
2539
    file_trans_id = {}
908
 
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2540
    top_pb = ui.ui_factory.nested_progress_bar()
909
2541
    pp = ProgressPhase("Build phase", 2, top_pb)
 
2542
    if tree.inventory.root is not None:
 
2543
        # This is kind of a hack: we should be altering the root
 
2544
        # as part of the regular tree shape diff logic.
 
2545
        # The conditional test here is to avoid doing an
 
2546
        # expensive operation (flush) every time the root id
 
2547
        # is set within the tree, nor setting the root and thus
 
2548
        # marking the tree as dirty, because we use two different
 
2549
        # idioms here: tree interfaces and inventory interfaces.
 
2550
        if wt.get_root_id() != tree.get_root_id():
 
2551
            wt.set_root_id(tree.get_root_id())
 
2552
            wt.flush()
910
2553
    tt = TreeTransform(wt)
 
2554
    divert = set()
911
2555
    try:
912
2556
        pp.next_phase()
913
 
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
914
 
        file_ids = topology_sorted_ids(tree)
915
 
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
2557
        file_trans_id[wt.get_root_id()] = \
 
2558
            tt.trans_id_tree_file_id(wt.get_root_id())
 
2559
        pb = ui.ui_factory.nested_progress_bar()
916
2560
        try:
917
 
            for num, file_id in enumerate(file_ids):
918
 
                pb.update("Building tree", num, len(file_ids))
919
 
                entry = tree.inventory[file_id]
 
2561
            deferred_contents = []
 
2562
            num = 0
 
2563
            total = len(tree.inventory)
 
2564
            if delta_from_tree:
 
2565
                precomputed_delta = []
 
2566
            else:
 
2567
                precomputed_delta = None
 
2568
            # Check if tree inventory has content. If so, we populate
 
2569
            # existing_files with the directory content. If there are no
 
2570
            # entries we skip populating existing_files as its not used.
 
2571
            # This improves performance and unncessary work on large
 
2572
            # directory trees. (#501307)
 
2573
            if total > 0:
 
2574
                existing_files = set()
 
2575
                for dir, files in wt.walkdirs():
 
2576
                    existing_files.update(f[0] for f in files)
 
2577
            for num, (tree_path, entry) in \
 
2578
                enumerate(tree.inventory.iter_entries_by_dir()):
 
2579
                pb.update("Building tree", num - len(deferred_contents), total)
920
2580
                if entry.parent_id is None:
921
2581
                    continue
922
 
                if entry.parent_id not in file_trans_id:
923
 
                    raise repr(entry.parent_id)
 
2582
                reparent = False
 
2583
                file_id = entry.file_id
 
2584
                if delta_from_tree:
 
2585
                    precomputed_delta.append((None, tree_path, file_id, entry))
 
2586
                if tree_path in existing_files:
 
2587
                    target_path = wt.abspath(tree_path)
 
2588
                    kind = file_kind(target_path)
 
2589
                    if kind == "directory":
 
2590
                        try:
 
2591
                            bzrdir.BzrDir.open(target_path)
 
2592
                        except errors.NotBranchError:
 
2593
                            pass
 
2594
                        else:
 
2595
                            divert.add(file_id)
 
2596
                    if (file_id not in divert and
 
2597
                        _content_match(tree, entry, file_id, kind,
 
2598
                        target_path)):
 
2599
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
 
2600
                        if kind == 'directory':
 
2601
                            reparent = True
924
2602
                parent_id = file_trans_id[entry.parent_id]
925
 
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, 
926
 
                                                      tree)
 
2603
                if entry.kind == 'file':
 
2604
                    # We *almost* replicate new_by_entry, so that we can defer
 
2605
                    # getting the file text, and get them all at once.
 
2606
                    trans_id = tt.create_path(entry.name, parent_id)
 
2607
                    file_trans_id[file_id] = trans_id
 
2608
                    tt.version_file(file_id, trans_id)
 
2609
                    executable = tree.is_executable(file_id, tree_path)
 
2610
                    if executable:
 
2611
                        tt.set_executability(executable, trans_id)
 
2612
                    trans_data = (trans_id, tree_path, entry.text_sha1)
 
2613
                    deferred_contents.append((file_id, trans_data))
 
2614
                else:
 
2615
                    file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
 
2616
                                                          tree)
 
2617
                if reparent:
 
2618
                    new_trans_id = file_trans_id[file_id]
 
2619
                    old_parent = tt.trans_id_tree_path(tree_path)
 
2620
                    _reparent_children(tt, old_parent, new_trans_id)
 
2621
            offset = num + 1 - len(deferred_contents)
 
2622
            _create_files(tt, tree, deferred_contents, pb, offset,
 
2623
                          accelerator_tree, hardlink)
927
2624
        finally:
928
2625
            pb.finished()
929
2626
        pp.next_phase()
930
 
        tt.apply()
 
2627
        divert_trans = set(file_trans_id[f] for f in divert)
 
2628
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
 
2629
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
 
2630
        if len(raw_conflicts) > 0:
 
2631
            precomputed_delta = None
 
2632
        conflicts = cook_conflicts(raw_conflicts, tt)
 
2633
        for conflict in conflicts:
 
2634
            trace.warning(unicode(conflict))
 
2635
        try:
 
2636
            wt.add_conflicts(conflicts)
 
2637
        except errors.UnsupportedOperation:
 
2638
            pass
 
2639
        result = tt.apply(no_conflicts=True,
 
2640
                          precomputed_delta=precomputed_delta)
931
2641
    finally:
932
2642
        tt.finalize()
933
2643
        top_pb.finished()
 
2644
    return result
 
2645
 
 
2646
 
 
2647
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
 
2648
                  hardlink):
 
2649
    total = len(desired_files) + offset
 
2650
    wt = tt._tree
 
2651
    if accelerator_tree is None:
 
2652
        new_desired_files = desired_files
 
2653
    else:
 
2654
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
 
2655
        unchanged = [(f, p[1]) for (f, p, c, v, d, n, k, e)
 
2656
                     in iter if not (c or e[0] != e[1])]
 
2657
        if accelerator_tree.supports_content_filtering():
 
2658
            unchanged = [(f, p) for (f, p) in unchanged
 
2659
                         if not accelerator_tree.iter_search_rules([p]).next()]
 
2660
        unchanged = dict(unchanged)
 
2661
        new_desired_files = []
 
2662
        count = 0
 
2663
        for file_id, (trans_id, tree_path, text_sha1) in desired_files:
 
2664
            accelerator_path = unchanged.get(file_id)
 
2665
            if accelerator_path is None:
 
2666
                new_desired_files.append((file_id,
 
2667
                    (trans_id, tree_path, text_sha1)))
 
2668
                continue
 
2669
            pb.update('Adding file contents', count + offset, total)
 
2670
            if hardlink:
 
2671
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
 
2672
                                   trans_id)
 
2673
            else:
 
2674
                contents = accelerator_tree.get_file(file_id, accelerator_path)
 
2675
                if wt.supports_content_filtering():
 
2676
                    filters = wt._content_filter_stack(tree_path)
 
2677
                    contents = filtered_output_bytes(contents, filters,
 
2678
                        ContentFilterContext(tree_path, tree))
 
2679
                try:
 
2680
                    tt.create_file(contents, trans_id, sha1=text_sha1)
 
2681
                finally:
 
2682
                    try:
 
2683
                        contents.close()
 
2684
                    except AttributeError:
 
2685
                        # after filtering, contents may no longer be file-like
 
2686
                        pass
 
2687
            count += 1
 
2688
        offset += count
 
2689
    for count, ((trans_id, tree_path, text_sha1), contents) in enumerate(
 
2690
            tree.iter_files_bytes(new_desired_files)):
 
2691
        if wt.supports_content_filtering():
 
2692
            filters = wt._content_filter_stack(tree_path)
 
2693
            contents = filtered_output_bytes(contents, filters,
 
2694
                ContentFilterContext(tree_path, tree))
 
2695
        tt.create_file(contents, trans_id, sha1=text_sha1)
 
2696
        pb.update('Adding file contents', count + offset, total)
 
2697
 
 
2698
 
 
2699
def _reparent_children(tt, old_parent, new_parent):
 
2700
    for child in tt.iter_tree_children(old_parent):
 
2701
        tt.adjust_path(tt.final_name(child), new_parent, child)
 
2702
 
 
2703
 
 
2704
def _reparent_transform_children(tt, old_parent, new_parent):
 
2705
    by_parent = tt.by_parent()
 
2706
    for child in by_parent[old_parent]:
 
2707
        tt.adjust_path(tt.final_name(child), new_parent, child)
 
2708
    return by_parent[old_parent]
 
2709
 
 
2710
 
 
2711
def _content_match(tree, entry, file_id, kind, target_path):
 
2712
    if entry.kind != kind:
 
2713
        return False
 
2714
    if entry.kind == "directory":
 
2715
        return True
 
2716
    if entry.kind == "file":
 
2717
        f = file(target_path, 'rb')
 
2718
        try:
 
2719
            if tree.get_file_text(file_id) == f.read():
 
2720
                return True
 
2721
        finally:
 
2722
            f.close()
 
2723
    elif entry.kind == "symlink":
 
2724
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
 
2725
            return True
 
2726
    return False
 
2727
 
 
2728
 
 
2729
def resolve_checkout(tt, conflicts, divert):
 
2730
    new_conflicts = set()
 
2731
    for c_type, conflict in ((c[0], c) for c in conflicts):
 
2732
        # Anything but a 'duplicate' would indicate programmer error
 
2733
        if c_type != 'duplicate':
 
2734
            raise AssertionError(c_type)
 
2735
        # Now figure out which is new and which is old
 
2736
        if tt.new_contents(conflict[1]):
 
2737
            new_file = conflict[1]
 
2738
            old_file = conflict[2]
 
2739
        else:
 
2740
            new_file = conflict[2]
 
2741
            old_file = conflict[1]
 
2742
 
 
2743
        # We should only get here if the conflict wasn't completely
 
2744
        # resolved
 
2745
        final_parent = tt.final_parent(old_file)
 
2746
        if new_file in divert:
 
2747
            new_name = tt.final_name(old_file)+'.diverted'
 
2748
            tt.adjust_path(new_name, final_parent, new_file)
 
2749
            new_conflicts.add((c_type, 'Diverted to',
 
2750
                               new_file, old_file))
 
2751
        else:
 
2752
            new_name = tt.final_name(old_file)+'.moved'
 
2753
            tt.adjust_path(new_name, final_parent, old_file)
 
2754
            new_conflicts.add((c_type, 'Moved existing file to',
 
2755
                               old_file, new_file))
 
2756
    return new_conflicts
 
2757
 
934
2758
 
935
2759
def new_by_entry(tt, entry, parent_id, tree):
936
2760
    """Create a new file according to its inventory entry"""
939
2763
    if kind == 'file':
940
2764
        contents = tree.get_file(entry.file_id).readlines()
941
2765
        executable = tree.is_executable(entry.file_id)
942
 
        return tt.new_file(name, parent_id, contents, entry.file_id, 
 
2766
        return tt.new_file(name, parent_id, contents, entry.file_id,
943
2767
                           executable)
944
 
    elif kind == 'directory':
945
 
        return tt.new_directory(name, parent_id, entry.file_id)
 
2768
    elif kind in ('directory', 'tree-reference'):
 
2769
        trans_id = tt.new_directory(name, parent_id, entry.file_id)
 
2770
        if kind == 'tree-reference':
 
2771
            tt.set_tree_reference(entry.reference_revision, trans_id)
 
2772
        return trans_id
946
2773
    elif kind == 'symlink':
947
2774
        target = tree.get_symlink_target(entry.file_id)
948
2775
        return tt.new_symlink(name, parent_id, target, entry.file_id)
949
 
 
950
 
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
951
 
    """Create new file contents according to an inventory entry."""
952
 
    if entry.kind == "file":
953
 
        if lines == None:
954
 
            lines = tree.get_file(entry.file_id).readlines()
955
 
        tt.create_file(lines, trans_id, mode_id=mode_id)
956
 
    elif entry.kind == "symlink":
957
 
        tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
958
 
    elif entry.kind == "directory":
 
2776
    else:
 
2777
        raise errors.BadFileKindError(name, kind)
 
2778
 
 
2779
 
 
2780
def create_from_tree(tt, trans_id, tree, file_id, bytes=None,
 
2781
    filter_tree_path=None):
 
2782
    """Create new file contents according to tree contents.
 
2783
    
 
2784
    :param filter_tree_path: the tree path to use to lookup
 
2785
      content filters to apply to the bytes output in the working tree.
 
2786
      This only applies if the working tree supports content filtering.
 
2787
    """
 
2788
    kind = tree.kind(file_id)
 
2789
    if kind == 'directory':
959
2790
        tt.create_directory(trans_id)
 
2791
    elif kind == "file":
 
2792
        if bytes is None:
 
2793
            tree_file = tree.get_file(file_id)
 
2794
            try:
 
2795
                bytes = tree_file.readlines()
 
2796
            finally:
 
2797
                tree_file.close()
 
2798
        wt = tt._tree
 
2799
        if wt.supports_content_filtering() and filter_tree_path is not None:
 
2800
            filters = wt._content_filter_stack(filter_tree_path)
 
2801
            bytes = filtered_output_bytes(bytes, filters,
 
2802
                ContentFilterContext(filter_tree_path, tree))
 
2803
        tt.create_file(bytes, trans_id)
 
2804
    elif kind == "symlink":
 
2805
        tt.create_symlink(tree.get_symlink_target(file_id), trans_id)
 
2806
    else:
 
2807
        raise AssertionError('Unknown kind %r' % kind)
 
2808
 
960
2809
 
961
2810
def create_entry_executability(tt, entry, trans_id):
962
2811
    """Set the executability of a trans_id according to an inventory entry"""
964
2813
        tt.set_executability(entry.executable, trans_id)
965
2814
 
966
2815
 
967
 
def find_interesting(working_tree, target_tree, filenames):
968
 
    """Find the ids corresponding to specified filenames."""
969
 
    if not filenames:
970
 
        interesting_ids = None
971
 
    else:
972
 
        interesting_ids = set()
973
 
        for tree_path in filenames:
974
 
            not_found = True
975
 
            for tree in (working_tree, target_tree):
976
 
                file_id = tree.inventory.path2id(tree_path)
977
 
                if file_id is not None:
978
 
                    interesting_ids.add(file_id)
979
 
                    not_found = False
980
 
            if not_found:
981
 
                raise NotVersionedError(path=tree_path)
982
 
    return interesting_ids
983
 
 
984
 
 
985
 
def change_entry(tt, file_id, working_tree, target_tree, 
986
 
                 trans_id_file_id, backups, trans_id, by_parent):
987
 
    """Replace a file_id's contents with those from a target tree."""
988
 
    e_trans_id = trans_id_file_id(file_id)
989
 
    entry = target_tree.inventory[file_id]
990
 
    has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry, 
991
 
                                                           working_tree)
992
 
    if contents_mod:
993
 
        mode_id = e_trans_id
994
 
        if has_contents:
995
 
            if not backups:
996
 
                tt.delete_contents(e_trans_id)
997
 
            else:
998
 
                parent_trans_id = trans_id_file_id(entry.parent_id)
999
 
                backup_name = get_backup_name(entry, by_parent,
1000
 
                                              parent_trans_id, tt)
1001
 
                tt.adjust_path(backup_name, parent_trans_id, e_trans_id)
1002
 
                tt.unversion_file(e_trans_id)
1003
 
                e_trans_id = tt.create_path(entry.name, parent_trans_id)
1004
 
                tt.version_file(file_id, e_trans_id)
1005
 
                trans_id[file_id] = e_trans_id
1006
 
        create_by_entry(tt, entry, target_tree, e_trans_id, mode_id=mode_id)
1007
 
        create_entry_executability(tt, entry, e_trans_id)
1008
 
 
1009
 
    elif meta_mod:
1010
 
        tt.set_executability(entry.executable, e_trans_id)
1011
 
    if tt.final_name(e_trans_id) != entry.name:
1012
 
        adjust_path  = True
1013
 
    else:
1014
 
        parent_id = tt.final_parent(e_trans_id)
1015
 
        parent_file_id = tt.final_file_id(parent_id)
1016
 
        if parent_file_id != entry.parent_id:
1017
 
            adjust_path = True
1018
 
        else:
1019
 
            adjust_path = False
1020
 
    if adjust_path:
1021
 
        parent_trans_id = trans_id_file_id(entry.parent_id)
1022
 
        tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
1023
 
 
1024
 
 
 
2816
@deprecated_function(deprecated_in((2, 3, 0)))
1025
2817
def get_backup_name(entry, by_parent, parent_trans_id, tt):
 
2818
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
 
2819
 
 
2820
 
 
2821
@deprecated_function(deprecated_in((2, 3, 0)))
 
2822
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1026
2823
    """Produce a backup-style name that appears to be available"""
1027
2824
    def name_gen():
1028
2825
        counter = 1
1029
2826
        while True:
1030
 
            yield "%s.~%d~" % (entry.name, counter)
 
2827
            yield "%s.~%d~" % (name, counter)
1031
2828
            counter += 1
1032
 
    for name in name_gen():
1033
 
        if not tt.has_named_child(by_parent, parent_trans_id, name):
1034
 
            return name
 
2829
    for new_name in name_gen():
 
2830
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
 
2831
            return new_name
 
2832
 
1035
2833
 
1036
2834
def _entry_changes(file_id, entry, working_tree):
1037
2835
    """Determine in which ways the inventory entry has changed.
1045
2843
    try:
1046
2844
        working_kind = working_tree.kind(file_id)
1047
2845
        has_contents = True
1048
 
    except OSError, e:
1049
 
        if e.errno != errno.ENOENT:
1050
 
            raise
 
2846
    except NoSuchFile:
1051
2847
        has_contents = False
1052
2848
        contents_mod = True
1053
2849
        meta_mod = False
1054
2850
    if has_contents is True:
1055
 
        real_e_kind = entry.kind
1056
 
        if real_e_kind == 'root_directory':
1057
 
            real_e_kind = 'directory'
1058
 
        if real_e_kind != working_kind:
 
2851
        if entry.kind != working_kind:
1059
2852
            contents_mod, meta_mod = True, False
1060
2853
        else:
1061
 
            cur_entry._read_tree_state(working_tree.id2path(file_id), 
 
2854
            cur_entry._read_tree_state(working_tree.id2path(file_id),
1062
2855
                                       working_tree)
1063
2856
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
1064
2857
            cur_entry._forget_tree_state()
1065
2858
    return has_contents, contents_mod, meta_mod
1066
2859
 
1067
2860
 
1068
 
def revert(working_tree, target_tree, filenames, backups=False, 
1069
 
           pb=DummyProgress()):
 
2861
def revert(working_tree, target_tree, filenames, backups=False,
 
2862
           pb=None, change_reporter=None):
1070
2863
    """Revert a working tree's contents to those of a target tree."""
1071
 
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
1072
 
    def interesting(file_id):
1073
 
        return interesting_ids is None or file_id in interesting_ids
1074
 
 
 
2864
    target_tree.lock_read()
 
2865
    pb = ui.ui_factory.nested_progress_bar()
1075
2866
    tt = TreeTransform(working_tree, pb)
1076
2867
    try:
1077
 
        merge_modified = working_tree.merge_modified()
1078
 
        trans_id = {}
1079
 
        def trans_id_file_id(file_id):
1080
 
            try:
1081
 
                return trans_id[file_id]
1082
 
            except KeyError:
1083
 
                return tt.trans_id_tree_file_id(file_id)
1084
 
 
1085
 
        pp = ProgressPhase("Revert phase", 4, pb)
1086
 
        pp.next_phase()
1087
 
        sorted_interesting = [i for i in topology_sorted_ids(target_tree) if
1088
 
                              interesting(i)]
1089
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1090
 
        try:
1091
 
            by_parent = tt.by_parent()
1092
 
            for id_num, file_id in enumerate(sorted_interesting):
1093
 
                child_pb.update("Reverting file", id_num+1, 
1094
 
                                len(sorted_interesting))
1095
 
                if file_id not in working_tree.inventory:
1096
 
                    entry = target_tree.inventory[file_id]
1097
 
                    parent_id = trans_id_file_id(entry.parent_id)
1098
 
                    e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1099
 
                    trans_id[file_id] = e_trans_id
1100
 
                else:
1101
 
                    backup_this = backups
1102
 
                    if file_id in merge_modified:
1103
 
                        backup_this = False
1104
 
                        del merge_modified[file_id]
1105
 
                    change_entry(tt, file_id, working_tree, target_tree, 
1106
 
                                 trans_id_file_id, backup_this, trans_id,
1107
 
                                 by_parent)
1108
 
        finally:
1109
 
            child_pb.finished()
1110
 
        pp.next_phase()
1111
 
        wt_interesting = [i for i in working_tree.inventory if interesting(i)]
1112
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1113
 
        try:
1114
 
            for id_num, file_id in enumerate(wt_interesting):
1115
 
                child_pb.update("New file check", id_num+1, 
1116
 
                                len(sorted_interesting))
1117
 
                if file_id not in target_tree:
1118
 
                    trans_id = tt.trans_id_tree_file_id(file_id)
1119
 
                    tt.unversion_file(trans_id)
1120
 
                    if file_id in merge_modified:
 
2868
        pp = ProgressPhase("Revert phase", 3, pb)
 
2869
        conflicts, merge_modified = _prepare_revert_transform(
 
2870
            working_tree, target_tree, tt, filenames, backups, pp)
 
2871
        if change_reporter:
 
2872
            change_reporter = delta._ChangeReporter(
 
2873
                unversioned_filter=working_tree.is_ignored)
 
2874
            delta.report_changes(tt.iter_changes(), change_reporter)
 
2875
        for conflict in conflicts:
 
2876
            trace.warning(unicode(conflict))
 
2877
        pp.next_phase()
 
2878
        tt.apply()
 
2879
        working_tree.set_merge_modified(merge_modified)
 
2880
    finally:
 
2881
        target_tree.unlock()
 
2882
        tt.finalize()
 
2883
        pb.clear()
 
2884
    return conflicts
 
2885
 
 
2886
 
 
2887
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
 
2888
                              backups, pp, basis_tree=None,
 
2889
                              merge_modified=None):
 
2890
    child_pb = ui.ui_factory.nested_progress_bar()
 
2891
    try:
 
2892
        if merge_modified is None:
 
2893
            merge_modified = working_tree.merge_modified()
 
2894
        merge_modified = _alter_files(working_tree, target_tree, tt,
 
2895
                                      child_pb, filenames, backups,
 
2896
                                      merge_modified, basis_tree)
 
2897
    finally:
 
2898
        child_pb.finished()
 
2899
    child_pb = ui.ui_factory.nested_progress_bar()
 
2900
    try:
 
2901
        raw_conflicts = resolve_conflicts(tt, child_pb,
 
2902
            lambda t, c: conflict_pass(t, c, target_tree))
 
2903
    finally:
 
2904
        child_pb.finished()
 
2905
    conflicts = cook_conflicts(raw_conflicts, tt)
 
2906
    return conflicts, merge_modified
 
2907
 
 
2908
 
 
2909
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
 
2910
                 backups, merge_modified, basis_tree=None):
 
2911
    if basis_tree is not None:
 
2912
        basis_tree.lock_read()
 
2913
    # We ask the working_tree for its changes relative to the target, rather
 
2914
    # than the target changes relative to the working tree. Because WT4 has an
 
2915
    # optimizer to compare itself to a target, but no optimizer for the
 
2916
    # reverse.
 
2917
    change_list = working_tree.iter_changes(target_tree,
 
2918
        specific_files=specific_files, pb=pb)
 
2919
    if target_tree.get_root_id() is None:
 
2920
        skip_root = True
 
2921
    else:
 
2922
        skip_root = False
 
2923
    try:
 
2924
        deferred_files = []
 
2925
        for id_num, (file_id, path, changed_content, versioned, parent, name,
 
2926
                kind, executable) in enumerate(change_list):
 
2927
            target_path, wt_path = path
 
2928
            target_versioned, wt_versioned = versioned
 
2929
            target_parent, wt_parent = parent
 
2930
            target_name, wt_name = name
 
2931
            target_kind, wt_kind = kind
 
2932
            target_executable, wt_executable = executable
 
2933
            if skip_root and wt_parent is None:
 
2934
                continue
 
2935
            trans_id = tt.trans_id_file_id(file_id)
 
2936
            mode_id = None
 
2937
            if changed_content:
 
2938
                keep_content = False
 
2939
                if wt_kind == 'file' and (backups or target_kind is None):
 
2940
                    wt_sha1 = working_tree.get_file_sha1(file_id)
 
2941
                    if merge_modified.get(file_id) != wt_sha1:
 
2942
                        # acquire the basis tree lazily to prevent the
 
2943
                        # expense of accessing it when it's not needed ?
 
2944
                        # (Guessing, RBC, 200702)
 
2945
                        if basis_tree is None:
 
2946
                            basis_tree = working_tree.basis_tree()
 
2947
                            basis_tree.lock_read()
 
2948
                        if basis_tree.has_id(file_id):
 
2949
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
 
2950
                                keep_content = True
 
2951
                        elif target_kind is None and not target_versioned:
 
2952
                            keep_content = True
 
2953
                if wt_kind is not None:
 
2954
                    if not keep_content:
1121
2955
                        tt.delete_contents(trans_id)
1122
 
                        del merge_modified[file_id]
1123
 
        finally:
1124
 
            child_pb.finished()
1125
 
        pp.next_phase()
1126
 
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1127
 
        try:
1128
 
            raw_conflicts = resolve_conflicts(tt, child_pb)
1129
 
        finally:
1130
 
            child_pb.finished()
1131
 
        conflicts = cook_conflicts(raw_conflicts, tt)
1132
 
        for conflict in conflicts:
1133
 
            warning(conflict)
1134
 
        pp.next_phase()
1135
 
        tt.apply()
1136
 
        working_tree.set_merge_modified({})
 
2956
                    elif target_kind is not None:
 
2957
                        parent_trans_id = tt.trans_id_file_id(wt_parent)
 
2958
                        backup_name = tt._available_backup_name(
 
2959
                            wt_name, parent_trans_id)
 
2960
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
 
2961
                        new_trans_id = tt.create_path(wt_name, parent_trans_id)
 
2962
                        if wt_versioned and target_versioned:
 
2963
                            tt.unversion_file(trans_id)
 
2964
                            tt.version_file(file_id, new_trans_id)
 
2965
                        # New contents should have the same unix perms as old
 
2966
                        # contents
 
2967
                        mode_id = trans_id
 
2968
                        trans_id = new_trans_id
 
2969
                if target_kind in ('directory', 'tree-reference'):
 
2970
                    tt.create_directory(trans_id)
 
2971
                    if target_kind == 'tree-reference':
 
2972
                        revision = target_tree.get_reference_revision(file_id,
 
2973
                                                                      target_path)
 
2974
                        tt.set_tree_reference(revision, trans_id)
 
2975
                elif target_kind == 'symlink':
 
2976
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
 
2977
                                      trans_id)
 
2978
                elif target_kind == 'file':
 
2979
                    deferred_files.append((file_id, (trans_id, mode_id)))
 
2980
                    if basis_tree is None:
 
2981
                        basis_tree = working_tree.basis_tree()
 
2982
                        basis_tree.lock_read()
 
2983
                    new_sha1 = target_tree.get_file_sha1(file_id)
 
2984
                    if (basis_tree.has_id(file_id) and
 
2985
                        new_sha1 == basis_tree.get_file_sha1(file_id)):
 
2986
                        if file_id in merge_modified:
 
2987
                            del merge_modified[file_id]
 
2988
                    else:
 
2989
                        merge_modified[file_id] = new_sha1
 
2990
 
 
2991
                    # preserve the execute bit when backing up
 
2992
                    if keep_content and wt_executable == target_executable:
 
2993
                        tt.set_executability(target_executable, trans_id)
 
2994
                elif target_kind is not None:
 
2995
                    raise AssertionError(target_kind)
 
2996
            if not wt_versioned and target_versioned:
 
2997
                tt.version_file(file_id, trans_id)
 
2998
            if wt_versioned and not target_versioned:
 
2999
                tt.unversion_file(trans_id)
 
3000
            if (target_name is not None and
 
3001
                (wt_name != target_name or wt_parent != target_parent)):
 
3002
                if target_name == '' and target_parent is None:
 
3003
                    parent_trans = ROOT_PARENT
 
3004
                else:
 
3005
                    parent_trans = tt.trans_id_file_id(target_parent)
 
3006
                if wt_parent is None and wt_versioned:
 
3007
                    tt.adjust_root_path(target_name, parent_trans)
 
3008
                else:
 
3009
                    tt.adjust_path(target_name, parent_trans, trans_id)
 
3010
            if wt_executable != target_executable and target_kind == "file":
 
3011
                tt.set_executability(target_executable, trans_id)
 
3012
        if working_tree.supports_content_filtering():
 
3013
            for index, ((trans_id, mode_id), bytes) in enumerate(
 
3014
                target_tree.iter_files_bytes(deferred_files)):
 
3015
                file_id = deferred_files[index][0]
 
3016
                # We're reverting a tree to the target tree so using the
 
3017
                # target tree to find the file path seems the best choice
 
3018
                # here IMO - Ian C 27/Oct/2009
 
3019
                filter_tree_path = target_tree.id2path(file_id)
 
3020
                filters = working_tree._content_filter_stack(filter_tree_path)
 
3021
                bytes = filtered_output_bytes(bytes, filters,
 
3022
                    ContentFilterContext(filter_tree_path, working_tree))
 
3023
                tt.create_file(bytes, trans_id, mode_id)
 
3024
        else:
 
3025
            for (trans_id, mode_id), bytes in target_tree.iter_files_bytes(
 
3026
                deferred_files):
 
3027
                tt.create_file(bytes, trans_id, mode_id)
 
3028
        tt.fixup_new_roots()
1137
3029
    finally:
1138
 
        tt.finalize()
1139
 
        pb.clear()
1140
 
    return conflicts
1141
 
 
1142
 
 
1143
 
def resolve_conflicts(tt, pb=DummyProgress()):
 
3030
        if basis_tree is not None:
 
3031
            basis_tree.unlock()
 
3032
    return merge_modified
 
3033
 
 
3034
 
 
3035
def resolve_conflicts(tt, pb=None, pass_func=None):
1144
3036
    """Make many conflict-resolution attempts, but die if they fail"""
 
3037
    if pass_func is None:
 
3038
        pass_func = conflict_pass
1145
3039
    new_conflicts = set()
 
3040
    pb = ui.ui_factory.nested_progress_bar()
1146
3041
    try:
1147
3042
        for n in range(10):
1148
3043
            pb.update('Resolution pass', n+1, 10)
1149
3044
            conflicts = tt.find_conflicts()
1150
3045
            if len(conflicts) == 0:
1151
3046
                return new_conflicts
1152
 
            new_conflicts.update(conflict_pass(tt, conflicts))
 
3047
            new_conflicts.update(pass_func(tt, conflicts))
1153
3048
        raise MalformedTransform(conflicts=conflicts)
1154
3049
    finally:
1155
 
        pb.clear()
1156
 
 
1157
 
 
1158
 
def conflict_pass(tt, conflicts):
1159
 
    """Resolve some classes of conflicts."""
 
3050
        pb.finished()
 
3051
 
 
3052
 
 
3053
def conflict_pass(tt, conflicts, path_tree=None):
 
3054
    """Resolve some classes of conflicts.
 
3055
 
 
3056
    :param tt: The transform to resolve conflicts in
 
3057
    :param conflicts: The conflicts to resolve
 
3058
    :param path_tree: A Tree to get supplemental paths from
 
3059
    """
1160
3060
    new_conflicts = set()
1161
3061
    for c_type, conflict in ((c[0], c) for c in conflicts):
1162
3062
        if c_type == 'duplicate id':
1165
3065
                               conflict[1], conflict[2], ))
1166
3066
        elif c_type == 'duplicate':
1167
3067
            # files that were renamed take precedence
1168
 
            new_name = tt.final_name(conflict[1])+'.moved'
1169
3068
            final_parent = tt.final_parent(conflict[1])
1170
3069
            if tt.path_changed(conflict[1]):
1171
 
                tt.adjust_path(new_name, final_parent, conflict[2])
1172
 
                new_conflicts.add((c_type, 'Moved existing file to', 
1173
 
                                   conflict[2], conflict[1]))
 
3070
                existing_file, new_file = conflict[2], conflict[1]
1174
3071
            else:
1175
 
                tt.adjust_path(new_name, final_parent, conflict[1])
1176
 
                new_conflicts.add((c_type, 'Moved existing file to', 
1177
 
                                  conflict[1], conflict[2]))
 
3072
                existing_file, new_file = conflict[1], conflict[2]
 
3073
            new_name = tt.final_name(existing_file)+'.moved'
 
3074
            tt.adjust_path(new_name, final_parent, existing_file)
 
3075
            new_conflicts.add((c_type, 'Moved existing file to',
 
3076
                               existing_file, new_file))
1178
3077
        elif c_type == 'parent loop':
1179
3078
            # break the loop by undoing one of the ops that caused the loop
1180
3079
            cur = conflict[1]
1183
3082
            new_conflicts.add((c_type, 'Cancelled move', cur,
1184
3083
                               tt.final_parent(cur),))
1185
3084
            tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
1186
 
            
 
3085
 
1187
3086
        elif c_type == 'missing parent':
1188
3087
            trans_id = conflict[1]
1189
 
            try:
1190
 
                tt.cancel_deletion(trans_id)
1191
 
                new_conflicts.add((c_type, 'Not deleting', trans_id))
1192
 
            except KeyError:
1193
 
                tt.create_directory(trans_id)
1194
 
                new_conflicts.add((c_type, 'Created directory.', trans_id))
 
3088
            if trans_id in tt._removed_contents:
 
3089
                cancel_deletion = True
 
3090
                orphans = tt._get_potential_orphans(trans_id)
 
3091
                if orphans:
 
3092
                    cancel_deletion = False
 
3093
                    # All children are orphans
 
3094
                    for o in orphans:
 
3095
                        try:
 
3096
                            tt.new_orphan(o, trans_id)
 
3097
                        except OrphaningError:
 
3098
                            # Something bad happened so we cancel the directory
 
3099
                            # deletion which will leave it in place with a
 
3100
                            # conflict. The user can deal with it from there.
 
3101
                            # Note that this also catch the case where we don't
 
3102
                            # want to create orphans and leave the directory in
 
3103
                            # place.
 
3104
                            cancel_deletion = True
 
3105
                            break
 
3106
                if cancel_deletion:
 
3107
                    # Cancel the directory deletion
 
3108
                    tt.cancel_deletion(trans_id)
 
3109
                    new_conflicts.add(('deleting parent', 'Not deleting',
 
3110
                                       trans_id))
 
3111
            else:
 
3112
                create = True
 
3113
                try:
 
3114
                    tt.final_name(trans_id)
 
3115
                except NoFinalPath:
 
3116
                    if path_tree is not None:
 
3117
                        file_id = tt.final_file_id(trans_id)
 
3118
                        if file_id is None:
 
3119
                            file_id = tt.inactive_file_id(trans_id)
 
3120
                        _, entry = path_tree.iter_entries_by_dir(
 
3121
                            [file_id]).next()
 
3122
                        # special-case the other tree root (move its
 
3123
                        # children to current root)
 
3124
                        if entry.parent_id is None:
 
3125
                            create = False
 
3126
                            moved = _reparent_transform_children(
 
3127
                                tt, trans_id, tt.root)
 
3128
                            for child in moved:
 
3129
                                new_conflicts.add((c_type, 'Moved to root',
 
3130
                                                   child))
 
3131
                        else:
 
3132
                            parent_trans_id = tt.trans_id_file_id(
 
3133
                                entry.parent_id)
 
3134
                            tt.adjust_path(entry.name, parent_trans_id,
 
3135
                                           trans_id)
 
3136
                if create:
 
3137
                    tt.create_directory(trans_id)
 
3138
                    new_conflicts.add((c_type, 'Created directory', trans_id))
1195
3139
        elif c_type == 'unversioned parent':
1196
 
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
 
3140
            file_id = tt.inactive_file_id(conflict[1])
 
3141
            # special-case the other tree root (move its children instead)
 
3142
            if path_tree and path_tree.has_id(file_id):
 
3143
                if path_tree.path2id('') == file_id:
 
3144
                    # This is the root entry, skip it
 
3145
                    continue
 
3146
            tt.version_file(file_id, conflict[1])
1197
3147
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
 
3148
        elif c_type == 'non-directory parent':
 
3149
            parent_id = conflict[1]
 
3150
            parent_parent = tt.final_parent(parent_id)
 
3151
            parent_name = tt.final_name(parent_id)
 
3152
            parent_file_id = tt.final_file_id(parent_id)
 
3153
            new_parent_id = tt.new_directory(parent_name + '.new',
 
3154
                parent_parent, parent_file_id)
 
3155
            _reparent_transform_children(tt, parent_id, new_parent_id)
 
3156
            if parent_file_id is not None:
 
3157
                tt.unversion_file(parent_id)
 
3158
            new_conflicts.add((c_type, 'Created directory', new_parent_id))
 
3159
        elif c_type == 'versioning no contents':
 
3160
            tt.cancel_versioning(conflict[1])
1198
3161
    return new_conflicts
1199
3162
 
 
3163
 
1200
3164
def cook_conflicts(raw_conflicts, tt):
1201
3165
    """Generate a list of cooked conflicts, sorted by file path"""
1202
 
    def key(conflict):
1203
 
        if conflict.path is not None:
1204
 
            return conflict.path, conflict.typestring
1205
 
        elif getattr(conflict, "conflict_path", None) is not None:
1206
 
            return conflict.conflict_path, conflict.typestring
1207
 
        else:
1208
 
            return None, conflict.typestring
 
3166
    conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
 
3167
    return sorted(conflict_iter, key=conflicts.Conflict.sort_key)
1209
3168
 
1210
 
    return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1211
3169
 
1212
3170
def iter_cook_conflicts(raw_conflicts, tt):
1213
 
    from bzrlib.conflicts import Conflict
1214
3171
    fp = FinalPaths(tt)
1215
3172
    for conflict in raw_conflicts:
1216
3173
        c_type = conflict[0]
1218
3175
        modified_path = fp.get_path(conflict[2])
1219
3176
        modified_id = tt.final_file_id(conflict[2])
1220
3177
        if len(conflict) == 3:
1221
 
            yield Conflict.factory(c_type, action=action, path=modified_path,
1222
 
                                     file_id=modified_id)
1223
 
             
 
3178
            yield conflicts.Conflict.factory(
 
3179
                c_type, action=action, path=modified_path, file_id=modified_id)
 
3180
 
1224
3181
        else:
1225
3182
            conflicting_path = fp.get_path(conflict[3])
1226
3183
            conflicting_id = tt.final_file_id(conflict[3])
1227
 
            yield Conflict.factory(c_type, action=action, path=modified_path,
1228
 
                                   file_id=modified_id, 
1229
 
                                   conflict_path=conflicting_path,
1230
 
                                   conflict_file_id=conflicting_id)
 
3184
            yield conflicts.Conflict.factory(
 
3185
                c_type, action=action, path=modified_path,
 
3186
                file_id=modified_id,
 
3187
                conflict_path=conflicting_path,
 
3188
                conflict_file_id=conflicting_id)
 
3189
 
 
3190
 
 
3191
class _FileMover(object):
 
3192
    """Moves and deletes files for TreeTransform, tracking operations"""
 
3193
 
 
3194
    def __init__(self):
 
3195
        self.past_renames = []
 
3196
        self.pending_deletions = []
 
3197
 
 
3198
    def rename(self, from_, to):
 
3199
        """Rename a file from one path to another."""
 
3200
        try:
 
3201
            os.rename(from_, to)
 
3202
        except OSError, e:
 
3203
            if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
 
3204
                raise errors.FileExists(to, str(e))
 
3205
            # normal OSError doesn't include filenames so it's hard to see where
 
3206
            # the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
 
3207
            raise errors.TransformRenameFailed(from_, to, str(e), e.errno)
 
3208
        self.past_renames.append((from_, to))
 
3209
 
 
3210
    def pre_delete(self, from_, to):
 
3211
        """Rename a file out of the way and mark it for deletion.
 
3212
 
 
3213
        Unlike os.unlink, this works equally well for files and directories.
 
3214
        :param from_: The current file path
 
3215
        :param to: A temporary path for the file
 
3216
        """
 
3217
        self.rename(from_, to)
 
3218
        self.pending_deletions.append(to)
 
3219
 
 
3220
    def rollback(self):
 
3221
        """Reverse all renames that have been performed"""
 
3222
        for from_, to in reversed(self.past_renames):
 
3223
            try:
 
3224
                os.rename(to, from_)
 
3225
            except OSError, e:
 
3226
                raise errors.TransformRenameFailed(to, from_, str(e), e.errno)
 
3227
        # after rollback, don't reuse _FileMover
 
3228
        past_renames = None
 
3229
        pending_deletions = None
 
3230
 
 
3231
    def apply_deletions(self):
 
3232
        """Apply all marked deletions"""
 
3233
        for path in self.pending_deletions:
 
3234
            delete_any(path)
 
3235
        # after apply_deletions, don't reuse _FileMover
 
3236
        past_renames = None
 
3237
        pending_deletions = None