~bzr-pqm/bzr/bzr.dev

1534.7.106 by Aaron Bentley
Cleaned up imports, added copyright statements
1
# Copyright (C) 2006 Canonical Ltd
2
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.
7
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.
12
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
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
21
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
1534.7.162 by Aaron Bentley
Handle failures creating/deleting the Limbo directory
22
                           ReusingTransform, NotVersionedError, CantMoveRoot,
23
                           ExistingLimbo, ImmortalLimbo)
1534.7.106 by Aaron Bentley
Cleaned up imports, added copyright statements
24
from bzrlib.inventory import InventoryEntry
1534.8.3 by Aaron Bentley
Added Diff3 merging for tree transforms
25
from bzrlib.osutils import file_kind, supports_executable, pathjoin
1534.7.173 by Aaron Bentley
Added conflict warnings to revert
26
from bzrlib.trace import mutter, warning
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
27
1534.7.167 by Aaron Bentley
PEP8 and comment cleanups
28
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
29
ROOT_PARENT = "root-parent"
30
1534.7.167 by Aaron Bentley
PEP8 and comment cleanups
31
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
32
def unique_add(map, key, value):
33
    if key in map:
1534.7.5 by Aaron Bentley
Got unique_add under test
34
        raise DuplicateKey(key=key)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
35
    map[key] = value
36
1534.7.167 by Aaron Bentley
PEP8 and comment cleanups
37
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
38
class TreeTransform(object):
1534.7.179 by Aaron Bentley
Added Transform docs
39
    """Represent a tree transformation.
40
    
41
    This object is designed to support incremental generation of the transform,
42
    in any order.  
43
    
44
    It is easy to produce malformed transforms, but they are generally
45
    harmless.  Attempting to apply a malformed transform will cause an
46
    exception to be raised before any modifications are made to the tree.  
47
48
    Many kinds of malformed transforms can be corrected with the 
49
    resolve_conflicts function.  The remaining ones indicate programming error,
50
    such as trying to create a file with no path.
51
52
    Two sets of file creation methods are supplied.  Convenience methods are:
53
     * new_file
54
     * new_directory
55
     * new_symlink
56
57
    These are composed of the low-level methods:
58
     * create_path
59
     * create_file or create_directory or create_symlink
60
     * version_file
61
     * set_executability
62
    """
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
63
    def __init__(self, tree):
64
        """Note: a write lock is taken on the tree.
65
        
66
        Use TreeTransform.finalize() to release the lock
67
        """
68
        object.__init__(self)
69
        self._tree = tree
70
        self._tree.lock_write()
1534.7.162 by Aaron Bentley
Handle failures creating/deleting the Limbo directory
71
        try:
72
            control_files = self._tree._control_files
73
            self._limbodir = control_files.controlfilename('limbo')
74
            try:
75
                os.mkdir(self._limbodir)
76
            except OSError, e:
77
                if e.errno == errno.EEXIST:
78
                    raise ExistingLimbo(self._limbodir)
79
        except: 
80
            self._tree.unlock()
81
            raise
82
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
83
        self._id_number = 0
84
        self._new_name = {}
85
        self._new_parent = {}
1534.7.4 by Aaron Bentley
Unified all file types as 'contents'
86
        self._new_contents = {}
1534.7.34 by Aaron Bentley
Proper conflicts for removals
87
        self._removed_contents = set()
1534.7.25 by Aaron Bentley
Added set_executability
88
        self._new_executability = {}
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
89
        self._new_id = {}
1534.7.143 by Aaron Bentley
Prevented get_trans_id from automatically versioning file ids
90
        self._non_present_ids = {}
1534.7.75 by Aaron Bentley
Added reverse-lookup for versioned files and get_trans_id
91
        self._r_new_id = {}
1534.7.39 by Aaron Bentley
Ensured that files can be unversioned (de-versioned?)
92
        self._removed_id = set()
1534.7.7 by Aaron Bentley
Added support for all-file path ids
93
        self._tree_path_ids = {}
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
94
        self._tree_id_paths = {}
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
95
        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
96
        self.__done = False
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
97
1534.7.132 by Aaron Bentley
Got cooked conflicts working
98
    def __get_root(self):
99
        return self._new_root
100
101
    root = property(__get_root)
102
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
103
    def finalize(self):
1534.7.157 by Aaron Bentley
Added more docs
104
        """Release the working tree lock, if held, clean up limbo dir."""
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
105
        if self._tree is None:
106
            return
1534.7.162 by Aaron Bentley
Handle failures creating/deleting the Limbo directory
107
        try:
108
            for trans_id, kind in self._new_contents.iteritems():
109
                path = self._limbo_name(trans_id)
110
                if kind == "directory":
111
                    os.rmdir(path)
112
                else:
113
                    os.unlink(path)
114
            try:
115
                os.rmdir(self._limbodir)
116
            except OSError:
117
                # We don't especially care *why* the dir is immortal.
118
                raise ImmortalLimbo(self._limbodir)
119
        finally:
120
            self._tree.unlock()
121
            self._tree = None
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
122
123
    def _assign_id(self):
124
        """Produce a new tranform id"""
125
        new_id = "new-%s" % self._id_number
126
        self._id_number +=1
127
        return new_id
128
129
    def create_path(self, name, parent):
130
        """Assign a transaction id to a new path"""
131
        trans_id = self._assign_id()
132
        unique_add(self._new_name, trans_id, name)
133
        unique_add(self._new_parent, trans_id, parent)
134
        return trans_id
135
1534.7.6 by Aaron Bentley
Added conflict handling
136
    def adjust_path(self, name, parent, trans_id):
1534.7.21 by Aaron Bentley
Updated docstrings
137
        """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
138
        if trans_id == self._new_root:
139
            raise CantMoveRoot
1534.7.6 by Aaron Bentley
Added conflict handling
140
        self._new_name[trans_id] = name
141
        self._new_parent[trans_id] = parent
142
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
143
    def adjust_root_path(self, name, parent):
144
        """Emulate moving the root by moving all children, instead.
145
        
146
        We do this by undoing the association of root's transaction id with the
147
        current tree.  This allows us to create a new directory with that
1534.7.69 by Aaron Bentley
Got real root moves working
148
        transaction id.  We unversion the root directory and version the 
149
        physically new directory, and hope someone versions the tree root
150
        later.
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
151
        """
152
        old_root = self._new_root
153
        old_root_file_id = self.final_file_id(old_root)
154
        # force moving all children of root
155
        for child_id in self.iter_tree_children(old_root):
156
            if child_id != parent:
157
                self.adjust_path(self.final_name(child_id), 
158
                                 self.final_parent(child_id), child_id)
1534.7.69 by Aaron Bentley
Got real root moves working
159
            file_id = self.final_file_id(child_id)
160
            if file_id is not None:
161
                self.unversion_file(child_id)
162
            self.version_file(file_id, child_id)
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
163
        
164
        # the physical root needs a new transaction id
165
        self._tree_path_ids.pop("")
166
        self._tree_id_paths.pop(old_root)
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
167
        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
168
        if parent == old_root:
169
            parent = self._new_root
170
        self.adjust_path(name, parent, old_root)
171
        self.create_directory(old_root)
1534.7.69 by Aaron Bentley
Got real root moves working
172
        self.version_file(old_root_file_id, old_root)
173
        self.unversion_file(self._new_root)
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
174
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
175
    def trans_id_tree_file_id(self, inventory_id):
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
176
        """Determine the transaction id of a working tree file.
177
        
178
        This reflects only files that already exist, not ones that will be
179
        added by transactions.
180
        """
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
181
        path = self._tree.inventory.id2path(inventory_id)
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
182
        return self.trans_id_tree_path(path)
1534.7.7 by Aaron Bentley
Added support for all-file path ids
183
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
184
    def trans_id_file_id(self, file_id):
1534.7.156 by Aaron Bentley
PEP8 fixes
185
        """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
