/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: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
import contextlib
18
17
import os
19
18
import errno
20
 
from stat import S_ISREG, S_IEXEC
21
 
import time
 
19
from stat import S_ISREG
22
20
 
23
 
from . import (
24
 
    config as _mod_config,
25
 
    controldir,
 
21
from bzrlib.lazy_import import lazy_import
 
22
lazy_import(globals(), """
 
23
from bzrlib import (
 
24
    bzrdir,
 
25
    delta,
26
26
    errors,
27
 
    lazy_import,
28
 
    lock,
29
 
    osutils,
30
 
    registry,
31
 
    trace,
32
 
    )
33
 
lazy_import.lazy_import(globals(), """
34
 
from breezy import (
35
 
    multiparent,
36
 
    revision as _mod_revision,
37
 
    ui,
38
 
    urlutils,
39
 
    )
40
 
from breezy.i18n import gettext
 
27
    inventory
 
28
    )
41
29
""")
42
 
 
43
 
from .errors import (DuplicateKey,
44
 
                     BzrError, InternalBzrError)
45
 
from .filters import filtered_output_bytes, ContentFilterContext
46
 
from .mutabletree import MutableTree
47
 
from .osutils import (
48
 
    delete_any,
49
 
    file_kind,
50
 
    pathjoin,
51
 
    sha_file,
52
 
    splitpath,
53
 
    supports_symlinks,
54
 
    )
55
 
from .progress import ProgressPhase
56
 
from .tree import (
57
 
    InterTree,
58
 
    find_previous_path,
59
 
    )
 
30
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
 
31
                           ReusingTransform, NotVersionedError, CantMoveRoot,
 
32
                           ExistingLimbo, ImmortalLimbo, NoFinalPath)
 
33
from bzrlib.inventory import InventoryEntry
 
34
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
 
35
                            delete_any)
 
36
from bzrlib.progress import DummyProgress, ProgressPhase
 
37
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen
 
38
from bzrlib.trace import mutter, warning
 
39
from bzrlib import tree
 
40
import bzrlib.ui
 
41
import bzrlib.urlutils as urlutils
60
42
 
61
43
 
62
44
ROOT_PARENT = "root-parent"
63
45
 
64
46
 
65
 
class NoFinalPath(BzrError):
66
 
 
67
 
    _fmt = ("No final name for trans_id %(trans_id)r\n"
68
 
            "root trans-id: %(root_trans_id)r\n")
69
 
 
70
 
    def __init__(self, trans_id, transform):
71
 
        self.trans_id = trans_id
72
 
        self.root_trans_id = transform.root
73
 
 
74
 
 
75
 
class ReusingTransform(BzrError):
76
 
 
77
 
    _fmt = "Attempt to reuse a transform that has already been applied."
78
 
 
79
 
 
80
 
class MalformedTransform(InternalBzrError):
81
 
 
82
 
    _fmt = "Tree transform is malformed %(conflicts)r"
83
 
 
84
 
 
85
 
class CantMoveRoot(BzrError):
86
 
 
87
 
    _fmt = "Moving the root directory is not supported at this time"
88
 
 
89
 
 
90
 
class ImmortalLimbo(BzrError):
91
 
 
92
 
    _fmt = """Unable to delete transform temporary directory %(limbo_dir)s.
93
 
    Please examine %(limbo_dir)s to see if it contains any files you wish to
94
 
    keep, and delete it when you are done."""
95
 
 
96
 
    def __init__(self, limbo_dir):
97
 
        BzrError.__init__(self)
98
 
        self.limbo_dir = limbo_dir
99
 
 
100
 
 
101
 
class TransformRenameFailed(BzrError):
102
 
 
103
 
    _fmt = "Failed to rename %(from_path)s to %(to_path)s: %(why)s"
104
 
 
105
 
    def __init__(self, from_path, to_path, why, errno):
106
 
        self.from_path = from_path
107
 
        self.to_path = to_path
108
 
        self.why = why
109
 
        self.errno = errno
110
 
 
111
 
 
112
47
def unique_add(map, key, value):
113
48
    if key in map:
114
49
        raise DuplicateKey(key=key)
116
51
 
117
52
 
118
53
class _TransformResults(object):
119
 
 
120
 
    def __init__(self, modified_paths, rename_count):
 
54
    def __init__(self, modified_paths):
121
55
        object.__init__(self)
122
56
        self.modified_paths = modified_paths
123
 
        self.rename_count = rename_count
124
57
 
125
58
 
126
59
class TreeTransform(object):
127
60
    """Represent a tree transformation.
128
 
 
 
61
    
129
62
    This object is designed to support incremental generation of the transform,
130
 
    in any order.
131
 
 
132
 
    However, it gives optimum performance when parent directories are created
133
 
    before their contents.  The transform is then able to put child files
134
 
    directly in their parent directory, avoiding later renames.
135
 
 
 
63
    in any order.  
 
64
    
136
65
    It is easy to produce malformed transforms, but they are generally
137
66
    harmless.  Attempting to apply a malformed transform will cause an
138
 
    exception to be raised before any modifications are made to the tree.
 
67
    exception to be raised before any modifications are made to the tree.  
139
68
 
140
 
    Many kinds of malformed transforms can be corrected with the
 
69
    Many kinds of malformed transforms can be corrected with the 
141
70
    resolve_conflicts function.  The remaining ones indicate programming error,
142
71
    such as trying to create a file with no path.
143
72
 
151
80
     * create_file or create_directory or create_symlink
152
81
     * version_file
153
82
     * set_executability
154
 
 
155
 
    Transform/Transaction ids
156
 
    -------------------------
157
 
    trans_ids are temporary ids assigned to all files involved in a transform.
158
 
    It's possible, even common, that not all files in the Tree have trans_ids.
159
 
 
160
 
    trans_ids are only valid for the TreeTransform that generated them.
161
83
    """
162
 
 
163
 
    def __init__(self, tree, pb=None):
 
84
    def __init__(self, tree, pb=DummyProgress()):
 
85
        """Note: a tree_write lock is taken on the tree.
 
86
        
 
87
        Use TreeTransform.finalize() to release the lock
 
88
        """
 
89
        object.__init__(self)
164
90
        self._tree = tree
165
 
        # A progress bar
166
 
        self._pb = pb
 
91
        self._tree.lock_tree_write()
 
92
        try:
 
93
            control_files = self._tree._control_files
 
94
            self._limbodir = urlutils.local_path_from_url(
 
95
                control_files.controlfilename('limbo'))
 
96
            try:
 
97
                os.mkdir(self._limbodir)
 
98
            except OSError, e:
 
99
                if e.errno == errno.EEXIST:
 
100
                    raise ExistingLimbo(self._limbodir)
 
101
        except: 
 
102
            self._tree.unlock()
 
103
            raise
 
104
 
167
105
        self._id_number = 0
168
 
        # Mapping of path in old tree -> trans_id
169
 
        self._tree_path_ids = {}
170
 
        # Mapping trans_id -> path in old tree
171
 
        self._tree_id_paths = {}
172
 
        # mapping of trans_id -> new basename
173
106
        self._new_name = {}
174
 
        # mapping of trans_id -> new parent trans_id
175
107
        self._new_parent = {}
176
 
        # mapping of trans_id with new contents -> new file_kind
177
108
        self._new_contents = {}
178
 
        # Set of trans_ids whose contents will be removed
179
109
        self._removed_contents = set()
180
 
        # Mapping of trans_id -> new execute-bit value
181
110
        self._new_executability = {}
182
 
        # Mapping of trans_id -> new tree-reference value
183
111
        self._new_reference_revision = {}
184
 
        # Set of trans_ids that will be removed
 
112
        self._new_id = {}
 
113
        self._non_present_ids = {}
 
114
        self._r_new_id = {}
185
115
        self._removed_id = set()
186
 
        # Indicator of whether the transform has been applied
187
 
        self._done = False
188
 
 
189
 
    def __enter__(self):
190
 
        """Support Context Manager API."""
191
 
        return self
192
 
 
193
 
    def __exit__(self, exc_type, exc_val, exc_tb):
194
 
        """Support Context Manager API."""
195
 
        self.finalize()
196
 
 
197
 
    def iter_tree_children(self, trans_id):
198
 
        """Iterate through the entry's tree children, if any.
199
 
 
200
 
        :param trans_id: trans id to iterate
201
 
        :returns: Iterator over paths
202
 
        """
203
 
        raise NotImplementedError(self.iter_tree_children)
204
 
 
205
 
    def canonical_path(self, path):
206
 
        return path
207
 
 
208
 
    def tree_kind(self, trans_id):
209
 
        raise NotImplementedError(self.tree_kind)
210
 
 
211
 
    def by_parent(self):
212
 
        """Return a map of parent: children for known parents.
213
 
 
214
 
        Only new paths and parents of tree files with assigned ids are used.
215
 
        """
216
 
        by_parent = {}
217
 
        items = list(self._new_parent.items())
218
 
        items.extend((t, self.final_parent(t))
219
 
                     for t in list(self._tree_id_paths))
220
 
        for trans_id, parent_id in items:
221
 
            if parent_id not in by_parent:
222
 
                by_parent[parent_id] = set()
223
 
            by_parent[parent_id].add(trans_id)
224
 
        return by_parent
 
116
        self._tree_path_ids = {}
 
117
        self._tree_id_paths = {}
 
118
        self._realpaths = {}
 
119
        # Cache of realpath results, to speed up canonical_path
 
120
        self._relpaths = {}
 
121
        # Cache of relpath results, to speed up canonical_path
 
122
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
 
123
        self.__done = False
 
124
        self._pb = pb
 
125
 
 
126
    def __get_root(self):
 
127
        return self._new_root
 
128
 
 
129
    root = property(__get_root)
225
130
 
226
131
    def finalize(self):
227
 
        """Release the working tree lock, if held.
 
132
        """Release the working tree lock, if held, clean up limbo dir."""
 
133
        if self._tree is None:
 
134
            return
 
135
        try:
 
136
            for trans_id, kind in self._new_contents.iteritems():
 
137
                path = self._limbo_name(trans_id)
 
138
                if kind == "directory":
 
139
                    os.rmdir(path)
 
140
                else:
 
141
                    os.unlink(path)
 
142
            try:
 
143
                os.rmdir(self._limbodir)
 
144
            except OSError:
 
145
                # We don't especially care *why* the dir is immortal.
 
146
                raise ImmortalLimbo(self._limbodir)
 
147
        finally:
 
148
            self._tree.unlock()
 
149
            self._tree = None
228
150
 
229
 
        This is required if apply has not been invoked, but can be invoked
230
 
        even after apply.
231
 
        """
232
 
        raise NotImplementedError(self.finalize)
 
151
    def _assign_id(self):
 
152
        """Produce a new tranform id"""
 
153
        new_id = "new-%s" % self._id_number
 
154
        self._id_number +=1
 
155
        return new_id
233
156
 
234
157
    def create_path(self, name, parent):
235
158
        """Assign a transaction id to a new path"""
236
 
        trans_id = self.assign_id()
 
159
        trans_id = self._assign_id()
237
160
        unique_add(self._new_name, trans_id, name)
238
161
        unique_add(self._new_parent, trans_id, parent)
239
162
        return trans_id
240
163
 
241
164
    def adjust_path(self, name, parent, trans_id):
242
165
        """Change the path that is assigned to a transaction id."""
243
 
        if parent is None:
244
 
            raise ValueError("Parent trans-id may not be None")
245
 
        if trans_id == self.root:
 
166
        if trans_id == self._new_root:
246
167
            raise CantMoveRoot
247
168
        self._new_name[trans_id] = name
248
169
        self._new_parent[trans_id] = parent
249
170
 
250
171
    def adjust_root_path(self, name, parent):
251
172
        """Emulate moving the root by moving all children, instead.
252
 
 
 
173
        
253
174
        We do this by undoing the association of root's transaction id with the
254
175
        current tree.  This allows us to create a new directory with that
255
 
        transaction id.  We unversion the root directory and version the
 
176
        transaction id.  We unversion the root directory and version the 
256
177
        physically new directory, and hope someone versions the tree root
257
178
        later.
258
179
        """
259
 
        raise NotImplementedError(self.adjust_root_path)
260
 
 
261
 
    def fixup_new_roots(self):
262
 
        """Reinterpret requests to change the root directory
263
 
 
264
 
        Instead of creating a root directory, or moving an existing directory,
265
 
        all the attributes and children of the new root are applied to the
266
 
        existing root directory.
267
 
 
268
 
        This means that the old root trans-id becomes obsolete, so it is
269
 
        recommended only to invoke this after the root trans-id has become
270
 
        irrelevant.
271
 
        """
272
 
        raise NotImplementedError(self.fixup_new_roots)
273
 
 
274
 
    def assign_id(self):
275
 
        """Produce a new tranform id"""
276
 
        new_id = "new-%s" % self._id_number
277
 
        self._id_number += 1
278
 
        return new_id
 
180
        old_root = self._new_root
 
181
        old_root_file_id = self.final_file_id(old_root)
 
182
        # force moving all children of root
 
183
        for child_id in self.iter_tree_children(old_root):
 
184
            if child_id != parent:
 
185
                self.adjust_path(self.final_name(child_id), 
 
186
                                 self.final_parent(child_id), child_id)
 
187
            file_id = self.final_file_id(child_id)
 
188
            if file_id is not None:
 
189
                self.unversion_file(child_id)
 
190
            self.version_file(file_id, child_id)
 
191
        
 
192
        # the physical root needs a new transaction id
 
193
        self._tree_path_ids.pop("")
 
194
        self._tree_id_paths.pop(old_root)
 
195
        self._new_root = self.trans_id_tree_file_id(self._tree.get_root_id())
 
196
        if parent == old_root:
 
197
            parent = self._new_root
 
198
        self.adjust_path(name, parent, old_root)
 
199
        self.create_directory(old_root)
 
200
        self.version_file(old_root_file_id, old_root)
 
201
        self.unversion_file(self._new_root)
 
202
 
 
203
    def trans_id_tree_file_id(self, inventory_id):
 
204
        """Determine the transaction id of a working tree file.
 
205
        
 
206
        This reflects only files that already exist, not ones that will be
 
207
        added by transactions.
 
208
        """
 
209
        path = self._tree.inventory.id2path(inventory_id)
 
210
        return self.trans_id_tree_path(path)
 
211
 
 
212
    def trans_id_file_id(self, file_id):
 
213
        """Determine or set the transaction id associated with a file ID.
 
214
        A new id is only created for file_ids that were never present.  If
 
215
        a transaction has been unversioned, it is deliberately still returned.
 
216
        (this will likely lead to an unversioned parent conflict.)
 
217
        """
 
218
        if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
 
219
            return self._r_new_id[file_id]
 
220
        elif file_id in self._tree.inventory:
 
221
            return self.trans_id_tree_file_id(file_id)
 
222
        elif file_id in self._non_present_ids:
 
223
            return self._non_present_ids[file_id]
 
224
        else:
 
225
            trans_id = self._assign_id()
 
226
            self._non_present_ids[file_id] = trans_id
 
227
            return trans_id
 
228
 
 
229
    def canonical_path(self, path):
 
230
        """Get the canonical tree-relative path"""
 
231
        # don't follow final symlinks
 
232
        abs = self._tree.abspath(path)
 
233
        if abs in self._relpaths:
 
234
            return self._relpaths[abs]
 
235
        dirname, basename = os.path.split(abs)
 
236
        if dirname not in self._realpaths:
 
237
            self._realpaths[dirname] = os.path.realpath(dirname)
 
238
        dirname = self._realpaths[dirname]
 
239
        abs = pathjoin(dirname, basename)
 
240
        if dirname in self._relpaths:
 
241
            relpath = pathjoin(self._relpaths[dirname], basename)
 
242
            relpath = relpath.rstrip('/\\')
 
243
        else:
 
244
            relpath = self._tree.relpath(abs)
 
245
        self._relpaths[abs] = relpath
 
246
        return relpath
279
247
 
280
248
    def trans_id_tree_path(self, path):
281
249
        """Determine (and maybe set) the transaction ID for a tree path."""
282
250
        path = self.canonical_path(path)
283
251
        if path not in self._tree_path_ids:
284
 
            self._tree_path_ids[path] = self.assign_id()
 
252
            self._tree_path_ids[path] = self._assign_id()
285
253
            self._tree_id_paths[self._tree_path_ids[path]] = path
286
254
        return self._tree_path_ids[path]
287
255
 
292
260
            return ROOT_PARENT
293
261
        return self.trans_id_tree_path(os.path.dirname(path))
294
262
 
 
263
    def create_file(self, contents, trans_id, mode_id=None):
 
264
        """Schedule creation of a new file.
 
265
 
 
266
        See also new_file.
 
267
        
 
268
        Contents is an iterator of strings, all of which will be written
 
269
        to the target destination.
 
270
 
 
271
        New file takes the permissions of any existing file with that id,
 
272
        unless mode_id is specified.
 
273
        """
 
274
        name = self._limbo_name(trans_id)
 
275
        f = open(name, 'wb')
 
276
        try:
 
277
            try:
 
278
                unique_add(self._new_contents, trans_id, 'file')
 
279
            except:
 
280
                # Clean up the file, it never got registered so
 
281
                # TreeTransform.finalize() won't clean it up.
 
282
                f.close()
 
283
                os.unlink(name)
 
284
                raise
 
285
 
 
286
            f.writelines(contents)
 
287
        finally:
 
288
            f.close()
 
289
        self._set_mode(trans_id, mode_id, S_ISREG)
 
290
 
 
291
    def _set_mode(self, trans_id, mode_id, typefunc):
 
292
        """Set the mode of new file contents.
 
293
        The mode_id is the existing file to get the mode from (often the same
 
294
        as trans_id).  The operation is only performed if there's a mode match
 
295
        according to typefunc.
 
296
        """
 
297
        if mode_id is None:
 
298
            mode_id = trans_id
 
299
        try:
 
300
            old_path = self._tree_id_paths[mode_id]
 
301
        except KeyError:
 
302
            return
 
303
        try:
 
304
            mode = os.stat(self._tree.abspath(old_path)).st_mode
 
305
        except OSError, e:
 
306
            if e.errno == errno.ENOENT:
 
307
                return
 
308
            else:
 
309
                raise
 
310
        if typefunc(mode):
 
311
            os.chmod(self._limbo_name(trans_id), mode)
 
312
 
 
313
    def create_directory(self, trans_id):
 
314
        """Schedule creation of a new directory.
 
315
        
 
316
        See also new_directory.
 
317
        """
 
318
        os.mkdir(self._limbo_name(trans_id))
 
319
        unique_add(self._new_contents, trans_id, 'directory')
 
320
 
 
321
    def create_symlink(self, target, trans_id):
 
322
        """Schedule creation of a new symbolic link.
 
323
 
 
324
        target is a bytestring.
 
325
        See also new_symlink.
 
326
        """
 
327
        os.symlink(target, self._limbo_name(trans_id))
 
328
        unique_add(self._new_contents, trans_id, 'symlink')
 
329
 
 
330
    def cancel_creation(self, trans_id):
 
331
        """Cancel the creation of new file contents."""
 
332
        del self._new_contents[trans_id]
 
333
        delete_any(self._limbo_name(trans_id))
 
334
 
295
335
    def delete_contents(self, trans_id):
296
336
        """Schedule the contents of a path entry for deletion"""
297
 
        kind = self.tree_kind(trans_id)
298
 
        if kind is not None:
299
 
            self._removed_contents.add(trans_id)
 
337
        self.tree_kind(trans_id)
 
338
        self._removed_contents.add(trans_id)
300
339
 
301
340
    def cancel_deletion(self, trans_id):
302
341
        """Cancel a scheduled deletion"""
303
342
        self._removed_contents.remove(trans_id)
304
343
 
 
344
    def unversion_file(self, trans_id):
 
345
        """Schedule a path entry to become unversioned"""
 
346
        self._removed_id.add(trans_id)
 
347
 
305
348
    def delete_versioned(self, trans_id):
306
349
        """Delete and unversion a versioned file"""
307
350
        self.delete_contents(trans_id)
320
363
        """Set the reference associated with a directory"""
321
364
        unique_add(self._new_reference_revision, trans_id, revision_id)
322
365
 
323
 
    def version_file(self, trans_id, file_id=None):
 
366
    def version_file(self, file_id, trans_id):
324
367
        """Schedule a file to become versioned."""
325
 
        raise NotImplementedError(self.version_file)
 
368
        assert file_id is not None
 
369
        unique_add(self._new_id, trans_id, file_id)
 
370
        unique_add(self._r_new_id, file_id, trans_id)
326
371
 
327
372
    def cancel_versioning(self, trans_id):
328
373
        """Undo a previous versioning of a file"""
329
 
        raise NotImplementedError(self.cancel_versioning)
330
 
 
331
 
    def unversion_file(self, trans_id):
332
 
        """Schedule a path entry to become unversioned"""
333
 
        self._removed_id.add(trans_id)
334
 
 
335
 
    def new_paths(self, filesystem_only=False):
336
 
        """Determine the paths of all new and changed files.
337
 
 
338
 
        :param filesystem_only: if True, only calculate values for files
339
 
            that require renames or execute bit changes.
 
374
        file_id = self._new_id[trans_id]
 
375
        del self._new_id[trans_id]
 
376
        del self._r_new_id[file_id]
 
377
 
 
378
    def new_paths(self):
 
379
        """Determine the paths of all new and changed files"""
 
380
        new_ids = set()
 
381
        fp = FinalPaths(self)
 
382
        for id_set in (self._new_name, self._new_parent, self._new_contents,
 
383
                       self._new_id, self._new_executability):
 
384
            new_ids.update(id_set)
 
385
        new_paths = [(fp.get_path(t), t) for t in new_ids]
 
386
        new_paths.sort()
 
387
        return new_paths
 
388
 
 
389
    def tree_kind(self, trans_id):
 
390
        """Determine the file kind in the working tree.
 
391
 
 
392
        Raises NoSuchFile if the file does not exist
340
393
        """
341
 
        raise NotImplementedError(self.new_paths)
 
394
        path = self._tree_id_paths.get(trans_id)
 
395
        if path is None:
 
396
            raise NoSuchFile(None)
 
397
        try:
 
398
            return file_kind(self._tree.abspath(path))
 
399
        except OSError, e:
 
400
            if e.errno != errno.ENOENT:
 
401
                raise
 
402
            else:
 
403
                raise NoSuchFile(path)
342
404
 
343
405
    def final_kind(self, trans_id):
344
406
        """Determine the final file kind, after any changes applied.
345
 
 
346
 
        :return: None if the file does not exist/has no contents.  (It is
347
 
            conceivable that a path would be created without the corresponding
348
 
            contents insertion command)
 
407
        
 
408
        Raises NoSuchFile if the file does not exist/has no contents.
 
409
        (It is conceivable that a path would be created without the
 
410
        corresponding contents insertion command)
349
411
        """
350
412
        if trans_id in self._new_contents:
351
 
            if trans_id in self._new_reference_revision:
352
 
                return 'tree-reference'
353
413
            return self._new_contents[trans_id]
354
414
        elif trans_id in self._removed_contents:
355
 
            return None
 
415
            raise NoSuchFile(None)
356
416
        else:
357
417
            return self.tree_kind(trans_id)
358
418
 
359
 
    def tree_path(self, trans_id):
360
 
        """Determine the tree path associated with the trans_id."""
361
 
        return self._tree_id_paths.get(trans_id)
362
 
 
363
 
    def final_is_versioned(self, trans_id):
364
 
        raise NotImplementedError(self.final_is_versioned)
 
419
    def tree_file_id(self, trans_id):
 
420
        """Determine the file id associated with the trans_id in the tree"""
 
421
        try:
 
422
            path = self._tree_id_paths[trans_id]
 
423
        except KeyError:
 
424
            # the file is a new, unversioned file, or invalid trans_id
 
425
            return None
 
426
        # the file is old; the old id is still valid
 
427
        if self._new_root == trans_id:
 
428
            return self._tree.inventory.root.file_id
 
429
        return self._tree.inventory.path2id(path)
 
430
 
 
431
    def final_file_id(self, trans_id):
 
432
        """Determine the file id after any changes are applied, or None.
 
433
        
 
434
        None indicates that the file will not be versioned after changes are
 
435
        applied.
 
436
        """
 
437
        try:
 
438
            # there is a new id for this file
 
439
            assert self._new_id[trans_id] is not None
 
440
            return self._new_id[trans_id]
 
441
        except KeyError:
 
442
            if trans_id in self._removed_id:
 
443
                return None
 
444
        return self.tree_file_id(trans_id)
 
445
 
 
446
    def inactive_file_id(self, trans_id):
 
447
        """Return the inactive file_id associated with a transaction id.
 
448
        That is, the one in the tree or in non_present_ids.
 
449
        The file_id may actually be active, too.
 
450
        """
 
451
        file_id = self.tree_file_id(trans_id)
 
452
        if file_id is not None:
 
453
            return file_id
 
454
        for key, value in self._non_present_ids.iteritems():
 
455
            if value == trans_id:
 
456
                return key
365
457
 
366
458
    def final_parent(self, trans_id):
367
459
        """Determine the parent file_id, after any changes are applied.
383
475
            except KeyError:
384
476
                raise NoFinalPath(trans_id, self)
385
477
 
 
478
    def by_parent(self):
 
479
        """Return a map of parent: children for known parents.
 
480
        
 
481
        Only new paths and parents of tree files with assigned ids are used.
 
482
        """
 
483
        by_parent = {}
 
484
        items = list(self._new_parent.iteritems())
 
485
        items.extend((t, self.final_parent(t)) for t in 
 
486
                      self._tree_id_paths.keys())
 
487
        for trans_id, parent_id in items:
 
488
            if parent_id not in by_parent:
 
489
                by_parent[parent_id] = set()
 
490
            by_parent[parent_id].add(trans_id)
 
491
        return by_parent
 
492
 
386
493
    def path_changed(self, trans_id):
387
494
        """Return True if a trans_id's path has changed."""
388
495
        return (trans_id in self._new_name) or (trans_id in self._new_parent)
390
497
    def new_contents(self, trans_id):
391
498
        return (trans_id in self._new_contents)
392
499
 
393
 
    def find_raw_conflicts(self):
 
500
    def find_conflicts(self):
394
501
        """Find any violations of inventory or filesystem invariants"""
395
 
        raise NotImplementedError(self.find_raw_conflicts)
396
 
 
397
 
    def new_file(self, name, parent_id, contents, file_id=None,
398
 
                 executable=None, sha1=None):
 
502
        if self.__done is True:
 
503
            raise ReusingTransform()
 
504
        conflicts = []
 
505
        # ensure all children of all existent parents are known
 
506
        # all children of non-existent parents are known, by definition.
 
507
        self._add_tree_children()
 
508
        by_parent = self.by_parent()
 
509
        conflicts.extend(self._unversioned_parents(by_parent))
 
510
        conflicts.extend(self._parent_loops())
 
511
        conflicts.extend(self._duplicate_entries(by_parent))
 
512
        conflicts.extend(self._duplicate_ids())
 
513
        conflicts.extend(self._parent_type_conflicts(by_parent))
 
514
        conflicts.extend(self._improper_versioning())
 
515
        conflicts.extend(self._executability_conflicts())
 
516
        conflicts.extend(self._overwrite_conflicts())
 
517
        return conflicts
 
518
 
 
519
    def _add_tree_children(self):
 
520
        """Add all the children of all active parents to the known paths.
 
521
 
 
522
        Active parents are those which gain children, and those which are
 
523
        removed.  This is a necessary first step in detecting conflicts.
 
524
        """
 
525
        parents = self.by_parent().keys()
 
526
        parents.extend([t for t in self._removed_contents if 
 
527
                        self.tree_kind(t) == 'directory'])
 
528
        for trans_id in self._removed_id:
 
529
            file_id = self.tree_file_id(trans_id)
 
530
            if self._tree.inventory[file_id].kind == 'directory':
 
531
                parents.append(trans_id)
 
532
 
 
533
        for parent_id in parents:
 
534
            # ensure that all children are registered with the transaction
 
535
            list(self.iter_tree_children(parent_id))
 
536
 
 
537
    def iter_tree_children(self, parent_id):
 
538
        """Iterate through the entry's tree children, if any"""
 
539
        try:
 
540
            path = self._tree_id_paths[parent_id]
 
541
        except KeyError:
 
542
            return
 
543
        try:
 
544
            children = os.listdir(self._tree.abspath(path))
 
545
        except OSError, e:
 
546
            if e.errno != errno.ENOENT and e.errno != errno.ESRCH:
 
547
                raise
 
548
            return
 
549
            
 
550
        for child in children:
 
551
            childpath = joinpath(path, child)
 
552
            if self._tree.is_control_filename(childpath):
 
553
                continue
 
554
            yield self.trans_id_tree_path(childpath)
 
555
 
 
556
    def has_named_child(self, by_parent, parent_id, name):
 
557
        try:
 
558
            children = by_parent[parent_id]
 
559
        except KeyError:
 
560
            children = []
 
561
        for child in children:
 
562
            if self.final_name(child) == name:
 
563
                return True
 
564
        try:
 
565
            path = self._tree_id_paths[parent_id]
 
566
        except KeyError:
 
567
            return False
 
568
        childpath = joinpath(path, name)
 
569
        child_id = self._tree_path_ids.get(childpath)
 
570
        if child_id is None:
 
571
            return lexists(self._tree.abspath(childpath))
 
572
        else:
 
573
            if self.final_parent(child_id) != parent_id:
 
574
                return False
 
575
            if child_id in self._removed_contents:
 
576
                # XXX What about dangling file-ids?
 
577
                return False
 
578
            else:
 
579
                return True
 
580
 
 
581
    def _parent_loops(self):
 
582
        """No entry should be its own ancestor"""
 
583
        conflicts = []
 
584
        for trans_id in self._new_parent:
 
585
            seen = set()
 
586
            parent_id = trans_id
 
587
            while parent_id is not ROOT_PARENT:
 
588
                seen.add(parent_id)
 
589
                try:
 
590
                    parent_id = self.final_parent(parent_id)
 
591
                except KeyError:
 
592
                    break
 
593
                if parent_id == trans_id:
 
594
                    conflicts.append(('parent loop', trans_id))
 
595
                if parent_id in seen:
 
596
                    break
 
597
        return conflicts
 
598
 
 
599
    def _unversioned_parents(self, by_parent):
 
600
        """If parent directories are versioned, children must be versioned."""
 
601
        conflicts = []
 
602
        for parent_id, children in by_parent.iteritems():
 
603
            if parent_id is ROOT_PARENT:
 
604
                continue
 
605
            if self.final_file_id(parent_id) is not None:
 
606
                continue
 
607
            for child_id in children:
 
608
                if self.final_file_id(child_id) is not None:
 
609
                    conflicts.append(('unversioned parent', parent_id))
 
610
                    break;
 
611
        return conflicts
 
612
 
 
613
    def _improper_versioning(self):
 
614
        """Cannot version a file with no contents, or a bad type.
 
615
        
 
616
        However, existing entries with no contents are okay.
 
617
        """
 
618
        conflicts = []
 
619
        for trans_id in self._new_id.iterkeys():
 
620
            try:
 
621
                kind = self.final_kind(trans_id)
 
622
            except NoSuchFile:
 
623
                conflicts.append(('versioning no contents', trans_id))
 
624
                continue
 
625
            if not InventoryEntry.versionable_kind(kind):
 
626
                conflicts.append(('versioning bad kind', trans_id, kind))
 
627
        return conflicts
 
628
 
 
629
    def _executability_conflicts(self):
 
630
        """Check for bad executability changes.
 
631
        
 
632
        Only versioned files may have their executability set, because
 
633
        1. only versioned entries can have executability under windows
 
634
        2. only files can be executable.  (The execute bit on a directory
 
635
           does not indicate searchability)
 
636
        """
 
637
        conflicts = []
 
638
        for trans_id in self._new_executability:
 
639
            if self.final_file_id(trans_id) is None:
 
640
                conflicts.append(('unversioned executability', trans_id))
 
641
            else:
 
642
                try:
 
643
                    non_file = self.final_kind(trans_id) != "file"
 
644
                except NoSuchFile:
 
645
                    non_file = True
 
646
                if non_file is True:
 
647
                    conflicts.append(('non-file executability', trans_id))
 
648
        return conflicts
 
649
 
 
650
    def _overwrite_conflicts(self):
 
651
        """Check for overwrites (not permitted on Win32)"""
 
652
        conflicts = []
 
653
        for trans_id in self._new_contents:
 
654
            try:
 
655
                self.tree_kind(trans_id)
 
656
            except NoSuchFile:
 
657
                continue
 
658
            if trans_id not in self._removed_contents:
 
659
                conflicts.append(('overwrite', trans_id,
 
660
                                 self.final_name(trans_id)))
 
661
        return conflicts
 
662
 
 
663
    def _duplicate_entries(self, by_parent):
 
664
        """No directory may have two entries with the same name."""
 
665
        conflicts = []
 
666
        for children in by_parent.itervalues():
 
667
            name_ids = [(self.final_name(t), t) for t in children]
 
668
            name_ids.sort()
 
669
            last_name = None
 
670
            last_trans_id = None
 
671
            for name, trans_id in name_ids:
 
672
                try:
 
673
                    kind = self.final_kind(trans_id)
 
674
                except NoSuchFile:
 
675
                    kind = None
 
676
                file_id = self.final_file_id(trans_id)
 
677
                if kind is None and file_id is None:
 
678
                    continue
 
679
                if name == last_name:
 
680
                    conflicts.append(('duplicate', last_trans_id, trans_id,
 
681
                    name))
 
682
                last_name = name
 
683
                last_trans_id = trans_id
 
684
        return conflicts
 
685
 
 
686
    def _duplicate_ids(self):
 
687
        """Each inventory id may only be used once"""
 
688
        conflicts = []
 
689
        removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
 
690
                                self._removed_id))
 
