~bzr-pqm/bzr/bzr.dev

1185.14.3 by Aaron Bentley
Copied conflict lister in
1
# Copyright (C) 2005 by Aaron Bentley
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
1185.14.3 by Aaron Bentley
Copied conflict lister in
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
1185.14.3 by Aaron Bentley
Copied conflict lister in
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
1185.14.3 by Aaron Bentley
Copied conflict lister in
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
1185.16.11 by Martin Pool
todo
17
# TODO: Move this into builtins
18
19
# TODO: 'bzr resolve' should accept a directory name and work from that 
20
# point down
21
1185.16.33 by Martin Pool
- move 'conflict' and 'resolved' from shipped plugin to regular builtins
22
import os
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
23
24
from bzrlib.lazy_import import lazy_import
25
lazy_import(globals(), """
1185.16.33 by Martin Pool
- move 'conflict' and 'resolved' from shipped plugin to regular builtins
26
import errno
27
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
28
from bzrlib import (
29
    commands,
30
    errors,
31
    osutils,
32
    rio,
33
    )
34
""")
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
35
from bzrlib.option import Option
1534.10.6 by Aaron Bentley
Conflict serialization working for WorkingTree3
36
37
38
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
39
1185.14.3 by Aaron Bentley
Copied conflict lister in
40
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
41
class cmd_conflicts(commands.Command):
1185.14.3 by Aaron Bentley
Copied conflict lister in
42
    """List files with conflicts.
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
43
44
    Merge will do its best to combine the changes in two branches, but there
45
    are some kinds of problems only a human can fix.  When it encounters those,
46
    it will mark a conflict.  A conflict means that you need to fix something,
47
    before you should commit.
48
49
    Use bzr resolve when you have fixed a problem.
50
1185.14.3 by Aaron Bentley
Copied conflict lister in
51
    (conflicts are determined by the presence of .BASE .TREE, and .OTHER 
52
    files.)
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
53
54
    See also bzr resolve.
1185.14.3 by Aaron Bentley
Copied conflict lister in
55
    """
56
    def run(self):
1534.10.6 by Aaron Bentley
Conflict serialization working for WorkingTree3
57
        from bzrlib.workingtree import WorkingTree
1534.10.9 by Aaron Bentley
Switched display functions to conflict_lines
58
        wt = WorkingTree.open_containing(u'.')[0]
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
59
        for conflict in wt.conflicts():
1534.10.9 by Aaron Bentley
Switched display functions to conflict_lines
60
            print conflict
1185.14.3 by Aaron Bentley
Copied conflict lister in
61
1652.1.1 by Martin Pool
Fix 'bzr resolve' run from subdirectory
62
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
63
class cmd_resolve(commands.Command):
1185.14.3 by Aaron Bentley
Copied conflict lister in
64
    """Mark a conflict as resolved.
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
65
66
    Merge will do its best to combine the changes in two branches, but there
67
    are some kinds of problems only a human can fix.  When it encounters those,
68
    it will mark a conflict.  A conflict means that you need to fix something,
69
    before you should commit.
70
71
    Once you have fixed a problem, use "bzr resolve FILE.." to mark
72
    individual files as fixed, or "bzr resolve --all" to mark all conflicts as
73
    resolved.
74
75
    See also bzr conflicts.
1185.14.3 by Aaron Bentley
Copied conflict lister in
76
    """
1185.33.24 by Martin Pool
Add alias 'resolved'
77
    aliases = ['resolved']
1185.14.3 by Aaron Bentley
Copied conflict lister in
78
    takes_args = ['file*']
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
79
    takes_options = [Option('all', help='Resolve all conflicts in this tree')]
1185.14.3 by Aaron Bentley
Copied conflict lister in
80
    def run(self, file_list=None, all=False):
1534.10.6 by Aaron Bentley
Conflict serialization working for WorkingTree3
81
        from bzrlib.workingtree import WorkingTree
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
82
        if all:
83
            if file_list:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
84
                raise errors.BzrCommandError("If --all is specified,"
85
                                             " no FILE may be provided")
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
86
            tree = WorkingTree.open_containing('.')[0]