186
        A new id is only created for file_ids that were never present.  If
187
        a transaction has been unversioned, it is deliberately still returned.
188
        (this will likely lead to an unversioned parent conflict.)
189
        """
190
        if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
191
            return self._r_new_id[file_id]
192
        elif file_id in self._tree.inventory:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
193
            return self.trans_id_tree_file_id(file_id)
1534.7.143 by Aaron Bentley
Prevented get_trans_id from automatically versioning file ids
194
        elif file_id in self._non_present_ids:
195
            return self._non_present_ids[file_id]
1534.7.75 by Aaron Bentley
Added reverse-lookup for versioned files and get_trans_id
196
        else:
197
            trans_id = self._assign_id()
1534.7.143 by Aaron Bentley
Prevented get_trans_id from automatically versioning file ids
198
            self._non_present_ids[file_id] = trans_id
1534.7.75 by Aaron Bentley
Added reverse-lookup for versioned files and get_trans_id
199
            return trans_id
200
1534.7.12 by Aaron Bentley
Added canonical_path function
201
    def canonical_path(self, path):
202
        """Get the canonical tree-relative path"""
203
        # don't follow final symlinks
204
        dirname, basename = os.path.split(self._tree.abspath(path))
205
        dirname = os.path.realpath(dirname)
1534.7.166 by Aaron Bentley
Swapped os.path.join for pathjoin everywhere
206
        return self._tree.relpath(pathjoin(dirname, basename))
1534.7.12 by Aaron Bentley
Added canonical_path function
207
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
208
    def trans_id_tree_path(self, path):
1534.7.7 by Aaron Bentley
Added support for all-file path ids
209
        """Determine (and maybe set) the transaction ID for a tree path."""
1534.7.12 by Aaron Bentley
Added canonical_path function
210
        path = self.canonical_path(path)
1534.7.7 by Aaron Bentley
Added support for all-file path ids
211
        if path not in self._tree_path_ids:
212
            self._tree_path_ids[path] = self._assign_id()
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
213
            self._tree_id_paths[self._tree_path_ids[path]] = path
1534.7.7 by Aaron Bentley
Added support for all-file path ids
214
        return self._tree_path_ids[path]
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
215
1534.7.16 by Aaron Bentley
Added get_tree_parent
216
    def get_tree_parent(self, trans_id):
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
217
        """Determine id of the parent in the tree."""
1534.7.16 by Aaron Bentley
Added get_tree_parent
218
        path = self._tree_id_paths[trans_id]
219
        if path == "":
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
220
            return ROOT_PARENT
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
221
        return self.trans_id_tree_path(os.path.dirname(path))
1534.7.16 by Aaron Bentley
Added get_tree_parent
222
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
223
    def create_file(self, contents, trans_id, mode_id=None):
1534.7.21 by Aaron Bentley
Updated docstrings
224
        """Schedule creation of a new file.
225
226
        See also new_file.
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
227
        
228
        Contents is an iterator of strings, all of which will be written
1534.7.21 by Aaron Bentley
Updated docstrings
229
        to the target destination.
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
230
231
        New file takes the permissions of any existing file with that id,
232
        unless mode_id is specified.
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
233
        """
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
234
        f = file(self._limbo_name(trans_id), 'wb')
1534.8.1 by Aaron Bentley
Reference files in limbo before their creation is finished, for finalize.
235
        unique_add(self._new_contents, trans_id, 'file')
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
236
        for segment in contents:
237
            f.write(segment)
238
        f.close()
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
239
        self._set_mode(trans_id, mode_id, S_ISREG)
240
241
    def _set_mode(self, trans_id, mode_id, typefunc):
1534.7.157 by Aaron Bentley
Added more docs
242
        """Set the mode of new file contents.
243
        The mode_id is the existing file to get the mode from (often the same
244
        as trans_id).  The operation is only performed if there's a mode match
245
        according to typefunc.
246
        """
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
247
        if mode_id is None:
248
            mode_id = trans_id
249
        try:
250
            old_path = self._tree_id_paths[mode_id]
251
        except KeyError:
252
            return
253
        try:
254
            mode = os.stat(old_path).st_mode
255
        except OSError, e:
256
            if e.errno == errno.ENOENT:
257
                return
258
            else:
259
                raise
260
        if typefunc(mode):
261
            os.chmod(self._limbo_name(trans_id), mode)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
262
1534.7.20 by Aaron Bentley
Added directory handling
263
    def create_directory(self, trans_id):
1534.7.21 by Aaron Bentley
Updated docstrings
264
        """Schedule creation of a new directory.
265
        
266
        See also new_directory.
267
        """
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
268
        os.mkdir(self._limbo_name(trans_id))
269
        unique_add(self._new_contents, trans_id, 'directory')
1534.7.20 by Aaron Bentley
Added directory handling
270
1534.7.22 by Aaron Bentley
Added symlink support
271
    def create_symlink(self, target, trans_id):
272
        """Schedule creation of a new symbolic link.
273
274
        target is a bytestring.
275
        See also new_symlink.
276
        """
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
277
        os.symlink(target, self._limbo_name(trans_id))
278
        unique_add(self._new_contents, trans_id, 'symlink')
1534.7.22 by Aaron Bentley
Added symlink support
279
1534.7.129 by Aaron Bentley
Converted test cases to Tree Transform
280
    @staticmethod
281
    def delete_any(full_path):
1534.7.157 by Aaron Bentley
Added more docs
282
        """Delete a file or directory."""
1534.7.129 by Aaron Bentley
Converted test cases to Tree Transform
283
        try:
284
            os.unlink(full_path)
285
        except OSError, e:
286
        # We may be renaming a dangling inventory id
1185.50.85 by John Arbash Meinel
Mac OSX raises EPERM when you try to unlink a directory
287
            if e.errno not in (errno.EISDIR, errno.EACCES, errno.EPERM):
1534.7.129 by Aaron Bentley
Converted test cases to Tree Transform
288
                raise
289
            os.rmdir(full_path)
290
291
    def cancel_creation(self, trans_id):
1534.7.157 by Aaron Bentley
Added more docs
292
        """Cancel the creation of new file contents."""
1534.7.129 by Aaron Bentley
Converted test cases to Tree Transform
293
        del self._new_contents[trans_id]
294
        self.delete_any(self._limbo_name(trans_id))
295
1534.7.34 by Aaron Bentley
Proper conflicts for removals
296
    def delete_contents(self, trans_id):
297
        """Schedule the contents of a path entry for deletion"""
1534.7.130 by Aaron Bentley
More conflict handling, test porting
298
        self.tree_kind(trans_id)
1534.7.34 by Aaron Bentley
Proper conflicts for removals
299
        self._removed_contents.add(trans_id)
300
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
301
    def cancel_deletion(self, trans_id):
302
        """Cancel a scheduled deletion"""
303
        self._removed_contents.remove(trans_id)
304
1534.7.39 by Aaron Bentley
Ensured that files can be unversioned (de-versioned?)
305
    def unversion_file(self, trans_id):
306
        """Schedule a path entry to become unversioned"""
307
        self._removed_id.add(trans_id)
308
309
    def delete_versioned(self, trans_id):
310
        """Delete and unversion a versioned file"""
311
        self.delete_contents(trans_id)
312
        self.unversion_file(trans_id)
313
1534.7.25 by Aaron Bentley
Added set_executability
314
    def set_executability(self, executability, trans_id):
1534.7.167 by Aaron Bentley
PEP8 and comment cleanups
315
        """Schedule setting of the 'execute' bit
316
        To unschedule, set to None