691
        active_tree_ids = set((f for f in self._tree.inventory if
 
692
                               f not in removed_tree_ids))
 
693
        for trans_id, file_id in self._new_id.iteritems():
 
694
            if file_id in active_tree_ids:
 
695
                old_trans_id = self.trans_id_tree_file_id(file_id)
 
696
                conflicts.append(('duplicate id', old_trans_id, trans_id))
 
697
        return conflicts
 
698
 
 
699
    def _parent_type_conflicts(self, by_parent):
 
700
        """parents must have directory 'contents'."""
 
701
        conflicts = []
 
702
        for parent_id, children in by_parent.iteritems():
 
703
            if parent_id is ROOT_PARENT:
 
704
                continue
 
705
            if not self._any_contents(children):
 
706
                continue
 
707
            for child in children:
 
708
                try:
 
709
                    self.final_kind(child)
 
710
                except NoSuchFile:
 
711
                    continue
 
712
            try:
 
713
                kind = self.final_kind(parent_id)
 
714
            except NoSuchFile:
 
715
                kind = None
 
716
            if kind is None:
 
717
                conflicts.append(('missing parent', parent_id))
 
718
            elif kind != "directory":
 
719
                conflicts.append(('non-directory parent', parent_id))
 
720
        return conflicts
 
721
 
 
722
    def _any_contents(self, trans_ids):
 
