~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.
210
        """
211
        path_set = set(paths)
212
        ids = {}
213
        selected_paths = set()
214
        new_conflicts = ConflictList()
215
        selected_conflicts = ConflictList()
216
        for path in paths:
217
            file_id = tree.path2id(path)
218
            if file_id is not None:
219
                ids[file_id] = path
220
221
        for conflict in self:
222
            selected = False
223
            for key in ('path', 'conflict_path'):
224
                cpath = getattr(conflict, key, None)
225
                if cpath is None:
226
                    continue
227
                if cpath in path_set:
228
                    selected = True
229
                    selected_paths.add(cpath)
230
            for key in ('file_id', 'conflict_file_id'):
231
                cfile_id = getattr(conflict, key, None)
232
                if cfile_id is None:
233
                    continue
234
                try:
235
                    cpath = ids[cfile_id]
236
                except KeyError:
237
                    continue
238
                selected = True
239
                selected_paths.add(cpath)
240
            if selected:
241
                selected_conflicts.append(conflict)
242
            else:
243
                new_conflicts.append(conflict)
244
        if ignore_misses is not True:
245
            for path in [p for p in paths if p not in selected_paths]:
246
                if not os.path.exists(tree.abspath(path)):
247
                    print "%s does not exist" % path
248
                else:
249
                    print "%s is not conflicted" % path
250
        return new_conflicts, selected_conflicts
251
252
 
1534.10.18 by Aaron Bentley
Defined all new Conflict types
253
class Conflict(object):
254
    """Base class for all types of conflict"""
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
255
256
    has_files = False
257
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
258
    def __init__(self, path, file_id=None):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
259
        self.path = path
260
        self.file_id = file_id
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
261
262
    def as_stanza(self):
263
        s = Stanza(type=self.typestring, path=self.path)
264
        if self.file_id is not None:
265
            s.add('file_id', self.file_id)
266
        return s
267
1534.10.22 by Aaron Bentley
Got ConflictList implemented
268
    def _cmp_list(self):
269
        return [type(self), self.path, self.file_id]
270
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
271
    def __cmp__(self, other):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
272
        if getattr(other, "_cmp_list", None) is None:
273
            return -1
274
        return cmp(self._cmp_list(), other._cmp_list())
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
275
276
    def __eq__(self, other):
277
        return self.__cmp__(other) == 0
278
279
    def __ne__(self, other):
280
        return not self.__eq__(other)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
281
1534.10.20 by Aaron Bentley
Got all tests passing
282
    def __str__(self):
283
        return self.format % self.__dict__
284
1534.10.22 by Aaron Bentley
Got ConflictList implemented
285
    def __repr__(self):
286
        rdict = dict(self.__dict__)
287
        rdict['class'] = self.__class__.__name__
288
        return self.rformat % rdict
289
1534.10.18 by Aaron Bentley
Defined all new Conflict types
290
    @staticmethod
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
291
    def factory(type, **kwargs):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
292
        global ctype
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
293
        return ctype[type](**kwargs)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
294
1666.1.4 by Robert Collins
* 'Metadir' is now the default disk format. This improves behaviour in
295
    @staticmethod
296
    def sort_key(conflict):
297
        if conflict.path is not None:
298
            return conflict.path, conflict.typestring
299
        elif getattr(conflict, "conflict_path", None) is not None:
300
            return conflict.conflict_path, conflict.typestring
301
        else:
302
            return None, conflict.typestring
303
1534.10.18 by Aaron Bentley
Defined all new Conflict types
304
305
class PathConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
306
    """A conflict was encountered merging file paths"""
307
1534.10.18 by Aaron Bentley
Defined all new Conflict types
308
    typestring = 'path conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
309
1534.10.20 by Aaron Bentley
Got all tests passing
310
    format = 'Path conflict: %(path)s / %(conflict_path)s'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
311
312
    rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'
1534.10.20 by Aaron Bentley
Got all tests passing
313
    def __init__(self, path, conflict_path=None, file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
314
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
315
        self.conflict_path = conflict_path
316
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
317
    def as_stanza(self):
318
        s = Conflict.as_stanza(self)
1534.10.20 by Aaron Bentley
Got all tests passing
319
        if self.conflict_path is not None:
320
            s.add('conflict_path', self.conflict_path)
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
321
        return s
322
1534.10.20 by Aaron Bentley
Got all tests passing
323
324
class ContentsConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
325
    """The files are of different types, or not present"""
326
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
327
    has_files = True
328
1534.10.20 by Aaron Bentley
Got all tests passing
329
    typestring = 'contents conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
330
1534.10.20 by Aaron Bentley
Got all tests passing
331
    format = 'Contents conflict in %(path)s'
332
333
334
class TextConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
335
    """The merge algorithm could not resolve all differences encountered."""
336
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
337
    has_files = True
338
1534.10.20 by Aaron Bentley
Got all tests passing
339
    typestring = 'text conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
340
1534.10.20 by Aaron Bentley
Got all tests passing
341
    format = 'Text conflict in %(path)s'
342
343
1534.10.18 by Aaron Bentley
Defined all new Conflict types
344
class HandledConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
345
    """A path problem that has been provisionally resolved.