317
        """
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
318
        if executability is None:
319
            del self._new_executability[trans_id]
320
        else:
321
            unique_add(self._new_executability, trans_id, executability)
1534.7.25 by Aaron Bentley
Added set_executability
322
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
323
    def version_file(self, file_id, trans_id):
1534.7.21 by Aaron Bentley
Updated docstrings
324
        """Schedule a file to become versioned."""
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
325
        assert file_id is not None
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
326
        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
327
        unique_add(self._r_new_id, file_id, trans_id)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
328
1534.7.105 by Aaron Bentley
Got merge with rename working
329
    def cancel_versioning(self, trans_id):
330
        """Undo a previous versioning of a file"""
331
        file_id = self._new_id[trans_id]
332
        del self._new_id[trans_id]
333
        del self._r_new_id[file_id]
334
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
335
    def new_paths(self):
1534.7.21 by Aaron Bentley
Updated docstrings
336
        """Determine the paths of all new and changed files"""
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
337
        new_ids = set()
1534.7.132 by Aaron Bentley
Got cooked conflicts working
338
        fp = FinalPaths(self)
1534.7.4 by Aaron Bentley
Unified all file types as 'contents'
339
        for id_set in (self._new_name, self._new_parent, self._new_contents,
1534.7.25 by Aaron Bentley
Added set_executability
340
                       self._new_id, self._new_executability):
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
341
            new_ids.update(id_set)
342
        new_paths = [(fp.get_path(t), t) for t in new_ids]
343
        new_paths.sort()
344
        return new_paths
1534.7.6 by Aaron Bentley
Added conflict handling
345
1534.7.34 by Aaron Bentley
Proper conflicts for removals
346
    def tree_kind(self, trans_id):
1534.7.40 by Aaron Bentley
Updated docs
347
        """Determine the file kind in the working tree.
348
349
        Raises NoSuchFile if the file does not exist
350
        """
1534.7.34 by Aaron Bentley
Proper conflicts for removals
351
        path = self._tree_id_paths.get(trans_id)
352
        if path is None:
353
            raise NoSuchFile(None)
354
        try:
355
            return file_kind(self._tree.abspath(path))
356
        except OSError, e:
357
            if e.errno != errno.ENOENT:
358
                raise
359
            else:
360
                raise NoSuchFile(path)
361
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
362
    def final_kind(self, trans_id):
1534.7.156 by Aaron Bentley
PEP8 fixes
363
        """Determine the final file kind, after any changes applied.
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
364
        
365
        Raises NoSuchFile if the file does not exist/has no contents.
366
        (It is conceivable that a path would be created without the
367
        corresponding contents insertion command)
368
        """
369
        if trans_id in self._new_contents:
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
370
            return self._new_contents[trans_id]
1534.7.34 by Aaron Bentley
Proper conflicts for removals
371
        elif trans_id in self._removed_contents:
372
            raise NoSuchFile(None)
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
373
        else:
1534.7.34 by Aaron Bentley
Proper conflicts for removals
374
            return self.tree_kind(trans_id)
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
375
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
376
    def tree_file_id(self, trans_id):
1534.7.41 by Aaron Bentley
Got inventory ID movement working
377
        """Determine the file id associated with the trans_id in the tree"""
378
        try:
379
            path = self._tree_id_paths[trans_id]
380
        except KeyError:
381
            # the file is a new, unversioned file, or invalid trans_id
382
            return None
383
        # the file is old; the old id is still valid
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
384
        if self._new_root == trans_id:
385
            return self._tree.inventory.root.file_id
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
386
        return self._tree.inventory.path2id(path)
1534.7.41 by Aaron Bentley
Got inventory ID movement working
387
1534.7.13 by Aaron Bentley
Implemented final_file_id
388
    def final_file_id(self, trans_id):
1534.7.156 by Aaron Bentley
PEP8 fixes
389
        """Determine the file id after any changes are applied, or None.
1534.7.21 by Aaron Bentley
Updated docstrings
390
        
391
        None indicates that the file will not be versioned after changes are
392
        applied.
393
        """
1534.7.13 by Aaron Bentley
Implemented final_file_id
394
        try:
395
            # there is a new id for this file
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
396
            assert self._new_id[trans_id] is not None
1534.7.13 by Aaron Bentley
Implemented final_file_id
397
            return self._new_id[trans_id]
398
        except KeyError:
1534.7.39 by Aaron Bentley
Ensured that files can be unversioned (de-versioned?)
399
            if trans_id in self._removed_id:
400
                return None
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
401
        return self.tree_file_id(trans_id)
1534.7.13 by Aaron Bentley
Implemented final_file_id
402
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
403
    def inactive_file_id(self, trans_id):
1534.7.157 by Aaron Bentley
Added more docs
404
        """Return the inactive file_id associated with a transaction id.
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
405
        That is, the one in the tree or in non_present_ids.
406
        The file_id may actually be active, too.
407
        """
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
408
        file_id = self.tree_file_id(trans_id)
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
409
        if file_id is not None:
410
            return file_id
411
        for key, value in self._non_present_ids.iteritems():
412
            if value == trans_id:
413
                return key
414
1534.7.17 by Aaron Bentley
Added final_parent function
415
    def final_parent(self, trans_id):
1534.7.156 by Aaron Bentley
PEP8 fixes
416
        """Determine the parent file_id, after any changes are applied.
1534.7.21 by Aaron Bentley
Updated docstrings
417
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
418
        ROOT_PARENT is returned for the tree root.
1534.7.21 by Aaron Bentley
Updated docstrings
419
        """
1534.7.17 by Aaron Bentley
Added final_parent function
420
        try:
421
            return self._new_parent[trans_id]
422
        except KeyError:
423
            return self.get_tree_parent(trans_id)
424
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
425
    def final_name(self, trans_id):
1534.7.40 by Aaron Bentley
Updated docs
426
        """Determine the final filename, after all changes are applied."""
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
427
        try:
428
            return self._new_name[trans_id]
429
        except KeyError:
430
            return os.path.basename(self._tree_id_paths[trans_id])
431
432
    def _by_parent(self):
1534.7.40 by Aaron Bentley
Updated docs
433
        """Return a map of parent: children for known parents.
434
        
435
        Only new paths and parents of tree files with assigned ids are used.
436
        """
1534.7.6 by Aaron Bentley
Added conflict handling
437
        by_parent = {}
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
438
        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.
439
        items.extend((t, self.final_parent(t)) for t in 
440
                      self._tree_id_paths.keys())
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
441
        for trans_id, parent_id in items:
1534.7.6 by Aaron Bentley
Added conflict handling
442
            if parent_id not in by_parent:
443
                by_parent[parent_id] = set()
444
            by_parent[parent_id].add(trans_id)
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
445
        return by_parent
1534.7.11 by Aaron Bentley
Refactored conflict handling
446
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
447
    def path_changed(self, trans_id):
1534.7.157 by Aaron Bentley
Added more docs
448
        """Return True if a trans_id's path has changed."""
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
449
        return trans_id in self._new_name or trans_id in self._new_parent
450
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
451
    def find_conflicts(self):
1534.7.40 by Aaron Bentley
Updated docs
452
        """Find any violations of inventory or filesystem invariants"""
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
453
        if self.__done is True:
454
            raise ReusingTransform()
455
        conflicts = []
456
        # ensure all children of all existent parents are known
457
        # all children of non-existent parents are known, by definition.
458
        self._add_tree_children()
459
        by_parent = self._by_parent()
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
460
        conflicts.extend(self._unversioned_parents(by_parent))
1534.7.19 by Aaron Bentley
Added tests for parent loops
461
        conflicts.extend(self._parent_loops())
1534.7.11 by Aaron Bentley
Refactored conflict handling
462
        conflicts.extend(self._duplicate_entries(by_parent))
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
463
        conflicts.extend(self._duplicate_ids())
1534.7.11 by Aaron Bentley
Refactored conflict handling
464
        conflicts.extend(self._parent_type_conflicts(by_parent))
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
465
        conflicts.extend(self._improper_versioning())
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
466
        conflicts.extend(self._executability_conflicts())