723
        """Return true if any of the trans_ids, will have contents."""
 
724
        for trans_id in trans_ids:
 
725
            try:
 
726
                kind = self.final_kind(trans_id)
 
727
            except NoSuchFile:
 
728
                continue
 
729
            return True
 
730
        return False
 
731
            
 
732
    def apply(self):
 
733
        """Apply all changes to the inventory and filesystem.
 
734
        
 
735
        If filesystem or inventory conflicts are present, MalformedTransform
 
736
        will be thrown.
 
737
        """
 
738
        conflicts = self.find_conflicts()
 
739
        if len(conflicts) != 0:
 
740
            raise MalformedTransform(conflicts=conflicts)
 
741
        inv = self._tree.inventory
 
742
        inventory_delta = []
 
743
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
744
        try:
 
745
            child_pb.update('Apply phase', 0, 2)
 
746
            self._apply_removals(inv, inventory_delta)
 
747
            child_pb.update('Apply phase', 1, 2)
 
748
            modified_paths = self._apply_insertions(inv, inventory_delta)
 
749
        finally:
 
750
            child_pb.finished()
 
751
        self._tree.apply_inventory_delta(inventory_delta)
 
752
        self.__done = True
 
753
        self.finalize()
 
754
        return _TransformResults(modified_paths)
 
755
 
 
756
    def _limbo_name(self, trans_id):
 
757
        """Generate the limbo name of a file"""
 
758
        return pathjoin(self._limbodir, trans_id)
 
759
 
 
760
    def _apply_removals(self, inv, inventory_delta):
 
761
        """Perform tree operations that remove directory/inventory names.
 
762
        
 
763
        That is, delete files that are to be deleted, and put any files that
 
764
        need renaming into limbo.  This must be done in strict child-to-parent
 
765
        order.
 
766
        """
 
767
        tree_paths = list(self._tree_path_ids.iteritems())
 
768
        tree_paths.sort(reverse=True)
 
769
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
770
        try:
 
771
            for num, data in enumerate(tree_paths):
 
772
                path, trans_id = data
 
773
                child_pb.update('removing file', num, len(tree_paths))
 
774
                full_path = self._tree.abspath(path)
 
775
                if trans_id in self._removed_contents:
 
776
                    delete_any(full_path)
 
777
                elif trans_id in self._new_name or trans_id in \
 
778
                    self._new_parent:
 
779
                    try:
 
780
                        os.rename(full_path, self._limbo_name(trans_id))
 
781
                    except OSError, e:
 
782
                        if e.errno != errno.ENOENT:
 
783
                            raise
 
784
                if trans_id in self._removed_id:
 
785
                    if trans_id == self._new_root:
 
786
                        file_id = self._tree.inventory.root.file_id
 
787
                    else:
 
788
                        file_id = self.tree_file_id(trans_id)
 
789
                    assert file_id is not None
 
790
                    inventory_delta.append((path, None, file_id, None))
 
791
        finally:
 
792
            child_pb.finished()
 
793
 
 
794
    def _apply_insertions(self, inv, inventory_delta):
 
795
        """Perform tree operations that insert directory/inventory names.
 
796
        
 
797
        That is, create any files that need to be created, and restore from
 
798
        limbo any files that needed renaming.  This must be done in strict
 
799
        parent-to-child order.
 
800
        """
 
801
        new_paths = self.new_paths()
 
802
        modified_paths = []
 
803
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
804
        try:
 
805
            for num, (path, trans_id) in enumerate(new_paths):
 
806
                new_entry = None
 
807
                child_pb.update('adding file', num, len(new_paths))
 
808
                try:
 
809
                    kind = self._new_contents[trans_id]
 
810
                except KeyError:
 
811
                    kind = contents = None
 
812
                if trans_id in self._new_contents or \
 
813
                    self.path_changed(trans_id):
 
814
                    full_path = self._tree.abspath(path)
 
815
                    try:
 
816
                        os.rename(self._limbo_name(trans_id), full_path)
 
817
                    except OSError, e:
 
818
                        # We may be renaming a dangling inventory id
 
819
                        if e.errno != errno.ENOENT:
 
820
                            raise
 
821
                    if trans_id in self._new_contents:
 
822
                        modified_paths.append(full_path)
 
823
                        del self._new_contents[trans_id]
 
824
 
 
825
                if trans_id in self._new_id:
 
826
                    if kind is None:
 
827
                        kind = file_kind(self._tree.abspath(path))
 
828
                    if trans_id in self._new_reference_revision:
 
829
                        new_entry = inventory.TreeReference(
 
830
                            self._new_id[trans_id],
 
831
                            self._new_name[trans_id], 
 
832
                            self.final_file_id(self._new_parent[trans_id]),
 
833
                            None, self._new_reference_revision[trans_id])
 
834
                    else:
 
835
                        new_entry = inventory.make_entry(kind,
 
836
                            self.final_name(trans_id),
 
837
                            self.final_file_id(self.final_parent(trans_id)),
 
838
                            self._new_id[trans_id])
 
839
                else:
 
840
                    if trans_id in self._new_name or trans_id in\
 
841
                        self._new_parent or\
 
842
                        trans_id in self._new_executability:
 
843
                        file_id = self.final_file_id(trans_id)
 
844
                        if file_id is not None:
 
845
                            entry = inv[file_id]
 
846
                            new_entry = entry.copy()
 
847
 
 
848
                    if trans_id in self._new_name or trans_id in\
 
849
                        self._new_parent:
 
850
                            if new_entry is not None:
 
851
                                new_entry.name = self.final_name(trans_id)
 
852
                                parent = self.final_parent(trans_id)
 
853
                                parent_id = self.final_file_id(parent)
 
854
                                new_entry.parent_id = parent_id
 
855
 
 
856
                if trans_id in self._new_executability:
 
857
                    self._set_executability(path, new_entry, trans_id)
 
858
                if new_entry is not None:
 
859
                    if new_entry.file_id in inv:
 
860
                        old_path = inv.id2path(new_entry.file_id)
 
861
                    else:
 
862
                        old_path = None
 
863
                    inventory_delta.append((old_path, path,
 
864
                                            new_entry.file_id,
 
865
                                            new_entry))
 
866
        finally:
 
867
            child_pb.finished()
 
868
        return modified_paths
 
869
 
 
870
    def _set_executability(self, path, entry, trans_id):
 
871
        """Set the executability of versioned files """
 
872
        new_executability = self._new_executability[trans_id]
 
873
        entry.executable = new_executability
 
874
        if supports_executable():
 
875
            abspath = self._tree.abspath(path)
 
876
            current_mode = os.stat(abspath).st_mode
 
877
            if new_executability:
 
878
                umask = os.umask(0)
 
879
                os.umask(umask)
 
880
                to_mode = current_mode | (0100 & ~umask)
 
881
                # Enable x-bit for others only if they can read it.
 
882
                if current_mode & 0004:
 
883
                    to_mode |= 0001 & ~umask
 
884
                if current_mode & 0040:
 
885
                    to_mode |= 0010 & ~umask
 
886
            else:
 
887
                to_mode = current_mode & ~0111
 
888
            os.chmod(abspath, to_mode)
 
889
 
 
890
    def _new_entry(self, name, parent_id, file_id):
 
891
        """Helper function to create a new filesystem entry."""
 
892
        trans_id = self.create_path(name, parent_id)
 
893
        if file_id is not None:
 
894
            self.version_file(file_id, trans_id)
 
895
        return trans_id
 
896
 
 
897
    def new_file(self, name, parent_id, contents, file_id=None, 
 
898
                 executable=None):
399
899
        """Convenience method to create files.
400
 
 
 
900
        
401
901
        name is the name of the file to create.
402
902
        parent_id is the transaction id of the parent directory of the file.
403
903
        contents is an iterator of bytestrings, which will be used to produce
405
905
        :param file_id: The inventory ID of the file, if it is to be versioned.
406
906
        :param executable: Only valid when a file_id has been supplied.
407
907
        """
408
 
        raise NotImplementedError(self.new_file)
 
908
        trans_id = self._new_entry(name, parent_id, file_id)
 
909
        # TODO: rather than scheduling a set_executable call,
 
910
        # have create_file create the file with the right mode.
 
911
        self.create_file(contents, trans_id)
 
912
        if executable is not None:
 
913
            self.set_executability(executable, trans_id)
 
914
        return trans_id
409
915
 
410
916
    def new_directory(self, name, parent_id, file_id=None):
