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