1534.7.152 by Aaron Bentley
Fixed overwrites
467
        conflicts.extend(self._overwrite_conflicts())
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
468
        return conflicts
469
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
470
    def _add_tree_children(self):
1534.7.156 by Aaron Bentley
PEP8 fixes
471
        """Add all the children of all active parents to the known paths.
1534.7.40 by Aaron Bentley
Updated docs
472
473
        Active parents are those which gain children, and those which are
474
        removed.  This is a necessary first step in detecting conflicts.
475
        """
1534.7.34 by Aaron Bentley
Proper conflicts for removals
476
        parents = self._by_parent().keys()
477
        parents.extend([t for t in self._removed_contents if 
478
                        self.tree_kind(t) == 'directory'])
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
479
        for trans_id in self._removed_id:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
480
            file_id = self.tree_file_id(trans_id)
1534.7.55 by Aaron Bentley
Fixed up the change detection
481
            if self._tree.inventory[file_id].kind in ('directory', 
482
                                                      'root_directory'):
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
483
                parents.append(trans_id)
484
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
485
        for parent_id in parents:
1534.7.67 by Aaron Bentley
Refactored _add_tree_children
486
            # ensure that all children are registered with the transaction
487
            list(self.iter_tree_children(parent_id))
488
489
    def iter_tree_children(self, parent_id):
490
        """Iterate through the entry's tree children, if any"""
491
        try:
492
            path = self._tree_id_paths[parent_id]
493
        except KeyError:
494
            return
495
        try:
496
            children = os.listdir(self._tree.abspath(path))
497
        except OSError, e:
1534.7.71 by abentley
All tests pass under Windows
498
            if e.errno != errno.ENOENT and e.errno != errno.ESRCH:
1534.7.67 by Aaron Bentley
Refactored _add_tree_children
499
                raise
500
            return
501
            
502
        for child in children:
503
            childpath = joinpath(path, child)
1534.7.180 by Aaron Bentley
Merge from mainline
504
            if self._tree.is_control_filename(childpath):
1534.7.67 by Aaron Bentley
Refactored _add_tree_children
505
                continue
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
506
            yield self.trans_id_tree_path(childpath)
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
507
1534.7.19 by Aaron Bentley
Added tests for parent loops
508
    def _parent_loops(self):
509
        """No entry should be its own ancestor"""
510
        conflicts = []
511
        for trans_id in self._new_parent:
512
            seen = set()
513
            parent_id = trans_id
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
514
            while parent_id is not ROOT_PARENT:
1534.7.19 by Aaron Bentley
Added tests for parent loops
515
                seen.add(parent_id)
516
                parent_id = self.final_parent(parent_id)
517
                if parent_id == trans_id:
518
                    conflicts.append(('parent loop', trans_id))
519
                if parent_id in seen:
520
                    break
521
        return conflicts
522
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
523
    def _unversioned_parents(self, by_parent):
524
        """If parent directories are versioned, children must be versioned."""
525
        conflicts = []
526
        for parent_id, children in by_parent.iteritems():
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
527
            if parent_id is ROOT_PARENT:
528
                continue
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
529
            if self.final_file_id(parent_id) is not None:
530
                continue
531
            for child_id in children:
532
                if self.final_file_id(child_id) is not None:
533
                    conflicts.append(('unversioned parent', parent_id))
534
                    break;
535
        return conflicts
536
537
    def _improper_versioning(self):
1534.7.156 by Aaron Bentley
PEP8 fixes
538
        """Cannot version a file with no contents, or a bad type.
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
539
        
540
        However, existing entries with no contents are okay.
541
        """
542
        conflicts = []
543
        for trans_id in self._new_id.iterkeys():
544
            try:
545
                kind = self.final_kind(trans_id)
546
            except NoSuchFile:
547
                conflicts.append(('versioning no contents', trans_id))
548
                continue
549
            if not InventoryEntry.versionable_kind(kind):
1534.7.20 by Aaron Bentley
Added directory handling
550
                conflicts.append(('versioning bad kind', trans_id, kind))
1534.7.11 by Aaron Bentley
Refactored conflict handling
551
        return conflicts
552
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
553
    def _executability_conflicts(self):
1534.7.40 by Aaron Bentley
Updated docs
554
        """Check for bad executability changes.
555
        
556
        Only versioned files may have their executability set, because
557
        1. only versioned entries can have executability under windows
558
        2. only files can be executable.  (The execute bit on a directory
559
           does not indicate searchability)
560
        """
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
561
        conflicts = []
562
        for trans_id in self._new_executability:
563
            if self.final_file_id(trans_id) is None:
564
                conflicts.append(('unversioned executability', trans_id))
1534.7.34 by Aaron Bentley
Proper conflicts for removals
565
            else:
566
                try:
567
                    non_file = self.final_kind(trans_id) != "file"
568
                except NoSuchFile:
569
                    non_file = True
570
                if non_file is True:
571
                    conflicts.append(('non-file executability', trans_id))
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
572
        return conflicts
573
1534.7.152 by Aaron Bentley
Fixed overwrites
574
    def _overwrite_conflicts(self):
575
        """Check for overwrites (not permitted on Win32)"""
576
        conflicts = []
577
        for trans_id in self._new_contents:
578
            try:
579
                self.tree_kind(trans_id)
580
            except NoSuchFile:
581
                continue
582
            if trans_id not in self._removed_contents:
583
                conflicts.append(('overwrite', trans_id,
584
                                 self.final_name(trans_id)))
585
        return conflicts
586
1534.7.11 by Aaron Bentley
Refactored conflict handling
587
    def _duplicate_entries(self, by_parent):
588
        """No directory may have two entries with the same name."""
589
        conflicts = []
1534.7.6 by Aaron Bentley
Added conflict handling
590
        for children in by_parent.itervalues():
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
591
            name_ids = [(self.final_name(t), t) for t in children]
1534.7.6 by Aaron Bentley
Added conflict handling
592
            name_ids.sort()
593
            last_name = None
594
            last_trans_id = None
595
            for name, trans_id in name_ids:
596
                if name == last_name:
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
597
                    conflicts.append(('duplicate', last_trans_id, trans_id,
598
                    name))
1534.7.6 by Aaron Bentley
Added conflict handling
599
                last_name = name
600
                last_trans_id = trans_id
1534.7.11 by Aaron Bentley
Refactored conflict handling
601
        return conflicts
602
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
603
    def _duplicate_ids(self):
604
        """Each inventory id may only be used once"""
605
        conflicts = []
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
606
        removed_tree_ids = set((self.tree_file_id(trans_id) for trans_id in
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
607
                                self._removed_id))
608
        active_tree_ids = set((f for f in self._tree.inventory if
609
                               f not in removed_tree_ids))
610
        for trans_id, file_id in self._new_id.iteritems():
611
            if file_id in active_tree_ids:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
612
                old_trans_id = self.trans_id_tree_file_id(file_id)
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
613
                conflicts.append(('duplicate id', old_trans_id, trans_id))
614
        return conflicts
615
1534.7.11 by Aaron Bentley
Refactored conflict handling
616
    def _parent_type_conflicts(self, by_parent):
617
        """parents must have directory 'contents'."""
618
        conflicts = []
1534.7.37 by Aaron Bentley
Allowed removed dirs to have content-free children.
619
        for parent_id, children in by_parent.iteritems():
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
620
            if parent_id is ROOT_PARENT:
621
                continue
1534.7.37 by Aaron Bentley
Allowed removed dirs to have content-free children.
622
            if not self._any_contents(children):
623
                continue
624
            for child in children:
625
                try:
626
                    self.final_kind(child)
627
                except NoSuchFile:
628
                    continue
1534.7.10 by Aaron Bentley
Implemented missing parent and non-directory parent conflicts
629
            try:
630
                kind = self.final_kind(parent_id)
631
            except NoSuchFile:
632
                kind = None
633
            if kind is None:
634
                conflicts.append(('missing parent', parent_id))
635
            elif kind != "directory":
636
                conflicts.append(('non-directory parent', parent_id))
1534.7.6 by Aaron Bentley
Added conflict handling
637
        return conflicts
1534.7.37 by Aaron Bentley
Allowed removed dirs to have content-free children.
638
639
    def _any_contents(self, trans_ids):
640
        """Return true if any of the trans_ids, will have contents."""
641
        for trans_id in trans_ids:
642
            try:
643
                kind = self.final_kind(trans_id)
644
            except NoSuchFile:
645
                continue
646
            return True
647
        return False
1534.7.6 by Aaron Bentley
Added conflict handling
648
            
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
649
    def apply(self):
1534.7.156 by Aaron Bentley
PEP8 fixes
650
        """Apply all changes to the inventory and filesystem.
1534.7.21 by Aaron Bentley
Updated docstrings
651
        
652
        If filesystem or inventory conflicts are present, MalformedTransform
653
        will be thrown.
654
        """
1534.7.49 by Aaron Bentley
Printed conflicts in MalformedTransform
655
        conflicts = self.find_conflicts()
656
        if len(conflicts) != 0:
657
            raise MalformedTransform(conflicts=conflicts)
1534.7.41 by Aaron Bentley
Got inventory ID movement working
658
        limbo_inv = {}
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
659
        inv = self._tree.inventory
1534.7.41 by Aaron Bentley
Got inventory ID movement working
660
        self._apply_removals(inv, limbo_inv)
661
        self._apply_insertions(inv, limbo_inv)
1534.7.35 by Aaron Bentley
Got file renaming working
662
        self._tree._write_inventory(inv)
663
        self.__done = True
1534.7.59 by Aaron Bentley
Simplified tests
664
        self.finalize()
1534.7.35 by Aaron Bentley
Got file renaming working
665
1534.7.72 by Aaron Bentley
Moved new content generation to pre-renames
666
    def _limbo_name(self, trans_id):
667
        """Generate the limbo name of a file"""
1534.7.166 by Aaron Bentley
Swapped os.path.join for pathjoin everywhere
668
        return pathjoin(self._limbodir, trans_id)
1534.7.72 by Aaron Bentley
Moved new content generation to pre-renames
669
1534.7.41 by Aaron Bentley
Got inventory ID movement working
670
    def _apply_removals(self, inv, limbo_inv):
1534.7.36 by Aaron Bentley
Added rename tests
671
        """Perform tree operations that remove directory/inventory names.
672
        
673
        That is, delete files that are to be deleted, and put any files that
674
        need renaming into limbo.  This must be done in strict child-to-parent
675
        order.
676
        """
1534.7.35 by Aaron Bentley
Got file renaming working
677
        tree_paths = list(self._tree_path_ids.iteritems())
678
        tree_paths.sort(reverse=True)
679
        for path, trans_id in tree_paths:
1534.7.43 by abentley
Fixed some Windows bugs, introduced a conflicts bug
680
            full_path = self._tree.abspath(path)
1534.7.34 by Aaron Bentley
Proper conflicts for removals
681
            if trans_id in self._removed_contents:
1534.7.129 by Aaron Bentley
Converted test cases to Tree Transform
682
                self.delete_any(full_path)
1534.7.35 by Aaron Bentley
Got file renaming working
683
            elif trans_id in self._new_name or trans_id in self._new_parent:
1534.7.48 by Aaron Bentley
Ensured we can move/rename dangling inventory entries
684
                try:
1534.7.118 by Aaron Bentley
Dirty merge of the mainline
685
                    os.rename(full_path, self._limbo_name(trans_id))
1534.7.48 by Aaron Bentley
Ensured we can move/rename dangling inventory entries
686
                except OSError, e:
687
                    if e.errno != errno.ENOENT:
688
                        raise
1534.7.39 by Aaron Bentley
Ensured that files can be unversioned (de-versioned?)
689
            if trans_id in self._removed_id:
1534.7.69 by Aaron Bentley
Got real root moves working
690
                if trans_id == self._new_root:
691
                    file_id = self._tree.inventory.root.file_id
692
                else:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
693
                    file_id = self.tree_file_id(trans_id)
1534.7.69 by Aaron Bentley
Got real root moves working
694
                del inv[file_id]
1534.7.41 by Aaron Bentley
Got inventory ID movement working
695
            elif trans_id in self._new_name or trans_id in self._new_parent:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
696
                file_id = self.tree_file_id(trans_id)
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
697
                if file_id is not None:
698
                    limbo_inv[trans_id] = inv[file_id]
699
                    del inv[file_id]
1534.7.34 by Aaron Bentley
Proper conflicts for removals
700
1534.7.41 by Aaron Bentley
Got inventory ID movement working
701
    def _apply_insertions(self, inv, limbo_inv):
1534.7.36 by Aaron Bentley
Added rename tests
702
        """Perform tree operations that insert directory/inventory names.
703
        
704
        That is, create any files that need to be created, and restore from
705
        limbo any files that needed renaming.  This must be done in strict
706
        parent-to-child order.
707
        """
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
708
        for path, trans_id in self.new_paths():
1534.7.4 by Aaron Bentley
Unified all file types as 'contents'
709
            try:
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
710
                kind = self._new_contents[trans_id]
1534.7.4 by Aaron Bentley
Unified all file types as 'contents'
711
            except KeyError:
712
                kind = contents = None
1534.7.72 by Aaron Bentley
Moved new content generation to pre-renames
713
            if trans_id in self._new_contents or self.path_changed(trans_id):
1534.7.48 by Aaron Bentley
Ensured we can move/rename dangling inventory entries
714
                full_path = self._tree.abspath(path)
715
                try:
1534.7.72 by Aaron Bentley
Moved new content generation to pre-renames
716
                    os.rename(self._limbo_name(trans_id), full_path)
1534.7.48 by Aaron Bentley
Ensured we can move/rename dangling inventory entries
717
                except OSError, e:
718
                    # We may be renaming a dangling inventory id
719
                    if e.errno != errno.ENOENT:
720
                        raise
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
721
                if trans_id in self._new_contents:
722
                    del self._new_contents[trans_id]
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
723
724
            if trans_id in self._new_id:
725
                if kind is None:
1534.7.14 by Aaron Bentley
Fixed file_kind call
726
                    kind = file_kind(self._tree.abspath(path))
1534.7.149 by Aaron Bentley
Removed bare except
727
                inv.add_path(path, kind, self._new_id[trans_id])
1534.7.41 by Aaron Bentley
Got inventory ID movement working
728
            elif trans_id in self._new_name or trans_id in self._new_parent:
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
729
                entry = limbo_inv.get(trans_id)
730
                if entry is not None:
731
                    entry.name = self.final_name(trans_id)
1534.7.150 by Aaron Bentley
Handled simultaneous renames of parent and child better
732
                    parent_path = os.path.dirname(path)
733
                    entry.parent_id = self._tree.inventory.path2id(parent_path)
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
734
                    inv.add(entry)
1534.7.41 by Aaron Bentley
Got inventory ID movement working
735
1534.7.25 by Aaron Bentley
Added set_executability
736
            # requires files and inventory entries to be in place
737
            if trans_id in self._new_executability:
738
                self._set_executability(path, inv, trans_id)
1534.7.40 by Aaron Bentley
Updated docs
739
1534.7.25 by Aaron Bentley
Added set_executability
740
    def _set_executability(self, path, inv, trans_id):
1534.7.40 by Aaron Bentley
Updated docs
741
        """Set the executability of versioned files """
1534.7.25 by Aaron Bentley
Added set_executability
742
        file_id = inv.path2id(path)