411
917
        """Convenience method to create directories.
415
921
        directory.
416
922
        file_id is the inventory ID of the directory, if it is to be versioned.
417
923
        """
418
 
        raise NotImplementedError(self.new_directory)
 
924
        trans_id = self._new_entry(name, parent_id, file_id)
 
925
        self.create_directory(trans_id)
 
926
        return trans_id 
419
927
 
420
928
    def new_symlink(self, name, parent_id, target, file_id=None):
421
929
        """Convenience method to create symbolic link.
422
 
 
 
930
        
423
931
        name is the name of the symlink to create.
424
932
        parent_id is the transaction id of the parent directory of the symlink.
425
933
        target is a bytestring of the target of the symlink.
426
934
        file_id is the inventory ID of the file, if it is to be versioned.
427
935
        """
428
 
        raise NotImplementedError(self.new_symlink)
429
 
 
430
 
    def new_orphan(self, trans_id, parent_id):
431
 
        """Schedule an item to be orphaned.
432
 
 
433
 
        When a directory is about to be removed, its children, if they are not
434
 
        versioned are moved out of the way: they don't have a parent anymore.
435
 
 
436
 
        :param trans_id: The trans_id of the existing item.
437
 
        :param parent_id: The parent trans_id of the item.
438
 
        """
439
 
        raise NotImplementedError(self.new_orphan)
440
 
 
441
 
    def iter_changes(self):
442
 
        """Produce output in the same format as Tree.iter_changes.
 
936
        trans_id = self._new_entry(name, parent_id, file_id)
 
937
        self.create_symlink(target, trans_id)
 
938
        return trans_id
 
939
 
 
940
    def _affected_ids(self):
 
941
        """Return the set of transform ids affected by the transform"""
 
942
        trans_ids = set(self._removed_id)
 
943
        trans_ids.update(self._new_id.keys())
 
944
        trans_ids.update(self._removed_contents)
 
945
        trans_ids.update(self._new_contents.keys())
 
946
        trans_ids.update(self._new_executability.keys())
 
947
        trans_ids.update(self._new_name.keys())
 
948
        trans_ids.update(self._new_parent.keys())
 
949
        return trans_ids
 
950
 
 
951
    def _get_file_id_maps(self):
 
952
        """Return mapping of file_ids to trans_ids in the to and from states"""
 
953
        trans_ids = self._affected_ids()
 
954
        from_trans_ids = {}
 
955
        to_trans_ids = {}
 
956
        # Build up two dicts: trans_ids associated with file ids in the
 
957
        # FROM state, vs the TO state.
 
958
        for trans_id in trans_ids:
 
959
            from_file_id = self.tree_file_id(trans_id)
 
960
            if from_file_id is not None:
 
961
                from_trans_ids[from_file_id] = trans_id
 
962
            to_file_id = self.final_file_id(trans_id)
 
963
            if to_file_id is not None:
 
964
                to_trans_ids[to_file_id] = trans_id
 
965
        return from_trans_ids, to_trans_ids
 
966
 
 
967
    def _from_file_data(self, from_trans_id, from_versioned, file_id):
 
968
        """Get data about a file in the from (tree) state
 
969
 
 
970
        Return a (name, parent, kind, executable) tuple
 
971
        """
 
972
        from_path = self._tree_id_paths.get(from_trans_id)
 
973
        if from_versioned:
 
974
            # get data from working tree if versioned
 
975
            from_entry = self._tree.inventory[file_id]
 
976
            from_name = from_entry.name
 
977
            from_parent = from_entry.parent_id
 
978
        else:
 
979
            from_entry = None
 
980
            if from_path is None:
 
981
                # File does not exist in FROM state
 
982
                from_name = None
 
983
                from_parent = None
 
984
            else:
 
985
                # File exists, but is not versioned.  Have to use path-
 
986
                # splitting stuff
 
987
                from_name = os.path.basename(from_path)
 
988
                tree_parent = self.get_tree_parent(from_trans_id)
 
989
                from_parent = self.tree_file_id(tree_parent)
 
990
        if from_path is not None:
 
991
            from_kind, from_executable, from_stats = \
 
992
                self._tree._comparison_data(from_entry, from_path)
 
993
        else:
 
994
            from_kind = None
 
995
            from_executable = False
 
996
        return from_name, from_parent, from_kind, from_executable
 
997
 
 
998
    def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
 
999
        """Get data about a file in the to (target) state
 
1000
 
 
1001
        Return a (name, parent, kind, executable) tuple
 
1002
        """
 
1003
        to_name = self.final_name(to_trans_id)
 
1004
        try:
 
1005
            to_kind = self.final_kind(to_trans_id)
 
1006
        except NoSuchFile:
 
1007
            to_kind = None
 
1008
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
 
1009
        if to_trans_id in self._new_executability:
 
1010
            to_executable = self._new_executability[to_trans_id]
 
1011
        elif to_trans_id == from_trans_id:
 
1012
            to_executable = from_executable
 
1013
        else:
 
1014
            to_executable = False
 
1015
        return to_name, to_parent, to_kind, to_executable
 
1016
 
 
1017
    def _iter_changes(self):
 
1018
        """Produce output in the same format as Tree._iter_changes.
443
1019
 
444
1020
        Will produce nonsensical results if invoked while inventory/filesystem
445
 
        conflicts (as reported by TreeTransform.find_raw_conflicts()) are present.
 
1021
        conflicts (as reported by TreeTransform.find_conflicts()) are present.
446
1022
 
447
1023
        This reads the Transform, but only reproduces changes involving a
448
1024
        file_id.  Files that are not versioned in either of the FROM or TO
449
1025
        states are not reflected.
450
1026
        """
451
 
        raise NotImplementedError(self.iter_changes)
452
 
 
453
 
    def get_preview_tree(self):
454
 
        """Return a tree representing the result of the transform.
455
 
 
456
 
        The tree is a snapshot, and altering the TreeTransform will invalidate
457
 
        it.
458
 
        """
459
 
        raise NotImplementedError(self.get_preview_tree)
460
 
 
461
 
    def commit(self, branch, message, merge_parents=None, strict=False,
462
 
               timestamp=None, timezone=None, committer=None, authors=None,
463
 
               revprops=None, revision_id=None):
464
 
        """Commit the result of this TreeTransform to a branch.
465
 
 
466
 
        :param branch: The branch to commit to.
467
 
        :param message: The message to attach to the commit.
468
 
        :param merge_parents: Additional parent revision-ids specified by
469
 
            pending merges.
470
 
        :param strict: If True, abort the commit if there are unversioned
471
 
            files.
472
 
        :param timestamp: if not None, seconds-since-epoch for the time and
473
 
            date.  (May be a float.)
474
 
        :param timezone: Optional timezone for timestamp, as an offset in
475
 
            seconds.
476
 
        :param committer: Optional committer in email-id format.
477
 
            (e.g. "J Random Hacker <jrandom@example.com>")
478
 
        :param authors: Optional list of authors in email-id format.
479
 
        :param revprops: Optional dictionary of revision properties.
480
 
        :param revision_id: Optional revision id.  (Specifying a revision-id
481
 
            may reduce performance for some non-native formats.)
482
 
        :return: The revision_id of the revision committed.
483
 
        """
484
 
        raise NotImplementedError(self.commit)
485
 
 
486
 
    def create_file(self, contents, trans_id, mode_id=None, sha1=None):
487
 
        """Schedule creation of a new file.
488
 
 
489
 
        :seealso: new_file.
490
 
 
491
 
        :param contents: an iterator of strings, all of which will be written
492
 
            to the target destination.
493
 
        :param trans_id: TreeTransform handle
494
 
        :param mode_id: If not None, force the mode of the target file to match
495
 
            the mode of the object referenced by mode_id.
496
 
            Otherwise, we will try to preserve mode bits of an existing file.
497
 
        :param sha1: If the sha1 of this content is already known, pass it in.
498
 
            We can use it to prevent future sha1 computations.
499
 
        """
500
 
        raise NotImplementedError(self.create_file)
501
 
 
502
 
    def create_directory(self, trans_id):
503
 
        """Schedule creation of a new directory.
504
 
 
505
 
        See also new_directory.
506
 
        """
507
 
        raise NotImplementedError(self.create_directory)
508
 
 
509
 
    def create_symlink(self, target, trans_id):
510
 
        """Schedule creation of a new symbolic link.
511
 
 
512
 
        target is a bytestring.
513
 
        See also new_symlink.
514
 
        """
515
 
        raise NotImplementedError(self.create_symlink)
516
 
 
517
 
    def create_hardlink(self, path, trans_id):
518
 
        """Schedule creation of a hard link"""
519
 
        raise NotImplementedError(self.create_hardlink)
520
 
 
521
 
    def cancel_creation(self, trans_id):
522
 
        """Cancel the creation of new file contents."""
523
 
        raise NotImplementedError(self.cancel_creation)
524
 
 
525
 
    def cook_conflicts(self, raw_conflicts):
526
 
        """Cook conflicts.
527
 
        """
528
 
        raise NotImplementedError(self.cook_conflicts)
529
 
 
530
 
 
531
 
class OrphaningError(errors.BzrError):
532
 
 
533
 
    # Only bugs could lead to such exception being seen by the user
534
 
    internal_error = True
535
 
    _fmt = "Error while orphaning %s in %s directory"
536
 
 
537
 
    def __init__(self, orphan, parent):
538
 
        errors.BzrError.__init__(self)
539
 
        self.orphan = orphan
540
 
        self.parent = parent
541
 
 
542
 
 
543
 
class OrphaningForbidden(OrphaningError):
544
 
 
545
 
    _fmt = "Policy: %s doesn't allow creating orphans."
546
 
 
547
 
    def __init__(self, policy):
548
 
        errors.BzrError.__init__(self)
549
 
        self.policy = policy
550
 
 
551
 
 
552
 
def move_orphan(tt, orphan_id, parent_id):
553
 
    """See TreeTransformBase.new_orphan.
554
 
 
555
 
    This creates a new orphan in the `brz-orphans` dir at the root of the
556
 
    `TreeTransform`.
557
 
 
558
 
    :param tt: The TreeTransform orphaning `trans_id`.
559
 
 
560
 
    :param orphan_id: The trans id that should be orphaned.
561
 
 
562
 
    :param parent_id: The orphan parent trans id.
563
 
    """
564
 
    # Add the orphan dir if it doesn't exist
565
 
    orphan_dir_basename = 'brz-orphans'
566
 
    od_id = tt.trans_id_tree_path(orphan_dir_basename)
567
 
    if tt.final_kind(od_id) is None:
568
 
        tt.create_directory(od_id)
569
 
    parent_path = tt._tree_id_paths[parent_id]
570
 
    # Find a name that doesn't exist yet in the orphan dir
571
 
    actual_name = tt.final_name(orphan_id)
572
 
    new_name = tt._available_backup_name(actual_name, od_id)
573
 
    tt.adjust_path(new_name, od_id, orphan_id)
574
 
    trace.warning('%s has been orphaned in %s'
575
 
                  % (joinpath(parent_path, actual_name), orphan_dir_basename))
576
 
 
577
 
 
578
 
def refuse_orphan(tt, orphan_id, parent_id):
579
 
    """See TreeTransformBase.new_orphan.
580
 
 
581
 
    This refuses to create orphan, letting the caller handle the conflict.
582
 
    """
583
 
    raise OrphaningForbidden('never')
584
 
 
585
 
 
586
 
orphaning_registry = registry.Registry()
587
 
orphaning_registry.register(
588
 
    u'conflict', refuse_orphan,
589
 
    'Leave orphans in place and create a conflict on the directory.')
590
 
orphaning_registry.register(
591
 
    u'move', move_orphan,
592
 
    'Move orphans into the brz-orphans directory.')
593
 
orphaning_registry._set_default_key(u'conflict')
594
 
 
595
 
 
596
 
opt_transform_orphan = _mod_config.RegistryOption(
597
 
    'transform.orphan_policy', orphaning_registry,
598
 
    help='Policy for orphaned files during transform operations.',
599
 
    invalid='warning')
 
1027
        final_paths = FinalPaths(self)
 
1028
        from_trans_ids, to_trans_ids = self._get_file_id_maps()
 
1029
        results = []
 
1030
        # Now iterate through all active file_ids
 
1031
        for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
 
1032
            modified = False
 
1033
            from_trans_id = from_trans_ids.get(file_id)
 
1034
            # find file ids, and determine versioning state
 
1035
            if from_trans_id is None:
 
1036
                from_versioned = False
 
1037
                from_trans_id = to_trans_ids[file_id]
 
1038
            else:
 
1039
                from_versioned = True
 
1040
            to_trans_id = to_trans_ids.get(file_id)
 
1041
            if to_trans_id is None:
 
1042
                to_versioned = False
 
1043
                to_trans_id = from_trans_id
 
1044
            else:
 
1045
                to_versioned = True
 
1046
 
 
1047
            from_name, from_parent, from_kind, from_executable = \
 
1048
                self._from_file_data(from_trans_id, from_versioned, file_id)
 
1049
 
 
1050
            to_name, to_parent, to_kind, to_executable = \
 
1051
                self._to_file_data(to_trans_id, from_trans_id, from_executable)
 
1052
 
 
1053
            if not from_versioned:
 
1054
                from_path = None
 
1055
            else:
 
1056
                from_path = self._tree_id_paths.get(from_trans_id)
 
1057
            if not to_versioned:
 
1058
                to_path = None
 
1059
            else:
 
1060
                to_path = final_paths.get_path(to_trans_id)
 
1061
            if from_kind != to_kind:
 
1062
                modified = True
 
1063
            elif to_kind in ('file', 'symlink') and (
 
1064
                to_trans_id != from_trans_id or
 
1065
                to_trans_id in self._new_contents):
 
1066
                modified = True
 
1067
            if (not modified and from_versioned == to_versioned and
 
1068
                from_parent==to_parent and from_name == to_name and
 
1069
                from_executable == to_executable):
 
1070
                continue
 
1071
            results.append((file_id, (from_path, to_path), modified,
 
1072
                   (from_versioned, to_versioned),
 
1073
                   (from_parent, to_parent),
 
1074
                   (from_name, to_name),
 
1075
                   (from_kind, to_kind),
 
1076
                   (from_executable, to_executable)))
 
1077
        return iter(sorted(results, key=lambda x:x[1]))
600
1078
 
601
1079
 
602
1080
def joinpath(parent, child):
613
1091
    The underlying tree must not be manipulated between calls, or else
614
1092
    the results will likely be incorrect.
615
1093
    """
616
 
 
617
1094
    def __init__(self, transform):
618
1095
        object.__init__(self)
