/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
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.8.3 by Aaron Bentley
Added Diff3 merging for tree transforms
19
from tempfile import mkdtemp
20
from shutil import rmtree
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
21
from stat import S_ISREG
1534.7.106 by Aaron Bentley
Cleaned up imports, added copyright statements
22
23
from bzrlib import BZRDIR
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
24
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
1534.7.124 by Aaron Bentley
Fixed merge_core bug
25
                           ReusingTransform, NotVersionedError, CantMoveRoot,
26
                           WorkingTreeNotRevision)
1534.7.106 by Aaron Bentley
Cleaned up imports, added copyright statements
27
from bzrlib.inventory import InventoryEntry
1534.8.3 by Aaron Bentley
Added Diff3 merging for tree transforms
28
from bzrlib.osutils import file_kind, supports_executable, pathjoin
1534.7.106 by Aaron Bentley
Cleaned up imports, added copyright statements
29
from bzrlib.merge3 import Merge3
1534.7.116 by Aaron Bentley
Started work on retaining file mode across replacements
30
from bzrlib.trace import mutter
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
31
32
ROOT_PARENT = "root-parent"
33
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
34
def unique_add(map, key, value):
35
    if key in map:
1534.7.5 by Aaron Bentley
Got unique_add under test
36
        raise DuplicateKey(key=key)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
37
    map[key] = value
38
39
class TreeTransform(object):
40
    """Represent a tree transformation."""
41
    def __init__(self, tree):
42
        """Note: a write lock is taken on the tree.
43
        
44
        Use TreeTransform.finalize() to release the lock
45
        """
46
        object.__init__(self)
47
        self._tree = tree
48
        self._tree.lock_write()
49
        self._id_number = 0
50
        self._new_name = {}
51
        self._new_parent = {}
1534.7.4 by Aaron Bentley
Unified all file types as 'contents'
52
        self._new_contents = {}
1534.7.34 by Aaron Bentley
Proper conflicts for removals
53
        self._removed_contents = set()
1534.7.25 by Aaron Bentley
Added set_executability
54
        self._new_executability = {}
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
55
        self._new_id = {}
1534.7.75 by Aaron Bentley
Added reverse-lookup for versioned files and get_trans_id
56
        self._r_new_id = {}
1534.7.39 by Aaron Bentley
Ensured that files can be unversioned (de-versioned?)
57
        self._removed_id = set()
1534.7.7 by Aaron Bentley
Added support for all-file path ids
58
        self._tree_path_ids = {}
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
59
        self._tree_id_paths = {}
1534.7.3 by Aaron Bentley
Updated to use a real root id. Or whatever the working tree considers real.
60
        self._new_root = self.get_id_tree(tree.get_root_id())
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
61
        self.__done = False
1534.7.118 by Aaron Bentley
Dirty merge of the mainline
62
        # XXX use the WorkingTree LockableFiles, when available
63
        control_files = self._tree.branch.control_files
64
        self._limbodir = control_files.controlfilename('limbo')
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
65
        os.mkdir(self._limbodir)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
66
67
    def finalize(self):
1534.7.40 by Aaron Bentley
Updated docs
68
        """Release the working tree lock, if held."""
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
69
        if self._tree is None:
70
            return
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
71
        for trans_id, kind in self._new_contents.iteritems():
72
            path = self._limbo_name(trans_id)
73
            if kind == "directory":
74
                os.rmdir(path)
75
            else:
76
                os.unlink(path)
77
        os.rmdir(self._limbodir)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
78
        self._tree.unlock()
79
        self._tree = None
80
81
    def _assign_id(self):
82
        """Produce a new tranform id"""
83
        new_id = "new-%s" % self._id_number
84
        self._id_number +=1
85
        return new_id
86
87
    def create_path(self, name, parent):
88
        """Assign a transaction id to a new path"""
89
        trans_id = self._assign_id()
90
        unique_add(self._new_name, trans_id, name)
91
        unique_add(self._new_parent, trans_id, parent)
92
        return trans_id
93
1534.7.6 by Aaron Bentley
Added conflict handling
94
    def adjust_path(self, name, parent, trans_id):
1534.7.21 by Aaron Bentley
Updated docstrings
95
        """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
96
        if trans_id == self._new_root:
97
            raise CantMoveRoot
1534.7.6 by Aaron Bentley
Added conflict handling
98
        self._new_name[trans_id] = name
99
        self._new_parent[trans_id] = parent
100
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
101
    def adjust_root_path(self, name, parent):
102
        """Emulate moving the root by moving all children, instead.
103
        
104
        We do this by undoing the association of root's transaction id with the
105
        current tree.  This allows us to create a new directory with that
1534.7.69 by Aaron Bentley
Got real root moves working
106
        transaction id.  We unversion the root directory and version the 
107
        physically new directory, and hope someone versions the tree root
108
        later.
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
109
        """
110
        old_root = self._new_root
111
        old_root_file_id = self.final_file_id(old_root)
112
        # force moving all children of root
113
        for child_id in self.iter_tree_children(old_root):
114
            if child_id != parent:
115
                self.adjust_path(self.final_name(child_id), 
116
                                 self.final_parent(child_id), child_id)
1534.7.69 by Aaron Bentley
Got real root moves working
117
            file_id = self.final_file_id(child_id)
118
            if file_id is not None:
119
                self.unversion_file(child_id)
120
            self.version_file(file_id, child_id)
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
121
        
122
        # the physical root needs a new transaction id
123
        self._tree_path_ids.pop("")
124
        self._tree_id_paths.pop(old_root)
125
        self._new_root = self.get_id_tree(self._tree.get_root_id())
126
        if parent == old_root:
127
            parent = self._new_root
128
        self.adjust_path(name, parent, old_root)
129
        self.create_directory(old_root)
1534.7.69 by Aaron Bentley
Got real root moves working
130
        self.version_file(old_root_file_id, old_root)
131
        self.unversion_file(self._new_root)
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
132
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
133
    def get_id_tree(self, inventory_id):
134
        """Determine the transaction id of a working tree file.
135
        
136
        This reflects only files that already exist, not ones that will be
137
        added by transactions.
138
        """
1534.7.7 by Aaron Bentley
Added support for all-file path ids
139
        return self.get_tree_path_id(self._tree.id2path(inventory_id))
140
1534.7.75 by Aaron Bentley
Added reverse-lookup for versioned files and get_trans_id
141
    def get_trans_id(self, file_id):
142
        """\
143
        Determine or set the transaction id associated with a file ID.
144
        A new id is only created for file_ids that were never present.  If
145
        a transaction has been unversioned, it is deliberately still returned.
146
        (this will likely lead to an unversioned parent conflict.)
147
        """
148
        if file_id in self._r_new_id and self._r_new_id[file_id] is not None:
149
            return self._r_new_id[file_id]
150
        elif file_id in self._tree.inventory:
151
            return self.get_id_tree(file_id)
152
        else:
153
            trans_id = self._assign_id()
154
            self.version_file(file_id, trans_id)
155
            return trans_id
156
1534.7.12 by Aaron Bentley
Added canonical_path function
157
    def canonical_path(self, path):
158
        """Get the canonical tree-relative path"""
159
        # don't follow final symlinks
160
        dirname, basename = os.path.split(self._tree.abspath(path))
161
        dirname = os.path.realpath(dirname)
162
        return self._tree.relpath(os.path.join(dirname, basename))
163
1534.7.7 by Aaron Bentley
Added support for all-file path ids
164
    def get_tree_path_id(self, path):
165
        """Determine (and maybe set) the transaction ID for a tree path."""
1534.7.12 by Aaron Bentley
Added canonical_path function
166
        path = self.canonical_path(path)
1534.7.7 by Aaron Bentley
Added support for all-file path ids
167
        if path not in self._tree_path_ids:
168
            self._tree_path_ids[path] = self._assign_id()
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
169
            self._tree_id_paths[self._tree_path_ids[path]] = path
1534.7.7 by Aaron Bentley
Added support for all-file path ids
170
        return self._tree_path_ids[path]
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
171
1534.7.16 by Aaron Bentley
Added get_tree_parent
172
    def get_tree_parent(self, trans_id):
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
173
        """Determine id of the parent in the tree."""
1534.7.16 by Aaron Bentley
Added get_tree_parent
174
        path = self._tree_id_paths[trans_id]
175
        if path == "":
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
176
            return ROOT_PARENT
1534.7.16 by Aaron Bentley
Added get_tree_parent
177
        return self.get_tree_path_id(os.path.dirname(path))
178
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
179
    def create_file(self, contents, trans_id, mode_id=None):
1534.7.21 by Aaron Bentley
Updated docstrings
180
        """Schedule creation of a new file.
181
182
        See also new_file.
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
183
        
184
        Contents is an iterator of strings, all of which will be written
1534.7.21 by Aaron Bentley
Updated docstrings
185
        to the target destination.
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
186
187
        New file takes the permissions of any existing file with that id,