743
        new_executability = self._new_executability[trans_id]
744
        inv[file_id].executable = new_executability
745
        if supports_executable():
746
            abspath = self._tree.abspath(path)
747
            current_mode = os.stat(abspath).st_mode
748
            if new_executability:
749
                umask = os.umask(0)
750
                os.umask(umask)
751
                to_mode = current_mode | (0100 & ~umask)
752
                # Enable x-bit for others only if they can read it.
753
                if current_mode & 0004:
754
                    to_mode |= 0001 & ~umask
755
                if current_mode & 0040:
756
                    to_mode |= 0010 & ~umask
757
            else:
758
                to_mode = current_mode & ~0111
759
            os.chmod(abspath, to_mode)
760
1534.7.23 by Aaron Bentley
Transform.new_entry -> Transform._new_entry
761
    def _new_entry(self, name, parent_id, file_id):
1534.7.21 by Aaron Bentley
Updated docstrings
762
        """Helper function to create a new filesystem entry."""
1534.7.2 by Aaron Bentley
Added convenience function
763
        trans_id = self.create_path(name, parent_id)
764
        if file_id is not None:
765
            self.version_file(file_id, trans_id)
766
        return trans_id
767
1534.7.27 by Aaron Bentley
Added execute bit to new_file method
768
    def new_file(self, name, parent_id, contents, file_id=None, 
769
                 executable=None):
1534.7.156 by Aaron Bentley
PEP8 fixes
770
        """Convenience method to create files.
1534.7.21 by Aaron Bentley
Updated docstrings
771
        
772
        name is the name of the file to create.
773
        parent_id is the transaction id of the parent directory of the file.
774
        contents is an iterator of bytestrings, which will be used to produce
775
        the file.
776
        file_id is the inventory ID of the file, if it is to be versioned.
777
        """
1534.7.23 by Aaron Bentley
Transform.new_entry -> Transform._new_entry
778
        trans_id = self._new_entry(name, parent_id, file_id)
1534.7.20 by Aaron Bentley
Added directory handling
779
        self.create_file(contents, trans_id)
1534.7.27 by Aaron Bentley
Added execute bit to new_file method
780
        if executable is not None:
781
            self.set_executability(executable, trans_id)
1534.7.20 by Aaron Bentley
Added directory handling
782
        return trans_id
783
784
    def new_directory(self, name, parent_id, file_id=None):
1534.7.156 by Aaron Bentley
PEP8 fixes
785
        """Convenience method to create directories.
1534.7.21 by Aaron Bentley
Updated docstrings
786
787
        name is the name of the directory to create.
788
        parent_id is the transaction id of the parent directory of the
789
        directory.
790
        file_id is the inventory ID of the directory, if it is to be versioned.
791
        """
1534.7.23 by Aaron Bentley
Transform.new_entry -> Transform._new_entry
792
        trans_id = self._new_entry(name, parent_id, file_id)
1534.7.20 by Aaron Bentley
Added directory handling
793
        self.create_directory(trans_id)
794
        return trans_id 
795
1534.7.22 by Aaron Bentley
Added symlink support
796
    def new_symlink(self, name, parent_id, target, file_id=None):
1534.7.156 by Aaron Bentley
PEP8 fixes
797
        """Convenience method to create symbolic link.
1534.7.22 by Aaron Bentley
Added symlink support
798
        
799
        name is the name of the symlink to create.
800
        parent_id is the transaction id of the parent directory of the symlink.
801
        target is a bytestring of the target of the symlink.
802
        file_id is the inventory ID of the file, if it is to be versioned.
803
        """
1534.7.23 by Aaron Bentley
Transform.new_entry -> Transform._new_entry
804
        trans_id = self._new_entry(name, parent_id, file_id)
1534.7.22 by Aaron Bentley
Added symlink support
805
        self.create_symlink(target, trans_id)
806
        return trans_id
807
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
808
def joinpath(parent, child):
1534.7.40 by Aaron Bentley
Updated docs
809
    """Join tree-relative paths, handling the tree root specially"""
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
810
    if parent is None or parent == "":
811
        return child
812
    else:
1534.7.166 by Aaron Bentley
Swapped os.path.join for pathjoin everywhere
813
        return pathjoin(parent, child)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
814
1534.7.167 by Aaron Bentley
PEP8 and comment cleanups
815
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
816
class FinalPaths(object):
1534.7.156 by Aaron Bentley
PEP8 fixes
817
    """Make path calculation cheap by memoizing paths.
1534.7.21 by Aaron Bentley
Updated docstrings
818
819
    The underlying tree must not be manipulated between calls, or else
820
    the results will likely be incorrect.
821
    """
1534.7.132 by Aaron Bentley
Got cooked conflicts working
822
    def __init__(self, transform):
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
823
        object.__init__(self)
824
        self._known_paths = {}
1534.7.33 by Aaron Bentley
Fixed naming
825
        self.transform = transform
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
826
827
    def _determine_path(self, trans_id):
1534.7.132 by Aaron Bentley
Got cooked conflicts working
828
        if trans_id == self.transform.root:
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
829
            return ""
1534.7.33 by Aaron Bentley
Fixed naming
830
        name = self.transform.final_name(trans_id)
831
        parent_id = self.transform.final_parent(trans_id)
1534.7.132 by Aaron Bentley
Got cooked conflicts working
832
        if parent_id == self.transform.root:
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
833
            return name
834
        else:
1534.7.166 by Aaron Bentley
Swapped os.path.join for pathjoin everywhere
835
            return pathjoin(self.get_path(parent_id), name)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
836
837
    def get_path(self, trans_id):
1534.7.157 by Aaron Bentley
Added more docs
838
        """Find the final path associated with a trans_id"""
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
839
        if trans_id not in self._known_paths:
840
            self._known_paths[trans_id] = self._determine_path(trans_id)
841
        return self._known_paths[trans_id]
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
842
1534.7.30 by Aaron Bentley
Factored out topological id sorting
843
def topology_sorted_ids(tree):
1534.7.40 by Aaron Bentley
Updated docs
844
    """Determine the topological order of the ids in a tree"""
1534.7.30 by Aaron Bentley
Factored out topological id sorting
845
    file_ids = list(tree)
846
    file_ids.sort(key=tree.id2path)
847
    return file_ids
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
848
1534.7.165 by Aaron Bentley
Switched to build_tree instead of revert
849
def build_tree(tree, wt):
1534.7.40 by Aaron Bentley
Updated docs
850
    """Create working tree for a branch, using a Transaction."""
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
851
    file_trans_id = {}
852
    tt = TreeTransform(wt)
853
    try:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
854
        file_trans_id[wt.get_root_id()] = tt.trans_id_tree_file_id(wt.get_root_id())
1534.7.30 by Aaron Bentley
Factored out topological id sorting
855
        file_ids = topology_sorted_ids(tree)
1534.7.29 by Aaron Bentley
Got build passing all tests
856
        for file_id in file_ids:
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
857
            entry = tree.inventory[file_id]
858
            if entry.parent_id is None:
859
                continue
860
            if entry.parent_id not in file_trans_id:
861
                raise repr(entry.parent_id)
862
            parent_id = file_trans_id[entry.parent_id]
1534.7.47 by Aaron Bentley
Started work on 'revert'
863
            file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, tree)
864
        tt.apply()
865
    finally:
866
        tt.finalize()
867
868
def new_by_entry(tt, entry, parent_id, tree):
1534.7.157 by Aaron Bentley
Added more docs
869
    """Create a new file according to its inventory entry"""
1534.7.47 by Aaron Bentley
Started work on 'revert'
870
    name = entry.name
871
    kind = entry.kind
872
    if kind == 'file':
1534.7.79 by Aaron Bentley
Stopped calling get_file_lines on WorkingTree
873
        contents = tree.get_file(entry.file_id).readlines()
