/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
2255.2.152 by Martin Pool
(broken) merge aaron's workingtree format changes
1
# Copyright (C) 2006, 2007 Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
1534.7.106 by Aaron Bentley
Cleaned up imports, added copyright statements
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
1534.7.106 by Aaron Bentley
Cleaned up imports, added copyright statements
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
1534.7.106 by Aaron Bentley
Cleaned up imports, added copyright statements
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
17
import os
1534.7.106 by Aaron Bentley
Cleaned up imports, added copyright statements
18
import errno
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
19
from stat import S_ISREG
1534.7.106 by Aaron Bentley
Cleaned up imports, added copyright statements
20
1551.11.12 by Aaron Bentley
Changes from review
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
2100.3.21 by Aaron Bentley
Work on checking out by-reference trees
23
from bzrlib import (
24
    bzrdir,
2100.3.31 by Aaron Bentley
Merged bzr.dev (17 tests failing)
25
    delta,
2100.3.21 by Aaron Bentley
Work on checking out by-reference trees
26
    errors,
27
    inventory
28
    )
1551.11.12 by Aaron Bentley
Changes from review
29
""")
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
30
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
1534.7.162 by Aaron Bentley
Handle failures creating/deleting the Limbo directory
31
                           ReusingTransform, NotVersionedError, CantMoveRoot,
1731.1.33 by Aaron Bentley
Revert no-special-root changes
32
                           ExistingLimbo, ImmortalLimbo, NoFinalPath)
1534.7.106 by Aaron Bentley
Cleaned up imports, added copyright statements
33
from bzrlib.inventory import InventoryEntry
1558.12.9 by Aaron Bentley
Handle resolving conflicts with directories properly
34
from bzrlib.osutils import (file_kind, supports_executable, pathjoin, lexists,
1685.1.45 by John Arbash Meinel
Moved url functions into bzrlib.urlutils
35
                            delete_any)
1551.2.34 by Aaron Bentley
Refactored the revert phases
36
from bzrlib.progress import DummyProgress, ProgressPhase
2255.7.48 by Robert Collins
Deprecated and make work with DirState trees 'transform.find_interesting'.
37
from bzrlib.symbol_versioning import deprecated_function, zero_fifteen
1534.7.173 by Aaron Bentley
Added conflict warnings to revert
38
from bzrlib.trace import mutter, warning
1551.7.14 by Aaron Bentley
Use specified_file_ids instead of is_inside_any in compare_trees
39
from bzrlib import tree
2255.2.152 by Martin Pool
(broken) merge aaron's workingtree format changes
40
import bzrlib.ui
1685.1.45 by John Arbash Meinel
Moved url functions into bzrlib.urlutils
41
import bzrlib.urlutils as urlutils
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
42
1534.7.167 by Aaron Bentley
PEP8 and comment cleanups
43
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
44
ROOT_PARENT = "root-parent"
45
1534.7.167 by Aaron Bentley
PEP8 and comment cleanups
46
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
47
def unique_add(map, key, value):
48
    if key in map:
1534.7.5 by Aaron Bentley
Got unique_add under test
49
        raise DuplicateKey(key=key)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
50
    map[key] = value
51
1534.7.167 by Aaron Bentley
PEP8 and comment cleanups
52
1534.7.191 by Aaron Bentley
Got transform.apply to list modified paths
53
class _TransformResults(object):
54
    def __init__(self, modified_paths):
55
        object.__init__(self)
56
        self.modified_paths = modified_paths
57
58
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
59
class TreeTransform(object):
1534.7.179 by Aaron Bentley
Added Transform docs
60
    """Represent a tree transformation.
61
    
62
    This object is designed to support incremental generation of the transform,
63
    in any order.  
64
    
65
    It is easy to produce malformed transforms, but they are generally
66
    harmless.  Attempting to apply a malformed transform will cause an
67
    exception to be raised before any modifications are made to the tree.  
68
69
    Many kinds of malformed transforms can be corrected with the 
70
    resolve_conflicts function.  The remaining ones indicate programming error,
71
    such as trying to create a file with no path.
72
73
    Two sets of file creation methods are supplied.  Convenience methods are:
74
     * new_file
75
     * new_directory
76
     * new_symlink
77
78
    These are composed of the low-level methods:
79
     * create_path
80
     * create_file or create_directory or create_symlink
81
     * version_file
82
     * set_executability
83
    """
1534.9.1 by Aaron Bentley
Added progress bars to merge
84
    def __init__(self, tree, pb=DummyProgress()):
1997.1.3 by Robert Collins
All WorkingTree methods which write to the tree, but not to the branch
85
        """Note: a tree_write lock is taken on the tree.
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
86
        
87
        Use TreeTransform.finalize() to release the lock
88
        """
89
        object.__init__(self)
90
        self._tree = tree
1997.1.3 by Robert Collins
All WorkingTree methods which write to the tree, but not to the branch
91
        self._tree.lock_tree_write()
1534.7.162 by Aaron Bentley
Handle failures creating/deleting the Limbo directory
92
        try:
93
            control_files = self._tree._control_files
1685.1.45 by John Arbash Meinel
Moved url functions into bzrlib.urlutils
94
            self._limbodir = urlutils.local_path_from_url(
1685.1.9 by John Arbash Meinel
Updated LocalTransport so that it's base is now a URL rather than a local path. This helps consistency with all other functions. To do so, I added local_abspath() which returns the local path, and local_path_to/from_url
95
                control_files.controlfilename('limbo'))
1534.7.162 by Aaron Bentley
Handle failures creating/deleting the Limbo directory
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
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
105
        self._id_number = 0
106
        self._new_name = {}
107
        self._new_parent = {}
1534.7.4 by Aaron Bentley
Unified all file types as 'contents'
108
        self._new_contents = {}
2502.1.1 by Aaron Bentley
Ensure renames only root children are renamed when building trees
109
        self._limbo_files = {}
2502.1.2 by Aaron Bentley
Make the limited-renames functionality safer in the general case
110
        self._limbo_children = {}
2502.1.1 by Aaron Bentley
Ensure renames only root children are renamed when building trees
111
        self._needs_rename = set()
1534.7.34 by Aaron Bentley
Proper conflicts for removals
112
        self._removed_contents = set()
1534.7.25 by Aaron Bentley
Added set_executability
113
        self._new_executability = {}
2100.3.21 by Aaron Bentley
Work on checking out by-reference trees
114
        self._new_reference_revision = {}
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
115
        self._new_id = {}
1534.7.143 by Aaron Bentley
Prevented get_trans_id from automatically versioning file ids
116
        self._non_present_ids = {}
1534.7.75 by Aaron Bentley
Added reverse-lookup for versioned files and get_trans_id
117
        self._r_new_id = {}
1534.7.39 by Aaron Bentley
Ensured that files can be unversioned (de-versioned?)
118
        self._removed_id = set()
1534.7.7 by Aaron Bentley
Added support for all-file path ids
119
        self._tree_path_ids = {}
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
120
        self._tree_id_paths = {}
1534.10.31 by Aaron Bentley
Add caching to speed canonical_path
121
        self._realpaths = {}
122
        # Cache of realpath results, to speed up canonical_path
123
        self._relpaths = {}
124
        # Cache of relpath results, to speed up canonical_path
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
125
        self._new_root = self.trans_id_tree_file_id(tree.get_root_id())
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
126
        self.__done = False
1534.9.1 by Aaron Bentley
Added progress bars to merge
127
        self._pb = pb
2502.1.1 by Aaron Bentley
Ensure renames only root children are renamed when building trees
128
        self.rename_count = 0
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
129
1534.7.132 by Aaron Bentley
Got cooked conflicts working
130
    def __get_root(self):
131
        return self._new_root
132
133
    root = property(__get_root)
134
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
135
    def finalize(self):
1534.7.157 by Aaron Bentley
Added more docs
136
        """Release the working tree lock, if held, clean up limbo dir."""
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
137
        if self._tree is None:
138
            return
1534.7.162 by Aaron Bentley
Handle failures creating/deleting the Limbo directory
139
        try:
140
            for trans_id, kind in self._new_contents.iteritems():
141
                path = self._limbo_name(trans_id)
142
                if kind == "directory":
143
                    os.rmdir(path)
144
                else:
145
                    os.unlink(path)
146
            try:
147
                os.rmdir(self._limbodir)
148
            except OSError:
149
                # We don't especially care *why* the dir is immortal.
150
                raise ImmortalLimbo(self._limbodir)
151
        finally:
152
            self._tree.unlock()
153
            self._tree = None
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
154
155
    def _assign_id(self):
156
        """Produce a new tranform id"""
157
        new_id = "new-%s" % self._id_number
158
        self._id_number +=1
159
        return new_id
160
161
    def create_path(self, name, parent):
162
        """Assign a transaction id to a new path"""
163
        trans_id = self._assign_id()
164
        unique_add(self._new_name, trans_id, name)
165
        unique_add(self._new_parent, trans_id, parent)
166
        return trans_id
167
1534.7.6 by Aaron Bentley
Added conflict handling
168
    def adjust_path(self, name, parent, trans_id):
1534.7.21 by Aaron Bentley
Updated docstrings
169
        """Change the path that is assigned to a transaction id."""
1534.7.66 by Aaron Bentley
Ensured we don't accidentally move the root directory
170
        if trans_id == self._new_root:
171
            raise CantMoveRoot
2502.1.2 by Aaron Bentley
Make the limited-renames functionality safer in the general case
172
        previous_parent = self._new_parent.get(trans_id)
1534.7.6 by Aaron Bentley
Added conflict handling
173
        self._new_name[trans_id] = name
174
        self._new_parent[trans_id] = parent
2502.1.2 by Aaron Bentley
Make the limited-renames functionality safer in the general case
175
        if (trans_id in self._limbo_files and
176
            trans_id not in self._needs_rename):
177
            self._rename_in_limbo([trans_id])
178
            self._limbo_children[previous_parent].remove(trans_id)
179
180
    def _rename_in_limbo(self, trans_ids):
181
        """Fix a limbo name so that the right final path is produced.
182
183
        This means we outsmarted ourselves-- we tried to avoid renaming
184
        these file later by creating them with their final names in their
185
        final parent.  But now the previous name or parent is no longer
186
        suitable, so we have to rename them.
187
        """
188
        for trans_id in trans_ids:
189
            old_path = self._limbo_files[trans_id]
190
            new_path = self._limbo_name(trans_id, from_scratch=True)
191
            os.rename(old_path, new_path)
1534.7.6 by Aaron Bentley
Added conflict handling
192
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
193
    def adjust_root_path(self, name, parent):
194
        """Emulate moving the root by moving all children, instead.
195
        
196
        We do this by undoing the association of root's transaction id with the
197
        current tree.  This allows us to create a new directory with that
1534.7.69 by Aaron Bentley
Got real root moves working
198
        transaction id.  We unversion the root directory and version the 
199
        physically new directory, and hope someone versions the tree root
200
        later.
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
201
        """
202
        old_root = self._new_root
203
        old_root_file_id = self.final_file_id(old_root)
204
        # force moving all children of root
205
        for child_id in self.iter_tree_children(old_root):
206
            if child_id != parent:
207
                self.adjust_path(self.final_name(child_id), 
208
                                 self.final_parent(child_id), child_id)
1534.7.69 by Aaron Bentley
Got real root moves working
209
            file_id = self.final_file_id(child_id)
210
            if file_id is not None:
211
                self.unversion_file(child_id)
212
            self.version_file(file_id, child_id)
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
213
        
214
        # the physical root needs a new transaction id
215
        self._tree_path_ids.pop("")
216
        self._tree_id_paths.pop(old_root)
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
217
        self._new_root = self.trans_id_tree_file_id(self._tree.get_root_id())
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
218
        if parent == old_root:
219
            parent = self._new_root
220
        self.adjust_path(name, parent, old_root)
221
        self.create_directory(old_root)
1534.7.69 by Aaron Bentley
Got real root moves working
222
        self.version_file(old_root_file_id, old_root)
223
        self.unversion_file(self._new_root)
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
224
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
225
    def trans_id_tree_file_id(self, inventory_id):
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
226
        """Determine the transaction id of a working tree file.