188
        unless mode_id is specified.
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
189
        """
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
190
        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.
191
        unique_add(self._new_contents, trans_id, 'file')
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
192
        for segment in contents:
193
            f.write(segment)
194
        f.close()
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
195
        self._set_mode(trans_id, mode_id, S_ISREG)
196
197
    def _set_mode(self, trans_id, mode_id, typefunc):
198
        if mode_id is None:
199
            mode_id = trans_id
200
        try:
201
            old_path = self._tree_id_paths[mode_id]
202
        except KeyError:
203
            return
204
        try:
205
            mode = os.stat(old_path).st_mode
206
        except OSError, e:
207
            if e.errno == errno.ENOENT:
208
                return
209
            else:
210
                raise
211
        if typefunc(mode):
212
            os.chmod(self._limbo_name(trans_id), mode)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
213
1534.7.20 by Aaron Bentley
Added directory handling
214
    def create_directory(self, trans_id):
1534.7.21 by Aaron Bentley
Updated docstrings
215
        """Schedule creation of a new directory.
216
        
217
        See also new_directory.
218
        """
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
219
        os.mkdir(self._limbo_name(trans_id))
220
        unique_add(self._new_contents, trans_id, 'directory')
1534.7.20 by Aaron Bentley
Added directory handling
221
1534.7.22 by Aaron Bentley
Added symlink support
222
    def create_symlink(self, target, trans_id):
223
        """Schedule creation of a new symbolic link.
224
225
        target is a bytestring.
226
        See also new_symlink.
227
        """
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
228
        os.symlink(target, self._limbo_name(trans_id))
229
        unique_add(self._new_contents, trans_id, 'symlink')
1534.7.22 by Aaron Bentley
Added symlink support
230
1534.7.129 by Aaron Bentley
Converted test cases to Tree Transform
231
    @staticmethod
232
    def delete_any(full_path):
233
        try:
234
            os.unlink(full_path)
235
        except OSError, e:
236
        # We may be renaming a dangling inventory id
237
            if e.errno != errno.EISDIR and e.errno != errno.EACCES:
238
                raise
239
            os.rmdir(full_path)
240
241
    def cancel_creation(self, trans_id):
242
        del self._new_contents[trans_id]
243
        self.delete_any(self._limbo_name(trans_id))
244
1534.7.34 by Aaron Bentley
Proper conflicts for removals
245
    def delete_contents(self, trans_id):
246
        """Schedule the contents of a path entry for deletion"""
1534.7.130 by Aaron Bentley
More conflict handling, test porting
247
        self.tree_kind(trans_id)
1534.7.34 by Aaron Bentley
Proper conflicts for removals
248
        self._removed_contents.add(trans_id)
249
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
250
    def cancel_deletion(self, trans_id):
251
        """Cancel a scheduled deletion"""
252
        self._removed_contents.remove(trans_id)
253
1534.7.39 by Aaron Bentley
Ensured that files can be unversioned (de-versioned?)
254
    def unversion_file(self, trans_id):
255
        """Schedule a path entry to become unversioned"""
256
        self._removed_id.add(trans_id)
257
258
    def delete_versioned(self, trans_id):
259
        """Delete and unversion a versioned file"""
260
        self.delete_contents(trans_id)
261
        self.unversion_file(trans_id)
262
1534.7.25 by Aaron Bentley
Added set_executability
263
    def set_executability(self, executability, trans_id):
264
        """Schedule setting of the 'execute' bit"""
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
265
        if executability is None:
266
            del self._new_executability[trans_id]
267
        else:
268
            unique_add(self._new_executability, trans_id, executability)
1534.7.25 by Aaron Bentley
Added set_executability
269
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
270
    def version_file(self, file_id, trans_id):
1534.7.21 by Aaron Bentley
Updated docstrings
271
        """Schedule a file to become versioned."""
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
272
        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
273
        unique_add(self._r_new_id, file_id, trans_id)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
274
1534.7.105 by Aaron Bentley
Got merge with rename working
275
    def cancel_versioning(self, trans_id):
276
        """Undo a previous versioning of a file"""
277
        file_id = self._new_id[trans_id]
278
        del self._new_id[trans_id]
279
        del self._r_new_id[file_id]
280
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
281
    def new_paths(self):
1534.7.21 by Aaron Bentley
Updated docstrings
282
        """Determine the paths of all new and changed files"""
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
283
        new_ids = set()
1534.7.33 by Aaron Bentley
Fixed naming
284
        fp = FinalPaths(self._new_root, self)
1534.7.4 by Aaron Bentley
Unified all file types as 'contents'
285
        for id_set in (self._new_name, self._new_parent, self._new_contents,
1534.7.25 by Aaron Bentley
Added set_executability
286
                       self._new_id, self._new_executability):
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
287
            new_ids.update(id_set)
288
        new_paths = [(fp.get_path(t), t) for t in new_ids]
289
        new_paths.sort()
290
        return new_paths
1534.7.6 by Aaron Bentley
Added conflict handling
291
1534.7.34 by Aaron Bentley
Proper conflicts for removals
292
    def tree_kind(self, trans_id):
1534.7.40 by Aaron Bentley
Updated docs
293
        """Determine the file kind in the working tree.
294
295
        Raises NoSuchFile if the file does not exist
296
        """
1534.7.34 by Aaron Bentley
Proper conflicts for removals
297
        path = self._tree_id_paths.get(trans_id)
298
        if path is None:
299
            raise NoSuchFile(None)
300
        try:
301
            return file_kind(self._tree.abspath(path))
302
        except OSError, e:
303
            if e.errno != errno.ENOENT:
304
                raise
305
            else:
306
                raise NoSuchFile(path)
307
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
308
    def final_kind(self, trans_id):
1534.7.21 by Aaron Bentley
Updated docstrings
309
        """\
310
        Determine the final file kind, after any changes applied.
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
311
        
312
        Raises NoSuchFile if the file does not exist/has no contents.
313
        (It is conceivable that a path would be created without the
314
        corresponding contents insertion command)
315
        """
316
        if trans_id in self._new_contents:
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
317
            return self._new_contents[trans_id]
1534.7.34 by Aaron Bentley
Proper conflicts for removals
318
        elif trans_id in self._removed_contents:
319
            raise NoSuchFile(None)
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
320
        else:
1534.7.34 by Aaron Bentley
Proper conflicts for removals
321
            return self.tree_kind(trans_id)
1534.7.8 by Aaron Bentley
Added TreeTransform.final_kind
322
1534.7.41 by Aaron Bentley
Got inventory ID movement working
323
    def get_tree_file_id(self, trans_id):
324
        """Determine the file id associated with the trans_id in the tree"""
325
        try:
326
            path = self._tree_id_paths[trans_id]
327
        except KeyError:
328
            # the file is a new, unversioned file, or invalid trans_id
329
            return None
330
        # the file is old; the old id is still valid
1534.7.68 by Aaron Bentley
Got semi-reasonable root directory renaming working
331
        if self._new_root == trans_id:
332
            return self._tree.inventory.root.file_id
1534.7.41 by Aaron Bentley
Got inventory ID movement working
333
        return self._tree.path2id(path)
334
1534.7.13 by Aaron Bentley
Implemented final_file_id
335
    def final_file_id(self, trans_id):
1534.7.21 by Aaron Bentley
Updated docstrings
336
        """\
337
        Determine the file id after any changes are applied, or None.
338
        
339
        None indicates that the file will not be versioned after changes are
340
        applied.
341
        """
1534.7.13 by Aaron Bentley
Implemented final_file_id
342
        try:
343
            # there is a new id for this file
344
            return self._new_id[trans_id]
345
        except KeyError:
1534.7.39 by Aaron Bentley
Ensured that files can be unversioned (de-versioned?)
346
            if trans_id in self._removed_id:
347
                return None
1534.7.41 by Aaron Bentley
Got inventory ID movement working
348
        return self.get_tree_file_id(trans_id)
1534.7.13 by Aaron Bentley
Implemented final_file_id
349
1534.7.17 by Aaron Bentley
Added final_parent function
350
    def final_parent(self, trans_id):
1534.7.21 by Aaron Bentley
Updated docstrings
351
        """\
352
        Determine the parent file_id, after any changes are applied.
353
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
354
        ROOT_PARENT is returned for the tree root.
1534.7.21 by Aaron Bentley
Updated docstrings
355
        """
1534.7.17 by Aaron Bentley
Added final_parent function
356
        try:
357
            return self._new_parent[trans_id]
358
        except KeyError:
359
            return self.get_tree_parent(trans_id)
360
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
361
    def final_name(self, trans_id):
1534.7.40 by Aaron Bentley
Updated docs
362
        """Determine the final filename, after all changes are applied."""
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
363
        try:
364
            return self._new_name[trans_id]
365
        except KeyError:
366
            return os.path.basename(self._tree_id_paths[trans_id])
367
368
    def _by_parent(self):
1534.7.40 by Aaron Bentley
Updated docs
369
        """Return a map of parent: children for known parents.
370
        
371
        Only new paths and parents of tree files with assigned ids are used.