1534.7.47 by Aaron Bentley
Started work on 'revert'
874
        executable = tree.is_executable(entry.file_id)
875
        return tt.new_file(name, parent_id, contents, entry.file_id, 
876
                           executable)
877
    elif kind == 'directory':
1534.7.54 by Aaron Bentley
Fixed thinko
878
        return tt.new_directory(name, parent_id, entry.file_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
879
    elif kind == 'symlink':
880
        target = entry.get_symlink_target(file_id)
881
        return tt.new_symlink(name, parent_id, target, file_id)
882
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
883
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1534.7.157 by Aaron Bentley
Added more docs
884
    """Create new file contents according to an inventory entry."""
1534.7.47 by Aaron Bentley
Started work on 'revert'
885
    if entry.kind == "file":
1534.7.97 by Aaron Bentley
Ensured foo.BASE is a directory if there's a conflict
886
        if lines == None:
887
            lines = tree.get_file(entry.file_id).readlines()
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
888
        tt.create_file(lines, trans_id, mode_id=mode_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
889
    elif entry.kind == "symlink":
1534.7.101 by Aaron Bentley
Got conflicts on symlinks working properly
890
        tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
891
    elif entry.kind == "directory":
1534.7.51 by Aaron Bentley
New approach to revert
892
        tt.create_directory(trans_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
893
1534.7.89 by Aaron Bentley
Handle all content types in three-way
894
def create_entry_executability(tt, entry, trans_id):
1534.7.157 by Aaron Bentley
Added more docs
895
    """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
896
    if entry.kind == "file":
897
        tt.set_executability(entry.executable, trans_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
898
1534.7.157 by Aaron Bentley
Added more docs
899
1534.7.55 by Aaron Bentley
Fixed up the change detection
900
def find_interesting(working_tree, target_tree, filenames):
1534.7.157 by Aaron Bentley
Added more docs
901
    """Find the ids corresponding to specified filenames."""
1534.7.55 by Aaron Bentley
Fixed up the change detection
902
    if not filenames:
903
        interesting_ids = None
904
    else:
905
        interesting_ids = set()
1534.7.118 by Aaron Bentley
Dirty merge of the mainline
906
        for tree_path in filenames:
1534.7.55 by Aaron Bentley
Fixed up the change detection
907
            for tree in (working_tree, target_tree):
908
                not_found = True
909
                file_id = tree.inventory.path2id(tree_path)
910
                if file_id is not None:
911
                    interesting_ids.add(file_id)
912
                    not_found = False
913
                if not_found:
1534.7.123 by Aaron Bentley
Fixed handling of unversioned files
914
                    raise NotVersionedError(path=tree_path)
1534.7.55 by Aaron Bentley
Fixed up the change detection
915
    return interesting_ids
916
917
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
918
def change_entry(tt, file_id, working_tree, target_tree, 
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
919
                 trans_id_file_id, backups, trans_id):
1534.7.157 by Aaron Bentley
Added more docs
920
    """Replace a file_id's contents with those from a target tree."""
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
921
    e_trans_id = trans_id_file_id(file_id)
1534.7.55 by Aaron Bentley
Fixed up the change detection
922
    entry = target_tree.inventory[file_id]
923
    has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry, 
924
                                                           working_tree)
925
    if contents_mod:
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
926
        mode_id = e_trans_id
1534.7.55 by Aaron Bentley
Fixed up the change detection
927
        if has_contents:
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
928
            if not backups:
929
                tt.delete_contents(e_trans_id)
930
            else:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
931
                parent_trans_id = trans_id_file_id(entry.parent_id)
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
932
                tt.adjust_path(entry.name+"~", parent_trans_id, e_trans_id)
933
                tt.unversion_file(e_trans_id)
934
                e_trans_id = tt.create_path(entry.name, parent_trans_id)
935
                tt.version_file(file_id, e_trans_id)
936
                trans_id[file_id] = e_trans_id
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
937
        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
938
        create_entry_executability(tt, entry, e_trans_id)
939
1534.7.55 by Aaron Bentley
Fixed up the change detection
940
    elif meta_mod:
1534.7.58 by abentley
Fixed executability bug
941
        tt.set_executability(entry.executable, e_trans_id)
1534.7.55 by Aaron Bentley
Fixed up the change detection
942
    if tt.final_name(e_trans_id) != entry.name:
943
        adjust_path  = True
944
    else:
945
        parent_id = tt.final_parent(e_trans_id)
946
        parent_file_id = tt.final_file_id(parent_id)
947
        if parent_file_id != entry.parent_id:
948
            adjust_path = True
949
        else:
950
            adjust_path = False
951
    if adjust_path:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
952
        parent_trans_id = trans_id_file_id(entry.parent_id)
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
953
        tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
1534.7.55 by Aaron Bentley
Fixed up the change detection
954
955
956
def _entry_changes(file_id, entry, working_tree):
1534.7.156 by Aaron Bentley
PEP8 fixes
957
    """Determine in which ways the inventory entry has changed.
1534.7.55 by Aaron Bentley
Fixed up the change detection
958
959
    Returns booleans: has_contents, content_mod, meta_mod
960
    has_contents means there are currently contents, but they differ
961
    contents_mod means contents need to be modified
962
    meta_mod means the metadata needs to be modified
963
    """
964
    cur_entry = working_tree.inventory[file_id]
965
    try:
966
        working_kind = working_tree.kind(file_id)
967
        has_contents = True
968
    except OSError, e:
969
        if e.errno != errno.ENOENT:
970
            raise
971
        has_contents = False
972
        contents_mod = True
973
        meta_mod = False
974
    if has_contents is True:
975
        real_e_kind = entry.kind
976
        if real_e_kind == 'root_directory':
977
            real_e_kind = 'directory'
978
        if real_e_kind != working_kind:
979
            contents_mod, meta_mod = True, False
980
        else:
981
            cur_entry._read_tree_state(working_tree.id2path(file_id), 
982
                                       working_tree)
983
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
1534.7.175 by Aaron Bentley
Ensured revert writes a normal inventory
984
            cur_entry._forget_tree_state()
1534.7.55 by Aaron Bentley
Fixed up the change detection
985
    return has_contents, contents_mod, meta_mod
986
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
987
988
def revert(working_tree, target_tree, filenames, backups=False):
1534.7.157 by Aaron Bentley
Added more docs
989
    """Revert a working tree's contents to those of a target tree."""
1534.7.55 by Aaron Bentley
Fixed up the change detection
990
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
991
    def interesting(file_id):
992
        return interesting_ids is None or file_id in interesting_ids
993
1534.7.47 by Aaron Bentley
Started work on 'revert'
994
    tt = TreeTransform(working_tree)
995
    try:
1534.7.51 by Aaron Bentley
New approach to revert
996
        trans_id = {}
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
997
        def trans_id_file_id(file_id):
1534.7.47 by Aaron Bentley
Started work on 'revert'
998
            try:
1534.7.51 by Aaron Bentley
New approach to revert
999
                return trans_id[file_id]
1534.7.47 by Aaron Bentley
Started work on 'revert'
1000
            except KeyError:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
1001
                return tt.trans_id_tree_file_id(file_id)
1534.7.51 by Aaron Bentley
New approach to revert
1002
1003
        for file_id in topology_sorted_ids(target_tree):
1534.7.55 by Aaron Bentley
Fixed up the change detection
1004
            if not interesting(file_id):
1534.7.51 by Aaron Bentley
New approach to revert
1005
                continue
1534.7.52 by Aaron Bentley
Revert fixes with disappearing roots
1006
            if file_id not in working_tree.inventory:
1534.7.51 by Aaron Bentley
New approach to revert
1007
                entry = target_tree.inventory[file_id]
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
1008
                parent_id = trans_id_file_id(entry.parent_id)
1534.7.51 by Aaron Bentley
New approach to revert
1009
                e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
1010
                trans_id[file_id] = e_trans_id
1534.7.47 by Aaron Bentley
Started work on 'revert'
1011
            else:
1534.7.55 by Aaron Bentley
Fixed up the change detection
1012
                change_entry(tt, file_id, working_tree, target_tree, 
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
1013
                             trans_id_file_id, backups, trans_id)
1534.7.178 by Aaron Bentley
Fixed dangling inventory ids in revert
1014
        for file_id in working_tree.inventory:
1534.7.55 by Aaron Bentley
Fixed up the change detection
1015
            if not interesting(file_id):
1016
                continue
1017
            if file_id not in target_tree:
1534.7.181 by Aaron Bentley
Renamed a bunch of functions
1018
                tt.unversion_file(tt.trans_id_tree_file_id(file_id))
1534.7.173 by Aaron Bentley
Added conflict warnings to revert
1019
        raw_conflicts = resolve_conflicts(tt)
1020
        for line in conflicts_strings(cook_conflicts(raw_conflicts, tt)):
1021
            warning(line)
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
1022
        tt.apply()
1023
    finally:
1024
        tt.finalize()
1534.7.51 by Aaron Bentley
New approach to revert
1025
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
1026
1534.7.51 by Aaron Bentley
New approach to revert
1027
def resolve_conflicts(tt):
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
1028
    """Make many conflict-resolution attempts, but die if they fail"""
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1029
    new_conflicts = set()
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
1030
    for n in range(10):
1031
        conflicts = tt.find_conflicts()
1032
        if len(conflicts) == 0:
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1033
            return new_conflicts
1034
        new_conflicts.update(conflict_pass(tt, conflicts))
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
1035
    raise MalformedTransform(conflicts=conflicts)
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
1036
1037
1038
def conflict_pass(tt, conflicts):
1534.7.157 by Aaron Bentley
Added more docs
1039
    """Resolve some classes of conflicts."""
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1040
    new_conflicts = set()
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
1041
    for c_type, conflict in ((c[0], c) for c in conflicts):
1042
        if c_type == 'duplicate id':
1534.7.51 by Aaron Bentley
New approach to revert
1043
            tt.unversion_file(conflict[1])
1534.7.170 by Aaron Bentley
Cleaned up filesystem conflict handling
1044
            new_conflicts.add((c_type, 'Unversioned existing file',
1045
                               conflict[1], conflict[2], ))
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
1046
        elif c_type == 'duplicate':
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
1047
            # files that were renamed take precedence
1048
            new_name = tt.final_name(conflict[1])+'.moved'
1049
            final_parent = tt.final_parent(conflict[1])
1050
            if tt.path_changed(conflict[1]):
1051
                tt.adjust_path(new_name, final_parent, conflict[2])
1534.7.171 by Aaron Bentley
Implemented stringifying filesystem conflicts
1052
                new_conflicts.add((c_type, 'Moved existing file to', 
1053
                                   conflict[2], conflict[1]))
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
1054
            else:
1055
                tt.adjust_path(new_name, final_parent, conflict[1])
1534.7.171 by Aaron Bentley
Implemented stringifying filesystem conflicts
1056
                new_conflicts.add((c_type, 'Moved existing file to', 
1057
                                  conflict[1], conflict[2]))
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
1058
        elif c_type == 'parent loop':
1059
            # break the loop by undoing one of the ops that caused the loop
1060
            cur = conflict[1]
1061
            while not tt.path_changed(cur):
1062
                cur = tt.final_parent(cur)
1534.7.170 by Aaron Bentley
Cleaned up filesystem conflict handling
1063
            new_conflicts.add((c_type, 'Cancelled move', cur,
1064
                               tt.final_parent(cur),))
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
1065
            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
1066
            
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
1067
        elif c_type == 'missing parent':
1534.7.128 by Aaron Bentley
Got missing contents test working
1068
            trans_id = conflict[1]
1069
            try:
1070
                tt.cancel_deletion(trans_id)
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1071
                new_conflicts.add((c_type, 'Not deleting', trans_id))
1534.7.128 by Aaron Bentley
Got missing contents test working
1072
            except KeyError:
1073
                tt.create_directory(trans_id)
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1074
                new_conflicts.add((c_type, 'Created directory.', trans_id))
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
1075
        elif c_type == 'unversioned parent':
1534.7.148 by Aaron Bentley
Handled the remaining file versioning case
1076
            tt.version_file(tt.inactive_file_id(conflict[1]), conflict[1])
1534.7.171 by Aaron Bentley
Implemented stringifying filesystem conflicts
1077
            new_conflicts.add((c_type, 'Versioned directory', conflict[1]))
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1078
    return new_conflicts
1079
1080
def cook_conflicts(raw_conflicts, tt):
1534.7.170 by Aaron Bentley
Cleaned up filesystem conflict handling
1081
    """Generate a list of cooked conflicts, sorted by file path"""
1082
    def key(conflict):
1083
        if conflict[2] is not None:
1084
            return conflict[2], conflict[0]
1085
        elif len(conflict) == 6:
1086
            return conflict[4], conflict[0]
1087
        else:
1088
            return None, conflict[0]
1089
1090
    return sorted(list(iter_cook_conflicts(raw_conflicts, tt)), key=key)
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1091
1092
def iter_cook_conflicts(raw_conflicts, tt):
1093
    cooked_conflicts = []
1534.7.170 by Aaron Bentley
Cleaned up filesystem conflict handling
1094
    fp = FinalPaths(tt)
1534.7.169 by Aaron Bentley
Add filesystem/inventory conflicts to conflict output
1095
    for conflict in raw_conflicts:
1096
        c_type = conflict[0]
1534.7.170 by Aaron Bentley
Cleaned up filesystem conflict handling
1097
        action = conflict[1]
1098
        modified_path = fp.get_path(conflict[2])
1099
        modified_id = tt.final_file_id(conflict[2])
1100
        if len(conflict) == 3:
1101
            yield c_type, action, modified_path, modified_id
1102
        else:
1103
            conflicting_path = fp.get_path(conflict[3])
1104
            conflicting_id = tt.final_file_id(conflict[3])
1105
            yield (c_type, action, modified_path, modified_id, 
1106
                   conflicting_path, conflicting_id)
1534.7.171 by Aaron Bentley
Implemented stringifying filesystem conflicts
1107
1108
1109
def conflicts_strings(conflicts):
1110
    """Generate strings for the provided conflicts"""
1111
    for conflict in conflicts:
1112
        conflict_type = conflict[0]
1113
        if conflict_type == 'text conflict':
1114
            yield 'Text conflict in %s' % conflict[2]
1115
        elif conflict_type == 'contents conflict':
1116
            yield 'Contents conflict in %s' % conflict[2]
1117
        elif conflict_type == 'path conflict':
1118
            yield 'Path conflict: %s / %s' % conflict[2:]
1119
        elif conflict_type == 'duplicate id':
1120
            vals = (conflict[4], conflict[1], conflict[2])
1121
            yield 'Conflict adding id to %s.  %s %s.' % vals
1122
        elif conflict_type == 'duplicate':
1123
            vals = (conflict[4], conflict[1], conflict[2])
1124
            yield 'Conflict adding file %s.  %s %s.' % vals
1125
        elif conflict_type == 'parent loop':
1126
            vals = (conflict[4], conflict[2], conflict[1])
1127
            yield 'Conflict moving %s into %s.  %s.' % vals
1128
        elif conflict_type == 'unversioned parent':
1129
            vals = (conflict[2], conflict[1])
1130
            yield 'Conflict adding versioned files to %s.  %s.' % vals
1131
        elif conflict_type == 'missing parent':
1132
            vals = (conflict[2], conflict[1])
1133
            yield 'Conflict adding files to %s.  %s.' % vals