227
        
228
        This reflects only files that already exist, not ones that will be
229
        added by transactions.
230
        """
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
231
        path = self._tree.inventory.id2path(inventory_id)
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
232
        return self.trans_id_tree_path(path)
1534.7.7 by Aaron Bentley
Added support for all-file path ids
233
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
234
    def trans_id_file_id(self, file_id):
1534.7.156 by Aaron Bentley
PEP8 fixes
235
        """Determine or set the transaction id associated with a file ID.
1534.7.75 by Aaron Bentley
Added reverse-lookup for versioned files and get_trans_id
236
        A new id is only created for file_ids that were never present.  If
237
        a transaction has been unversioned, it is deliberately still returned.
238
        (this will likely lead to an unversioned parent conflict.)
239
        """
240
        if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
241
            return self._r_new_id[file_id]
242
        elif file_id in self._tree.inventory:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
243
            return self.trans_id_tree_file_id(file_id)
1534.7.143 by Aaron Bentley
Prevented get_trans_id from automatically versioning file ids
244
        elif file_id in self._non_present_ids:
245
            return self._non_present_ids[file_id]
1534.7.75 by Aaron Bentley
Added reverse-lookup for versioned files and get_trans_id
246
        else:
247
            trans_id = self._assign_id()
1534.7.143 by Aaron Bentley
Prevented get_trans_id from automatically versioning file ids
248
            self._non_present_ids[file_id] = trans_id
1534.7.75 by Aaron Bentley
Added reverse-lookup for versioned files and get_trans_id
249
            return trans_id
250
1534.7.12 by Aaron Bentley
Added canonical_path function
251
    def canonical_path(self, path):
252
        """Get the canonical tree-relative path"""
253
        # don't follow final symlinks
1534.10.31 by Aaron Bentley
Add caching to speed canonical_path
254
        abs = self._tree.abspath(path)
255
        if abs in self._relpaths:
256
            return self._relpaths[abs]
257
        dirname, basename = os.path.split(abs)
258
        if dirname not in self._realpaths:
259
            self._realpaths[dirname] = os.path.realpath(dirname)
260
        dirname = self._realpaths[dirname]
261
        abs = pathjoin(dirname, basename)
262
        if dirname in self._relpaths:
263
            relpath = pathjoin(self._relpaths[dirname], basename)
1534.10.32 by Aaron Bentley
Test and fix case where name has trailing slash
264
            relpath = relpath.rstrip('/\\')
1534.10.31 by Aaron Bentley
Add caching to speed canonical_path
265
        else:
266
            relpath = self._tree.relpath(abs)
267
        self._relpaths[abs] = relpath
268
        return relpath
1534.7.12 by Aaron Bentley
Added canonical_path function
269
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
270
    def trans_id_tree_path(self, path):
1534.7.7 by Aaron Bentley
Added support for all-file path ids
271
        """Determine (and maybe set) the transaction ID for a tree path."""
1534.7.12 by Aaron Bentley
Added canonical_path function
272
        path = self.canonical_path(path)
1534.7.7 by Aaron Bentley
Added support for all-file path ids
273
        if path not in self._tree_path_ids:
274
            self._tree_path_ids[path] = self._assign_id()
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
275
            self._tree_id_paths[self._tree_path_ids[path]] = path
1534.7.7 by Aaron Bentley
Added support for all-file path ids
276
        return self._tree_path_ids[path]
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
277
1534.7.16 by Aaron Bentley
Added get_tree_parent
278
    def get_tree_parent(self, trans_id):
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
279
        """Determine id of the parent in the tree."""
1534.7.16 by Aaron Bentley
Added get_tree_parent
280
        path = self._tree_id_paths[trans_id]
281
        if path == "":
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
282
            return ROOT_PARENT
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
283
        return self.trans_id_tree_path(os.path.dirname(path))
1534.7.16 by Aaron Bentley
Added get_tree_parent
284
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
285
    def create_file(self, contents, trans_id, mode_id=None):
1534.7.21 by Aaron Bentley
Updated docstrings
286
        """Schedule creation of a new file.
287
288
        See also new_file.
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
289
        
290
        Contents is an iterator of strings, all of which will be written
1534.7.21 by Aaron Bentley
Updated docstrings
291
        to the target destination.
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
292
293
        New file takes the permissions of any existing file with that id,
294
        unless mode_id is specified.
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
295
        """
1711.7.22 by John Arbash Meinel
transform: cleanup the temporary file even if unique_add fails.
296
        name = self._limbo_name(trans_id)
297
        f = open(name, 'wb')
1711.7.8 by John Arbash Meinel
Use try/finally inside create_file for TreeTransform to ensure the file handle gets closed
298
        try:
1711.7.22 by John Arbash Meinel
transform: cleanup the temporary file even if unique_add fails.
299
            try:
300
                unique_add(self._new_contents, trans_id, 'file')
301
            except:
302
                # Clean up the file, it never got registered so
303
                # TreeTransform.finalize() won't clean it up.
304
                f.close()
305
                os.unlink(name)
306
                raise
307
2247.1.2 by John Arbash Meinel
Switch from for line in foo: f.write(line) to f.writelines(foo)
308
            f.writelines(contents)
1711.7.8 by John Arbash Meinel
Use try/finally inside create_file for TreeTransform to ensure the file handle gets closed
309
        finally:
310
            f.close()
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
311
        self._set_mode(trans_id, mode_id, S_ISREG)
312
313
    def _set_mode(self, trans_id, mode_id, typefunc):
1534.7.157 by Aaron Bentley
Added more docs
314
        """Set the mode of new file contents.
315
        The mode_id is the existing file to get the mode from (often the same
316
        as trans_id).  The operation is only performed if there's a mode match
317
        according to typefunc.
318
        """
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
319
        if mode_id is None:
320
            mode_id = trans_id
321
        try:
322
            old_path = self._tree_id_paths[mode_id]
323
        except KeyError:
324
            return
325
        try:
2027.1.1 by John Arbash Meinel
Fix bug #56549, and write a direct test that the right path is being statted
326
            mode = os.stat(self._tree.abspath(old_path)).st_mode
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
327
        except OSError, e:
328
            if e.errno == errno.ENOENT:
329
                return
330
            else:
331
                raise
332
        if typefunc(mode):
333
            os.chmod(self._limbo_name(trans_id), mode)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
334
1534.7.20 by Aaron Bentley
Added directory handling
335
    def create_directory(self, trans_id):
1534.7.21 by Aaron Bentley
Updated docstrings
336
        """Schedule creation of a new directory.
337
        
338
        See also new_directory.
339
        """
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
340
        os.mkdir(self._limbo_name(trans_id))
341
        unique_add(self._new_contents, trans_id, 'directory')
1534.7.20 by Aaron Bentley
Added directory handling
342
1534.7.22 by Aaron Bentley
Added symlink support
343
    def create_symlink(self, target, trans_id):
344
        """Schedule creation of a new symbolic link.
345
346
        target is a bytestring.
347
        See also new_symlink.
348
        """
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
349
        os.symlink(target, self._limbo_name(trans_id))
350
        unique_add(self._new_contents, trans_id, 'symlink')
1534.7.22 by Aaron Bentley
Added symlink support
351
1534.7.129 by Aaron Bentley
Converted test cases to Tree Transform
352
    def cancel_creation(self, trans_id):
1534.7.157 by Aaron Bentley
Added more docs
353
        """Cancel the creation of new file contents."""
1534.7.129 by Aaron Bentley
Converted test cases to Tree Transform
354
        del self._new_contents[trans_id]
2502.1.2 by Aaron Bentley
Make the limited-renames functionality safer in the general case
355
        children = self._limbo_children.get(trans_id)
356
        # if this is a limbo directory with children, move them before removing
357
        # the directory
358
        if children is not None:
359
            self._rename_in_limbo(children)
360
            del self._limbo_children[trans_id]
1558.12.9 by Aaron Bentley
Handle resolving conflicts with directories properly
361
        delete_any(self._limbo_name(trans_id))
1534.7.129 by Aaron Bentley
Converted test cases to Tree Transform
362
1534.7.34 by Aaron Bentley
Proper conflicts for removals
363
    def delete_contents(self, trans_id):
364
        """Schedule the contents of a path entry for deletion"""
1534.7.130 by Aaron Bentley
More conflict handling, test porting
365
        self.tree_kind(trans_id)
1534.7.34 by Aaron Bentley
Proper conflicts for removals
366
        self._removed_contents.add(trans_id)
367
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
368
    def cancel_deletion(self, trans_id):
369
        """Cancel a scheduled deletion"""
370
        self._removed_contents.remove(trans_id)
371
1534.7.39 by Aaron Bentley
Ensured that files can be unversioned (de-versioned?)
372
    def unversion_file(self, trans_id):
373
        """Schedule a path entry to become unversioned"""
374
        self._removed_id.add(trans_id)
375
376
    def delete_versioned(self, trans_id):
377
        """Delete and unversion a versioned file"""
378
        self.delete_contents(trans_id)
379
        self.unversion_file(trans_id)
380
1534.7.25 by Aaron Bentley
Added set_executability
381
    def set_executability(self, executability, trans_id):
1534.7.167 by Aaron Bentley
PEP8 and comment cleanups
382
        """Schedule setting of the 'execute' bit
383
        To unschedule, set to None
384
        """
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
385
        if executability is None:
386
            del self._new_executability[trans_id]
387
        else:
388
            unique_add(self._new_executability, trans_id, executability)
1534.7.25 by Aaron Bentley
Added set_executability
389
2100.3.21 by Aaron Bentley
Work on checking out by-reference trees
390
    def set_tree_reference(self, revision_id, trans_id):
391
        """Set the reference associated with a directory"""
392
        unique_add(self._new_reference_revision, trans_id, revision_id)
393
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
394
    def version_file(self, file_id, trans_id):
1534.7.21 by Aaron Bentley
Updated docstrings
395
        """Schedule a file to become versioned."""
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
396
        assert file_id is not None
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
397
        unique_add(self._new_id, trans_id, file_id)
1534.7.75 by Aaron Bentley
Added reverse-lookup for versioned files and get_trans_id
398
        unique_add(self._r_new_id, file_id, trans_id)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
399
1534.7.105 by Aaron Bentley
Got merge with rename working
400
    def cancel_versioning(self, trans_id):
401
        """Undo a previous versioning of a file"""
402
        file_id = self._new_id[trans_id]
403
        del self._new_id[trans_id]
404
        del self._r_new_id[file_id]
405
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
406
    def new_paths(self):
1534.7.21 by Aaron Bentley
Updated docstrings
407
        """Determine the paths of all new and changed files"""
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
408
        new_ids = set()
1534.7.132 by Aaron Bentley
Got cooked conflicts working
409
        fp = FinalPaths(self)
1534.7.4 by Aaron Bentley
Unified all file types as 'contents'
410
        for id_set in (self._new_name, self._new_parent, self._new_contents,
1534.7.25 by Aaron Bentley
Added set_executability
411
                       self._new_id, self._new_executability):
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
412
            new_ids.update(id_set)
413
        new_paths = [(fp.get_path(t), t) for t in new_ids]
414
        new_paths.sort()
415
        return new_paths
1534.7.6 by Aaron Bentley
Added conflict handling
416
1534.7.34 by Aaron Bentley
Proper conflicts for removals
417
    def tree_kind(self, trans_id):
1534.7.40 by Aaron Bentley
Updated docs
418
        """Determine the file kind in the working tree.
419
420
        Raises NoSuchFile if the file does not exist
421
        """
1534.7.34 by Aaron Bentley
Proper conflicts for removals
422
        path = self._tree_id_paths.get(trans_id)
423
        if path is None:
424
            raise NoSuchFile(None)
425
        try:
426
            return file_kind(self._tree.abspath(path))
427
        except OSError, e:
428
            if e.errno != errno.ENOENT:
429
                raise
430
            else:
431
                raise NoSuchFile(path)
432
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
433
    def final_kind(self, trans_id):
1534.7.156 by Aaron Bentley
PEP8 fixes
434
        """Determine the final file kind, after any changes applied.
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
435
        
436
        Raises NoSuchFile if the file does not exist/has no contents.