619
1096
        self._known_paths = {}
620
1097
        self.transform = transform
621
1098
 
622
1099
    def _determine_path(self, trans_id):
623
 
        if trans_id == self.transform.root or trans_id == ROOT_PARENT:
624
 
            return u""
 
1100
        if trans_id == self.transform.root:
 
1101
            return ""
625
1102
        name = self.transform.final_name(trans_id)
626
1103
        parent_id = self.transform.final_parent(trans_id)
627
1104
        if parent_id == self.transform.root:
635
1112
            self._known_paths[trans_id] = self._determine_path(trans_id)
636
1113
        return self._known_paths[trans_id]
637
1114
 
638
 
    def get_paths(self, trans_ids):
639
 
        return [(self.get_path(t), t) for t in trans_ids]
640
 
 
641
 
 
642
 
def build_tree(tree, wt, accelerator_tree=None, hardlink=False,
643
 
               delta_from_tree=False):
 
1115
def topology_sorted_ids(tree):
 
1116
    """Determine the topological order of the ids in a tree"""
 
1117
    file_ids = list(tree)
 
1118
    file_ids.sort(key=tree.id2path)
 
1119
    return file_ids
 
1120
 
 
1121
 
 
1122
def build_tree(tree, wt):
644
1123
    """Create working tree for a branch, using a TreeTransform.
645
 
 
 
1124
    
646
1125
    This function should be used on empty trees, having a tree root at most.
647
1126
    (see merge and revert functionality for working with existing trees)
648
1127
 
649
1128
    Existing files are handled like so:
650
 
 
 
1129
    
651
1130
    - Existing bzrdirs take precedence over creating new items.  They are
652
1131
      created as '%s.diverted' % name.
653
1132
    - Otherwise, if the content on disk matches the content we are building,
654
1133
      it is silently replaced.
655
1134
    - Otherwise, conflict resolution will move the old file to 'oldname.moved'.
656
 
 
657
 
    :param tree: The tree to convert wt into a copy of
658
 
    :param wt: The working tree that files will be placed into
659
 
    :param accelerator_tree: A tree which can be used for retrieving file
660
 
        contents more quickly than tree itself, i.e. a workingtree.  tree
661
 
        will be used for cases where accelerator_tree's content is different.
662
 
    :param hardlink: If true, hard-link files to accelerator_tree, where
663
 
        possible.  accelerator_tree must implement abspath, i.e. be a
664
 
        working tree.
665
 
    :param delta_from_tree: If true, build_tree may use the input Tree to
666
 
        generate the inventory delta.
667
1135
    """
668
 
    with contextlib.ExitStack() as exit_stack:
669
 
        exit_stack.enter_context(wt.lock_tree_write())
670
 
        exit_stack.enter_context(tree.lock_read())
671
 
        if accelerator_tree is not None:
672
 
            exit_stack.enter_context(accelerator_tree.lock_read())
673
 
        return _build_tree(tree, wt, accelerator_tree, hardlink,
674
 
                           delta_from_tree)
675
 
 
676
 
 
677
 
def _build_tree(tree, wt, accelerator_tree, hardlink, delta_from_tree):
 
1136
    wt.lock_tree_write()
 
1137
    try:
 
1138
        tree.lock_read()
 
1139
        try:
 
1140
            return _build_tree(tree, wt)
 
1141
        finally:
 
1142
            tree.unlock()
 
1143
    finally:
 
1144
        wt.unlock()
 
1145
 
 
1146
def _build_tree(tree, wt):
678
1147
    """See build_tree."""
679
 
    for num, _unused in enumerate(wt.all_versioned_paths()):
680
 
        if num > 0:  # more than just a root
681
 
            raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
 
1148
    if len(wt.inventory) > 1:  # more than just a root
 
1149
        raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
682
1150
    file_trans_id = {}
683
 
    top_pb = ui.ui_factory.nested_progress_bar()
 
1151
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
684
1152
    pp = ProgressPhase("Build phase", 2, top_pb)
685
 
    if tree.path2id('') is not None:
686
 
        # This is kind of a hack: we should be altering the root
687
 
        # as part of the regular tree shape diff logic.
688
 
        # The conditional test here is to avoid doing an
 
1153
    if tree.inventory.root is not None:
 
1154
        # this is kindof a hack: we should be altering the root 
 
1155
        # as partof the regular tree shape diff logic.
 
1156
        # the conditional test hereis to avoid doing an
689
1157
        # expensive operation (flush) every time the root id
690
1158
        # is set within the tree, nor setting the root and thus
691
1159
        # marking the tree as dirty, because we use two different
692
1160
        # idioms here: tree interfaces and inventory interfaces.
693
 
        if wt.path2id('') != tree.path2id(''):
694
 
            wt.set_root_id(tree.path2id(''))
 
1161
        if wt.path2id('') != tree.inventory.root.file_id:
 
1162
            wt.set_root_id(tree.inventory.root.file_id)
695
1163
            wt.flush()
696
 
    tt = wt.transform()
 
1164
    tt = TreeTransform(wt)
697
1165
    divert = set()
698
1166
    try:
699
1167
        pp.next_phase()
700
 
        file_trans_id[find_previous_path(wt, tree, '')] = tt.trans_id_tree_path('')
701
 
        with ui.ui_factory.nested_progress_bar() as pb:
702
 
            deferred_contents = []
703
 
            num = 0
704
 
            total = len(tree.all_versioned_paths())
705
 
            if delta_from_tree:
706
 
                precomputed_delta = []
707
 
            else:
708
 
                precomputed_delta = None
709
 
            # Check if tree inventory has content. If so, we populate
710
 
            # existing_files with the directory content. If there are no
711
 
            # entries we skip populating existing_files as its not used.
712
 
            # This improves performance and unncessary work on large
713
 
            # directory trees. (#501307)
714
 
            if total > 0:
715
 
                existing_files = set()
716
 
                for dir, files in wt.walkdirs():
717
 
                    existing_files.update(f[0] for f in files)
 
1168
        file_trans_id[wt.get_root_id()] = \
 
1169
            tt.trans_id_tree_file_id(wt.get_root_id())
 
1170
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1171
        try:
718
1172
            for num, (tree_path, entry) in \
719
 
                    enumerate(tree.iter_entries_by_dir()):
720
 
                pb.update(gettext("Building tree"), num
721
 
                          - len(deferred_contents), total)
 
1173
                enumerate(tree.inventory.iter_entries_by_dir()):
 
1174
                pb.update("Building tree", num, len(tree.inventory))
722
1175
                if entry.parent_id is None:
723
1176
                    continue
724
1177
                reparent = False
725
1178
                file_id = entry.file_id
726
 
                if delta_from_tree:
727
 
                    precomputed_delta.append((None, tree_path, file_id, entry))
728
 
                if tree_path in existing_files:
729
 
                    target_path = wt.abspath(tree_path)
 
1179
                target_path = wt.abspath(tree_path)
 
1180
                try:
730
1181
                    kind = file_kind(target_path)
 
1182
                except NoSuchFile:
 
1183
                    pass
 
1184
                else:
731
1185
                    if kind == "directory":
732
1186
                        try:
733
 
                            controldir.ControlDir.open(target_path)
 
1187
                            bzrdir.BzrDir.open(target_path)
734
1188
                        except errors.NotBranchError:
735
1189
                            pass
736
1190
                        else:
737
 
                            divert.add(tree_path)
738
 
                    if (tree_path not in divert
739
 
                        and _content_match(
740
 
                            tree, entry, tree_path, kind, target_path)):
 
1191
                            divert.add(file_id)
 
1192
                    if (file_id not in divert and
 
1193
                        _content_match(tree, entry, file_id, kind,
 
1194
                        target_path)):
741
1195
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
742
1196
                        if kind == 'directory':
743
1197
                            reparent = True
744
 
                parent_id = file_trans_id[osutils.dirname(tree_path)]
745
 
                if entry.kind == 'file':
746
 
                    # We *almost* replicate new_by_entry, so that we can defer
747
 
                    # getting the file text, and get them all at once.
748
 
                    trans_id = tt.create_path(entry.name, parent_id)
749
 
                    file_trans_id[tree_path] = trans_id
750
 
                    tt.version_file(trans_id, file_id=file_id)
751
 
                    executable = tree.is_executable(tree_path)
752
 
                    if executable:
753
 
                        tt.set_executability(executable, trans_id)
754
 
                    trans_data = (trans_id, tree_path, entry.text_sha1)
755
 
                    deferred_contents.append((tree_path, trans_data))
756
 
                else:
757
 
                    file_trans_id[tree_path] = new_by_entry(
758
 
                        tree_path, tt, entry, parent_id, tree)
 
1198
                if entry.parent_id not in file_trans_id:
 
1199
                    raise AssertionError(
 
1200
                        'entry %s parent id %r is not in file_trans_id %r'
 
1201
                        % (entry, entry.parent_id, file_trans_id))
 
1202
                parent_id = file_trans_id[entry.parent_id]
 
1203
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
 
1204
                                                      tree)
759
1205
                if reparent:
760
 
                    new_trans_id = file_trans_id[tree_path]
 
1206
                    new_trans_id = file_trans_id[file_id]
761
1207
                    old_parent = tt.trans_id_tree_path(tree_path)
762
1208
                    _reparent_children(tt, old_parent, new_trans_id)
763
 
            offset = num + 1 - len(deferred_contents)
764
 
            _create_files(tt, tree, deferred_contents, pb, offset,
765
 
                          accelerator_tree, hardlink)
 
1209
        finally:
 
1210
            pb.finished()
766
1211
        pp.next_phase()
767
1212
        divert_trans = set(file_trans_id[f] for f in divert)
768
 
 
769
 
        def resolver(t, c):
770
 
            return resolve_checkout(t, c, divert_trans)
 
1213
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
771
1214
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
772
 
        if len(raw_conflicts) > 0:
773
 
            precomputed_delta = None
774
 
        conflicts = tt.cook_conflicts(raw_conflicts)
 
1215
        conflicts = cook_conflicts(raw_conflicts, tt)
775
1216
        for conflict in conflicts:
776
 
            trace.warning(str(conflict))
 
1217
            warning(conflict)
777
1218
        try:
778
1219
            wt.add_conflicts(conflicts)
779
1220
        except errors.UnsupportedOperation:
780
1221
            pass
781
 
        result = tt.apply(no_conflicts=True,
782
 
                          precomputed_delta=precomputed_delta)
 
1222
        tt.apply()
783
1223
    finally:
784
1224
        tt.finalize()
785
1225
        top_pb.finished()
786
 
    return result
787
 
 
788
 
 
789
 
def _create_files(tt, tree, desired_files, pb, offset, accelerator_tree,
790
 
                  hardlink):
791
 
    total = len(desired_files) + offset
792
 
    wt = tt._tree
793
 
    if accelerator_tree is None:
794
 
        new_desired_files = desired_files
795
 
    else:
796
 
        iter = accelerator_tree.iter_changes(tree, include_unchanged=True)
797
 
        unchanged = [
798
 
            change.path for change in iter
799
 
            if not (change.changed_content or change.executable[0] != change.executable[1])]
800
 
        if accelerator_tree.supports_content_filtering():
801
 
            unchanged = [(tp, ap) for (tp, ap) in unchanged
802
 
                         if not next(accelerator_tree.iter_search_rules([ap]))]
803
 
        unchanged = dict(unchanged)
804
 
        new_desired_files = []
805
 
        count = 0
806
 
        for unused_tree_path, (trans_id, tree_path, text_sha1) in desired_files:
807
 
            accelerator_path = unchanged.get(tree_path)
808
 
            if accelerator_path is None:
809
 
                new_desired_files.append((tree_path,
810
 
                                          (trans_id, tree_path, text_sha1)))
811
 
                continue
812
 
            pb.update(gettext('Adding file contents'), count + offset, total)
813
 
            if hardlink:
814
 
                tt.create_hardlink(accelerator_tree.abspath(accelerator_path),
815
 
                                   trans_id)
816
 
            else:
817
 
                with accelerator_tree.get_file(accelerator_path) as f:
818
 
                    chunks = osutils.file_iterator(f)
819
 
                    if wt.supports_content_filtering():
820
 
                        filters = wt._content_filter_stack(tree_path)
821
 
                        chunks = filtered_output_bytes(chunks, filters,
822
 
                                                       ContentFilterContext(tree_path, tree))
823
 
                    tt.create_file(chunks, trans_id, sha1=text_sha1)
824
 
            count += 1
825
 
        offset += count
826
 
    for count, ((trans_id, tree_path, text_sha1), contents) in enumerate(
827
 
            tree.iter_files_bytes(new_desired_files)):
828
 
        if wt.supports_content_filtering():
829
 
            filters = wt._content_filter_stack(tree_path)
830
 
            contents = filtered_output_bytes(contents, filters,
831
 
                                             ContentFilterContext(tree_path, tree))
832
 
        tt.create_file(contents, trans_id, sha1=text_sha1)
833
 
        pb.update(gettext('Adding file contents'), count + offset, total)
834
1226
 
835
1227
 
836
1228
def _reparent_children(tt, old_parent, new_parent):
838
1230
        tt.adjust_path(tt.final_name(child), new_parent, child)
839
1231
 
840
1232
 
841
 
def _reparent_transform_children(tt, old_parent, new_parent):
842
 
    by_parent = tt.by_parent()
843
 
    for child in by_parent[old_parent]:
844
 
        tt.adjust_path(tt.final_name(child), new_parent, child)
845
 
    return by_parent[old_parent]
846
 
 
847
 
 
848
 
def _content_match(tree, entry, tree_path, kind, target_path):
 
1233
def _content_match(tree, entry, file_id, kind, target_path):
849
1234
    if entry.kind != kind:
850
1235
        return False
851
1236
    if entry.kind == "directory":
852
1237
        return True
853
1238
    if entry.kind == "file":
854
 
        with open(target_path, 'rb') as f1, \
855
 
                tree.get_file(tree_path) as f2:
856
 
            if osutils.compare_files(f1, f2):
857
 
                return True
 
1239
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
 
1240
            return True
858
1241
    elif entry.kind == "symlink":
859
 
        if tree.get_symlink_target(tree_path) == os.readlink(target_path):
 
1242
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
860
1243
            return True
861
1244
    return False
862
1245
 
865
1248
    new_conflicts = set()
866
1249
    for c_type, conflict in ((c[0], c) for c in conflicts):
867
1250
        # Anything but a 'duplicate' would indicate programmer error
868
 
        if c_type != 'duplicate':
869
 
            raise AssertionError(c_type)
 
1251
        assert c_type == 'duplicate', c_type