372
        """
1534.7.6 by Aaron Bentley
Added conflict handling
373
        by_parent = {}
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
374
        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.
375
        items.extend((t, self.final_parent(t)) for t in 
376
                      self._tree_id_paths.keys())
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
377
        for trans_id, parent_id in items:
1534.7.6 by Aaron Bentley
Added conflict handling
378
            if parent_id not in by_parent:
379
                by_parent[parent_id] = set()
380
            by_parent[parent_id].add(trans_id)
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
381
        return by_parent
1534.7.11 by Aaron Bentley
Refactored conflict handling
382
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
383
    def path_changed(self, trans_id):
384
        return trans_id in self._new_name or trans_id in self._new_parent
385
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
386
    def find_conflicts(self):
1534.7.40 by Aaron Bentley
Updated docs
387
        """Find any violations of inventory or filesystem invariants"""
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
388
        if self.__done is True:
389
            raise ReusingTransform()
390
        conflicts = []
391
        # ensure all children of all existent parents are known
392
        # all children of non-existent parents are known, by definition.
393
        self._add_tree_children()
394
        by_parent = self._by_parent()
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
395
        conflicts.extend(self._unversioned_parents(by_parent))
1534.7.19 by Aaron Bentley
Added tests for parent loops
396
        conflicts.extend(self._parent_loops())
1534.7.11 by Aaron Bentley
Refactored conflict handling
397
        conflicts.extend(self._duplicate_entries(by_parent))
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
398
        conflicts.extend(self._duplicate_ids())
1534.7.11 by Aaron Bentley
Refactored conflict handling
399
        conflicts.extend(self._parent_type_conflicts(by_parent))
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
400
        conflicts.extend(self._improper_versioning())
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
401
        conflicts.extend(self._executability_conflicts())
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
402
        return conflicts
403
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
404
    def _add_tree_children(self):
1534.7.40 by Aaron Bentley
Updated docs
405
        """\
406
        Add all the children of all active parents to the known paths.
407
408
        Active parents are those which gain children, and those which are
409
        removed.  This is a necessary first step in detecting conflicts.
410
        """
1534.7.34 by Aaron Bentley
Proper conflicts for removals
411
        parents = self._by_parent().keys()
412
        parents.extend([t for t in self._removed_contents if 
413
                        self.tree_kind(t) == 'directory'])
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
414
        for trans_id in self._removed_id:
415
            file_id = self.get_tree_file_id(trans_id)
1534.7.55 by Aaron Bentley
Fixed up the change detection
416
            if self._tree.inventory[file_id].kind in ('directory', 
417
                                                      'root_directory'):
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
418
                parents.append(trans_id)
419
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
420
        for parent_id in parents:
1534.7.67 by Aaron Bentley
Refactored _add_tree_children
421
            # ensure that all children are registered with the transaction
422
            list(self.iter_tree_children(parent_id))
423
424
    def iter_tree_children(self, parent_id):
425
        """Iterate through the entry's tree children, if any"""
426
        try:
427
            path = self._tree_id_paths[parent_id]
428
        except KeyError:
429
            return
430
        try:
431
            children = os.listdir(self._tree.abspath(path))
432
        except OSError, e:
1534.7.71 by abentley
All tests pass under Windows
433
            if e.errno != errno.ENOENT and e.errno != errno.ESRCH:
1534.7.67 by Aaron Bentley
Refactored _add_tree_children
434
                raise
435
            return
436
            
437
        for child in children:
438
            childpath = joinpath(path, child)
439
            if childpath == BZRDIR:
440
                continue
441
            yield self.get_tree_path_id(childpath)
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
442
1534.7.19 by Aaron Bentley
Added tests for parent loops
443
    def _parent_loops(self):
444
        """No entry should be its own ancestor"""
445
        conflicts = []
446
        for trans_id in self._new_parent:
447
            seen = set()
448
            parent_id = trans_id
1534.7.31 by Aaron Bentley
Changed tree root parent to ROOT_PARENT
449
            while parent_id is not ROOT_PARENT:
1534.7.19 by Aaron Bentley
Added tests for parent loops
450
                seen.add(parent_id)
451
                parent_id = self.final_parent(parent_id)
452
                if parent_id == trans_id:
453
                    conflicts.append(('parent loop', trans_id))
454
                if parent_id in seen:
455
                    break
456
        return conflicts
457
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
458
    def _unversioned_parents(self, by_parent):
459
        """If parent directories are versioned, children must be versioned."""
460
        conflicts = []
461
        for parent_id, children in by_parent.iteritems():
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
462
            if parent_id is ROOT_PARENT:
463
                continue
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
464
            if self.final_file_id(parent_id) is not None:
465
                continue
466
            for child_id in children:
467
                if self.final_file_id(child_id) is not None:
468
                    conflicts.append(('unversioned parent', parent_id))
469
                    break;
470
        return conflicts
471
472
    def _improper_versioning(self):
1534.7.21 by Aaron Bentley
Updated docstrings
473
        """\
474
        Cannot version a file with no contents, or a bad type.
1534.7.15 by Aaron Bentley
Add conflict types related to versioning
475
        
476
        However, existing entries with no contents are okay.
477
        """
478
        conflicts = []
479
        for trans_id in self._new_id.iterkeys():
480
            try:
481
                kind = self.final_kind(trans_id)
482
            except NoSuchFile:
483
                conflicts.append(('versioning no contents', trans_id))
484
                continue
485
            if not InventoryEntry.versionable_kind(kind):
1534.7.20 by Aaron Bentley
Added directory handling
486
                conflicts.append(('versioning bad kind', trans_id, kind))
1534.7.11 by Aaron Bentley
Refactored conflict handling
487
        return conflicts
488
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
489
    def _executability_conflicts(self):
1534.7.40 by Aaron Bentley
Updated docs
490
        """Check for bad executability changes.
491
        
492
        Only versioned files may have their executability set, because
493
        1. only versioned entries can have executability under windows
494
        2. only files can be executable.  (The execute bit on a directory
495
           does not indicate searchability)
496
        """
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
497
        conflicts = []
498
        for trans_id in self._new_executability:
499
            if self.final_file_id(trans_id) is None:
500
                conflicts.append(('unversioned executability', trans_id))
1534.7.34 by Aaron Bentley
Proper conflicts for removals
501
            else:
502
                try:
503
                    non_file = self.final_kind(trans_id) != "file"
504
                except NoSuchFile:
505
                    non_file = True
506
                if non_file is True:
507
                    conflicts.append(('non-file executability', trans_id))
1534.7.26 by Aaron Bentley
Added conflicts for setting executability on unversioned/non-file entries
508
        return conflicts
509
1534.7.11 by Aaron Bentley
Refactored conflict handling
510
    def _duplicate_entries(self, by_parent):
511
        """No directory may have two entries with the same name."""
512
        conflicts = []
1534.7.6 by Aaron Bentley
Added conflict handling
513
        for children in by_parent.itervalues():
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
514
            name_ids = [(self.final_name(t), t) for t in children]
1534.7.6 by Aaron Bentley
Added conflict handling
515
            name_ids.sort()
516
            last_name = None
517
            last_trans_id = None
518
            for name, trans_id in name_ids:
519
                if name == last_name:
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
520
                    conflicts.append(('duplicate', last_trans_id, trans_id,
521
                    name))
1534.7.6 by Aaron Bentley
Added conflict handling
522
                last_name = name
523
                last_trans_id = trans_id
1534.7.11 by Aaron Bentley
Refactored conflict handling
524
        return conflicts
525
1534.7.50 by Aaron Bentley
Detect duplicate inventory ids
526
    def _duplicate_ids(self):
527
        """Each inventory id may only be used once"""
528
        conflicts = []
529
        removed_tree_ids = set((self.get_tree_file_id(trans_id) for trans_id in
530
                                self._removed_id))
531
        active_tree_ids = set((f for f in self._tree.inventory if
532
                               f not in removed_tree_ids))
533
        for trans_id, file_id in self._new_id.iteritems():
534
            if file_id in active_tree_ids:
535
                old_trans_id = self.get_id_tree(file_id)
536
                conflicts.append(('duplicate id', old_trans_id, trans_id))
537
        return conflicts
538
1534.7.11 by Aaron Bentley
Refactored conflict handling
539
    def _parent_type_conflicts(self, by_parent):
540
        """parents must have directory 'contents'."""
541
        conflicts = []
1534.7.37 by Aaron Bentley
Allowed removed dirs to have content-free children.
542
        for parent_id, children in by_parent.iteritems():
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
543
            if parent_id is ROOT_PARENT:
544
                continue
1534.7.37 by Aaron Bentley
Allowed removed dirs to have content-free children.
545
            if not self._any_contents(children):
546
                continue
547
            for child in children:
548
                try:
549
                    self.final_kind(child)
550
                except NoSuchFile:
551
                    continue
1534.7.10 by Aaron Bentley
Implemented missing parent and non-directory parent conflicts
552
            try:
553
                kind = self.final_kind(parent_id)
554
            except NoSuchFile:
555
                kind = None
556
            if kind is None:
557
                conflicts.append(('missing parent', parent_id))
558
            elif kind != "directory":
559
                conflicts.append(('non-directory parent', parent_id))
1534.7.6 by Aaron Bentley
Added conflict handling
560
        return conflicts
1534.7.37 by Aaron Bentley
Allowed removed dirs to have content-free children.
561
562
    def _any_contents(self, trans_ids):
563
        """Return true if any of the trans_ids, will have contents."""
564
        for trans_id in trans_ids:
565
            try:
566
                kind = self.final_kind(trans_id)
567
            except NoSuchFile:
568
                continue
569
            return True
570
        return False
1534.7.6 by Aaron Bentley
Added conflict handling
571
            
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
572
    def apply(self):
1534.7.21 by Aaron Bentley
Updated docstrings
573
        """\
574
        Apply all changes to the inventory and filesystem.
575
        
576
        If filesystem or inventory conflicts are present, MalformedTransform
577
        will be thrown.
578
        """
1534.7.49 by Aaron Bentley
Printed conflicts in MalformedTransform
579
        conflicts = self.find_conflicts()
580
        if len(conflicts) != 0:
581
            raise MalformedTransform(conflicts=conflicts)
1534.7.41 by Aaron Bentley
Got inventory ID movement working
582
        limbo_inv = {}
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
583
        inv = self._tree.inventory
1534.7.41 by Aaron Bentley
Got inventory ID movement working
584
        self._apply_removals(inv, limbo_inv)
585
        self._apply_insertions(inv, limbo_inv)
1534.7.35 by Aaron Bentley
Got file renaming working
586
        self._tree._write_inventory(inv)
587
        self.__done = True
1534.7.59 by Aaron Bentley
Simplified tests
588
        self.finalize()
1534.7.35 by Aaron Bentley
Got file renaming working
589
1534.7.72 by Aaron Bentley
Moved new content generation to pre-renames
590
    def _limbo_name(self, trans_id):
591
        """Generate the limbo name of a file"""
1534.7.118 by Aaron Bentley
Dirty merge of the mainline
592
        return os.path.join(self._limbodir, trans_id)
1534.7.72 by Aaron Bentley
Moved new content generation to pre-renames
593
1534.7.41 by Aaron Bentley
Got inventory ID movement working
594
    def _apply_removals(self, inv, limbo_inv):
1534.7.36 by Aaron Bentley
Added rename tests
595
        """Perform tree operations that remove directory/inventory names.
596
        
597
        That is, delete files that are to be deleted, and put any files that
598
        need renaming into limbo.  This must be done in strict child-to-parent
599
        order.
600
        """
1534.7.35 by Aaron Bentley
Got file renaming working
601
        tree_paths = list(self._tree_path_ids.iteritems())
602
        tree_paths.sort(reverse=True)
603
        for path, trans_id in tree_paths:
1534.7.43 by abentley
Fixed some Windows bugs, introduced a conflicts bug
604
            full_path = self._tree.abspath(path)
1534.7.34 by Aaron Bentley
Proper conflicts for removals
605
            if trans_id in self._removed_contents:
1534.7.129 by Aaron Bentley
Converted test cases to Tree Transform
606
                self.delete_any(full_path)
1534.7.35 by Aaron Bentley
Got file renaming working
607
            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
608
                try:
1534.7.118 by Aaron Bentley
Dirty merge of the mainline
609
                    os.rename(full_path, self._limbo_name(trans_id))
1534.7.48 by Aaron Bentley
Ensured we can move/rename dangling inventory entries
610
                except OSError, e:
611
                    if e.errno != errno.ENOENT:
612
                        raise
1534.7.39 by Aaron Bentley
Ensured that files can be unversioned (de-versioned?)
613
            if trans_id in self._removed_id:
1534.7.69 by Aaron Bentley
Got real root moves working
614
                if trans_id == self._new_root:
615
                    file_id = self._tree.inventory.root.file_id
616
                else:
617
                    file_id = self.get_tree_file_id(trans_id)
618
                del inv[file_id]
1534.7.41 by Aaron Bentley
Got inventory ID movement working
619
            elif trans_id in self._new_name or trans_id in self._new_parent:
620
                file_id = self.get_tree_file_id(trans_id)
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
621
                if file_id is not None:
622
                    limbo_inv[trans_id] = inv[file_id]
623
                    del inv[file_id]
1534.7.34 by Aaron Bentley
Proper conflicts for removals
624
1534.7.41 by Aaron Bentley
Got inventory ID movement working
625
    def _apply_insertions(self, inv, limbo_inv):
1534.7.36 by Aaron Bentley
Added rename tests
626
        """Perform tree operations that insert directory/inventory names.
627
        
628
        That is, create any files that need to be created, and restore from
629
        limbo any files that needed renaming.  This must be done in strict
630
        parent-to-child order.
631
        """
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
632
        for path, trans_id in self.new_paths():
1534.7.4 by Aaron Bentley
Unified all file types as 'contents'
633
            try:
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
634
                kind = self._new_contents[trans_id]
1534.7.4 by Aaron Bentley
Unified all file types as 'contents'
635
            except KeyError:
636
                kind = contents = None
1534.7.72 by Aaron Bentley
Moved new content generation to pre-renames
637
            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
638
                full_path = self._tree.abspath(path)
639
                try:
1534.7.72 by Aaron Bentley
Moved new content generation to pre-renames
640
                    os.rename(self._limbo_name(trans_id), full_path)
1534.7.48 by Aaron Bentley
Ensured we can move/rename dangling inventory entries
641
                except OSError, e:
642
                    # We may be renaming a dangling inventory id
643
                    if e.errno != errno.ENOENT:
644
                        raise
1534.7.73 by Aaron Bentley
Changed model again. Now iterator is used immediately.
645
                if trans_id in self._new_contents:
646
                    del self._new_contents[trans_id]
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
647
648
            if trans_id in self._new_id:
649
                if kind is None:
1534.7.14 by Aaron Bentley
Fixed file_kind call
650
                    kind = file_kind(self._tree.abspath(path))
1534.7.69 by Aaron Bentley
Got real root moves working
651
                try:
652
                    inv.add_path(path, kind, self._new_id[trans_id])
653
                except:
654
                    raise repr((path, kind, self._new_id[trans_id]))
1534.7.41 by Aaron Bentley
Got inventory ID movement working
655
            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
656
                entry = limbo_inv.get(trans_id)
657
                if entry is not None:
658
                    entry.name = self.final_name(trans_id)
659
                    parent_trans_id = self.final_parent(trans_id)
660
                    entry.parent_id = self.final_file_id(parent_trans_id)
661
                    inv.add(entry)
1534.7.41 by Aaron Bentley
Got inventory ID movement working
662
1534.7.25 by Aaron Bentley
Added set_executability
663
            # requires files and inventory entries to be in place
664
            if trans_id in self._new_executability:
665
                self._set_executability(path, inv, trans_id)
1534.7.40 by Aaron Bentley
Updated docs
666
1534.7.25 by Aaron Bentley
Added set_executability
667
    def _set_executability(self, path, inv, trans_id):
1534.7.40 by Aaron Bentley
Updated docs
668
        """Set the executability of versioned files """
1534.7.25 by Aaron Bentley
Added set_executability
669
        file_id = inv.path2id(path)
670
        new_executability = self._new_executability[trans_id]
671
        inv[file_id].executable = new_executability
672
        if supports_executable():
673
            abspath = self._tree.abspath(path)
674
            current_mode = os.stat(abspath).st_mode
675
            if new_executability:
676
                umask = os.umask(0)
677
                os.umask(umask)
678
                to_mode = current_mode | (0100 & ~umask)
679
                # Enable x-bit for others only if they can read it.
680
                if current_mode & 0004:
681
                    to_mode |= 0001 & ~umask
682
                if current_mode & 0040:
683
                    to_mode |= 0010 & ~umask
684
            else:
685
                to_mode = current_mode & ~0111
686
            os.chmod(abspath, to_mode)
687
1534.7.23 by Aaron Bentley
Transform.new_entry -> Transform._new_entry
688
    def _new_entry(self, name, parent_id, file_id):
1534.7.21 by Aaron Bentley
Updated docstrings
689
        """Helper function to create a new filesystem entry."""
1534.7.2 by Aaron Bentley
Added convenience function
690
        trans_id = self.create_path(name, parent_id)
691
        if file_id is not None:
692
            self.version_file(file_id, trans_id)
693
        return trans_id
694
1534.7.27 by Aaron Bentley
Added execute bit to new_file method
695
    def new_file(self, name, parent_id, contents, file_id=None, 
696
                 executable=None):
1534.7.21 by Aaron Bentley
Updated docstrings
697
        """\
698
        Convenience method to create files.
699
        
700
        name is the name of the file to create.
701
        parent_id is the transaction id of the parent directory of the file.
702
        contents is an iterator of bytestrings, which will be used to produce
703
        the file.
704
        file_id is the inventory ID of the file, if it is to be versioned.
705
        """