87
            resolve(tree)
1185.14.3 by Aaron Bentley
Copied conflict lister in
88
        else:
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
89
            if file_list is None:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
90
                raise errors.BzrCommandError("command 'resolve' needs one or"
91
                                             " more FILE, or --all")
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
92
            tree = WorkingTree.open_containing(file_list[0])[0]
93
            to_resolve = [tree.relpath(p) for p in file_list]
94
            resolve(tree, to_resolve)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
95
96
1534.10.15 by Aaron Bentley
Revert does resolve
97
def resolve(tree, paths=None, ignore_misses=False):
1997.1.3 by Robert Collins
All WorkingTree methods which write to the tree, but not to the branch
98
    tree.lock_tree_write()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
99
    try:
1534.10.23 by Aaron Bentley
Removed conflicts_to_stanzas and stanzas_to_conflicts
100
        tree_conflicts = tree.conflicts()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
101
        if paths is None:
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
102
            new_conflicts = ConflictList()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
103
            selected_conflicts = tree_conflicts
104
        else:
105
            new_conflicts, selected_conflicts = \
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
106
                tree_conflicts.select_conflicts(tree, paths, ignore_misses)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
107
        try:
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
108
            tree.set_conflicts(new_conflicts)
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
109
        except errors.UnsupportedOperation:
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
110
            pass
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
111
        selected_conflicts.remove_files(tree)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
112
    finally:
113
        tree.unlock()
114
115
1185.35.1 by Aaron Bentley
Implemented conflicts.restore
116
def restore(filename):
117
    """\
118
    Restore a conflicted file to the state it was in before merging.
119
    Only text restoration supported at present.
120
    """
121
    conflicted = False
122
    try:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
123
        osutils.rename(filename + ".THIS", filename)
1185.35.1 by Aaron Bentley
Implemented conflicts.restore
124
        conflicted = True
125
    except OSError, e:
126
        if e.errno != errno.ENOENT:
127
            raise
128
    try:
129
        os.unlink(filename + ".BASE")
130
        conflicted = True
131
    except OSError, e:
132
        if e.errno != errno.ENOENT:
133
            raise
134
    try:
135
        os.unlink(filename + ".OTHER")
136
        conflicted = True
137
    except OSError, e:
138
        if e.errno != errno.ENOENT:
139
            raise
140
    if not conflicted:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
141
        raise errors.NotConflicted(filename)
1534.10.4 by Aaron Bentley
Implemented conflict serialization
142
143
1534.10.22 by Aaron Bentley
Got ConflictList implemented
144
class ConflictList(object):
1652.1.1 by Martin Pool
Fix 'bzr resolve' run from subdirectory
145
    """List of conflicts.
146
147
    Typically obtained from WorkingTree.conflicts()
148
1534.10.22 by Aaron Bentley
Got ConflictList implemented
149
    Can be instantiated from stanzas or from Conflict subclasses.
150
    """
151
152
    def __init__(self, conflicts=None):
153
        object.__init__(self)
154
        if conflicts is None:
155
            self.__list = []
156
        else:
157
            self.__list = conflicts
158
1652.1.1 by Martin Pool
Fix 'bzr resolve' run from subdirectory
159
    def is_empty(self):
160
        return len(self.__list) == 0
161
1534.10.22 by Aaron Bentley
Got ConflictList implemented
162
    def __len__(self):
163
        return len(self.__list)
164
165
    def __iter__(self):
166
        return iter(self.__list)
167
168
    def __getitem__(self, key):
169
        return self.__list[key]
170
171
    def append(self, conflict):
172
        return self.__list.append(conflict)
173
174
    def __eq__(self, other_list):
175
        return list(self) == list(other_list)
176
177
    def __ne__(self, other_list):
178
        return not (self == other_list)
179
180
    def __repr__(self):
181
        return "ConflictList(%r)" % self.__list
182
183
    @staticmethod
184
    def from_stanzas(stanzas):
185
        """Produce a new ConflictList from an iterable of stanzas"""
186
        conflicts = ConflictList()
187
        for stanza in stanzas:
188
            conflicts.append(Conflict.factory(**stanza.as_dict()))