870
1252
        # Now figure out which is new and which is old
871
1253
        if tt.new_contents(conflict[1]):
872
1254
            new_file = conflict[1]
879
1261
        # resolved
880
1262
        final_parent = tt.final_parent(old_file)
881
1263
        if new_file in divert:
882
 
            new_name = tt.final_name(old_file) + '.diverted'
 
1264
            new_name = tt.final_name(old_file)+'.diverted'
883
1265
            tt.adjust_path(new_name, final_parent, new_file)
884
1266
            new_conflicts.add((c_type, 'Diverted to',
885
1267
                               new_file, old_file))
886
1268
        else:
887
 
            new_name = tt.final_name(old_file) + '.moved'
 
1269
            new_name = tt.final_name(old_file)+'.moved'
888
1270
            tt.adjust_path(new_name, final_parent, old_file)
889
1271
            new_conflicts.add((c_type, 'Moved existing file to',
890
1272
                               old_file, new_file))
891
1273
    return new_conflicts
892
1274
 
893
1275
 
894
 
def new_by_entry(path, tt, entry, parent_id, tree):
 
1276
def new_by_entry(tt, entry, parent_id, tree):
895
1277
    """Create a new file according to its inventory entry"""
896
1278
    name = entry.name
897
1279
    kind = entry.kind
898
1280
    if kind == 'file':
899
 
        with tree.get_file(path) as f:
900
 
            executable = tree.is_executable(path)
901
 
            return tt.new_file(
902
 
                name, parent_id, osutils.file_iterator(f), entry.file_id,
903
 
                executable)
 
1281
        contents = tree.get_file(entry.file_id).readlines()
 
1282
        executable = tree.is_executable(entry.file_id)
 
1283
        return tt.new_file(name, parent_id, contents, entry.file_id, 
 
1284
                           executable)
904
1285
    elif kind in ('directory', 'tree-reference'):
905
1286
        trans_id = tt.new_directory(name, parent_id, entry.file_id)
906
1287
        if kind == 'tree-reference':
907
1288
            tt.set_tree_reference(entry.reference_revision, trans_id)
908
 
        return trans_id
 
1289
        return trans_id 
909
1290
    elif kind == 'symlink':
910
 
        target = tree.get_symlink_target(path)
 
1291
        target = tree.get_symlink_target(entry.file_id)
911
1292
        return tt.new_symlink(name, parent_id, target, entry.file_id)
912
1293
    else:
913
1294
        raise errors.BadFileKindError(name, kind)
914
1295
 
915
 
 
916
 
def create_from_tree(tt, trans_id, tree, path, chunks=None,
917
 
                     filter_tree_path=None):
918
 
    """Create new file contents according to tree contents.
919
 
 
920
 
    :param filter_tree_path: the tree path to use to lookup
921
 
      content filters to apply to the bytes output in the working tree.
922
 
      This only applies if the working tree supports content filtering.
923
 
    """
924
 
    kind = tree.kind(path)
925
 
    if kind == 'directory':
 
1296
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
 
1297
    """Create new file contents according to an inventory entry."""
 
1298
    if entry.kind == "file":
 
1299
        if lines is None:
 
1300
            lines = tree.get_file(entry.file_id).readlines()
 
1301
        tt.create_file(lines, trans_id, mode_id=mode_id)
 
1302
    elif entry.kind == "symlink":
 
1303
        tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
 
1304
    elif entry.kind == "directory":
926
1305
        tt.create_directory(trans_id)
927
 
    elif kind == "file":
928
 
        if chunks is None:
929
 
            f = tree.get_file(path)
930
 
            chunks = osutils.file_iterator(f)
931
 
        else:
932
 
            f = None
933
 
        try:
934
 
            wt = tt._tree
935
 
            if wt.supports_content_filtering() and filter_tree_path is not None:
936
 
                filters = wt._content_filter_stack(filter_tree_path)
937
 
                chunks = filtered_output_bytes(
938
 
                    chunks, filters,
939
 
                    ContentFilterContext(filter_tree_path, tree))
940
 
            tt.create_file(chunks, trans_id)
941
 
        finally:
942
 
            if f is not None:
943
 
                f.close()
944
 
    elif kind == "symlink":
945
 
        tt.create_symlink(tree.get_symlink_target(path), trans_id)
946
 
    else:
947
 
        raise AssertionError('Unknown kind %r' % kind)
948
 
 
949
1306
 
950
1307
def create_entry_executability(tt, entry, trans_id):
951
1308
    """Set the executability of a trans_id according to an inventory entry"""
953
1310
        tt.set_executability(entry.executable, trans_id)
954
1311
 
955
1312
 
 
1313
@deprecated_function(zero_fifteen)
 
1314
def find_interesting(working_tree, target_tree, filenames):
 
1315
    """Find the ids corresponding to specified filenames.
 
1316
    
 
1317
    Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
 
1318
    """
 
1319
    working_tree.lock_read()
 
1320
    try:
 
1321
        target_tree.lock_read()
 
1322
        try:
 
1323
            return working_tree.paths2ids(filenames, [target_tree])
 
1324
        finally:
 
1325
            target_tree.unlock()
 
1326
    finally:
 
1327
        working_tree.unlock()
 
1328
 
 
1329
 
 
1330
def change_entry(tt, file_id, working_tree, target_tree, 
 
1331
                 trans_id_file_id, backups, trans_id, by_parent):
 
1332
    """Replace a file_id's contents with those from a target tree."""
 
1333
    e_trans_id = trans_id_file_id(file_id)
 
1334
    entry = target_tree.inventory[file_id]
 
1335
    has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry, 
 
1336
                                                           working_tree)
 
1337
    if contents_mod:
 
1338
        mode_id = e_trans_id
 
1339
        if has_contents:
 
1340
            if not backups:
 
1341
                tt.delete_contents(e_trans_id)
 
1342
            else:
 
1343
                parent_trans_id = trans_id_file_id(entry.parent_id)
 
1344
                backup_name = get_backup_name(entry, by_parent,
 
1345
                                              parent_trans_id, tt)
 
1346
                tt.adjust_path(backup_name, parent_trans_id, e_trans_id)
 
1347
                tt.unversion_file(e_trans_id)
 
1348
                e_trans_id = tt.create_path(entry.name, parent_trans_id)
 
1349
                tt.version_file(file_id, e_trans_id)
 
1350
                trans_id[file_id] = e_trans_id
 
1351
        create_by_entry(tt, entry, target_tree, e_trans_id, mode_id=mode_id)
 
1352
        create_entry_executability(tt, entry, e_trans_id)
 
1353
 
 
1354
    elif meta_mod:
 
1355
        tt.set_executability(entry.executable, e_trans_id)
 
1356
    if tt.final_name(e_trans_id) != entry.name:
 
1357
        adjust_path  = True
 
1358
    else:
 
1359
        parent_id = tt.final_parent(e_trans_id)
 
1360
        parent_file_id = tt.final_file_id(parent_id)
 
1361
        if parent_file_id != entry.parent_id:
 
1362
            adjust_path = True
 
1363
        else:
 
1364
            adjust_path = False
 
1365
    if adjust_path:
 
1366
        parent_trans_id = trans_id_file_id(entry.parent_id)
 
1367
        tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
 
1368
 
 
1369
 
 
1370
def get_backup_name(entry, by_parent, parent_trans_id, tt):
 
1371
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
 
1372
 
 
1373
 
 
1374
def _get_backup_name(name, by_parent, parent_trans_id, tt):
 
1375
    """Produce a backup-style name that appears to be available"""
 
1376
    def name_gen():
 
1377
        counter = 1
 
1378
        while True:
 
1379
            yield "%s.~%d~" % (name, counter)
 
1380
            counter += 1
 
1381
    for new_name in name_gen():
 
1382
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
 
1383
            return new_name
 
1384
 
 
1385
 
 
1386
def _entry_changes(file_id, entry, working_tree):
 
1387
    """Determine in which ways the inventory entry has changed.
 
1388
 
 
1389
    Returns booleans: has_contents, content_mod, meta_mod
 
1390
    has_contents means there are currently contents, but they differ
 
1391
    contents_mod means contents need to be modified
 
1392
    meta_mod means the metadata needs to be modified
 
1393
    """
 
1394
    cur_entry = working_tree.inventory[file_id]
 
1395
    try:
 
1396
        working_kind = working_tree.kind(file_id)
 
1397
        has_contents = True
 
1398
    except NoSuchFile:
 
1399
        has_contents = False
 
1400
        contents_mod = True
 
1401
        meta_mod = False
 
1402
    if has_contents is True:
 
1403
        if entry.kind != working_kind:
 
1404
            contents_mod, meta_mod = True, False
 
1405
        else:
 
1406
            cur_entry._read_tree_state(working_tree.id2path(file_id), 
 
1407
                                       working_tree)
 
1408
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
 
1409
            cur_entry._forget_tree_state()
 
1410
    return has_contents, contents_mod, meta_mod
 
1411
 
 
1412
 
956
1413
def revert(working_tree, target_tree, filenames, backups=False,
957
 
           pb=None, change_reporter=None):
 
1414
           pb=DummyProgress(), change_reporter=None):
958
1415
    """Revert a working tree's contents to those of a target tree."""
959
 
    pb = ui.ui_factory.nested_progress_bar()
 
1416
    target_tree.lock_read()
 
1417
    tt = TreeTransform(working_tree, pb)
960
1418
    try:
961
 
        with target_tree.lock_read(), working_tree.transform(pb) as tt:
962
 
            pp = ProgressPhase("Revert phase", 3, pb)
963
 
            conflicts, merge_modified = _prepare_revert_transform(
964
 
                working_tree, target_tree, tt, filenames, backups, pp)
965
 
            if change_reporter:
966
 
                from . import delta
967
 
                change_reporter = delta._ChangeReporter(
968
 
                    unversioned_filter=working_tree.is_ignored)
969
 
                delta.report_changes(tt.iter_changes(), change_reporter)
970
 
            for conflict in conflicts:
971
 
                trace.warning(str(conflict))
972
 
            pp.next_phase()
973
 
            tt.apply()
974
 
            if working_tree.supports_merge_modified():
975
 
                working_tree.set_merge_modified(merge_modified)
 
1419
        pp = ProgressPhase("Revert phase", 3, pb)
 
1420
        pp.next_phase()
 
1421
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1422
        try:
 
1423
            _alter_files(working_tree, target_tree, tt, child_pb,
 
1424
                         filenames, backups)
 
1425
        finally:
 
1426
            child_pb.finished()
 
1427
        pp.next_phase()
 
1428
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1429
        try:
 
1430
            raw_conflicts = resolve_conflicts(tt, child_pb)
 
1431
        finally:
 
1432
            child_pb.finished()
 
1433
        conflicts = cook_conflicts(raw_conflicts, tt)
 
1434
        if change_reporter:
 
1435
            change_reporter = delta._ChangeReporter(
 
1436
                unversioned_filter=working_tree.is_ignored)
 
1437
            delta.report_changes(tt._iter_changes(), change_reporter)
 
1438
        for conflict in conflicts:
 
1439
            warning(conflict)
 
1440
        pp.next_phase()
 
1441
        tt.apply()
 
1442
        working_tree.set_merge_modified({})
976
1443
    finally:
 
1444
        target_tree.unlock()
 
1445
        tt.finalize()
977
1446
        pb.clear()
978
1447
    return conflicts
979
1448
 
980
1449
 
981
 
def _prepare_revert_transform(working_tree, target_tree, tt, filenames,
982
 
                              backups, pp, basis_tree=None,
983
 
                              merge_modified=None):
984
 
    with ui.ui_factory.nested_progress_bar() as child_pb:
985
 
        if merge_modified is None:
986
 
            merge_modified = working_tree.merge_modified()
987
 
        merge_modified = _alter_files(working_tree, target_tree, tt,
988
 
                                      child_pb, filenames, backups,
989
 
                                      merge_modified, basis_tree)
990
 
    with ui.ui_factory.nested_progress_bar() as child_pb:
991
 
        raw_conflicts = resolve_conflicts(
992
 
            tt, child_pb, lambda t, c: conflict_pass(t, c, target_tree))
993
 
    conflicts = tt.cook_conflicts(raw_conflicts)
994
 
    return conflicts, merge_modified
995
 
 
996
 
 
997
1450
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
998
 
                 backups, merge_modified, basis_tree=None):
999
 
    if basis_tree is not None:
1000
 
        basis_tree.lock_read()
1001
 
    # We ask the working_tree for its changes relative to the target, rather
1002
 
    # than the target changes relative to the working tree. Because WT4 has an
1003
 
    # optimizer to compare itself to a target, but no optimizer for the
1004
 
    # reverse.
1005
 
    change_list = working_tree.iter_changes(
1006
 
        target_tree, specific_files=specific_files, pb=pb)
1007
 
    if not target_tree.is_versioned(u''):
 
1451
                 backups):
 
1452
    merge_modified = working_tree.merge_modified()
 
1453
    change_list = target_tree._iter_changes(working_tree,
 
1454
        specific_files=specific_files, pb=pb)
 
1455
    if target_tree.inventory.root is None:
1008
1456
        skip_root = True
1009
1457
    else:
1010
1458
        skip_root = False
 
1459
    basis_tree = None
1011
1460
    try:
1012
 
        deferred_files = []
1013
 
        for id_num, change in enumerate(change_list):
1014
 
            target_path, wt_path = change.path
1015
 
            target_versioned, wt_versioned = change.versioned
1016
 
            target_parent = change.parent_id[0]
1017
 
            target_name, wt_name = change.name
1018
 
            target_kind, wt_kind = change.kind
1019
 
            target_executable, wt_executable = change.executable
1020
 
            if skip_root and wt_path == '':
 
1461
        for id_num, (file_id, path, changed_content, versioned, parent, name,
 
1462
                kind, executable) in enumerate(change_list):
 
1463
            if skip_root and file_id[0] is not None and parent[0] is None:
1021
1464
                continue
1022
 
            trans_id = tt.trans_id_file_id(change.file_id)
 
1465
            trans_id = tt.trans_id_file_id(file_id)
1023
1466
            mode_id = None
1024
 
            if change.changed_content:
 
1467
            if changed_content:
1025
1468
                keep_content = False
1026
 
                if wt_kind == 'file' and (backups or target_kind is None):
1027
 
                    wt_sha1 = working_tree.get_file_sha1(wt_path)
1028
 
                    if merge_modified.get(wt_path) != wt_sha1:
1029
 
                        # acquire the basis tree lazily to prevent the
1030
 
                        # expense of accessing it when it's not needed ?
1031
 
                        # (Guessing, RBC, 200702)
 