437
        (It is conceivable that a path would be created without the
438
        corresponding contents insertion command)
439
        """
440
        if trans_id in self._new_contents:
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
441
            return self._new_contents[trans_id]
1534.7.34 by Aaron Bentley
Proper conflicts for removals
442
        elif trans_id in self._removed_contents:
443
            raise NoSuchFile(None)
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
444
        else:
1534.7.34 by Aaron Bentley
Proper conflicts for removals
445
            return self.tree_kind(trans_id)
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
446
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
447
    def tree_file_id(self, trans_id):
1534.7.41 by Aaron Bentley
Got inventory ID movement working
448
        """Determine the file id associated with the trans_id in the tree"""
449
        try:
450
            path = self._tree_id_paths[trans_id]
451
        except KeyError:
452
            # the file is a new, unversioned file, or invalid trans_id
453
            return None
454
        # the file is old; the old id is still valid
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
455
        if self._new_root == trans_id:
456
            return self._tree.inventory.root.file_id
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
457
        return self._tree.inventory.path2id(path)
1534.7.41 by Aaron Bentley
Got inventory ID movement working
458
1534.7.13 by Aaron Bentley
Implemented final_file_id
459
    def final_file_id(self, trans_id):
1534.7.156 by Aaron Bentley
PEP8 fixes
460
        """Determine the file id after any changes are applied, or None.
1534.7.21 by Aaron Bentley
Updated docstrings
461
        
462
        None indicates that the file will not be versioned after changes are
463
        applied.
464
        """
1534.7.13 by Aaron Bentley
Implemented final_file_id
465
        try:
466
            # there is a new id for this file
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
467
            assert self._new_id[trans_id] is not None
1534.7.13 by Aaron Bentley
Implemented final_file_id
468
            return self._new_id[trans_id]
469
        except KeyError:
1534.7.39 by Aaron Bentley
Ensured that files can be unversioned (de-versioned?)
470
            if trans_id in self._removed_id:
471
                return None
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
472
        return self.tree_file_id(trans_id)
1534.7.13 by Aaron Bentley
Implemented final_file_id
473
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
474
    def inactive_file_id(self, trans_id):
1534.7.157 by Aaron Bentley
Added more docs
475
        """Return the inactive file_id associated with a transaction id.
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
476
        That is, the one in the tree or in non_present_ids.
477
        The file_id may actually be active, too.
478
        """
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
479
        file_id = self.tree_file_id(trans_id)
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
480
        if file_id is not None:
481
            return file_id
482
        for key, value in self._non_present_ids.iteritems():
483
            if value == trans_id:
484
                return key
485
1534.7.17 by Aaron Bentley
Added final_parent function
486
    def final_parent(self, trans_id):
1534.7.156 by Aaron Bentley
PEP8 fixes
487
        """Determine the parent file_id, after any changes are applied.
1534.7.21 by Aaron Bentley
Updated docstrings
488
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
489
        ROOT_PARENT is returned for the tree root.
1534.7.21 by Aaron Bentley
Updated docstrings
490
        """
1534.7.17 by Aaron Bentley
Added final_parent function
491
        try:
492
            return self._new_parent[trans_id]
493
        except KeyError:
494
            return self.get_tree_parent(trans_id)
495
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
496
    def final_name(self, trans_id):
1534.7.40 by Aaron Bentley
Updated docs
497
        """Determine the final filename, after all changes are applied."""
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
498
        try:
499
            return self._new_name[trans_id]
500
        except KeyError:
1731.1.33 by Aaron Bentley
Revert no-special-root changes
501
            try:
502
                return os.path.basename(self._tree_id_paths[trans_id])
503
            except KeyError:
504
                raise NoFinalPath(trans_id, self)
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
505
1534.10.28 by Aaron Bentley
Use numbered backup files
506
    def by_parent(self):
1534.7.40 by Aaron Bentley
Updated docs
507
        """Return a map of parent: children for known parents.
508
        
509
        Only new paths and parents of tree files with assigned ids are used.
510
        """
1534.7.6 by Aaron Bentley
Added conflict handling
511
        by_parent = {}
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
512
        items = list(self._new_parent.iteritems())
1534.7.76 by Aaron Bentley
Fixed final_parent, for the case where finding a parent adds tree id paths.
513
        items.extend((t, self.final_parent(t)) for t in 
514
                      self._tree_id_paths.keys())
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
515
        for trans_id, parent_id in items:
1534.7.6 by Aaron Bentley
Added conflict handling
516
            if parent_id not in by_parent:
517
                by_parent[parent_id] = set()
518
            by_parent[parent_id].add(trans_id)
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
519
        return by_parent
1534.7.11 by Aaron Bentley
Refactored conflict handling
520
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
521
    def path_changed(self, trans_id):
1534.7.157 by Aaron Bentley
Added more docs
522
        """Return True if a trans_id's path has changed."""
1711.9.11 by John Arbash Meinel
change return foo in bar to return (foo in bar)
523
        return (trans_id in self._new_name) or (trans_id in self._new_parent)
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
524
1966.1.2 by Aaron Bentley
Divert files instead of failing to create them, update from review
525
    def new_contents(self, trans_id):
526
        return (trans_id in self._new_contents)
527
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
528
    def find_conflicts(self):
1534.7.40 by Aaron Bentley
Updated docs
529
        """Find any violations of inventory or filesystem invariants"""
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
530
        if self.__done is True:
531
            raise ReusingTransform()
532
        conflicts = []
533
        # ensure all children of all existent parents are known
534
        # all children of non-existent parents are known, by definition.
535
        self._add_tree_children()
1534.10.28 by Aaron Bentley
Use numbered backup files
536
        by_parent = self.by_parent()
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
537
        conflicts.extend(self._unversioned_parents(by_parent))
1534.7.19 by Aaron Bentley
Added tests for parent loops
538
        conflicts.extend(self._parent_loops())
1534.7.11 by Aaron Bentley
Refactored conflict handling
539
        conflicts.extend(self._duplicate_entries(by_parent))
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
540
        conflicts.extend(self._duplicate_ids())
1534.7.11 by Aaron Bentley
Refactored conflict handling
541
        conflicts.extend(self._parent_type_conflicts(by_parent))
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
542
        conflicts.extend(self._improper_versioning())
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
543
        conflicts.extend(self._executability_conflicts())
1534.7.152 by Aaron Bentley
Fixed overwrites
544
        conflicts.extend(self._overwrite_conflicts())
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
545
        return conflicts
546
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
547
    def _add_tree_children(self):
1534.7.156 by Aaron Bentley
PEP8 fixes
548
        """Add all the children of all active parents to the known paths.
1534.7.40 by Aaron Bentley
Updated docs
549
550
        Active parents are those which gain children, and those which are
551
        removed.  This is a necessary first step in detecting conflicts.
552
        """
1534.10.28 by Aaron Bentley
Use numbered backup files
553
        parents = self.by_parent().keys()
1534.7.34 by Aaron Bentley
Proper conflicts for removals
554
        parents.extend([t for t in self._removed_contents if 
555
                        self.tree_kind(t) == 'directory'])
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
556
        for trans_id in self._removed_id:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
557
            file_id = self.tree_file_id(trans_id)
1731.1.2 by Aaron Bentley
Removed all remaining uses of root_directory
558
            if self._tree.inventory[file_id].kind == 'directory':
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
559
                parents.append(trans_id)
560
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
561
        for parent_id in parents:
1534.7.67 by Aaron Bentley
Refactored _add_tree_children
562
            # ensure that all children are registered with the transaction
563
            list(self.iter_tree_children(parent_id))
564
565
    def iter_tree_children(self, parent_id):
566
        """Iterate through the entry's tree children, if any"""
567
        try:
568
            path = self._tree_id_paths[parent_id]
569
        except KeyError:
570
            return
571
        try:
572
            children = os.listdir(self._tree.abspath(path))
573
        except OSError, e:
1534.7.71 by abentley
All tests pass under Windows
574
            if e.errno != errno.ENOENT and e.errno != errno.ESRCH:
1534.7.67 by Aaron Bentley
Refactored _add_tree_children
575
                raise
576
            return
577
            
578
        for child in children:
579
            childpath = joinpath(path, child)
1534.7.180 by Aaron Bentley
Merge from mainline
580
            if self._tree.is_control_filename(childpath):
1534.7.67 by Aaron Bentley
Refactored _add_tree_children
581
                continue
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
582
            yield self.trans_id_tree_path(childpath)
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
583
1534.10.28 by Aaron Bentley
Use numbered backup files
584
    def has_named_child(self, by_parent, parent_id, name):
585
        try:
586
            children = by_parent[parent_id]
587
        except KeyError:
588
            children = []
589
        for child in children:
590
            if self.final_name(child) == name:
591
                return True
592
        try:
593
            path = self._tree_id_paths[parent_id]
594
        except KeyError:
595
            return False
596
        childpath = joinpath(path, name)
597
        child_id = self._tree_path_ids.get(childpath)
598
        if child_id is None:
599
            return lexists(self._tree.abspath(childpath))
600
        else:
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
601
            if self.final_parent(child_id) != parent_id:
1534.10.28 by Aaron Bentley
Use numbered backup files
602
                return False
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
603
            if child_id in self._removed_contents:
1534.10.28 by Aaron Bentley
Use numbered backup files
604
                # XXX What about dangling file-ids?
605
                return False
606
            else:
607
                return True
608
1534.7.19 by Aaron Bentley
Added tests for parent loops
609
    def _parent_loops(self):
610
        """No entry should be its own ancestor"""
611
        conflicts = []
612
        for trans_id in self._new_parent:
613
            seen = set()
614
            parent_id = trans_id
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
615
            while parent_id is not ROOT_PARENT:
1534.7.19 by Aaron Bentley
Added tests for parent loops
616
                seen.add(parent_id)
1731.1.33 by Aaron Bentley
Revert no-special-root changes
617
                try:
618
                    parent_id = self.final_parent(parent_id)
619
                except KeyError:
620
                    break
1534.7.19 by Aaron Bentley
Added tests for parent loops
621
                if parent_id == trans_id:
622
                    conflicts.append(('parent loop', trans_id))
623
                if parent_id in seen:
624
                    break
625
        return conflicts
626
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
627
    def _unversioned_parents(self, by_parent):
628
        """If parent directories are versioned, children must be versioned."""
629
        conflicts = []
630
        for parent_id, children in by_parent.iteritems():
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
631
            if parent_id is ROOT_PARENT:
632
                continue
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
633
            if self.final_file_id(parent_id) is not None:
634
                continue
635
            for child_id in children:
636
                if self.final_file_id(child_id) is not None:
637
                    conflicts.append(('unversioned parent', parent_id))
638
                    break;
639
        return conflicts
640
641
    def _improper_versioning(self):
1534.7.156 by Aaron Bentley
PEP8 fixes
642
        """Cannot version a file with no contents, or a bad type.
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
643
        
644
        However, existing entries with no contents are okay.
645
        """
646
        conflicts = []
647
        for trans_id in self._new_id.iterkeys():
648
            try:
649
                kind = self.final_kind(trans_id)
650
            except NoSuchFile:
651
                conflicts.append(('versioning no contents', trans_id))
652
                continue
653
            if not InventoryEntry.versionable_kind(kind):
1534.7.20 by Aaron Bentley
Added directory handling
654
                conflicts.append(('versioning bad kind', trans_id, kind))
1534.7.11 by Aaron Bentley
Refactored conflict handling
655
        return conflicts
656
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
657
    def _executability_conflicts(self):
1534.7.40 by Aaron Bentley
Updated docs
658
        """Check for bad executability changes.
659
        
660
        Only versioned files may have their executability set, because
661
        1. only versioned entries can have executability under windows
662
        2. only files can be executable.  (The execute bit on a directory
663
           does not indicate searchability)
664
        """
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
665
        conflicts = []
666
        for trans_id in self._new_executability:
667
            if self.final_file_id(trans_id) is None:
668
                conflicts.append(('unversioned executability', trans_id))
1534.7.34 by Aaron Bentley
Proper conflicts for removals
669
            else:
670
                try:
671
                    non_file = self.final_kind(trans_id) != "file"
672
                except NoSuchFile:
673
                    non_file = True
674
                if non_file is True:
675
                    conflicts.append(('non-file executability', trans_id))
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
676
        return conflicts
677
1534.7.152 by Aaron Bentley
Fixed overwrites
678
    def _overwrite_conflicts(self):
679
        """Check for overwrites (not permitted on Win32)"""
680
        conflicts = []
681
        for trans_id in self._new_contents:
682
            try:
683
                self.tree_kind(trans_id)
684
            except NoSuchFile:
685
                continue
686
            if trans_id not in self._removed_contents:
687
                conflicts.append(('overwrite', trans_id,
688
                                 self.final_name(trans_id)))
689
        return conflicts
690
1534.7.11 by Aaron Bentley
Refactored conflict handling
691
    def _duplicate_entries(self, by_parent):
692
        """No directory may have two entries with the same name."""
693
        conflicts = []
1534.7.6 by Aaron Bentley
Added conflict handling
694
        for children in by_parent.itervalues():
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
695
            name_ids = [(self.final_name(t), t) for t in children]
1534.7.6 by Aaron Bentley
Added conflict handling
696
            name_ids.sort()
697
            last_name = None
698
            last_trans_id = None
699
            for name, trans_id in name_ids:
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
700
                try:
701
                    kind = self.final_kind(trans_id)
702
                except NoSuchFile:
703
                    kind = None
704
                file_id = self.final_file_id(trans_id)
705
                if kind is None and file_id is None:
706
                    continue
1534.7.6 by Aaron Bentley
Added conflict handling
707
                if name == last_name:
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
708
                    conflicts.append(('duplicate', last_trans_id, trans_id,
709
                    name))
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
710
                last_name = name
711
                last_trans_id = trans_id
1534.7.11 by Aaron Bentley
Refactored conflict handling
712
        return conflicts
713
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
714
    def _duplicate_ids(self):
715
        """Each inventory id may only be used once"""
716
        conflicts = []
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
717
        removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
718
                                self._removed_id))
719
        active_tree_ids = set((f for f in self._tree.inventory if
720
                               f not in removed_tree_ids))
721
        for trans_id, file_id in self._new_id.iteritems():
722
            if file_id in active_tree_ids:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
723
                old_trans_id = self.trans_id_tree_file_id(file_id)
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
724
                conflicts.append(('duplicate id', old_trans_id, trans_id))
725
        return conflicts
726
1534.7.11 by Aaron Bentley
Refactored conflict handling
727
    def _parent_type_conflicts(self, by_parent):
728
        """parents must have directory 'contents'."""
729
        conflicts = []
1534.7.37 by Aaron Bentley
Allowed removed dirs to have content-free children.
730
        for parent_id, children in by_parent.iteritems():
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
731
            if parent_id is ROOT_PARENT:
732
                continue
1534.7.37 by Aaron Bentley
Allowed removed dirs to have content-free children.
733
            if not self._any_contents(children):
734
                continue
735
            for child in children:
736
                try:
737
                    self.final_kind(child)
738
                except NoSuchFile:
739
                    continue
1534.7.10 by Aaron Bentley
Implemented missing parent and non-directory parent conflicts
740
            try:
741
                kind = self.final_kind(parent_id)
742
            except NoSuchFile:
743
                kind = None
744
            if kind is None:
745
                conflicts.append(('missing parent', parent_id))
746
            elif kind != "directory":
747
                conflicts.append(('non-directory parent', parent_id))
1534.7.6 by Aaron Bentley
Added conflict handling
748
        return conflicts
1534.7.37 by Aaron Bentley
Allowed removed dirs to have content-free children.
749
750
    def _any_contents(self, trans_ids):
751
        """Return true if any of the trans_ids, will have contents."""
752
        for trans_id in trans_ids:
753
            try:
754
                kind = self.final_kind(trans_id)
755
            except NoSuchFile:
756
                continue
757
            return True
758
        return False
1534.7.6 by Aaron Bentley
Added conflict handling
759
            
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
760
    def apply(self):
1534.7.156 by Aaron Bentley
PEP8 fixes
761
        """Apply all changes to the inventory and filesystem.
1534.7.21 by Aaron Bentley
Updated docstrings
762
        
763
        If filesystem or inventory conflicts are present, MalformedTransform
764
        will be thrown.
765
        """
1534.7.49 by Aaron Bentley
Printed conflicts in MalformedTransform
766
        conflicts = self.find_conflicts()
767
        if len(conflicts) != 0:
768
            raise MalformedTransform(conflicts=conflicts)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
769
        inv = self._tree.inventory
2376.2.5 by Aaron Bentley
Clean up apply methods
770
        inventory_delta = []
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
771
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
772
        try:
773
            child_pb.update('Apply phase', 0, 2)
2376.2.5 by Aaron Bentley
Clean up apply methods
774
            self._apply_removals(inv, inventory_delta)
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
775
            child_pb.update('Apply phase', 1, 2)
2376.2.5 by Aaron Bentley
Clean up apply methods
776
            modified_paths = self._apply_insertions(inv, inventory_delta)
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
777
        finally:
778
            child_pb.finished()
2376.2.5 by Aaron Bentley
Clean up apply methods
779
        self._tree.apply_inventory_delta(inventory_delta)
1534.7.35 by Aaron Bentley
Got file renaming working
780
        self.__done = True
1534.7.59 by Aaron Bentley
Simplified tests
781
        self.finalize()
1534.7.191 by Aaron Bentley
Got transform.apply to list modified paths
782
        return _TransformResults(modified_paths)
1534.7.35 by Aaron Bentley
Got file renaming working
783
2502.1.2 by Aaron Bentley
Make the limited-renames functionality safer in the general case
784
    def _limbo_name(self, trans_id, from_scratch=False):
1534.7.72 by Aaron Bentley
Moved new content generation to pre-renames
785
        """Generate the limbo name of a file"""
2502.1.2 by Aaron Bentley
Make the limited-renames functionality safer in the general case
786
        if trans_id in self._limbo_files and not from_scratch:
2502.1.1 by Aaron Bentley
Ensure renames only root children are renamed when building trees
787
            return self._limbo_files[trans_id]
788
        parent = self.final_parent(trans_id)
2502.1.2 by Aaron Bentley
Make the limited-renames functionality safer in the general case
789
        # if the parent directory is already in limbo (e.g. when building a
790
        # tree), choose a limbo name inside the parent, to reduce further
791
        # renames.
792
        if self._new_contents.get(parent) == 'directory':
2502.1.1 by Aaron Bentley
Ensure renames only root children are renamed when building trees
793
            limbo_name = pathjoin(self._limbo_files[parent],
794
                                  self.final_name(trans_id))
2502.1.2 by Aaron Bentley
Make the limited-renames functionality safer in the general case
795
            if parent not in self._limbo_children:
796
                self._limbo_children[parent] = set()
797
            self._limbo_children[parent].add(trans_id)
2502.1.1 by Aaron Bentley
Ensure renames only root children are renamed when building trees
798
        else:
799
            limbo_name = pathjoin(self._limbodir, trans_id)
800
            self._needs_rename.add(trans_id)
801
        self._limbo_files[trans_id] = limbo_name
802
        return limbo_name
1534.7.72 by Aaron Bentley
Moved new content generation to pre-renames
803
2376.2.5 by Aaron Bentley
Clean up apply methods
804
    def _apply_removals(self, inv, inventory_delta):
1534.7.36 by Aaron Bentley
Added rename tests
805
        """Perform tree operations that remove directory/inventory names.
806
        
807
        That is, delete files that are to be deleted, and put any files that
808
        need renaming into limbo.  This must be done in strict child-to-parent
809
        order.
810
        """
1534.7.35 by Aaron Bentley
Got file renaming working
811
        tree_paths = list(self._tree_path_ids.iteritems())
812
        tree_paths.sort(reverse=True)
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
813
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
814
        try:
815
            for num, data in enumerate(tree_paths):
816
                path, trans_id = data
817
                child_pb.update('removing file', num, len(tree_paths))
818
                full_path = self._tree.abspath(path)
819
                if trans_id in self._removed_contents:
1558.12.9 by Aaron Bentley
Handle resolving conflicts with directories properly
820
                    delete_any(full_path)
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
821
                elif trans_id in self._new_name or trans_id in \
822
                    self._new_parent:
823
                    try:
824
                        os.rename(full_path, self._limbo_name(trans_id))
825
                    except OSError, e:
826
                        if e.errno != errno.ENOENT:
827
                            raise
2502.1.1 by Aaron Bentley
Ensure renames only root children are renamed when building trees
828
                    else:
829
                        self.rename_count += 1
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
830
                if trans_id in self._removed_id:
831
                    if trans_id == self._new_root:
832
                        file_id = self._tree.inventory.root.file_id
833
                    else:
834
                        file_id = self.tree_file_id(trans_id)
2376.2.4 by Aaron Bentley
Switch TreeTransform to use WT.apply_inventory_delta
835
                    assert file_id is not None
2376.2.5 by Aaron Bentley
Clean up apply methods
836
                    inventory_delta.append((path, None, file_id, None))
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
837
        finally:
838
            child_pb.finished()
1534.7.34 by Aaron Bentley
Proper conflicts for removals
839
2376.2.5 by Aaron Bentley
Clean up apply methods
840
    def _apply_insertions(self, inv, inventory_delta):
1534.7.36 by Aaron Bentley
Added rename tests
841
        """Perform tree operations that insert directory/inventory names.
842
        
843
        That is, create any files that need to be created, and restore from
844
        limbo any files that needed renaming.  This must be done in strict
845
        parent-to-child order.
846
        """
1534.9.1 by Aaron Bentley
Added progress bars to merge
847
        new_paths = self.new_paths()
1534.7.191 by Aaron Bentley
Got transform.apply to list modified paths
848
        modified_paths = []
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
849
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
850
        try:
851
            for num, (path, trans_id) in enumerate(new_paths):
2376.2.4 by Aaron Bentley
Switch TreeTransform to use WT.apply_inventory_delta
852
                new_entry = None
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
853
                child_pb.update('adding file', num, len(new_paths))
1534.7.48 by Aaron Bentley
Ensured we can move/rename dangling inventory entries
854
                try:
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
855
                    kind = self._new_contents[trans_id]
856
                except KeyError:
857
                    kind = contents = None
858
                if trans_id in self._new_contents or \
859
                    self.path_changed(trans_id):
860
                    full_path = self._tree.abspath(path)
2502.1.1 by Aaron Bentley
Ensure renames only root children are renamed when building trees
861
                    if trans_id in self._needs_rename:
862
                        try:
863
                            os.rename(self._limbo_name(trans_id), full_path)
864
                        except OSError, e:
865
                            # We may be renaming a dangling inventory id
866
                            if e.errno != errno.ENOENT:
867
                                raise
868
                        else:
869
                            self.rename_count += 1
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
870
                    if trans_id in self._new_contents:
871
                        modified_paths.append(full_path)
872
                        del self._new_contents[trans_id]
873
874
                if trans_id in self._new_id:
875
                    if kind is None:
876
                        kind = file_kind(self._tree.abspath(path))
2100.3.21 by Aaron Bentley
Work on checking out by-reference trees
877
                    if trans_id in self._new_reference_revision:
2376.2.4 by Aaron Bentley
Switch TreeTransform to use WT.apply_inventory_delta
878
                        new_entry = inventory.TreeReference(
879
                            self._new_id[trans_id],
2100.3.21 by Aaron Bentley
Work on checking out by-reference trees
880
                            self._new_name[trans_id], 
881
                            self.final_file_id(self._new_parent[trans_id]),
882
                            None, self._new_reference_revision[trans_id])
883
                    else:
2376.2.4 by Aaron Bentley
Switch TreeTransform to use WT.apply_inventory_delta
884
                        new_entry = inventory.make_entry(kind,
885
                            self.final_name(trans_id),
886
                            self.final_file_id(self.final_parent(trans_id)),
887
                            self._new_id[trans_id])
888
                else:
889
                    if trans_id in self._new_name or trans_id in\
890
                        self._new_parent or\
891
                        trans_id in self._new_executability:
892
                        file_id = self.final_file_id(trans_id)
893
                        if file_id is not None:
894
                            entry = inv[file_id]
895
                            new_entry = entry.copy()
896
897
                    if trans_id in self._new_name or trans_id in\
898
                        self._new_parent:
899
                            if new_entry is not None:
900
                                new_entry.name = self.final_name(trans_id)
901
                                parent = self.final_parent(trans_id)
902
                                parent_id = self.final_file_id(parent)
903
                                new_entry.parent_id = parent_id
904
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
905
                if trans_id in self._new_executability:
2376.2.4 by Aaron Bentley
Switch TreeTransform to use WT.apply_inventory_delta
906
                    self._set_executability(path, new_entry, trans_id)
907
                if new_entry is not None:
908
                    if new_entry.file_id in inv:
909
                        old_path = inv.id2path(new_entry.file_id)
910
                    else:
911
                        old_path = None
2376.2.5 by Aaron Bentley
Clean up apply methods
912
                    inventory_delta.append((old_path, path,
913
                                            new_entry.file_id,
914
                                            new_entry))
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
915
        finally:
916
            child_pb.finished()
1534.7.191 by Aaron Bentley
Got transform.apply to list modified paths
917
        return modified_paths
1534.7.40 by Aaron Bentley
Updated docs
918
2376.2.4 by Aaron Bentley
Switch TreeTransform to use WT.apply_inventory_delta
919
    def _set_executability(self, path, entry, trans_id):
1534.7.40 by Aaron Bentley
Updated docs
920
        """Set the executability of versioned files """
1534.7.25 by Aaron Bentley
Added set_executability
921
        new_executability = self._new_executability[trans_id]
2376.2.4 by Aaron Bentley
Switch TreeTransform to use WT.apply_inventory_delta
922
        entry.executable = new_executability
1534.7.25 by Aaron Bentley
Added set_executability
923
        if supports_executable():
924
            abspath = self._tree.abspath(path)
925
            current_mode = os.stat(abspath).st_mode
926
            if new_executability:
927
                umask = os.umask(0)
928
                os.umask(umask)
929
                to_mode = current_mode | (0100 & ~umask)
930
                # Enable x-bit for others only if they can read it.
931
                if current_mode & 0004:
932
                    to_mode |= 0001 & ~umask
933
                if current_mode & 0040:
934
                    to_mode |= 0010 & ~umask
935
            else:
936
                to_mode = current_mode & ~0111
937
            os.chmod(abspath, to_mode)
938
1534.7.23 by Aaron Bentley
Transform.new_entry -> Transform._new_entry
939
    def _new_entry(self, name, parent_id, file_id):
1534.7.21 by Aaron Bentley
Updated docstrings
940
        """Helper function to create a new filesystem entry."""
1534.7.2 by Aaron Bentley
Added convenience function
941
        trans_id = self.create_path(name, parent_id)
942
        if file_id is not None:
943
            self.version_file(file_id, trans_id)
944
        return trans_id
945
1534.7.27 by Aaron Bentley
Added execute bit to new_file method
946
    def new_file(self, name, parent_id, contents, file_id=None, 
947
                 executable=None):
1534.7.156 by Aaron Bentley
PEP8 fixes
948
        """Convenience method to create files.
1534.7.21 by Aaron Bentley
Updated docstrings
949
        
950
        name is the name of the file to create.
951
        parent_id is the transaction id of the parent directory of the file.
952
        contents is an iterator of bytestrings, which will be used to produce
953
        the file.
1740.2.4 by Aaron Bentley
Update transform tests and docs
954
        :param file_id: The inventory ID of the file, if it is to be versioned.
955
        :param executable: Only valid when a file_id has been supplied.
1534.7.21 by Aaron Bentley
Updated docstrings
956
        """
1534.7.23 by Aaron Bentley
Transform.new_entry -> Transform._new_entry
957
        trans_id = self._new_entry(name, parent_id, file_id)
1740.2.4 by Aaron Bentley
Update transform tests and docs
958
        # TODO: rather than scheduling a set_executable call,
959
        # have create_file create the file with the right mode.
1534.7.20 by Aaron Bentley
Added directory handling
960
        self.create_file(contents, trans_id)
1534.7.27 by Aaron Bentley
Added execute bit to new_file method
961
        if executable is not None:
962
            self.set_executability(executable, trans_id)
1534.7.20 by Aaron Bentley
Added directory handling
963
        return trans_id
964
965
    def new_directory(self, name, parent_id, file_id=None):
1534.7.156 by Aaron Bentley
PEP8 fixes
966
        """Convenience method to create directories.
1534.7.21 by Aaron Bentley
Updated docstrings
967
968
        name is the name of the directory to create.
969
        parent_id is the transaction id of the parent directory of the
970
        directory.
971
        file_id is the inventory ID of the directory, if it is to be versioned.
972
        """
1534.7.23 by Aaron Bentley
Transform.new_entry -> Transform._new_entry
973
        trans_id = self._new_entry(name, parent_id, file_id)
1534.7.20 by Aaron Bentley
Added directory handling
974
        self.create_directory(trans_id)
975
        return trans_id 
976
1534.7.22 by Aaron Bentley
Added symlink support
977
    def new_symlink(self, name, parent_id, target, file_id=None):
1534.7.156 by Aaron Bentley
PEP8 fixes
978
        """Convenience method to create symbolic link.
1534.7.22 by Aaron Bentley
Added symlink support
979
        
980
        name is the name of the symlink to create.
981
        parent_id is the transaction id of the parent directory of the symlink.
982
        target is a bytestring of the target of the symlink.
983
        file_id is the inventory ID of the file, if it is to be versioned.
984
        """
1534.7.23 by Aaron Bentley
Transform.new_entry -> Transform._new_entry
985
        trans_id = self._new_entry(name, parent_id, file_id)
1534.7.22 by Aaron Bentley
Added symlink support
986
        self.create_symlink(target, trans_id)
987
        return trans_id
988
1551.11.12 by Aaron Bentley
Changes from review
989
    def _affected_ids(self):
990
        """Return the set of transform ids affected by the transform"""
1551.11.1 by Aaron Bentley
Initial work on converting TreeTransform to iter_changes format
991
        trans_ids = set(self._removed_id)
992
        trans_ids.update(self._new_id.keys())
1551.11.2 by Aaron Bentley
Get kind change detection working for iter_changes
993
        trans_ids.update(self._removed_contents)
994
        trans_ids.update(self._new_contents.keys())
995
        trans_ids.update(self._new_executability.keys())
996
        trans_ids.update(self._new_name.keys())
997
        trans_ids.update(self._new_parent.keys())
1551.11.12 by Aaron Bentley
Changes from review
998
        return trans_ids
999
1000
    def _get_file_id_maps(self):
1001
        """Return mapping of file_ids to trans_ids in the to and from states"""
1002
        trans_ids = self._affected_ids()
1551.11.1 by Aaron Bentley
Initial work on converting TreeTransform to iter_changes format
1003
        from_trans_ids = {}
1004
        to_trans_ids = {}
1005
        # Build up two dicts: trans_ids associated with file ids in the
1006
        # FROM state, vs the TO state.
1007
        for trans_id in trans_ids:
1008
            from_file_id = self.tree_file_id(trans_id)
1009
            if from_file_id is not None:
1010
                from_trans_ids[from_file_id] = trans_id
1011
            to_file_id = self.final_file_id(trans_id)
1012
            if to_file_id is not None:
1013
                to_trans_ids[to_file_id] = trans_id
1551.11.12 by Aaron Bentley
Changes from review
1014
        return from_trans_ids, to_trans_ids
1015
1016
    def _from_file_data(self, from_trans_id, from_versioned, file_id):
1017
        """Get data about a file in the from (tree) state
1018
1019
        Return a (name, parent, kind, executable) tuple
1020
        """
1021
        from_path = self._tree_id_paths.get(from_trans_id)
1022
        if from_versioned:
1023
            # get data from working tree if versioned
1024
            from_entry = self._tree.inventory[file_id]
1025
            from_name = from_entry.name
1026
            from_parent = from_entry.parent_id
1027
        else:
1028
            from_entry = None
1029
            if from_path is None:
1030
                # File does not exist in FROM state
1031
                from_name = None
1032
                from_parent = None
1033
            else:
1034
                # File exists, but is not versioned.  Have to use path-
1035
                # splitting stuff
1036
                from_name = os.path.basename(from_path)
1037
                tree_parent = self.get_tree_parent(from_trans_id)
1038
                from_parent = self.tree_file_id(tree_parent)
1039
        if from_path is not None:
1040
            from_kind, from_executable, from_stats = \
1041
                self._tree._comparison_data(from_entry, from_path)
1042
        else:
1043
            from_kind = None
1044
            from_executable = False
1045
        return from_name, from_parent, from_kind, from_executable
1046
1047
    def _to_file_data(self, to_trans_id, from_trans_id, from_executable):
1048
        """Get data about a file in the to (target) state
1049
1050
        Return a (name, parent, kind, executable) tuple
1051
        """
1052
        to_name = self.final_name(to_trans_id)
1053
        try:
1054
            to_kind = self.final_kind(to_trans_id)
1055
        except NoSuchFile:
1056
            to_kind = None
1057
        to_parent = self.final_file_id(self.final_parent(to_trans_id))
1058
        if to_trans_id in self._new_executability:
1059
            to_executable = self._new_executability[to_trans_id]
1060
        elif to_trans_id == from_trans_id:
1061
            to_executable = from_executable
1062
        else:
1063
            to_executable = False
1064
        return to_name, to_parent, to_kind, to_executable
1065
1066
    def _iter_changes(self):
1067
        """Produce output in the same format as Tree._iter_changes.
1068
1069
        Will produce nonsensical results if invoked while inventory/filesystem
1070
        conflicts (as reported by TreeTransform.find_conflicts()) are present.
1071
1072
        This reads the Transform, but only reproduces changes involving a
1073
        file_id.  Files that are not versioned in either of the FROM or TO
1074
        states are not reflected.
1075
        """
1076
        final_paths = FinalPaths(self)
1077
        from_trans_ids, to_trans_ids = self._get_file_id_maps()
1551.11.4 by Aaron Bentley
Sort output of Transform.iter_changes by path
1078
        results = []
1551.11.1 by Aaron Bentley
Initial work on converting TreeTransform to iter_changes format
1079
        # Now iterate through all active file_ids
1080
        for file_id in set(from_trans_ids.keys() + to_trans_ids.keys()):
1551.11.7 by Aaron Bentley
Stop modified flag bleeding into later changes
1081
            modified = False
1551.11.1 by Aaron Bentley
Initial work on converting TreeTransform to iter_changes format
1082
            from_trans_id = from_trans_ids.get(file_id)
1083
            # find file ids, and determine versioning state
1084
            if from_trans_id is None:
1085
                from_versioned = False
1086
                from_trans_id = to_trans_ids[file_id]
1087
            else:
1088
                from_versioned = True
1089
            to_trans_id = to_trans_ids.get(file_id)
1090
            if to_trans_id is None:
1091
                to_versioned = False
1092
                to_trans_id = from_trans_id
1093
            else:
1094
                to_versioned = True
1551.11.12 by Aaron Bentley
Changes from review
1095
1096
            from_name, from_parent, from_kind, from_executable = \
1097
                self._from_file_data(from_trans_id, from_versioned, file_id)
1098
1099
            to_name, to_parent, to_kind, to_executable = \
1100
                self._to_file_data(to_trans_id, from_trans_id, from_executable)
1101
2255.7.96 by Robert Collins
Change _iter_changes interface to yield both old and new paths.
1102
            if not from_versioned:
1103
                from_path = None
1104
            else:
1105
                from_path = self._tree_id_paths.get(from_trans_id)
1106
            if not to_versioned:
1107
                to_path = None
1108
            else:
1109
                to_path = final_paths.get_path(to_trans_id)
1551.11.1 by Aaron Bentley
Initial work on converting TreeTransform to iter_changes format
1110
            if from_kind != to_kind:
1111
                modified = True
1551.10.37 by Aaron Bentley
recommit of TreeTransform._iter_changes fix with missing files
1112
            elif to_kind in ('file', 'symlink') and (
1551.11.2 by Aaron Bentley
Get kind change detection working for iter_changes
1113
                to_trans_id != from_trans_id or
1114
                to_trans_id in self._new_contents):