189
        return conflicts
190
191
    def to_stanzas(self):
192
        """Generator of stanzas"""
193
        for conflict in self:
194
            yield conflict.as_stanza()
195
            
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
196
    def to_strings(self):
197
        """Generate strings for the provided conflicts"""
198
        for conflict in self:
199
            yield str(conflict)
200
201
    def remove_files(self, tree):
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
202
        """Remove the THIS, BASE and OTHER files for listed conflicts"""
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
203
        for conflict in self:
204
            if not conflict.has_files:
205
                continue
206
            for suffix in CONFLICT_SUFFIXES:
207
                try:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
208
                    osutils.delete_any(tree.abspath(conflict.path+suffix))
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
209
                except OSError, e:
210
                    if e.errno != errno.ENOENT:
211
                        raise
1534.10.21 by Aaron Bentley
Moved and renamed conflict functions
212
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
213
    def select_conflicts(self, tree, paths, ignore_misses=False):
214
        """Select the conflicts associated with paths in a tree.
215
        
216
        File-ids are also used for this.
1551.7.10 by Aaron Bentley
Remerge doesn't clear unrelated conflicts
217
        :return: a pair of ConflictLists: (not_selected, selected)
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
218
        """
219
        path_set = set(paths)
220
        ids = {}
221
        selected_paths = set()
222
        new_conflicts = ConflictList()
223
        selected_conflicts = ConflictList()
224
        for path in paths:
225
            file_id = tree.path2id(path)
226
            if file_id is not None:
227
                ids[file_id] = path
228
229
        for conflict in self:
230
            selected = False
231
            for key in ('path', 'conflict_path'):
232
                cpath = getattr(conflict, key, None)
233
                if cpath is None:
234
                    continue
235
                if cpath in path_set:
236
                    selected = True
237
                    selected_paths.add(cpath)
238
            for key in ('file_id', 'conflict_file_id'):
239
                cfile_id = getattr(conflict, key, None)
240
                if cfile_id is None:
241
                    continue
242
                try:
243
                    cpath = ids[cfile_id]
244
                except KeyError:
245
                    continue
246
                selected = True
247
                selected_paths.add(cpath)
248
            if selected:
249
                selected_conflicts.append(conflict)
250
            else:
251
                new_conflicts.append(conflict)
252
        if ignore_misses is not True:
253
            for path in [p for p in paths if p not in selected_paths]:
254
                if not os.path.exists(tree.abspath(path)):
255
                    print "%s does not exist" % path
256
                else:
257
                    print "%s is not conflicted" % path
258
        return new_conflicts, selected_conflicts
259
260
 
1534.10.18 by Aaron Bentley
Defined all new Conflict types
261
class Conflict(object):
262
    """Base class for all types of conflict"""
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
263
264
    has_files = False
265
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
266
    def __init__(self, path, file_id=None):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
267
        self.path = path
268
        self.file_id = file_id
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
269
270
    def as_stanza(self):
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
271
        s = rio.Stanza(type=self.typestring, path=self.path)
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
272
        if self.file_id is not None:
273
            s.add('file_id', self.file_id)
274
        return s
275
1534.10.22 by Aaron Bentley
Got ConflictList implemented
276
    def _cmp_list(self):
277
        return [type(self), self.path, self.file_id]
278
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
279
    def __cmp__(self, other):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
280
        if getattr(other, "_cmp_list", None) is None:
281
            return -1
282
        return cmp(self._cmp_list(), other._cmp_list())
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
283
1551.7.11 by Aaron Bentley
Add WorkingTree.add_conflicts
284
    def __hash__(self):
285
        return hash((type(self), self.path, self.file_id))
286
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
287
    def __eq__(self, other):
288
        return self.__cmp__(other) == 0
289
290
    def __ne__(self, other):
291
        return not self.__eq__(other)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
292
1534.10.20 by Aaron Bentley
Got all tests passing
293
    def __str__(self):
294
        return self.format % self.__dict__
295
1534.10.22 by Aaron Bentley
Got ConflictList implemented
296
    def __repr__(self):
297
        rdict = dict(self.__dict__)