1469
                if kind[0] == 'file' and (backups or kind[1] is None):
 
1470
                    wt_sha1 = working_tree.get_file_sha1(file_id)
 
1471
                    if merge_modified.get(file_id) != wt_sha1:
 
1472
                        # acquire the basis tree lazyily to prevent the expense
 
1473
                        # of accessing it when its not needed ? (Guessing, RBC,
 
1474
                        # 200702)
1032
1475
                        if basis_tree is None:
1033
1476
                            basis_tree = working_tree.basis_tree()
1034
1477
                            basis_tree.lock_read()
1035
 
                        basis_inter = InterTree.get(basis_tree, working_tree)
1036
 
                        basis_path = basis_inter.find_source_path(wt_path)
1037
 
                        if basis_path is None:
1038
 
                            if target_kind is None and not target_versioned:
1039
 
                                keep_content = True
1040
 
                        else:
1041
 
                            if wt_sha1 != basis_tree.get_file_sha1(basis_path):
1042
 
                                keep_content = True
1043
 
                if wt_kind is not None:
 
1478
                        if file_id in basis_tree:
 
1479
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
 
1480
                                keep_content = True
 
1481
                        elif kind[1] is None and not versioned[1]:
 
1482
                            keep_content = True
 
1483
                if kind[0] is not None:
1044
1484
                    if not keep_content:
1045
1485
                        tt.delete_contents(trans_id)
1046
 
                    elif target_kind is not None:
1047
 
                        parent_trans_id = tt.trans_id_tree_path(osutils.dirname(wt_path))
1048
 
                        backup_name = tt._available_backup_name(
1049
 
                            wt_name, parent_trans_id)
 
1486
                    elif kind[1] is not None:
 
1487
                        parent_trans_id = tt.trans_id_file_id(parent[0])
 
1488
                        by_parent = tt.by_parent()
 
1489
                        backup_name = _get_backup_name(name[0], by_parent,
 
1490
                                                       parent_trans_id, tt)
1050
1491
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
1051
 
                        new_trans_id = tt.create_path(wt_name, parent_trans_id)
1052
 
                        if wt_versioned and target_versioned:
 
1492
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
 
1493
                        if versioned == (True, True):
1053
1494
                            tt.unversion_file(trans_id)
1054
 
                            tt.version_file(
1055
 
                                new_trans_id, file_id=getattr(change, 'file_id', None))
 
1495
                            tt.version_file(file_id, new_trans_id)
1056
1496
                        # New contents should have the same unix perms as old
1057
1497
                        # contents
1058
1498
                        mode_id = trans_id
1059
1499
                        trans_id = new_trans_id
1060
 
                if target_kind in ('directory', 'tree-reference'):
 
1500
                if kind[1] == 'directory':
1061
1501
                    tt.create_directory(trans_id)
1062
 
                    if target_kind == 'tree-reference':
1063
 
                        revision = target_tree.get_reference_revision(
1064
 
                            target_path)
1065
 
                        tt.set_tree_reference(revision, trans_id)
1066
 
                elif target_kind == 'symlink':
1067
 
                    tt.create_symlink(target_tree.get_symlink_target(
1068
 
                        target_path), trans_id)
1069
 
                elif target_kind == 'file':
1070
 
                    deferred_files.append(
1071
 
                        (target_path, (trans_id, mode_id, target_path)))
1072
 
                    if basis_tree is None:
1073
 
                        basis_tree = working_tree.basis_tree()
1074
 
                        basis_tree.lock_read()
1075
 
                    new_sha1 = target_tree.get_file_sha1(target_path)
1076
 
                    basis_inter = InterTree.get(basis_tree, target_tree)
1077
 
                    basis_path = basis_inter.find_source_path(target_path)
1078
 
                    if (basis_path is not None and
1079
 
                            new_sha1 == basis_tree.get_file_sha1(basis_path)):
1080
 
                        # If the new contents of the file match what is in basis,
1081
 
                        # then there is no need to store in merge_modified.
1082
 
                        if basis_path in merge_modified:
1083
 
                            del merge_modified[basis_path]
1084
 
                    else:
1085
 
                        merge_modified[target_path] = new_sha1
1086
 
 
 
1502
                elif kind[1] == 'symlink':
 
1503
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
 
1504
                                      trans_id)
 
1505
                elif kind[1] == 'file':
 
1506
                    tt.create_file(target_tree.get_file_lines(file_id),
 
1507
                                   trans_id, mode_id)
1087
1508
                    # preserve the execute bit when backing up
1088
 
                    if keep_content and wt_executable == target_executable:
1089
 
                        tt.set_executability(target_executable, trans_id)
1090
 
                elif target_kind is not None:
1091
 
                    raise AssertionError(target_kind)
1092
 
            if not wt_versioned and target_versioned:
1093
 
                tt.version_file(
1094
 
                    trans_id, file_id=getattr(change, 'file_id', None))
1095
 
            if wt_versioned and not target_versioned:
 
1509
                    if keep_content and executable[0] == executable[1]:
 
1510
                        tt.set_executability(executable[1], trans_id)
 
1511
                else:
 
1512
                    assert kind[1] is None
 
1513
            if versioned == (False, True):
 
1514
                tt.version_file(file_id, trans_id)
 
1515
            if versioned == (True, False):
1096
1516
                tt.unversion_file(trans_id)
1097
 
            if (target_name is not None
1098
 
                    and (wt_name != target_name or change.is_reparented())):
1099
 
                if target_path == '':
1100
 
                    parent_trans = ROOT_PARENT
1101
 
                else:
1102
 
                    parent_trans = tt.trans_id_file_id(target_parent)
1103
 
                if wt_path == '' and wt_versioned:
1104
 
                    tt.adjust_root_path(target_name, parent_trans)
1105
 
                else:
1106
 
                    tt.adjust_path(target_name, parent_trans, trans_id)
1107
 
            if wt_executable != target_executable and target_kind == "file":
1108
 
                tt.set_executability(target_executable, trans_id)
1109
 
        if working_tree.supports_content_filtering():
1110
 
            for (trans_id, mode_id, target_path), bytes in (
1111
 
                    target_tree.iter_files_bytes(deferred_files)):
1112
 
                # We're reverting a tree to the target tree so using the
1113
 
                # target tree to find the file path seems the best choice
1114
 
                # here IMO - Ian C 27/Oct/2009
1115
 
                filters = working_tree._content_filter_stack(target_path)
1116
 
                bytes = filtered_output_bytes(
1117
 
                    bytes, filters,
1118
 
                    ContentFilterContext(target_path, working_tree))
1119
 
                tt.create_file(bytes, trans_id, mode_id)
1120
 
        else:
1121
 
            for (trans_id, mode_id, target_path), bytes in target_tree.iter_files_bytes(
1122
 
                    deferred_files):
1123
 
                tt.create_file(bytes, trans_id, mode_id)
1124
 
        tt.fixup_new_roots()
 
1517
            if (name[1] is not None and 
 
1518
                (name[0] != name[1] or parent[0] != parent[1])):
 