1115
                modified = True
1116
            if (not modified and from_versioned == to_versioned and
1117
                from_parent==to_parent and from_name == to_name and
1118
                from_executable == to_executable):
1119
                continue
2255.7.96 by Robert Collins
Change _iter_changes interface to yield both old and new paths.
1120
            results.append((file_id, (from_path, to_path), modified,
1551.11.1 by Aaron Bentley
Initial work on converting TreeTransform to iter_changes format
1121
                   (from_versioned, to_versioned),
1551.11.2 by Aaron Bentley
Get kind change detection working for iter_changes
1122
                   (from_parent, to_parent),
1551.11.1 by Aaron Bentley
Initial work on converting TreeTransform to iter_changes format
1123
                   (from_name, to_name),
1124
                   (from_kind, to_kind),
1551.11.4 by Aaron Bentley
Sort output of Transform.iter_changes by path
1125
                   (from_executable, to_executable)))
1126
        return iter(sorted(results, key=lambda x:x[1]))
1551.11.1 by Aaron Bentley
Initial work on converting TreeTransform to iter_changes format
1127
1128
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
1129
def joinpath(parent, child):
1534.7.40 by Aaron Bentley
Updated docs
1130
    """Join tree-relative paths, handling the tree root specially"""
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
1131
    if parent is None or parent == "":
1132
        return child
1133
    else:
1534.7.166 by Aaron Bentley
Swapped os.path.join for pathjoin everywhere
1134
        return pathjoin(parent, child)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
1135
1534.7.167 by Aaron Bentley
PEP8 and comment cleanups
1136
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
1137
class FinalPaths(object):
1759.2.2 by Jelmer Vernooij
Revert some of my spelling fixes and fix some typos after review by Aaron.
1138
    """Make path calculation cheap by memoizing paths.
1534.7.21 by Aaron Bentley
Updated docstrings
1139
1140
    The underlying tree must not be manipulated between calls, or else
1141
    the results will likely be incorrect.
1142
    """
1534.7.132 by Aaron Bentley
Got cooked conflicts working
1143
    def __init__(self, transform):
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
1144
        object.__init__(self)
1145
        self._known_paths = {}
1534.7.33 by Aaron Bentley
Fixed naming
1146
        self.transform = transform
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
1147
1148
    def _determine_path(self, trans_id):
1534.7.132 by Aaron Bentley
Got cooked conflicts working
1149
        if trans_id == self.transform.root:
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
1150
            return ""
1534.7.33 by Aaron Bentley
Fixed naming
1151
        name = self.transform.final_name(trans_id)
1152
        parent_id = self.transform.final_parent(trans_id)
1534.7.132 by Aaron Bentley
Got cooked conflicts working
1153
        if parent_id == self.transform.root:
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
1154
            return name
1155
        else:
1534.7.166 by Aaron Bentley
Swapped os.path.join for pathjoin everywhere
1156
            return pathjoin(self.get_path(parent_id), name)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
1157
1158
    def get_path(self, trans_id):
1534.7.157 by Aaron Bentley
Added more docs
1159
        """Find the final path associated with a trans_id"""
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
1160
        if trans_id not in self._known_paths:
1161
            self._known_paths[trans_id] = self._determine_path(trans_id)
1162
        return self._known_paths[trans_id]
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
1163
1534.7.30 by Aaron Bentley
Factored out topological id sorting
1164
def topology_sorted_ids(tree):
1534.7.40 by Aaron Bentley
Updated docs
1165
    """Determine the topological order of the ids in a tree"""
1534.7.30 by Aaron Bentley
Factored out topological id sorting
1166
    file_ids = list(tree)
1167
    file_ids.sort(key=tree.id2path)
1168
    return file_ids
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
1169
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1170
1534.7.165 by Aaron Bentley
Switched to build_tree instead of revert
1171
def build_tree(tree, wt):
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1172
    """Create working tree for a branch, using a TreeTransform.
1173
    
1966.1.2 by Aaron Bentley
Divert files instead of failing to create them, update from review
1174
    This function should be used on empty trees, having a tree root at most.
1175
    (see merge and revert functionality for working with existing trees)
1176
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1177
    Existing files are handled like so:
1178
    
1966.1.2 by Aaron Bentley
Divert files instead of failing to create them, update from review
1179
    - Existing bzrdirs take precedence over creating new items.  They are
1180
      created as '%s.diverted' % name.
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1181
    - Otherwise, if the content on disk matches the content we are building,
1182
      it is silently replaced.
1183
    - Otherwise, conflict resolution will move the old file to 'oldname.moved'.
1184
    """
2255.7.51 by Robert Collins
Lock build_tree trees in write-first order, to support older formats that dont do lock_tree_write nicely.
1185
    wt.lock_tree_write()
2255.7.49 by Robert Collins
Lock trees passed in to build_tree.
1186
    try:
2255.7.51 by Robert Collins
Lock build_tree trees in write-first order, to support older formats that dont do lock_tree_write nicely.
1187
        tree.lock_read()
2255.7.49 by Robert Collins
Lock trees passed in to build_tree.
1188
        try:
1189
            return _build_tree(tree, wt)
1190
        finally:
2255.7.51 by Robert Collins
Lock build_tree trees in write-first order, to support older formats that dont do lock_tree_write nicely.
1191
            tree.unlock()
2255.7.49 by Robert Collins
Lock trees passed in to build_tree.
1192
    finally:
2255.7.51 by Robert Collins
Lock build_tree trees in write-first order, to support older formats that dont do lock_tree_write nicely.
1193
        wt.unlock()
2255.7.49 by Robert Collins
Lock trees passed in to build_tree.
1194
1195
def _build_tree(tree, wt):
1196
    """See build_tree."""
2090.2.1 by Martin Pool
Fix some code which relies on assertions and breaks under python -O
1197
    if len(wt.inventory) > 1:  # more than just a root
1198
        raise errors.WorkingTreeAlreadyPopulated(base=wt.basedir)
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
1199
    file_trans_id = {}
1558.11.1 by Aaron Bentley
Progress indicator for tree builts
1200
    top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1201
    pp = ProgressPhase("Build phase", 2, top_pb)
2255.2.183 by Martin Pool
add missing _must_be_locked and a better message
1202
    if tree.inventory.root is not None:
2255.2.194 by Robert Collins
[BROKEN] Many updates to stop using experimental formats in tests.
1203
        # this is kindof a hack: we should be altering the root 
1204
        # as partof the regular tree shape diff logic.
1205
        # the conditional test hereis to avoid doing an
1206
        # expensive operation (flush) every time the root id
1207
        # is set within the tree, nor setting the root and thus
1208
        # marking the tree as dirty, because we use two different
1209
        # idioms here: tree interfaces and inventory interfaces.
1210
        if wt.path2id('') != tree.inventory.root.file_id:
1211
            wt.set_root_id(tree.inventory.root.file_id)
1212
            wt.flush()
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
1213
    tt = TreeTransform(wt)
1966.1.2 by Aaron Bentley
Divert files instead of failing to create them, update from review
1214
    divert = set()
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
1215
    try:
1558.11.1 by Aaron Bentley
Progress indicator for tree builts
1216
        pp.next_phase()
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1217
        file_trans_id[wt.get_root_id()] = \
1218
            tt.trans_id_tree_file_id(wt.get_root_id())
1558.11.1 by Aaron Bentley
Progress indicator for tree builts
1219
        pb = bzrlib.ui.ui_factory.nested_progress_bar()
1220
        try:
1966.1.2 by Aaron Bentley
Divert files instead of failing to create them, update from review
1221
            for num, (tree_path, entry) in \
1222
                enumerate(tree.inventory.iter_entries_by_dir()):
1223
                pb.update("Building tree", num, len(tree.inventory))
1558.11.1 by Aaron Bentley
Progress indicator for tree builts
1224
                if entry.parent_id is None:
1225
                    continue
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1226
                reparent = False
1966.1.2 by Aaron Bentley
Divert files instead of failing to create them, update from review
1227
                file_id = entry.file_id
1228
                target_path = wt.abspath(tree_path)
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1229
                try:
1230
                    kind = file_kind(target_path)
1231
                except NoSuchFile:
1232
                    pass
1233
                else:
1234
                    if kind == "directory":
1235
                        try:
1236
                            bzrdir.BzrDir.open(target_path)
1237
                        except errors.NotBranchError:
1238
                            pass
1239
                        else:
1966.1.2 by Aaron Bentley
Divert files instead of failing to create them, update from review
1240
                            divert.add(file_id)
1241
                    if (file_id not in divert and
1242
                        _content_match(tree, entry, file_id, kind,
1243
                        target_path)):
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1244
                        tt.delete_contents(tt.trans_id_tree_path(tree_path))
1245
                        if kind == 'directory':
1246
                            reparent = True
1558.11.1 by Aaron Bentley
Progress indicator for tree builts
1247
                if entry.parent_id not in file_trans_id:
2255.2.183 by Martin Pool
add missing _must_be_locked and a better message
1248
                    raise AssertionError(
1249
                        'entry %s parent id %r is not in file_trans_id %r'
1250
                        % (entry, entry.parent_id, file_trans_id))
1558.11.1 by Aaron Bentley
Progress indicator for tree builts
1251
                parent_id = file_trans_id[entry.parent_id]
1966.1.2 by Aaron Bentley
Divert files instead of failing to create them, update from review
1252
                file_trans_id[file_id] = new_by_entry(tt, entry, parent_id,
1558.11.1 by Aaron Bentley
Progress indicator for tree builts
1253
                                                      tree)
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1254
                if reparent:
1255
                    new_trans_id = file_trans_id[file_id]
1256
                    old_parent = tt.trans_id_tree_path(tree_path)
1257
                    _reparent_children(tt, old_parent, new_trans_id)
1558.11.1 by Aaron Bentley
Progress indicator for tree builts
1258
        finally:
1259
            pb.finished()
1260
        pp.next_phase()
1966.1.2 by Aaron Bentley
Divert files instead of failing to create them, update from review
1261
        divert_trans = set(file_trans_id[f] for f in divert)
1262
        resolver = lambda t, c: resolve_checkout(t, c, divert_trans)
1263
        raw_conflicts = resolve_conflicts(tt, pass_func=resolver)
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1264
        conflicts = cook_conflicts(raw_conflicts, tt)
1265
        for conflict in conflicts:
1266
            warning(conflict)
1267
        try:
1268
            wt.add_conflicts(conflicts)
1269
        except errors.UnsupportedOperation:
1270
            pass
1534.7.47 by Aaron Bentley
Started work on 'revert'
1271
        tt.apply()
1272
    finally:
1273
        tt.finalize()
1558.11.1 by Aaron Bentley
Progress indicator for tree builts
1274
        top_pb.finished()
2502.1.1 by Aaron Bentley
Ensure renames only root children are renamed when building trees
1275
    return tt
1534.7.47 by Aaron Bentley
Started work on 'revert'
1276
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1277
1278
def _reparent_children(tt, old_parent, new_parent):
1279
    for child in tt.iter_tree_children(old_parent):
1280
        tt.adjust_path(tt.final_name(child), new_parent, child)
1281
1282
1283
def _content_match(tree, entry, file_id, kind, target_path):
1284
    if entry.kind != kind:
1285
        return False
1286
    if entry.kind == "directory":
1287
        return True
1288
    if entry.kind == "file":
1289
        if tree.get_file(file_id).read() == file(target_path, 'rb').read():
1290
            return True
1291
    elif entry.kind == "symlink":
1292
        if tree.get_symlink_target(file_id) == os.readlink(target_path):
1293
            return True
1294
    return False
1295
1296
1966.1.2 by Aaron Bentley
Divert files instead of failing to create them, update from review
1297
def resolve_checkout(tt, conflicts, divert):
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1298
    new_conflicts = set()
1299
    for c_type, conflict in ((c[0], c) for c in conflicts):
1300
        # Anything but a 'duplicate' would indicate programmer error
1301
        assert c_type == 'duplicate', c_type
1302
        # Now figure out which is new and which is old