1534.7.23 by Aaron Bentley
Transform.new_entry -> Transform._new_entry
706
        trans_id = self._new_entry(name, parent_id, file_id)
1534.7.20 by Aaron Bentley
Added directory handling
707
        self.create_file(contents, trans_id)
1534.7.27 by Aaron Bentley
Added execute bit to new_file method
708
        if executable is not None:
709
            self.set_executability(executable, trans_id)
1534.7.20 by Aaron Bentley
Added directory handling
710
        return trans_id
711
712
    def new_directory(self, name, parent_id, file_id=None):
1534.7.21 by Aaron Bentley
Updated docstrings
713
        """\
714
        Convenience method to create directories.
715
716
        name is the name of the directory to create.
717
        parent_id is the transaction id of the parent directory of the
718
        directory.
719
        file_id is the inventory ID of the directory, if it is to be versioned.
720
        """
1534.7.23 by Aaron Bentley
Transform.new_entry -> Transform._new_entry
721
        trans_id = self._new_entry(name, parent_id, file_id)
1534.7.20 by Aaron Bentley
Added directory handling
722
        self.create_directory(trans_id)
723
        return trans_id 
724
1534.7.22 by Aaron Bentley
Added symlink support
725
    def new_symlink(self, name, parent_id, target, file_id=None):
726
        """\
727
        Convenience method to create symbolic link.
728
        
729
        name is the name of the symlink to create.
730
        parent_id is the transaction id of the parent directory of the symlink.
731
        target is a bytestring of the target of the symlink.
732
        file_id is the inventory ID of the file, if it is to be versioned.
733
        """
1534.7.23 by Aaron Bentley
Transform.new_entry -> Transform._new_entry
734
        trans_id = self._new_entry(name, parent_id, file_id)
1534.7.22 by Aaron Bentley
Added symlink support
735
        self.create_symlink(target, trans_id)
736
        return trans_id
737
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
738
def joinpath(parent, child):
1534.7.40 by Aaron Bentley
Updated docs
739
    """Join tree-relative paths, handling the tree root specially"""
1534.7.32 by Aaron Bentley
Got conflict handling working when conflicts involve existing files
740
    if parent is None or parent == "":
741
        return child
742
    else:
743
        return os.path.join(parent, child)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
744
745
class FinalPaths(object):
1534.7.21 by Aaron Bentley
Updated docstrings
746
    """\
747
    Make path calculation cheap by memoizing paths.
748
749
    The underlying tree must not be manipulated between calls, or else
750
    the results will likely be incorrect.
751
    """
1534.7.33 by Aaron Bentley
Fixed naming
752
    def __init__(self, root, transform):
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
753
        object.__init__(self)
754
        self.root = root
755
        self._known_paths = {}
1534.7.33 by Aaron Bentley
Fixed naming
756
        self.transform = transform
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
757
758
    def _determine_path(self, trans_id):
759
        if trans_id == self.root:
760
            return ""
1534.7.33 by Aaron Bentley
Fixed naming
761
        name = self.transform.final_name(trans_id)
762
        parent_id = self.transform.final_parent(trans_id)
1534.7.1 by Aaron Bentley
Got creation of a versioned file working
763
        if parent_id == self.root:
764
            return name
765
        else:
766
            return os.path.join(self.get_path(parent_id), name)
767
768
    def get_path(self, trans_id):
769
        if trans_id not in self._known_paths:
770
            self._known_paths[trans_id] = self._determine_path(trans_id)
771
        return self._known_paths[trans_id]
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
772
1534.7.30 by Aaron Bentley
Factored out topological id sorting
773
def topology_sorted_ids(tree):
1534.7.40 by Aaron Bentley
Updated docs
774
    """Determine the topological order of the ids in a tree"""
1534.7.30 by Aaron Bentley
Factored out topological id sorting
775
    file_ids = list(tree)
776
    file_ids.sort(key=tree.id2path)
777
    return file_ids
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
778
779
def build_tree(branch, tree):
1534.7.40 by Aaron Bentley
Updated docs
780
    """Create working tree for a branch, using a Transaction."""
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
781
    file_trans_id = {}
782
    wt = branch.working_tree()
783
    tt = TreeTransform(wt)
784
    try:
785
        file_trans_id[wt.get_root_id()] = tt.get_id_tree(wt.get_root_id())
1534.7.30 by Aaron Bentley
Factored out topological id sorting
786
        file_ids = topology_sorted_ids(tree)
1534.7.29 by Aaron Bentley
Got build passing all tests
787
        for file_id in file_ids:
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
788
            entry = tree.inventory[file_id]
789
            if entry.parent_id is None:
790
                continue
791
            if entry.parent_id not in file_trans_id:
792
                raise repr(entry.parent_id)
793
            parent_id = file_trans_id[entry.parent_id]
1534.7.47 by Aaron Bentley
Started work on 'revert'
794
            file_trans_id[file_id] = new_by_entry(tt, entry, parent_id, tree)
795
        tt.apply()
796
    finally:
797
        tt.finalize()
798
799
def new_by_entry(tt, entry, parent_id, tree):
800
    name = entry.name
801
    kind = entry.kind
802
    if kind == 'file':
1534.7.79 by Aaron Bentley
Stopped calling get_file_lines on WorkingTree
803
        contents = tree.get_file(entry.file_id).readlines()
1534.7.47 by Aaron Bentley
Started work on 'revert'
804
        executable = tree.is_executable(entry.file_id)
805
        return tt.new_file(name, parent_id, contents, entry.file_id, 
806
                           executable)
807
    elif kind == 'directory':