298
        rdict['class'] = self.__class__.__name__
299
        return self.rformat % rdict
300
1534.10.18 by Aaron Bentley
Defined all new Conflict types
301
    @staticmethod
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
302
    def factory(type, **kwargs):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
303
        global ctype
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
304
        return ctype[type](**kwargs)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
305
1666.1.4 by Robert Collins
* 'Metadir' is now the default disk format. This improves behaviour in
306
    @staticmethod
307
    def sort_key(conflict):
308
        if conflict.path is not None:
309
            return conflict.path, conflict.typestring
310
        elif getattr(conflict, "conflict_path", None) is not None:
311
            return conflict.conflict_path, conflict.typestring
312
        else:
313
            return None, conflict.typestring
314
1534.10.18 by Aaron Bentley
Defined all new Conflict types
315
316
class PathConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
317
    """A conflict was encountered merging file paths"""
318
1534.10.18 by Aaron Bentley
Defined all new Conflict types
319
    typestring = 'path conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
320
1534.10.20 by Aaron Bentley
Got all tests passing
321
    format = 'Path conflict: %(path)s / %(conflict_path)s'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
322
323
    rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'
1534.10.20 by Aaron Bentley
Got all tests passing
324
    def __init__(self, path, conflict_path=None, file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
325
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
326
        self.conflict_path = conflict_path
327
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
328
    def as_stanza(self):
329
        s = Conflict.as_stanza(self)
1534.10.20 by Aaron Bentley
Got all tests passing
330
        if self.conflict_path is not None:
331
            s.add('conflict_path', self.conflict_path)
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
332
        return s
333
1534.10.20 by Aaron Bentley
Got all tests passing
334
335
class ContentsConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
336
    """The files are of different types, or not present"""
337
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
338
    has_files = True
339
1534.10.20 by Aaron Bentley
Got all tests passing
340
    typestring = 'contents conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
341
1534.10.20 by Aaron Bentley
Got all tests passing
342
    format = 'Contents conflict in %(path)s'
343
344
345
class TextConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
346
    """The merge algorithm could not resolve all differences encountered."""
347
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
348
    has_files = True
349
1534.10.20 by Aaron Bentley
Got all tests passing
350
    typestring = 'text conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
351
1534.10.20 by Aaron Bentley
Got all tests passing
352
    format = 'Text conflict in %(path)s'
353
354
1534.10.18 by Aaron Bentley
Defined all new Conflict types
355
class HandledConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
356
    """A path problem that has been provisionally resolved.
357
    This is intended to be a base class.
358
    """
359
360
    rformat = "%(class)s(%(action)r, %(path)r, %(file_id)r)"
361
    
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
362
    def __init__(self, action, path, file_id=None):
363
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
364
        self.action = action
365
1534.10.22 by Aaron Bentley
Got ConflictList implemented
366
    def _cmp_list(self):
367
        return Conflict._cmp_list(self) + [self.action]
368
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
369
    def as_stanza(self):
370
        s = Conflict.as_stanza(self)
371
        s.add('action', self.action)
372
        return s
373
1534.10.20 by Aaron Bentley
Got all tests passing
374
1534.10.18 by Aaron Bentley
Defined all new Conflict types
375
class HandledPathConflict(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
376
    """A provisionally-resolved path problem involving two paths.
377
    This is intended to be a base class.
378
    """
379
380
    rformat = "%(class)s(%(action)r, %(path)r, %(conflict_path)r,"\
381
        " %(file_id)r, %(conflict_file_id)r)"
382
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
383
    def __init__(self, action, path, conflict_path, file_id=None,
1534.10.18 by Aaron Bentley
Defined all new Conflict types
384
                 conflict_file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
385
        HandledConflict.__init__(self, action, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
386
        self.conflict_path = conflict_path 
387
        self.conflict_file_id = conflict_file_id
388
        
1534.10.22 by Aaron Bentley
Got ConflictList implemented
389
    def _cmp_list(self):
390
        return HandledConflict._cmp_list(self) + [self.conflict_path, 
391
                                                  self.conflict_file_id]
392
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
393
    def as_stanza(self):
394
        s = HandledConflict.as_stanza(self)
395
        s.add('conflict_path', self.conflict_path)
396
        if self.conflict_file_id is not None:
397
            s.add('conflict_file_id', self.conflict_file_id)
398
            
399
        return s
1534.10.20 by Aaron Bentley
Got all tests passing
400
401
1534.10.18 by Aaron Bentley
Defined all new Conflict types
402
class DuplicateID(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
403
    """Two files want the same file_id."""
404
1534.10.18 by Aaron Bentley
Defined all new Conflict types
405
    typestring = 'duplicate id'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
406
1534.10.20 by Aaron Bentley
Got all tests passing
407
    format = 'Conflict adding id to %(conflict_path)s.  %(action)s %(path)s.'
408
1534.10.18 by Aaron Bentley
Defined all new Conflict types
409
410
class DuplicateEntry(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
411
    """Two directory entries want to have the same name."""
412
1534.10.18 by Aaron Bentley
Defined all new Conflict types
413
    typestring = 'duplicate'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
414
1534.10.20 by Aaron Bentley
Got all tests passing
415
    format = 'Conflict adding file %(conflict_path)s.  %(action)s %(path)s.'
416
1534.10.18 by Aaron Bentley
Defined all new Conflict types
417
418
class ParentLoop(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
419
    """An attempt to create an infinitely-looping directory structure.
420
    This is rare, but can be produced like so:
421
422
    tree A:
423
      mv foo/bar
424
    tree B:
425
      mv bar/foo
426
    merge A and B
427
    """
428
1534.10.18 by Aaron Bentley
Defined all new Conflict types
429
    typestring = 'parent loop'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
430
1534.10.20 by Aaron Bentley
Got all tests passing
431
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
432
1534.10.18 by Aaron Bentley
Defined all new Conflict types
433
434
class UnversionedParent(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
435
    """An attempt to version an file whose parent directory is not versioned.
436
    Typically, the result of a merge where one tree unversioned the directory
437
    and the other added a versioned file to it.
438
    """
439
1534.10.18 by Aaron Bentley
Defined all new Conflict types
440
    typestring = 'unversioned parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
441
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
442
    format = 'Conflict because %(path)s is not versioned, but has versioned'\
443
             ' children.  %(action)s.'
1534.10.20 by Aaron Bentley
Got all tests passing
444
1534.10.18 by Aaron Bentley
Defined all new Conflict types
445
446
class MissingParent(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
447
    """An attempt to add files to a directory that is not present.
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
448
    Typically, the result of a merge where THIS deleted the directory and
449
    the OTHER added a file to it.
450
    See also: DeletingParent (same situation, reversed THIS and OTHER)
1534.10.22 by Aaron Bentley
Got ConflictList implemented
451
    """
452
1534.10.18 by Aaron Bentley
Defined all new Conflict types
453
    typestring = 'missing parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
454
1534.10.20 by Aaron Bentley
Got all tests passing
455
    format = 'Conflict adding files to %(path)s.  %(action)s.'
456
457
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
458
class DeletingParent(HandledConflict):
459
    """An attempt to add files to a directory that is not present.
460
    Typically, the result of a merge where one OTHER deleted the directory and
461
    the THIS added a file to it.
462
    """
463
464
    typestring = 'deleting parent'
465
1551.8.23 by Aaron Bentley
Tweaked conflict message to be more understandable
466
    format = "Conflict: can't delete %(path)s because it is not empty.  "\
467
             "%(action)s."
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
468
1534.10.18 by Aaron Bentley
Defined all new Conflict types
469
470
ctype = {}
1534.10.20 by Aaron Bentley
Got all tests passing
471
472
1534.10.18 by Aaron Bentley
Defined all new Conflict types
473
def register_types(*conflict_types):
474
    """Register a Conflict subclass for serialization purposes"""
475
    global ctype
476
    for conflict_type in conflict_types:
477
        ctype[conflict_type.typestring] = conflict_type
478
1534.10.20 by Aaron Bentley
Got all tests passing
479
1534.10.18 by Aaron Bentley
Defined all new Conflict types
480
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
481
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
482
               DeletingParent,)