1966.1.2 by Aaron Bentley
Divert files instead of failing to create them, update from review
1303
        if tt.new_contents(conflict[1]):
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1304
            new_file = conflict[1]
1305
            old_file = conflict[2]
1306
        else:
1307
            new_file = conflict[2]
1308
            old_file = conflict[1]
1309
1310
        # We should only get here if the conflict wasn't completely
1311
        # resolved
1312
        final_parent = tt.final_parent(old_file)
1966.1.2 by Aaron Bentley
Divert files instead of failing to create them, update from review
1313
        if new_file in divert:
1314
            new_name = tt.final_name(old_file)+'.diverted'
1315
            tt.adjust_path(new_name, final_parent, new_file)
1316
            new_conflicts.add((c_type, 'Diverted to',
1317
                               new_file, old_file))
1318
        else:
1319
            new_name = tt.final_name(old_file)+'.moved'
1320
            tt.adjust_path(new_name, final_parent, old_file)
1321
            new_conflicts.add((c_type, 'Moved existing file to',
1322
                               old_file, new_file))
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1323
    return new_conflicts
1324
1325
1534.7.47 by Aaron Bentley
Started work on 'revert'
1326
def new_by_entry(tt, entry, parent_id, tree):
1534.7.157 by Aaron Bentley
Added more docs
1327
    """Create a new file according to its inventory entry"""
1534.7.47 by Aaron Bentley
Started work on 'revert'
1328
    name = entry.name
1329
    kind = entry.kind
1330
    if kind == 'file':
1534.7.79 by Aaron Bentley
Stopped calling get_file_lines on WorkingTree
1331
        contents = tree.get_file(entry.file_id).readlines()
1534.7.47 by Aaron Bentley
Started work on 'revert'
1332
        executable = tree.is_executable(entry.file_id)
1333
        return tt.new_file(name, parent_id, contents, entry.file_id, 
1334
                           executable)
2100.3.21 by Aaron Bentley
Work on checking out by-reference trees
1335
    elif kind in ('directory', 'tree-reference'):
1336
        trans_id = tt.new_directory(name, parent_id, entry.file_id)
1337
        if kind == 'tree-reference':
1338
            tt.set_tree_reference(entry.reference_revision, trans_id)
1339
        return trans_id 
1534.7.47 by Aaron Bentley
Started work on 'revert'
1340
    elif kind == 'symlink':
1534.7.183 by Aaron Bentley
Fixed build_tree with symlinks
1341
        target = tree.get_symlink_target(entry.file_id)
1342
        return tt.new_symlink(name, parent_id, target, entry.file_id)
2100.3.21 by Aaron Bentley
Work on checking out by-reference trees
1343
    else:
1344
        raise errors.BadFileKindError(name, kind)
1534.7.47 by Aaron Bentley
Started work on 'revert'
1345
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
1346
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1534.7.157 by Aaron Bentley
Added more docs
1347
    """Create new file contents according to an inventory entry."""
1534.7.47 by Aaron Bentley
Started work on 'revert'
1348
    if entry.kind == "file":
1963.2.6 by Robey Pointer
pychecker is on crack; go back to using 'is None'.
1349
        if lines is None:
1534.7.97 by Aaron Bentley
Ensured foo.BASE is a directory if there's a conflict
1350
            lines = tree.get_file(entry.file_id).readlines()
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
1351
        tt.create_file(lines, trans_id, mode_id=mode_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
1352
    elif entry.kind == "symlink":
1534.7.101 by Aaron Bentley
Got conflicts on symlinks working properly
1353
        tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
1354
    elif entry.kind == "directory":
1534.7.51 by Aaron Bentley
New approach to revert
1355
        tt.create_directory(trans_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
1356
1534.7.89 by Aaron Bentley
Handle all content types in three-way
1357
def create_entry_executability(tt, entry, trans_id):
1534.7.157 by Aaron Bentley
Added more docs
1358
    """Set the executability of a trans_id according to an inventory entry"""
1534.7.89 by Aaron Bentley
Handle all content types in three-way
1359
    if entry.kind == "file":
1360
        tt.set_executability(entry.executable, trans_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
1361
1534.7.157 by Aaron Bentley
Added more docs
1362
2255.7.48 by Robert Collins
Deprecated and make work with DirState trees 'transform.find_interesting'.
1363
@deprecated_function(zero_fifteen)
1534.7.55 by Aaron Bentley
Fixed up the change detection
1364
def find_interesting(working_tree, target_tree, filenames):
2255.7.48 by Robert Collins
Deprecated and make work with DirState trees 'transform.find_interesting'.
1365
    """Find the ids corresponding to specified filenames.
1366
    
1367
    Deprecated: Please use tree1.paths2ids(filenames, [tree2]).
1368
    """
1369
    working_tree.lock_read()
1370
    try:
1371
        target_tree.lock_read()
1372
        try:
1373
            return working_tree.paths2ids(filenames, [target_tree])
1374
        finally:
1375
            target_tree.unlock()
1376
    finally:
1377
        working_tree.unlock()
1534.7.55 by Aaron Bentley
Fixed up the change detection
1378
1379
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
1380
def change_entry(tt, file_id, working_tree, target_tree, 
1534.10.28 by Aaron Bentley
Use numbered backup files
1381
                 trans_id_file_id, backups, trans_id, by_parent):
1534.7.157 by Aaron Bentley
Added more docs
1382
    """Replace a file_id's contents with those from a target tree."""
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
1383
    e_trans_id = trans_id_file_id(file_id)
1534.7.55 by Aaron Bentley
Fixed up the change detection
1384
    entry = target_tree.inventory[file_id]
1385
    has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry, 
1386
                                                           working_tree)
1387
    if contents_mod:
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
1388
        mode_id = e_trans_id
1534.7.55 by Aaron Bentley
Fixed up the change detection
1389
        if has_contents:
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
1390
            if not backups:
1391
                tt.delete_contents(e_trans_id)
1392
            else:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
1393
                parent_trans_id = trans_id_file_id(entry.parent_id)
1534.10.28 by Aaron Bentley
Use numbered backup files
1394
                backup_name = get_backup_name(entry, by_parent,
1395
                                              parent_trans_id, tt)
1396
                tt.adjust_path(backup_name, parent_trans_id, e_trans_id)
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
1397
                tt.unversion_file(e_trans_id)
1398
                e_trans_id = tt.create_path(entry.name, parent_trans_id)
1399
                tt.version_file(file_id, e_trans_id)
1400
                trans_id[file_id] = e_trans_id
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
1401
        create_by_entry(tt, entry, target_tree, e_trans_id, mode_id=mode_id)
1534.7.89 by Aaron Bentley
Handle all content types in three-way
1402
        create_entry_executability(tt, entry, e_trans_id)
1403
1711.4.26 by John Arbash Meinel
Fix #45010 correctly. Don't forget the execute bit.
1404
    elif meta_mod:
1405
        tt.set_executability(entry.executable, e_trans_id)
1534.7.55 by Aaron Bentley
Fixed up the change detection
1406
    if tt.final_name(e_trans_id) != entry.name:
1407
        adjust_path  = True
1408
    else:
1409
        parent_id = tt.final_parent(e_trans_id)
1410
        parent_file_id = tt.final_file_id(parent_id)
1411
        if parent_file_id != entry.parent_id:
1412
            adjust_path = True
1413
        else:
1414
            adjust_path = False
1415
    if adjust_path:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
1416
        parent_trans_id = trans_id_file_id(entry.parent_id)
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
1417
        tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
1534.7.55 by Aaron Bentley
Fixed up the change detection
1418
1419
1534.10.28 by Aaron Bentley
Use numbered backup files
1420
def get_backup_name(entry, by_parent, parent_trans_id, tt):
2012.1.12 by Aaron Bentley
Use iter_changes for revert
1421
    return _get_backup_name(entry.name, by_parent, parent_trans_id, tt)
1422
1423
1424
def _get_backup_name(name, by_parent, parent_trans_id, tt):
1534.10.28 by Aaron Bentley
Use numbered backup files
1425
    """Produce a backup-style name that appears to be available"""
1426
    def name_gen():
1427
        counter = 1
1428
        while True:
2012.1.12 by Aaron Bentley
Use iter_changes for revert
1429
            yield "%s.~%d~" % (name, counter)
1534.10.28 by Aaron Bentley
Use numbered backup files
1430
            counter += 1
2012.1.12 by Aaron Bentley
Use iter_changes for revert
1431
    for new_name in name_gen():
1432
        if not tt.has_named_child(by_parent, parent_trans_id, new_name):
1433
            return new_name
1434
1534.10.28 by Aaron Bentley
Use numbered backup files
1435
1534.7.55 by Aaron Bentley
Fixed up the change detection
1436
def _entry_changes(file_id, entry, working_tree):
1534.7.156 by Aaron Bentley
PEP8 fixes
1437
    """Determine in which ways the inventory entry has changed.
1534.7.55 by Aaron Bentley
Fixed up the change detection
1438
1439
    Returns booleans: has_contents, content_mod, meta_mod
1440
    has_contents means there are currently contents, but they differ
1441
    contents_mod means contents need to be modified
1442
    meta_mod means the metadata needs to be modified
1443
    """
1444
    cur_entry = working_tree.inventory[file_id]
1445
    try:
1446
        working_kind = working_tree.kind(file_id)
1447
        has_contents = True
1757.2.4 by Robert Collins
Teach file_kind about NoSuchFile, reducing duplicate code, and add user files before entering the main loop in smart_add.
1448
    except NoSuchFile:
1534.7.55 by Aaron Bentley
Fixed up the change detection
1449
        has_contents = False
1450
        contents_mod = True
1451
        meta_mod = False
1452
    if has_contents is True:
1731.1.1 by Aaron Bentley
Make root entry an InventoryDirectory, make EmptyTree really empty
1453
        if entry.kind != working_kind:
1534.7.55 by Aaron Bentley
Fixed up the change detection
1454
            contents_mod, meta_mod = True, False
1455
        else:
1456
            cur_entry._read_tree_state(working_tree.id2path(file_id), 
1457
                                       working_tree)
1458
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
1534.7.175 by Aaron Bentley
Ensured revert writes a normal inventory
1459
            cur_entry._forget_tree_state()
1534.7.55 by Aaron Bentley
Fixed up the change detection
1460
    return has_contents, contents_mod, meta_mod
1461
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
1462
2255.2.53 by Robert Collins
Teach TreeTransform to lock basis_trees if it acquires them, fixing revert on a dirstate working tree.
1463
def revert(working_tree, target_tree, filenames, backups=False,
2225.1.1 by Aaron Bentley
Added revert change display, with tests
1464
           pb=DummyProgress(), change_reporter=None):
1534.7.157 by Aaron Bentley
Added more docs
1465
    """Revert a working tree's contents to those of a target tree."""
2255.2.53 by Robert Collins
Teach TreeTransform to lock basis_trees if it acquires them, fixing revert on a dirstate working tree.
1466
    target_tree.lock_read()
1534.9.7 by Aaron Bentley
Show progress bars in revert
1467
    tt = TreeTransform(working_tree, pb)
1534.7.47 by Aaron Bentley
Started work on 'revert'
1468
    try:
2012.1.12 by Aaron Bentley
Use iter_changes for revert
1469
        pp = ProgressPhase("Revert phase", 3, pb)
1470
        pp.next_phase()
1471
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1472
        try:
2499.1.1 by Aaron Bentley
Revert does not try to preserve file contents produced by revert
1473
            merge_modified = _alter_files(working_tree, target_tree, tt,
1474
                                          child_pb, filenames, backups)
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
1475
        finally:
1476
            child_pb.finished()
1551.2.34 by Aaron Bentley
Refactored the revert phases
1477
        pp.next_phase()
1551.2.31 by Aaron Bentley
Got merge and revert using nested pbs
1478
        child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
1479
        try:
1480
            raw_conflicts = resolve_conflicts(tt, child_pb)
1481
        finally:
1482
            child_pb.finished()
1558.7.13 by Aaron Bentley
WorkingTree.revert returns conflicts
1483
        conflicts = cook_conflicts(raw_conflicts, tt)
1551.11.5 by Aaron Bentley
cleanup
1484
        if change_reporter:
1551.10.25 by Aaron Bentley
Make ChangeReporter private
1485
            change_reporter = delta._ChangeReporter(
2255.7.98 by Robert Collins
Merge bzr.dev.
1486
                unversioned_filter=working_tree.is_ignored)
1551.11.5 by Aaron Bentley
cleanup
1487
            delta.report_changes(tt._iter_changes(), change_reporter)
1551.11.6 by Aaron Bentley
Emit change listings before conflict warnings
1488
        for conflict in conflicts:
1489
            warning(conflict)
1490
        pp.next_phase()
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
1491
        tt.apply()
2499.1.1 by Aaron Bentley
Revert does not try to preserve file contents produced by revert
1492
        working_tree.set_merge_modified(merge_modified)
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
1493
    finally:
2255.2.53 by Robert Collins
Teach TreeTransform to lock basis_trees if it acquires them, fixing revert on a dirstate working tree.
1494
        target_tree.unlock()
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
1495
        tt.finalize()
1534.9.4 by Aaron Bentley
Added progress bars to revert.
1496
        pb.clear()
1558.7.13 by Aaron Bentley
WorkingTree.revert returns conflicts
1497
    return conflicts
1534.7.51 by Aaron Bentley
New approach to revert
1498
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
1499
2255.2.149 by Robert Collins
Crufty but existing _iter_changes implementation for WorkingTreeFormat4.
1500
def _alter_files(working_tree, target_tree, tt, pb, specific_files,
1551.11.3 by Aaron Bentley
Use tree transform to emit upcoming change list
1501
                 backups):
2012.1.12 by Aaron Bentley
Use iter_changes for revert
1502
    merge_modified = working_tree.merge_modified()
1551.11.3 by Aaron Bentley
Use tree transform to emit upcoming change list
1503
    change_list = target_tree._iter_changes(working_tree,
2255.2.149 by Robert Collins
Crufty but existing _iter_changes implementation for WorkingTreeFormat4.
1504
        specific_files=specific_files, pb=pb)
2012.1.12 by Aaron Bentley
Use iter_changes for revert
1505
    if target_tree.inventory.root is None:
1506
        skip_root = True
1507
    else:
1508
        skip_root = False
1509
    basis_tree = None
2255.2.53 by Robert Collins
Teach TreeTransform to lock basis_trees if it acquires them, fixing revert on a dirstate working tree.
1510
    try:
1511
        for id_num, (file_id, path, changed_content, versioned, parent, name,
1512
                kind, executable) in enumerate(change_list):
1513
            if skip_root and file_id[0] is not None and parent[0] is None:
1514
                continue
1515
            trans_id = tt.trans_id_file_id(file_id)
1516
            mode_id = None
1517
            if changed_content:
1518
                keep_content = False
1519
                if kind[0] == 'file' and (backups or kind[1] is None):
1520
                    wt_sha1 = working_tree.get_file_sha1(file_id)
1521
                    if merge_modified.get(file_id) != wt_sha1:
1522
                        # acquire the basis tree lazyily to prevent the expense
1523
                        # of accessing it when its not needed ? (Guessing, RBC,
1524
                        # 200702)
1525
                        if basis_tree is None:
1526
                            basis_tree = working_tree.basis_tree()
1527
                            basis_tree.lock_read()
1528
                        if file_id in basis_tree:
1529
                            if wt_sha1 != basis_tree.get_file_sha1(file_id):
1530
                                keep_content = True
1531
                        elif kind[1] is None and not versioned[1]:
2012.1.12 by Aaron Bentley
Use iter_changes for revert
1532
                            keep_content = True
2255.2.53 by Robert Collins
Teach TreeTransform to lock basis_trees if it acquires them, fixing revert on a dirstate working tree.
1533
                if kind[0] is not None:
1534
                    if not keep_content:
1535
                        tt.delete_contents(trans_id)
1536
                    elif kind[1] is not None:
1537
                        parent_trans_id = tt.trans_id_file_id(parent[0])
1538
                        by_parent = tt.by_parent()
1539
                        backup_name = _get_backup_name(name[0], by_parent,
1540
                                                       parent_trans_id, tt)
1541
                        tt.adjust_path(backup_name, parent_trans_id, trans_id)
1542
                        new_trans_id = tt.create_path(name[0], parent_trans_id)
1543
                        if versioned == (True, True):
1544
                            tt.unversion_file(trans_id)
1545
                            tt.version_file(file_id, new_trans_id)
1546
                        # New contents should have the same unix perms as old
1547
                        # contents
1548
                        mode_id = trans_id
1549
                        trans_id = new_trans_id
1550
                if kind[1] == 'directory':
1551
                    tt.create_directory(trans_id)
1552
                elif kind[1] == 'symlink':
1553
                    tt.create_symlink(target_tree.get_symlink_target(file_id),
1554
                                      trans_id)
1555
                elif kind[1] == 'file':
1556
                    tt.create_file(target_tree.get_file_lines(file_id),
1557
                                   trans_id, mode_id)
2499.1.1 by Aaron Bentley
Revert does not try to preserve file contents produced by revert
1558
                    if basis_tree is None:
1559
                        basis_tree = working_tree.basis_tree()
1560
                        basis_tree.lock_read()
1561
                    new_sha1 = target_tree.get_file_sha1(file_id)
1562
                    if (file_id in basis_tree and new_sha1 ==
1563
                        basis_tree.get_file_sha1(file_id)):
1564
                        if file_id in merge_modified:
1565
                            del merge_modified[file_id]
1566
                    else:
1567
                        merge_modified[file_id] = new_sha1
1568
2255.2.53 by Robert Collins
Teach TreeTransform to lock basis_trees if it acquires them, fixing revert on a dirstate working tree.
1569
                    # preserve the execute bit when backing up
1570
                    if keep_content and executable[0] == executable[1]:
1571
                        tt.set_executability(executable[1], trans_id)
1572
                else:
1573
                    assert kind[1] is None
1574
            if versioned == (False, True):
1575
                tt.version_file(file_id, trans_id)
1576
            if versioned == (True, False):
1577
                tt.unversion_file(trans_id)
1578
            if (name[1] is not None and 
1579
                (name[0] != name[1] or parent[0] != parent[1])):
1580
                tt.adjust_path(
1581
                    name[1], tt.trans_id_file_id(parent[1]), trans_id)
1582
            if executable[0] != executable[1] and kind[1] == "file":
1583
                tt.set_executability(executable[1], trans_id)
1584
    finally:
1585
        if basis_tree is not None:
1586
            basis_tree.unlock()
2499.1.1 by Aaron Bentley
Revert does not try to preserve file contents produced by revert
1587
    return merge_modified
2012.1.12 by Aaron Bentley
Use iter_changes for revert
1588
1589
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1590
def resolve_conflicts(tt, pb=DummyProgress(), pass_func=None):
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
1591
    """Make many conflict-resolution attempts, but die if they fail"""
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1592
    if pass_func is None:
1593
        pass_func = conflict_pass
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1594
    new_conflicts = set()
1534.9.1 by Aaron Bentley
Added progress bars to merge
1595
    try:
1596
        for n in range(10):
1597
            pb.update('Resolution pass', n+1, 10)
1598
            conflicts = tt.find_conflicts()
1599
            if len(conflicts) == 0:
1600
                return new_conflicts
1966.1.1 by Aaron Bentley
Implement disk-content merge and conflict resolution for build_tree
1601
            new_conflicts.update(pass_func(tt, conflicts))
1534.9.1 by Aaron Bentley
Added progress bars to merge
1602
        raise MalformedTransform(conflicts=conflicts)
1603
    finally:
1604
        pb.clear()
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
1605
1606
1607
def conflict_pass(tt, conflicts):
1534.7.157 by Aaron Bentley
Added more docs
1608
    """Resolve some classes of conflicts."""
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1609
    new_conflicts = set()
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
1610
    for c_type, conflict in ((c[0], c) for c in conflicts):
1611
        if c_type == 'duplicate id':
1534.7.51 by Aaron Bentley
New approach to revert
1612
            tt.unversion_file(conflict[1])
1534.7.170 by Aaron Bentley
Cleaned up filesystem conflict handling
1613
            new_conflicts.add((c_type, 'Unversioned existing file',
1614
                               conflict[1], conflict[2], ))
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
1615
        elif c_type == 'duplicate':
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
1616
            # files that were renamed take precedence
1617
            new_name = tt.final_name(conflict[1])+'.moved'
1618
            final_parent = tt.final_parent(conflict[1])
1619
            if tt.path_changed(conflict[1]):
1620
                tt.adjust_path(new_name, final_parent, conflict[2])
1534.7.171 by Aaron Bentley
Implemented stringifying filesystem conflicts
1621
                new_conflicts.add((c_type, 'Moved existing file to', 
1622
                                   conflict[2], conflict[1]))
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
1623
            else:
1624
                tt.adjust_path(new_name, final_parent, conflict[1])
1534.7.171 by Aaron Bentley
Implemented stringifying filesystem conflicts
1625
                new_conflicts.add((c_type, 'Moved existing file to', 
1626
                                  conflict[1], conflict[2]))
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
1627
        elif c_type == 'parent loop':
1628
            # break the loop by undoing one of the ops that caused the loop
1629
            cur = conflict[1]
1630
            while not tt.path_changed(cur):
1631
                cur = tt.final_parent(cur)
1534.7.170 by Aaron Bentley
Cleaned up filesystem conflict handling
1632
            new_conflicts.add((c_type, 'Cancelled move', cur,
1633
                               tt.final_parent(cur),))
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
1634
            tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1635
            
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
1636
        elif c_type == 'missing parent':
1534.7.128 by Aaron Bentley
Got missing contents test working
1637
            trans_id = conflict[1]
1638
            try:
1639
                tt.cancel_deletion(trans_id)
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
1640
                new_conflicts.add(('deleting parent', 'Not deleting', 
1641
                                   trans_id))
1534.7.128 by Aaron Bentley
Got missing contents test working
1642
            except KeyError:
1643
                tt.create_directory(trans_id)
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
1644
                new_conflicts.add((c_type, 'Created directory', trans_id))
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
1645
        elif c_type == 'unversioned parent':
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
1646
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1534.7.171 by Aaron Bentley
Implemented stringifying filesystem conflicts
1647
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1648
    return new_conflicts
1649
1666.1.4 by Robert Collins
* 'Metadir' is now the default disk format. This improves behaviour in
1650
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1651
def cook_conflicts(raw_conflicts, tt):
1534.7.170 by Aaron Bentley
Cleaned up filesystem conflict handling
1652
    """Generate a list of cooked conflicts, sorted by file path"""
1666.1.4 by Robert Collins
* 'Metadir' is now the default disk format. This improves behaviour in
1653
    from bzrlib.conflicts import Conflict
1654
    conflict_iter = iter_cook_conflicts(raw_conflicts, tt)
1655
    return sorted(conflict_iter, key=Conflict.sort_key)
1534.7.170 by Aaron Bentley
Cleaned up filesystem conflict handling
1656
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1657
1658
def iter_cook_conflicts(raw_conflicts, tt):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
1659
    from bzrlib.conflicts import Conflict
1534.7.170 by Aaron Bentley
Cleaned up filesystem conflict handling
1660
    fp = FinalPaths(tt)
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1661
    for conflict in raw_conflicts:
1662
        c_type = conflict[0]
1534.7.170 by Aaron Bentley
Cleaned up filesystem conflict handling
1663
        action = conflict[1]
1664
        modified_path = fp.get_path(conflict[2])
1665
        modified_id = tt.final_file_id(conflict[2])
1666
        if len(conflict) == 3:
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
1667
            yield Conflict.factory(c_type, action=action, path=modified_path,
1668
                                     file_id=modified_id)
1669
             
1534.7.170 by Aaron Bentley
Cleaned up filesystem conflict handling
1670
        else:
1671
            conflicting_path = fp.get_path(conflict[3])
1672
            conflicting_id = tt.final_file_id(conflict[3])
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
1673
            yield Conflict.factory(c_type, action=action, path=modified_path,
1674
                                   file_id=modified_id, 
1675
                                   conflict_path=conflicting_path,
1676
                                   conflict_file_id=conflicting_id)