1519
                tt.adjust_path(
 
1520
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
 
1521
            if executable[0] != executable[1] and kind[1] == "file":
 
1522
                tt.set_executability(executable[1], trans_id)
1125
1523
    finally:
1126
1524
        if basis_tree is not None:
1127
1525
            basis_tree.unlock()
1128
 
    return merge_modified
1129
 
 
1130
 
 
1131
 
def resolve_conflicts(tt, pb=None, pass_func=None):
 
1526
 
 
1527
 
 
1528
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1132
1529
    """Make many conflict-resolution attempts, but die if they fail"""
1133
1530
    if pass_func is None:
1134
1531
        pass_func = conflict_pass
1135
1532
    new_conflicts = set()
1136
 
    with ui.ui_factory.nested_progress_bar() as pb:
 
1533
    try:
1137
1534
        for n in range(10):
1138
 
            pb.update(gettext('Resolution pass'), n + 1, 10)
1139
 
            conflicts = tt.find_raw_conflicts()
 
1535
            pb.update('Resolution pass', n+1, 10)
 
1536
            conflicts = tt.find_conflicts()
1140
1537
            if len(conflicts) == 0:
1141
1538
                return new_conflicts
1142
1539
            new_conflicts.update(pass_func(tt, conflicts))
1143
1540
        raise MalformedTransform(conflicts=conflicts)
1144
 
 
1145
 
 
1146
 
def resolve_duplicate_id(tt, path_tree, c_type, old_trans_id, trans_id):
1147
 
    tt.unversion_file(old_trans_id)
1148
 
    yield (c_type, 'Unversioned existing file', old_trans_id, trans_id)
1149
 
 
1150
 
 
1151
 
def resolve_duplicate(tt, path_tree, c_type, last_trans_id, trans_id, name):
1152
 
    # files that were renamed take precedence
1153
 
    final_parent = tt.final_parent(last_trans_id)
1154
 
    if tt.path_changed(last_trans_id):
1155
 
        existing_file, new_file = trans_id, last_trans_id
1156
 
    else:
1157
 
        existing_file, new_file = last_trans_id, trans_id
1158
 
    new_name = tt.final_name(existing_file) + '.moved'
1159
 
    tt.adjust_path(new_name, final_parent, existing_file)
1160
 
    yield (c_type, 'Moved existing file to', existing_file, new_file)
1161
 
 
1162
 
 
1163
 
def resolve_parent_loop(tt, path_tree, c_type, cur):
1164
 
    # break the loop by undoing one of the ops that caused the loop
1165
 
    while not tt.path_changed(cur):
1166
 
        cur = tt.final_parent(cur)
1167
 
    yield (c_type, 'Cancelled move', cur, tt.final_parent(cur),)
1168
 
    tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
1169
 
 
1170
 
 
1171
 
def resolve_missing_parent(tt, path_tree, c_type, trans_id):
1172
 
    if trans_id in tt._removed_contents:
1173
 
        cancel_deletion = True
1174
 
        orphans = tt._get_potential_orphans(trans_id)
1175
 
        if orphans:
1176
 
            cancel_deletion = False
1177
 
            # All children are orphans
1178
 
            for o in orphans:
1179
 
                try:
1180
 
                    tt.new_orphan(o, trans_id)
1181
 
                except OrphaningError:
1182
 
                    # Something bad happened so we cancel the directory
1183
 
                    # deletion which will leave it in place with a
1184
 
                    # conflict. The user can deal with it from there.
1185
 
                    # Note that this also catch the case where we don't
1186
 
                    # want to create orphans and leave the directory in
1187
 
                    # place.
1188
 
                    cancel_deletion = True
1189
 
                    break
1190
 
        if cancel_deletion:
1191
 
            # Cancel the directory deletion
1192
 
            tt.cancel_deletion(trans_id)
1193
 
            yield ('deleting parent', 'Not deleting', trans_id)
1194
 
    else:
1195
 
        create = True
1196
 
        try:
1197
 
            tt.final_name(trans_id)
1198
 
        except NoFinalPath:
1199
 
            if path_tree is not None:
1200
 
                file_id = tt.final_file_id(trans_id)
1201
 
                if file_id is None:
1202
 
                    file_id = tt.inactive_file_id(trans_id)
1203
 
                _, entry = next(path_tree.iter_entries_by_dir(
1204
 
                    specific_files=[path_tree.id2path(file_id)]))
1205
 
                # special-case the other tree root (move its
1206
 
                # children to current root)
1207
 
                if entry.parent_id is None:
1208
 
                    create = False
1209
 
                    moved = _reparent_transform_children(
1210
 
                        tt, trans_id, tt.root)
1211
 
                    for child in moved:
1212
 
                        yield (c_type, 'Moved to root', child)
1213
 
                else:
1214
 
                    parent_trans_id = tt.trans_id_file_id(
1215
 
                        entry.parent_id)
1216
 
                    tt.adjust_path(entry.name, parent_trans_id,
1217
 
                                   trans_id)
1218
 
        if create:
1219
 
            tt.create_directory(trans_id)
1220
 
            yield (c_type, 'Created directory', trans_id)
1221
 
 
1222
 
 
1223
 
def resolve_unversioned_parent(tt, path_tree, c_type, trans_id):
1224
 
    file_id = tt.inactive_file_id(trans_id)
1225
 
    # special-case the other tree root (move its children instead)
1226
 
    if path_tree and path_tree.path2id('') == file_id:
1227
 
        # This is the root entry, skip it
1228
 
        return
1229
 
    tt.version_file(trans_id, file_id=file_id)
1230
 
    yield (c_type, 'Versioned directory', trans_id)
1231
 
 
1232
 
 
1233
 
def resolve_non_directory_parent(tt, path_tree, c_type, parent_id):
1234
 
    parent_parent = tt.final_parent(parent_id)
1235
 
    parent_name = tt.final_name(parent_id)
1236
 
    # TODO(jelmer): Make this code transform-specific
1237
 
    if tt._tree.supports_setting_file_ids():
1238
 
        parent_file_id = tt.final_file_id(parent_id)
1239
 
    else:
1240
 
        parent_file_id = b'DUMMY'
1241
 
    new_parent_id = tt.new_directory(parent_name + '.new',
1242
 
                                     parent_parent, parent_file_id)
1243
 
    _reparent_transform_children(tt, parent_id, new_parent_id)
1244
 
    if parent_file_id is not None:
1245
 
        tt.unversion_file(parent_id)
1246
 
    yield (c_type, 'Created directory', new_parent_id)
1247
 
 
1248
 
 
1249
 
def resolve_versioning_no_contents(tt, path_tree, c_type, trans_id):
1250
 
    tt.cancel_versioning(trans_id)
1251
 
    return []
1252
 
 
1253
 
 
1254
 
CONFLICT_RESOLVERS = {
1255
 
    'duplicate id': resolve_duplicate_id,
1256
 
    'duplicate': resolve_duplicate,
1257
 
    'parent loop': resolve_parent_loop,
1258
 
    'missing parent': resolve_missing_parent,
1259
 
    'unversioned parent': resolve_unversioned_parent,
1260
 
    'non-directory parent': resolve_non_directory_parent,
1261
 
    'versioning no contents': resolve_versioning_no_contents,
1262
 
}
1263
 
 
1264
 
 
1265
 
def conflict_pass(tt, conflicts, path_tree=None):
1266
 
    """Resolve some classes of conflicts.
1267
 
 
1268
 
    :param tt: The transform to resolve conflicts in
1269
 
    :param conflicts: The conflicts to resolve
1270
 
    :param path_tree: A Tree to get supplemental paths from
1271
 
    """
 
1541
    finally:
 
1542
        pb.clear()
 
1543
 
 
1544
 
 
1545
def conflict_pass(tt, conflicts):
 
1546
    """Resolve some classes of conflicts."""
1272
1547
    new_conflicts = set()
1273
 
    for conflict in conflicts:
1274
 
        resolver = CONFLICT_RESOLVERS.get(conflict[0])
1275
 
        if resolver is None:
1276
 
            continue
1277
 
        new_conflicts.update(resolver(tt, path_tree, *conflict))
 
1548
    for c_type, conflict in ((c[0], c) for c in conflicts):
 
1549
        if c_type == 'duplicate id':
 
1550
            tt.unversion_file(conflict[1])
 
1551
            new_conflicts.add((c_type, 'Unversioned existing file',
 
1552
                               conflict[1], conflict[2], ))
 
1553
        elif c_type == 'duplicate':
 
1554
            # files that were renamed take precedence
 
1555
            new_name = tt.final_name(conflict[1])+'.moved'
 
1556
            final_parent = tt.final_parent(conflict[1])
 
1557
            if tt.path_changed(conflict[1]):
 
1558
                tt.adjust_path(new_name, final_parent, conflict[2])
 
1559
                new_conflicts.add((c_type, 'Moved existing file to', 
 
1560
                                   conflict[2], conflict[1]))
 
1561
            else:
 
1562
                tt.adjust_path(new_name, final_parent, conflict[1])
 
1563
                new_conflicts.add((c_type, 'Moved existing file to', 
 
1564
                                  conflict[1], conflict[2]))
 
1565
        elif c_type == 'parent loop':
 
1566
            # break the loop by undoing one of the ops that caused the loop
 
1567
            cur = conflict[1]
 
1568
            while not tt.path_changed(cur):
 
1569
                cur = tt.final_parent(cur)
 
1570
            new_conflicts.add((c_type, 'Cancelled move', cur,
 
1571
                               tt.final_parent(cur),))
 
1572
            tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
 
1573
            
 
1574
        elif c_type == 'missing parent':
 
1575
            trans_id = conflict[1]
 
1576
            try:
 
1577
                tt.cancel_deletion(trans_id)
 
1578
                new_conflicts.add(('deleting parent', 'Not deleting', 
 
1579
                                   trans_id))
 
1580
            except KeyError:
 
1581
                tt.create_directory(trans_id)
 
1582
                new_conflicts.add((c_type, 'Created directory', trans_id))
 
1583
        elif c_type == 'unversioned parent':
 
1584
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
 
1585
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1278
1586
    return new_conflicts
1279
1587
 
1280
1588
 
1281
 
class _FileMover(object):
1282
 
    """Moves and deletes files for TreeTransform, tracking operations"""
1283
 
 
1284
 
    def __init__(self):
1285
 
        self.past_renames = []
1286
 
        self.pending_deletions = []
1287
 
 
1288
 
    def rename(self, from_, to):
1289
 
        """Rename a file from one path to another."""
1290
 
        try:
1291
 
            os.rename(from_, to)
1292
 
        except OSError as e:
1293
 
            if e.errno in (errno.EEXIST, errno.ENOTEMPTY):
1294
 
                raise errors.FileExists(to, str(e))
1295
 
            # normal OSError doesn't include filenames so it's hard to see where
1296
 
            # the problem is, see https://bugs.launchpad.net/bzr/+bug/491763
1297
 
            raise TransformRenameFailed(from_, to, str(e), e.errno)
1298
 
        self.past_renames.append((from_, to))
1299
 
 
1300
 
    def pre_delete(self, from_, to):
1301
 
        """Rename a file out of the way and mark it for deletion.
1302
 
 
1303
 
        Unlike os.unlink, this works equally well for files and directories.
1304
 
        :param from_: The current file path
1305
 
        :param to: A temporary path for the file
1306
 
        """
1307
 
        self.rename(from_, to)
1308
 
        self.pending_deletions.append(to)
1309
 
 
1310
 
    def rollback(self):
1311
 
        """Reverse all renames that have been performed"""
1312
 
        for from_, to in reversed(self.past_renames):
1313
 
            try:
1314
 
                os.rename(to, from_)
1315
 
            except OSError as e:
1316
 
                raise TransformRenameFailed(to, from_, str(e), e.errno)
1317
 
        # after rollback, don't reuse _FileMover
1318
 
        self.past_renames = None
1319
 
        self.pending_deletions = None
1320
 
 
1321
 
    def apply_deletions(self):
1322
 
        """Apply all marked deletions"""
1323
 
        for path in self.pending_deletions:
1324
 
            delete_any(path)
1325
 
        # after apply_deletions, don't reuse _FileMover
1326
 
        self.past_renames = None
1327
 
        self.pending_deletions = None
1328
 
 
1329
 
 
1330
 
def link_tree(target_tree, source_tree):
1331
 
    """Where possible, hard-link files in a tree to those in another tree.
1332
 
 
1333
 
    :param target_tree: Tree to change
1334
 
    :param source_tree: Tree to hard-link from
1335
 
    """
1336
 
    with target_tree.transform() as tt:
1337
 
        for change in target_tree.iter_changes(source_tree, include_unchanged=True):
1338
 
            if change.changed_content:
1339
 
                continue
1340
 
            if change.kind != ('file', 'file'):
1341
 
                continue
1342
 
            if change.executable[0] != change.executable[1]:
1343
 
                continue
1344
 
            trans_id = tt.trans_id_tree_path(change.path[1])
1345
 
            tt.delete_contents(trans_id)
1346
 
            tt.create_hardlink(source_tree.abspath(change.path[0]), trans_id)
1347
 
        tt.apply()
1348
 
 
1349
 
 
1350
 
class PreviewTree(object):
1351
 
    """Preview tree."""
1352
 
 
1353
 
    def __init__(self, transform):
1354
 
        self._transform = transform
1355
 
        self._parent_ids = []
1356
 
        self.__by_parent = None
1357
 
        self._path2trans_id_cache = {}
1358
 
        self._all_children_cache = {}
1359
 
        self._final_name_cache = {}
1360
 
 
1361
 
    def supports_setting_file_ids(self):
1362
 
        raise NotImplementedError(self.supports_setting_file_ids)
1363
 
 
1364
 
    @property
1365
 
    def _by_parent(self):
1366
 
        if self.__by_parent is None:
1367
 
            self.__by_parent = self._transform.by_parent()
1368
 
        return self.__by_parent
1369
 
 
1370
 
    def get_parent_ids(self):
1371
 
        return self._parent_ids
1372
 
 
1373
 
    def set_parent_ids(self, parent_ids):
1374
 
        self._parent_ids = parent_ids
1375
 
 
1376
 
    def get_revision_tree(self, revision_id):
1377
 
        return self._transform._tree.get_revision_tree(revision_id)
1378
 
 
1379
 
    def is_locked(self):
1380
 
        return False
1381
 
 
1382
 
    def lock_read(self):
1383
 
        # Perhaps in theory, this should lock the TreeTransform?
1384
 
        return lock.LogicalLockResult(self.unlock)
1385
 
 
1386
 
    def unlock(self):
1387
 
        pass
1388
 
 
1389
 
    def _path2trans_id(self, path):
1390
 
        """Look up the trans id associated with a path.
1391
 
 
1392
 
        :param path: path to look up, None when the path does not exist
1393
 
        :return: trans_id
1394
 
        """
1395
 
        # We must not use None here, because that is a valid value to store.
1396
 
        trans_id = self._path2trans_id_cache.get(path, object)
1397
 
        if trans_id is not object:
1398
 
            return trans_id
1399
 
        segments = osutils.splitpath(path)
1400
 
        cur_parent = self._transform.root
1401
 
        for cur_segment in segments:
1402
 
            for child in self._all_children(cur_parent):
1403
 
                final_name = self._final_name_cache.get(child)
1404
 
                if final_name is None:
1405
 
                    final_name = self._transform.final_name(child)
1406
 
                    self._final_name_cache[child] = final_name
1407
 
                if final_name == cur_segment:
1408
 
                    cur_parent = child
1409
 
                    break
1410
 
            else:
1411
 
                self._path2trans_id_cache[path] = None
1412
 
                return None
1413
 
        self._path2trans_id_cache[path] = cur_parent
1414
 
        return cur_parent
1415
 
 
1416
 
    def _all_children(self, trans_id):
1417
 
        children = self._all_children_cache.get(trans_id)
1418
 
        if children is not None:
1419
 
            return children
1420
 
        children = set(self._transform.iter_tree_children(trans_id))
1421
 
        # children in the _new_parent set are provided by _by_parent.
1422
 
        children.difference_update(self._transform._new_parent)
1423
 
        children.update(self._by_parent.get(trans_id, []))
1424
 
        self._all_children_cache[trans_id] = children
1425
 
        return children
1426
 
 
1427
 
    def get_file_with_stat(self, path):
1428
 
        return self.get_file(path), None
1429
 
 
1430
 
    def is_executable(self, path):
1431
 
        trans_id = self._path2trans_id(path)
1432
 
        if trans_id is None:
1433
 
            return False
1434
 
        try:
1435
 
            return self._transform._new_executability[trans_id]
1436
 
        except KeyError:
1437
 
            try:
1438
 
                return self._transform._tree.is_executable(path)
1439
 
            except OSError as e:
1440
 
                if e.errno == errno.ENOENT:
1441
 
                    return False
1442
 
                raise
1443
 
            except errors.NoSuchFile:
1444
 
                return False
1445
 
 
1446
 
    def has_filename(self, path):
1447
 
        trans_id = self._path2trans_id(path)
1448
 
        if trans_id in self._transform._new_contents:
1449
 
            return True
1450
 
        elif trans_id in self._transform._removed_contents:
1451
 
            return False
1452
 
        else:
1453
 
            return self._transform._tree.has_filename(path)
1454
 
 
1455
 
    def get_file_sha1(self, path, stat_value=None):
1456
 
        trans_id = self._path2trans_id(path)
1457
 
        if trans_id is None:
1458
 
            raise errors.NoSuchFile(path)
1459
 
        kind = self._transform._new_contents.get(trans_id)
1460
 
        if kind is None:
1461
 
            return self._transform._tree.get_file_sha1(path)
1462
 
        if kind == 'file':
1463
 
            with self.get_file(path) as fileobj:
1464
 
                return osutils.sha_file(fileobj)
1465
 
 
1466
 
    def get_file_verifier(self, path, stat_value=None):
1467
 
        trans_id = self._path2trans_id(path)
1468
 
        if trans_id is None:
1469
 
            raise errors.NoSuchFile(path)
1470
 
        kind = self._transform._new_contents.get(trans_id)
1471
 
        if kind is None:
1472
 
            return self._transform._tree.get_file_verifier(path)
1473
 
        if kind == 'file':
1474
 
            with self.get_file(path) as fileobj:
1475
 
                return ("SHA1", osutils.sha_file(fileobj))
1476
 
 
1477
 
    def kind(self, path):
1478
 
        trans_id = self._path2trans_id(path)
1479
 
        if trans_id is None:
1480
 
            raise errors.NoSuchFile(path)
1481
 
        return self._transform.final_kind(trans_id)
1482
 
 
1483
 
    def stored_kind(self, path):
1484
 
        trans_id = self._path2trans_id(path)
1485
 
        if trans_id is None:
1486
 
            raise errors.NoSuchFile(path)
1487
 
        try:
1488
 
            return self._transform._new_contents[trans_id]
1489
 
        except KeyError:
1490
 
            return self._transform._tree.stored_kind(path)
1491
 
 
1492
 
    def _get_repository(self):
1493
 
        repo = getattr(self._transform._tree, '_repository', None)
1494
 
        if repo is None:
1495
 
            repo = self._transform._tree.branch.repository
1496
 
        return repo
1497
 
 
1498
 
    def _iter_parent_trees(self):
1499
 
        for revision_id in self.get_parent_ids():
1500
 
            try:
1501
 
                yield self.revision_tree(revision_id)
1502
 
            except errors.NoSuchRevisionInTree:
1503
 
                yield self._get_repository().revision_tree(revision_id)
1504
 
 
1505
 
    def get_file_size(self, path):
1506
 
        """See Tree.get_file_size"""
1507
 
        trans_id = self._path2trans_id(path)
1508
 
        if trans_id is None:
1509
 
            raise errors.NoSuchFile(path)
1510
 
        kind = self._transform.final_kind(trans_id)
1511
 
        if kind != 'file':
1512
 
            return None
1513
 
        if trans_id in self._transform._new_contents:
1514
 
            return self._stat_limbo_file(trans_id).st_size
1515
 
        if self.kind(path) == 'file':
1516
 
            return self._transform._tree.get_file_size(path)
1517
 
        else:
1518
 
            return None
1519
 
 
1520
 
    def get_reference_revision(self, path):
1521
 
        trans_id = self._path2trans_id(path)
1522
 
        if trans_id is None:
1523
 
            raise errors.NoSuchFile(path)
1524
 
        reference_revision = self._transform._new_reference_revision.get(trans_id)
1525
 
        if reference_revision is None:
1526
 
            return self._transform._tree.get_reference_revision(path)
1527
 
        return reference_revision
1528
 
 
1529
 
    def tree_kind(self, trans_id):
1530
 
        path = self._tree_id_paths.get(trans_id)
1531
 
        if path is None:
1532
 
            return None
1533
 
        kind = self._tree.path_content_summary(path)[0]
1534
 
        if kind == 'missing':
1535
 
            kind = None
1536
 
        return kind
 
1589
def cook_conflicts(raw_conflicts, tt):
 
1590
    """Generate a list of cooked conflicts, sorted by file path"""
 
1591
    from bzrlib.conflicts import Conflict
 
1592
    conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
 
1593
    return sorted(conflict_iter, key=Conflict.sort_key)
 
1594
 
 
1595
 
 
1596
def iter_cook_conflicts(raw_conflicts, tt):
 
1597
    from bzrlib.conflicts import Conflict
 
1598
    fp = FinalPaths(tt)
 
1599
    for conflict in raw_conflicts:
 
1600
        c_type = conflict[0]
 
1601
        action = conflict[1]
 
1602
        modified_path = fp.get_path(conflict[2])
 
1603
        modified_id = tt.final_file_id(conflict[2])
 
1604
        if len(conflict) == 3:
 
1605
            yield Conflict.factory(c_type, action=action, path=modified_path,
 
1606
                                     file_id=modified_id)
 
1607
             
 
1608
        else:
 
1609
            conflicting_path = fp.get_path(conflict[3])
 
1610
            conflicting_id = tt.final_file_id(conflict[3])
 
1611
            yield Conflict.factory(c_type, action=action, path=modified_path,
 
1612
                                   file_id=modified_id, 
 
1613
                                   conflict_path=conflicting_path,
 
1614
                                   conflict_file_id=conflicting_id)