1534.7.54 by Aaron Bentley
Fixed thinko
808
        return tt.new_directory(name, parent_id, entry.file_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
809
    elif kind == 'symlink':
810
        target = entry.get_symlink_target(file_id)
811
        return tt.new_symlink(name, parent_id, target, file_id)
812
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
813
def create_by_entry(tt, entry, tree, trans_id, lines=None, mode_id=None):
1534.7.47 by Aaron Bentley
Started work on 'revert'
814
    if entry.kind == "file":
1534.7.97 by Aaron Bentley
Ensured foo.BASE is a directory if there's a conflict
815
        if lines == None:
816
            lines = tree.get_file(entry.file_id).readlines()
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
817
        tt.create_file(lines, trans_id, mode_id=mode_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
818
    elif entry.kind == "symlink":
1534.7.101 by Aaron Bentley
Got conflicts on symlinks working properly
819
        tt.create_symlink(tree.get_symlink_target(entry.file_id), trans_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
820
    elif entry.kind == "directory":
1534.7.51 by Aaron Bentley
New approach to revert
821
        tt.create_directory(trans_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
822
1534.7.89 by Aaron Bentley
Handle all content types in three-way
823
def create_entry_executability(tt, entry, trans_id):
824
    if entry.kind == "file":
825
        tt.set_executability(entry.executable, trans_id)
1534.7.47 by Aaron Bentley
Started work on 'revert'
826
1534.7.55 by Aaron Bentley
Fixed up the change detection
827
def find_interesting(working_tree, target_tree, filenames):
828
    if not filenames:
829
        interesting_ids = None
830
    else:
831
        interesting_ids = set()
1534.7.118 by Aaron Bentley
Dirty merge of the mainline
832
        for tree_path in filenames:
1534.7.55 by Aaron Bentley
Fixed up the change detection
833
            for tree in (working_tree, target_tree):
834
                not_found = True
835
                file_id = tree.inventory.path2id(tree_path)
836
                if file_id is not None:
837
                    interesting_ids.add(file_id)
838
                    not_found = False
839
                if not_found:
1534.7.123 by Aaron Bentley
Fixed handling of unversioned files
840
                    raise NotVersionedError(path=tree_path)
1534.7.55 by Aaron Bentley
Fixed up the change detection
841
    return interesting_ids
842
843
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
844
def change_entry(tt, file_id, working_tree, target_tree, 
845
                 get_trans_id, backups, trans_id):
1534.7.55 by Aaron Bentley
Fixed up the change detection
846
    e_trans_id = get_trans_id(file_id)
847
    entry = target_tree.inventory[file_id]
848
    has_contents, contents_mod, meta_mod, = _entry_changes(file_id, entry, 
849
                                                           working_tree)
850
    if contents_mod:
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
851
        mode_id = e_trans_id
1534.7.55 by Aaron Bentley
Fixed up the change detection
852
        if has_contents:
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
853
            if not backups:
854
                tt.delete_contents(e_trans_id)
855
            else:
856
                parent_trans_id = get_trans_id(entry.parent_id)
857
                tt.adjust_path(entry.name+"~", parent_trans_id, e_trans_id)
858
                tt.unversion_file(e_trans_id)
859
                e_trans_id = tt.create_path(entry.name, parent_trans_id)
860
                tt.version_file(file_id, e_trans_id)
861
                trans_id[file_id] = e_trans_id
1534.7.117 by Aaron Bentley
Simplified permission handling of existing files in transform.
862
        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
863
        create_entry_executability(tt, entry, e_trans_id)
864
1534.7.55 by Aaron Bentley
Fixed up the change detection
865
    elif meta_mod:
1534.7.58 by abentley
Fixed executability bug
866
        tt.set_executability(entry.executable, e_trans_id)
1534.7.55 by Aaron Bentley
Fixed up the change detection
867
    if tt.final_name(e_trans_id) != entry.name:
868
        adjust_path  = True
869
    else:
870
        parent_id = tt.final_parent(e_trans_id)
871
        parent_file_id = tt.final_file_id(parent_id)
872
        if parent_file_id != entry.parent_id:
873
            adjust_path = True
874
        else:
875
            adjust_path = False
876
    if adjust_path:
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
877
        parent_trans_id = get_trans_id(entry.parent_id)
878
        tt.adjust_path(entry.name, parent_trans_id, e_trans_id)
1534.7.55 by Aaron Bentley
Fixed up the change detection
879
880
881
def _entry_changes(file_id, entry, working_tree):
882
    """\
883
    Determine in which ways the inventory entry has changed.
884
885
    Returns booleans: has_contents, content_mod, meta_mod
886
    has_contents means there are currently contents, but they differ
887
    contents_mod means contents need to be modified
888
    meta_mod means the metadata needs to be modified
889
    """
890
    cur_entry = working_tree.inventory[file_id]
891
    try:
892
        working_kind = working_tree.kind(file_id)
893
        has_contents = True
894
    except OSError, e:
895
        if e.errno != errno.ENOENT:
896
            raise
897
        has_contents = False
898
        contents_mod = True
899
        meta_mod = False
900
    if has_contents is True:
901
        real_e_kind = entry.kind
902
        if real_e_kind == 'root_directory':
903
            real_e_kind = 'directory'
904
        if real_e_kind != working_kind:
905
            contents_mod, meta_mod = True, False
906
        else:
907
            cur_entry._read_tree_state(working_tree.id2path(file_id), 
908
                                       working_tree)
909
            contents_mod, meta_mod = entry.detect_changes(cur_entry)
910
    return has_contents, contents_mod, meta_mod
911
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
912
913
def revert(working_tree, target_tree, filenames, backups=False):
1534.7.55 by Aaron Bentley
Fixed up the change detection
914
    interesting_ids = find_interesting(working_tree, target_tree, filenames)
915
    def interesting(file_id):
916
        return interesting_ids is None or file_id in interesting_ids
917
1534.7.47 by Aaron Bentley
Started work on 'revert'
918
    tt = TreeTransform(working_tree)
919
    try:
1534.7.51 by Aaron Bentley
New approach to revert
920
        trans_id = {}
921
        def get_trans_id(file_id):
1534.7.47 by Aaron Bentley
Started work on 'revert'
922
            try:
1534.7.51 by Aaron Bentley
New approach to revert
923
                return trans_id[file_id]
1534.7.47 by Aaron Bentley
Started work on 'revert'
924
            except KeyError:
1534.7.51 by Aaron Bentley
New approach to revert
925
                return tt.get_id_tree(file_id)
926
927
        for file_id in topology_sorted_ids(target_tree):
1534.7.55 by Aaron Bentley
Fixed up the change detection
928
            if not interesting(file_id):
1534.7.51 by Aaron Bentley
New approach to revert
929
                continue
1534.7.52 by Aaron Bentley
Revert fixes with disappearing roots
930
            if file_id not in working_tree.inventory:
1534.7.51 by Aaron Bentley
New approach to revert
931
                entry = target_tree.inventory[file_id]
932
                parent_id = get_trans_id(entry.parent_id)
933
                e_trans_id = new_by_entry(tt, entry, parent_id, target_tree)
934
                trans_id[file_id] = e_trans_id
1534.7.47 by Aaron Bentley
Started work on 'revert'
935
            else:
1534.7.55 by Aaron Bentley
Fixed up the change detection
936
                change_entry(tt, file_id, working_tree, target_tree, 
1534.7.56 by Aaron Bentley
Implemented the backup file detritus
937
                             get_trans_id, backups, trans_id)
1534.7.51 by Aaron Bentley
New approach to revert
938
        for file_id in working_tree:
1534.7.55 by Aaron Bentley
Fixed up the change detection
939
            if not interesting(file_id):
940
                continue
941
            if file_id not in target_tree:
1534.7.52 by Aaron Bentley
Revert fixes with disappearing roots
942
                tt.unversion_file(tt.get_id_tree(file_id))
1534.7.51 by Aaron Bentley
New approach to revert
943
        resolve_conflicts(tt)
1534.7.28 by Aaron Bentley
Nearly-working build_tree replacement
944
        tt.apply()
945
    finally:
946
        tt.finalize()
1534.7.51 by Aaron Bentley
New approach to revert
947
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
948
1534.7.51 by Aaron Bentley
New approach to revert
949
def resolve_conflicts(tt):
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
950
    """Make many conflict-resolution attempts, but die if they fail"""
951
    for n in range(10):
952
        conflicts = tt.find_conflicts()
953
        if len(conflicts) == 0:
954
            return
955
        conflict_pass(tt, conflicts)
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
956
    raise MalformedTransform(conflicts=conflicts)
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
957
958
959
def conflict_pass(tt, conflicts):
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
960
    for c_type, conflict in ((c[0], c) for c in conflicts):
961
        if c_type == 'duplicate id':
1534.7.51 by Aaron Bentley
New approach to revert
962
            tt.unversion_file(conflict[1])
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
963
        elif c_type == 'duplicate':
1534.7.57 by Aaron Bentley
Enhanced conflict resolution.
964
            # files that were renamed take precedence
965
            new_name = tt.final_name(conflict[1])+'.moved'
966
            final_parent = tt.final_parent(conflict[1])
967
            if tt.path_changed(conflict[1]):
968
                tt.adjust_path(new_name, final_parent, conflict[2])
969
            else:
970
                tt.adjust_path(new_name, final_parent, conflict[1])
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
971
        elif c_type == 'parent loop':
972
            # break the loop by undoing one of the ops that caused the loop
973
            cur = conflict[1]
974
            while not tt.path_changed(cur):
975
                cur = tt.final_parent(cur)
976
            tt.adjust_path(tt.final_name(cur), tt.get_tree_parent(cur), cur)
977
        elif c_type == 'missing parent':
1534.7.128 by Aaron Bentley
Got missing contents test working
978
            trans_id = conflict[1]
979
            try:
980
                tt.cancel_deletion(trans_id)
981
            except KeyError:
982
                tt.create_directory(trans_id)
1534.7.61 by Aaron Bentley
Handled parent loops, missing parents, unversioned parents
983
        elif c_type == 'unversioned parent':
984
            tt.version_file(tt.get_tree_file_id(conflict[1]), conflict[1])
1534.7.74 by Aaron Bentley
Started on TreeTransform merge
985
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
986
987
class Merge3Merger(object):
988
    requires_base = True
989
    supports_reprocess = True
1534.7.107 by Aaron Bentley
Implemented show-base
990
    supports_show_base = True
1534.7.84 by Aaron Bentley
Added reprocess support, support for varying merge types
991
    history_based = False
992
    def __init__(self, working_tree, this_tree, base_tree, other_tree, 
1534.7.107 by Aaron Bentley
Implemented show-base
993
                 reprocess=False, show_base=False):
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
994
        object.__init__(self)
1534.7.83 by Aaron Bentley
differentiated between this tree(input) and working tree(output) in merge.
995
        self.this_tree = working_tree
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
996
        self.base_tree = base_tree
997
        self.other_tree = other_tree
998
        self.conflicts = []
1534.7.87 by Aaron Bentley
Don't set executability on non-files
999
        self.reprocess = reprocess
1534.7.107 by Aaron Bentley
Implemented show-base
1000
        self.show_base = show_base
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
1001
1002
        all_ids = set(base_tree)
1003
        all_ids.update(other_tree)
1004
        self.tt = TreeTransform(working_tree)
1005
        try:
1006
            for file_id in all_ids:
1534.7.105 by Aaron Bentley
Got merge with rename working
1007
                self.merge_names(file_id)
1534.7.89 by Aaron Bentley
Handle all content types in three-way
1008
                file_status = self.merge_contents(file_id)
1534.7.88 by Aaron Bentley
Defer determining trans_id until we need it, because each trans_id carries a cost.
1009
                self.merge_executable(file_id, file_status)
1534.7.85 by Aaron Bentley
Handled merging the execute bit
1010
                
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
1011
            resolve_conflicts(self.tt)
1012
            self.tt.apply()
1013
        finally:
1014
            try:
1015
                self.tt.finalize()
1016
            except:
1017
                pass
1018
       
1019
    @staticmethod
1534.7.74 by Aaron Bentley
Started on TreeTransform merge
1020
    def parent(entry, file_id):
1534.7.105 by Aaron Bentley
Got merge with rename working
1021
        if entry is None:
1022
            return None
1023
        return entry.parent_id
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
1024
1025
    @staticmethod
1534.7.74 by Aaron Bentley
Started on TreeTransform merge
1026
    def name(entry, file_id):
1534.7.105 by Aaron Bentley
Got merge with rename working
1027
        if entry is None:
1028
            return None
1534.7.74 by Aaron Bentley
Started on TreeTransform merge
1029
        return entry.name
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
1030
    
1031
    @staticmethod
1534.7.74 by Aaron Bentley
Started on TreeTransform merge
1032
    def contents_sha1(tree, file_id):
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
1033
        if file_id not in tree:
1034
            return None
1534.7.74 by Aaron Bentley
Started on TreeTransform merge
1035
        return tree.get_file_sha1(file_id)
1036
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
1037
    @staticmethod
1534.7.85 by Aaron Bentley
Handled merging the execute bit
1038
    def executable(tree, file_id):
1039
        if file_id not in tree:
1040
            return None
1534.7.109 by Aaron Bentley
Fixed executability test, so it works with EmptyTree
1041
        if tree.kind(file_id) != "file":
1042
            return False
1534.7.85 by Aaron Bentley
Handled merging the execute bit
1043
        return tree.is_executable(file_id)
1044
1045
    @staticmethod
1534.7.89 by Aaron Bentley
Handle all content types in three-way
1046
    def kind(tree, file_id):
1047
        if file_id not in tree:
1048
            return None
1049
        return tree.kind(file_id)
1050
1051
    @staticmethod
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
1052
    def scalar_three_way(this_tree, base_tree, other_tree, file_id, key):
1053
        """Do a three-way test on a scalar.
1054
        Return "this", "other" or "conflict", depending whether a value wins.
1055
        """
1056
        key_base = key(base_tree, file_id)
1057
        key_other = key(other_tree, file_id)
1058
        #if base == other, either they all agree, or only THIS has changed.
1059
        if key_base == key_other:
1060
            return "this"
1061
        key_this = key(this_tree, file_id)
1062
        if key_this not in (key_base, key_other):
1063
            return "conflict"
1064
        # "Ambiguous clean merge"
1065
        elif key_this == key_other:
1066
            return "this"
1067
        else:
1068
            assert key_this == key_base
1069
            return "other"
1070
1534.7.105 by Aaron Bentley
Got merge with rename working
1071
    def merge_names(self, file_id):
1072
        def get_entry(tree):
1073
            if file_id in tree.inventory:
1074
                return tree.inventory[file_id]
1075
            else:
1076
                return None
1077
        this_entry = get_entry(self.this_tree)
1078
        other_entry = get_entry(self.other_tree)
1079
        base_entry = get_entry(self.base_tree)
1080
        name_winner = self.scalar_three_way(this_entry, base_entry, 
1081
                                            other_entry, file_id, self.name)
1082
        parent_id_winner = self.scalar_three_way(this_entry, base_entry, 
1083
                                                 other_entry, file_id, 
1084
                                                 self.parent)
1085
        if this_entry is None:
1086
            if name_winner == "this":
1087
                name_winner = "other"
1088
            if parent_id_winner == "this":
1089
                parent_id_winner = "other"
1090
        if name_winner == "this" and parent_id_winner == "this":
1091
            return
1534.7.130 by Aaron Bentley
More conflict handling, test porting
1092
        if name_winner == "conflict":
1093
            trans_id = self.tt.get_trans_id(file_id)
1094
            self.conflicts.append(('name conflict', trans_id, 
1095
                                  self.name(this_entry, file_id), 
1096
                                  self.name(other_entry, file_id)))
1097
        if parent_id_winner == "conflict":
1098
            trans_id = self.tt.get_trans_id(file_id)
1099
            self.conflicts.append(('parent conflict', trans_id, 
1100
                                   self.parent(this_entry, file_id), 
1101
                                   self.parent(other_entry, file_id)))
1534.7.105 by Aaron Bentley
Got merge with rename working
1102
        if other_entry is None:
1103
            # it doesn't matter whether the result was 'other' or 
1104
            # 'conflict'-- if there's no 'other', we leave it alone.
1105
            return
1106
        # if we get here, name_winner and parent_winner are set to safe values.
1107
        winner_entry = {"this": this_entry, "other": other_entry, 
1108
                        "conflict": other_entry}
1109
        trans_id = self.tt.get_trans_id(file_id)
1110
        parent_id = winner_entry[parent_id_winner].parent_id
1111
        parent_trans_id = self.tt.get_trans_id(parent_id)
1112
        self.tt.adjust_path(winner_entry[name_winner].name, parent_trans_id,
1113
                            trans_id)
1114
1115
1534.7.89 by Aaron Bentley
Handle all content types in three-way
1116
    def merge_contents(self, file_id):
1117
        def contents_pair(tree):
1118
            if file_id not in tree:
1119
                return (None, None)
1120
            kind = tree.kind(file_id)
1121
            if kind == "file":
1122
                contents = tree.get_file_sha1(file_id)
1123
            elif kind == "symlink":
1124
                contents = tree.get_symlink_target(file_id)
1125
            else:
1126
                contents = None
1127
            return kind, contents
1128
        # See SPOT run.  run, SPOT, run.
1129
        # So we're not QUITE repeating ourselves; we do tricky things with
1130
        # file kind...
1131
        base_pair = contents_pair(self.base_tree)
1132
        other_pair = contents_pair(self.other_tree)
1133
        if base_pair == other_pair:
1134
            return "unmodified"
1135
        this_pair = contents_pair(self.this_tree)
1136
        if this_pair == other_pair:
1137
            return "unmodified"
1534.7.90 by Aaron Bentley
fixed undefined trans_id
1138
        else:
1534.7.89 by Aaron Bentley
Handle all content types in three-way
1139
            trans_id = self.tt.get_trans_id(file_id)
1534.7.90 by Aaron Bentley
fixed undefined trans_id
1140
            if this_pair == base_pair:
1141
                if file_id in self.this_tree:
1142
                    self.tt.delete_contents(trans_id)
1534.7.110 by Aaron Bentley
Handled three-way with deletions better
1143
                if file_id in self.other_tree.inventory:
1144
                    create_by_entry(self.tt, 
1145
                                    self.other_tree.inventory[file_id], 
1146
                                    self.other_tree, trans_id)
1147
                    return "modified"
1148
                if file_id in self.this_tree:
1149
                    self.tt.unversion_file(trans_id)
1150
                    return "deleted"
1534.7.90 by Aaron Bentley
fixed undefined trans_id
1151
            elif this_pair[0] == "file" and other_pair[0] == "file":
1152
                # If this and other are both files, either base is a file, or
1153
                # both converted to files, so at least we have agreement that
1154
                # output should be a file.
1155
                self.text_merge(file_id, trans_id)
1156
                return "modified"
1534.7.89 by Aaron Bentley
Handle all content types in three-way
1157
            else:
1534.7.105 by Aaron Bentley
Got merge with rename working
1158
                trans_id = self.tt.get_trans_id(file_id)
1534.7.101 by Aaron Bentley
Got conflicts on symlinks working properly
1159
                name = self.tt.final_name(trans_id)
1160
                parent_id = self.tt.final_parent(trans_id)
1161
                if file_id in self.this_tree.inventory:
1162
                    self.tt.unversion_file(trans_id)
1534.7.102 by Aaron Bentley
Deleted old pre-conflict contents
1163
                    self.tt.delete_contents(trans_id)
1534.7.105 by Aaron Bentley
Got merge with rename working
1164
                else:
1165
                    self.tt.cancel_versioning(trans_id)
1534.7.128 by Aaron Bentley
Got missing contents test working
1166
                self.conflicts.append(('contents conflict', (file_id)))
1534.7.101 by Aaron Bentley
Got conflicts on symlinks working properly
1167
                file_group = self._dump_conflicts(name, parent_id, file_id, 
1168
                                                  set_version=True)
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
1169
1170
    def get_lines(self, tree, file_id):
1171
        if file_id in tree:
1534.7.79 by Aaron Bentley
Stopped calling get_file_lines on WorkingTree
1172
            return tree.get_file(file_id).readlines()
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
1173
        else:
1174
            return []
1175
1176
    def text_merge(self, file_id, trans_id):
1177
        """Perform a three-way text merge on a file_id"""
1534.7.92 by Aaron Bentley
Handle non-file bases
1178
        # it's possible that we got here with base as a different type.
1179
        # if so, we just want two-way text conflicts.
1534.7.99 by Aaron Bentley
Handle non-existent BASE properly
1180
        if file_id in self.base_tree and \
1181
            self.base_tree.kind(file_id) == "file":
1534.7.92 by Aaron Bentley
Handle non-file bases
1182
            base_lines = self.get_lines(self.base_tree, file_id)
1183
        else:
1184
            base_lines = []
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
1185
        other_lines = self.get_lines(self.other_tree, file_id)
1534.7.83 by Aaron Bentley
differentiated between this tree(input) and working tree(output) in merge.
1186
        this_lines = self.get_lines(self.this_tree, file_id)
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
1187
        m3 = Merge3(base_lines, this_lines, other_lines)
1188
        start_marker = "!START OF MERGE CONFLICT!" + "I HOPE THIS IS UNIQUE"
1534.7.107 by Aaron Bentley
Implemented show-base
1189
        if self.show_base is True:
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
1190
            base_marker = '|' * 7
1191
        else:
1192
            base_marker = None
1193
1194
        def iter_merge3(retval):
1195
            retval["text_conflicts"] = False
1196
            for line in m3.merge_lines(name_a = "TREE", 
1197
                                       name_b = "MERGE-SOURCE", 
1198
                                       name_base = "BASE-REVISION",
1199
                                       start_marker=start_marker, 
1200
                                       base_marker=base_marker,
1534.7.107 by Aaron Bentley
Implemented show-base
1201
                                       reprocess=self.reprocess):
1534.7.77 by Aaron Bentley
Got merge functionality starting to work
1202
                if line.startswith(start_marker):
1203
                    retval["text_conflicts"] = True
1204
                    yield line.replace(start_marker, '<' * 7)
1205
                else:
1206
                    yield line
1207
        retval = {}
1208
        merge3_iterator = iter_merge3(retval)
1209
        self.tt.create_file(merge3_iterator, trans_id)
1210
        if retval["text_conflicts"] is True:
1534.7.82 by Aaron Bentley
Reported text conflicts programatically
1211
            self.conflicts.append(('text conflict', (file_id)))
1534.7.101 by Aaron Bentley
Got conflicts on symlinks working properly
1212
            name = self.tt.final_name(trans_id)
1213
            parent_id = self.tt.final_parent(trans_id)
1214
            file_group = self._dump_conflicts(name, parent_id, file_id, 
1215
                                              this_lines, base_lines,
1216
                                              other_lines)
1217
            file_group.append(trans_id)
1218
1219
    def _dump_conflicts(self, name, parent_id, file_id, this_lines=None, 
1534.8.2 by Aaron Bentley
Implemented weave merge
1220
                        base_lines=None, other_lines=None, set_version=False,
1221
                        no_base=False):
1222
        data = [('OTHER', self.other_tree, other_lines), 
1223
                ('THIS', self.this_tree, this_lines)]
1224
        if not no_base:
1225
            data.append(('BASE', self.base_tree, base_lines))
1534.7.101 by Aaron Bentley
Got conflicts on symlinks working properly
1226
        versioned = False
1227
        file_group = []
1228
        for suffix, tree, lines in data:
1229
            if file_id in tree:
1230
                trans_id = self._conflict_file(name, parent_id, tree, file_id,
1231
                                               suffix, lines)
1232
                file_group.append(trans_id)
1534.7.104 by Aaron Bentley
Fixed set_versioned, enhanced conflict testing
1233
                if set_version and not versioned:
1234
                    self.tt.version_file(file_id, trans_id)
1235
                    versioned = True
1534.7.101 by Aaron Bentley
Got conflicts on symlinks working properly
1236
        return file_group
1534.7.85 by Aaron Bentley
Handled merging the execute bit
1237
           
1534.7.101 by Aaron Bentley
Got conflicts on symlinks working properly
1238
    def _conflict_file(self, name, parent_id, tree, file_id, suffix, 
1239
                       lines=None):
1240
        name = name + '.' + suffix
1534.7.97 by Aaron Bentley
Ensured foo.BASE is a directory if there's a conflict
1241
        trans_id = self.tt.create_path(name, parent_id)
1242
        entry = tree.inventory[file_id]
1243
        create_by_entry(self.tt, entry, tree, trans_id, lines)
1244
        return trans_id
1534.7.85 by Aaron Bentley
Handled merging the execute bit
1245
1534.7.88 by Aaron Bentley
Defer determining trans_id until we need it, because each trans_id carries a cost.
1246
    def merge_executable(self, file_id, file_status):
1247
        if file_status == "deleted":
1534.7.85 by Aaron Bentley
Handled merging the execute bit
1248
            return
1534.7.125 by Aaron Bentley
Fixed executability merge bug
1249
        trans_id = self.tt.get_trans_id(file_id)
1250
        try:
1251
            if self.tt.final_kind(trans_id) != "file":
1252
                return
1253
        except NoSuchFile:
1534.7.111 by Aaron Bentley
Avoid non-file executability
1254
            return
1534.7.85 by Aaron Bentley
Handled merging the execute bit
1255
        winner = self.scalar_three_way(self.this_tree, self.base_tree, 
1256
                                       self.other_tree, file_id, 
1257
                                       self.executable)
1258
        if winner == "conflict":
1259
        # There must be a None in here, if we have a conflict, but we
1260
        # need executability since file status was not deleted.
1261
            if self.other_tree.is_executable(file_id) is None:
1262
                winner == "this"
1263
            else:
1264
                winner == "other"
1265
        if winner == "this":
1266
            if file_status == "modified":
1267
                executability = self.this_tree.is_executable(file_id)
1534.7.108 by Aaron Bentley
Fixed executability handling
1268
                if executability is not None:
1269
                    trans_id = self.tt.get_trans_id(file_id)
1270
                    self.tt.set_executability(executability, trans_id)
1534.7.85 by Aaron Bentley
Handled merging the execute bit
1271
        else:
1272
            assert winner == "other"
1534.7.126 by Aaron Bentley
Fixed executability merge bug
1273
            if file_id in self.other_tree:
1274
                executability = self.other_tree.is_executable(file_id)
1275
            elif file_id in self.this_tree:
1276
                executability = self.this_tree.is_executable(file_id)
1277
            elif file_id in self.base_tree:
1278
                executability = self.base_tree.is_executable(file_id)
1279
            if executability is not None:
1280
                trans_id = self.tt.get_trans_id(file_id)
1281
                self.tt.set_executability(executability, trans_id)
1534.8.2 by Aaron Bentley
Implemented weave merge
1282
1283
class WeaveMerger(Merge3Merger):
1284
    supports_reprocess = False
1285
    supports_show_base = False
1286
    def _merged_lines(self, file_id):
1287
        """Generate the merged lines.
1288
        There is no distinction between lines that are meant to contain <<<<<<<
1289
        and conflicts.
1290
        """
1291
        if getattr(self.this_tree, 'get_weave', False) is False:
1292
            # If we have a WorkingTree, try using the basis
1293
            wt_sha1 = self.this_tree.get_file_sha1(file_id)
1294
            this_tree = self.this_tree.branch.basis_tree()
1295
            if this_tree.get_file_sha1(file_id) != wt_sha1:
1296
                raise WorkingTreeNotRevision(self.this_tree)
1297
        else:
1298
            this_tree = self.this_tree
1299
        weave = this_tree.get_weave(file_id)
1300
        this_revision_id = this_tree.inventory[file_id].revision
1301
        other_revision_id = self.other_tree.inventory[file_id].revision
1302
        this_i = weave.lookup(this_revision_id)
1303
        other_i = weave.lookup(other_revision_id)
1304
        plan =  weave.plan_merge(this_i, other_i)
1305
        return weave.weave_merge(plan)
1306
1307
    def text_merge(self, file_id, trans_id):
1308
        lines = self._merged_lines(file_id)
1309
        conflicts = '<<<<<<<\n' in lines
1310
        self.tt.create_file(lines, trans_id)
1311
        if conflicts:
1312
            self.conflicts.append(('text conflict', (file_id)))
1313
            name = self.tt.final_name(trans_id)
1314
            parent_id = self.tt.final_parent(trans_id)
1315
            file_group = self._dump_conflicts(name, parent_id, file_id, 
1316
                                              no_base=True)
1317
            file_group.append(trans_id)
1534.8.3 by Aaron Bentley
Added Diff3 merging for tree transforms
1318
1319
1320
class Diff3Merger(Merge3Merger):
1321
    """Use good ol' diff3 to do text merges"""
1322
    def dump_file(self, temp_dir, name, tree, file_id):
1323
        out_path = pathjoin(temp_dir, name)
1324
        out_file = file(out_path, "wb")
1325
        in_file = tree.get_file(file_id)
1326
        for line in in_file:
1327
            out_file.write(line)
1328
        return out_path
1329
1330
    def text_merge(self, file_id, trans_id):
1331
        import bzrlib.patch
1332
        temp_dir = mkdtemp(prefix="bzr-")
1333
        try:
1334
            new_file = os.path.join(temp_dir, "new")
1335
            this = self.dump_file(temp_dir, "this", self.this_tree, file_id)
1336
            base = self.dump_file(temp_dir, "base", self.base_tree, file_id)
1337
            other = self.dump_file(temp_dir, "other", self.other_tree, file_id)
1338
            status = bzrlib.patch.diff3(new_file, this, base, other)
1339
            if status not in (0, 1):
1340
                raise BzrError("Unhandled diff3 exit code")
1341
            self.tt.create_file(file(new_file, "rb"), trans_id)
1342
            if status == 1:
1343
                name = self.tt.final_name(trans_id)
1344
                parent_id = self.tt.final_parent(trans_id)
1345
                self._dump_conflicts(name, parent_id, file_id)
1346
            self.conflicts.append(('text conflict', (file_id)))
1347
        finally:
1348
            rmtree(temp_dir)