~bzr-pqm/bzr/bzr.dev

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