346
    This is intended to be a base class.
347
    """
348
349
    rformat = "%(class)s(%(action)r, %(path)r, %(file_id)r)"
350
    
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
351
    def __init__(self, action, path, file_id=None):
352
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
353
        self.action = action
354
1534.10.22 by Aaron Bentley
Got ConflictList implemented
355
    def _cmp_list(self):
356
        return Conflict._cmp_list(self) + [self.action]
357
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
358
    def as_stanza(self):
359
        s = Conflict.as_stanza(self)
360
        s.add('action', self.action)
361
        return s
362
1534.10.20 by Aaron Bentley
Got all tests passing
363
1534.10.18 by Aaron Bentley
Defined all new Conflict types
364
class HandledPathConflict(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
365
    """A provisionally-resolved path problem involving two paths.
366
    This is intended to be a base class.
367
    """
368
369
    rformat = "%(class)s(%(action)r, %(path)r, %(conflict_path)r,"\
370
        " %(file_id)r, %(conflict_file_id)r)"
371
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
372
    def __init__(self, action, path, conflict_path, file_id=None,
1534.10.18 by Aaron Bentley
Defined all new Conflict types
373
                 conflict_file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
374
        HandledConflict.__init__(self, action, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
375
        self.conflict_path = conflict_path 
376
        self.conflict_file_id = conflict_file_id
377
        
1534.10.22 by Aaron Bentley
Got ConflictList implemented
378
    def _cmp_list(self):
379
        return HandledConflict._cmp_list(self) + [self.conflict_path, 
380
                                                  self.conflict_file_id]
381
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
382
    def as_stanza(self):
383
        s = HandledConflict.as_stanza(self)
384
        s.add('conflict_path', self.conflict_path)
385
        if self.conflict_file_id is not None:
386
            s.add('conflict_file_id', self.conflict_file_id)
387
            
388
        return s
1534.10.20 by Aaron Bentley
Got all tests passing
389
390
1534.10.18 by Aaron Bentley
Defined all new Conflict types
391
class DuplicateID(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
392
    """Two files want the same file_id."""
393
1534.10.18 by Aaron Bentley
Defined all new Conflict types
394
    typestring = 'duplicate id'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
395
1534.10.20 by Aaron Bentley
Got all tests passing
396
    format = 'Conflict adding id to %(conflict_path)s.  %(action)s %(path)s.'
397
1534.10.18 by Aaron Bentley
Defined all new Conflict types
398
399
class DuplicateEntry(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
400
    """Two directory entries want to have the same name."""
401
1534.10.18 by Aaron Bentley
Defined all new Conflict types
402
    typestring = 'duplicate'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
403
1534.10.20 by Aaron Bentley
Got all tests passing
404
    format = 'Conflict adding file %(conflict_path)s.  %(action)s %(path)s.'
405
1534.10.18 by Aaron Bentley
Defined all new Conflict types
406
407
class ParentLoop(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
408
    """An attempt to create an infinitely-looping directory structure.
409
    This is rare, but can be produced like so:
410
411
    tree A:
412
      mv foo/bar
413
    tree B:
414
      mv bar/foo
415
    merge A and B
416
    """
417
1534.10.18 by Aaron Bentley
Defined all new Conflict types
418
    typestring = 'parent loop'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
419
1534.10.20 by Aaron Bentley
Got all tests passing
420
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
421
1534.10.18 by Aaron Bentley
Defined all new Conflict types
422
423
class UnversionedParent(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
424
    """An attempt to version an file whose parent directory is not versioned.
425
    Typically, the result of a merge where one tree unversioned the directory
426
    and the other added a versioned file to it.
427
    """
428
1534.10.18 by Aaron Bentley
Defined all new Conflict types
429
    typestring = 'unversioned parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
430
1534.10.20 by Aaron Bentley
Got all tests passing
431
    format = 'Conflict adding versioned files to %(path)s.  %(action)s.'
432
1534.10.18 by Aaron Bentley
Defined all new Conflict types
433
434
class MissingParent(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
435
    """An attempt to add files to a directory that is not present.
436
    Typically, the result of a merge where one tree deleted the directory and
437
    the other added a file to it.
438
    """
439
1534.10.18 by Aaron Bentley
Defined all new Conflict types
440
    typestring = 'missing parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
441
1534.10.20 by Aaron Bentley
Got all tests passing
442
    format = 'Conflict adding files to %(path)s.  %(action)s.'
443
444
1534.10.18 by Aaron Bentley
Defined all new Conflict types
445
446
ctype = {}
1534.10.20 by Aaron Bentley
Got all tests passing
447
448
1534.10.18 by Aaron Bentley
Defined all new Conflict types
449
def register_types(*conflict_types):
450
    """Register a Conflict subclass for serialization purposes"""
451
    global ctype
452
    for conflict_type in conflict_types:
453
        ctype[conflict_type.typestring] = conflict_type
454
1534.10.20 by Aaron Bentley
Got all tests passing
455
1534.10.18 by Aaron Bentley
Defined all new Conflict types
456
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
1534.10.20 by Aaron Bentley
Got all tests passing
457
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,)