/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/conflicts.py

  • Committer: Robert Collins
  • Date: 2010-05-06 23:41:35 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506234135-yivbzczw1sejxnxc
Lock methods on ``Tree``, ``Branch`` and ``Repository`` are now
expected to return an object which can be used to unlock them. This reduces
duplicate code when using cleanups. The previous 'tokens's returned by
``Branch.lock_write`` and ``Repository.lock_write`` are now attributes
on the result of the lock_write. ``repository.RepositoryWriteLockResult``
and ``branch.BranchWriteLockResult`` document this. (Robert Collins)

``log._get_info_for_log_files`` now takes an add_cleanup callable.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2009, 2010, 2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2009, 2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
# TODO: 'brz resolve' should accept a directory name and work from that
 
17
# TODO: 'bzr resolve' should accept a directory name and work from that
18
18
# point down
19
19
 
20
 
from __future__ import absolute_import
21
 
 
22
20
import os
23
 
import re
24
21
 
25
 
from .lazy_import import lazy_import
 
22
from bzrlib.lazy_import import lazy_import
26
23
lazy_import(globals(), """
27
24
import errno
28
25
 
29
 
from breezy import (
 
26
from bzrlib import (
 
27
    builtins,
 
28
    cleanup,
 
29
    commands,
 
30
    errors,
30
31
    osutils,
31
32
    rio,
32
33
    trace,
33
34
    transform,
34
35
    workingtree,
35
36
    )
36
 
from breezy.i18n import gettext, ngettext
37
37
""")
38
 
from . import (
39
 
    cache_utf8,
40
 
    errors,
41
 
    commands,
 
38
from bzrlib import (
42
39
    option,
43
40
    registry,
44
41
    )
45
 
from .sixish import text_type
46
42
 
47
43
 
48
44
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
54
50
    Merge will do its best to combine the changes in two branches, but there
55
51
    are some kinds of problems only a human can fix.  When it encounters those,
56
52
    it will mark a conflict.  A conflict means that you need to fix something,
57
 
    before you can commit.
 
53
    before you should commit.
58
54
 
59
55
    Conflicts normally are listed as short, human-readable messages.  If --text
60
56
    is supplied, the pathnames of files with text conflicts are listed,
61
57
    instead.  (This is useful for editing all files with text conflicts.)
62
58
 
63
 
    Use brz resolve when you have fixed a problem.
 
59
    Use bzr resolve when you have fixed a problem.
64
60
    """
65
61
    takes_options = [
66
 
        'directory',
67
 
        option.Option('text',
68
 
                      help='List paths of files with text conflicts.'),
 
62
            option.Option('text',
 
63
                          help='List paths of files with text conflicts.'),
69
64
        ]
70
65
    _see_also = ['resolve', 'conflict-types']
71
66
 
72
 
    def run(self, text=False, directory=u'.'):
73
 
        wt = workingtree.WorkingTree.open_containing(directory)[0]
 
67
    def run(self, text=False):
 
68
        wt = workingtree.WorkingTree.open_containing(u'.')[0]
74
69
        for conflict in wt.conflicts():
75
70
            if text:
76
71
                if conflict.typestring != 'text conflict':
77
72
                    continue
78
73
                self.outf.write(conflict.path + '\n')
79
74
            else:
80
 
                self.outf.write(text_type(conflict) + '\n')
 
75
                self.outf.write(str(conflict) + '\n')
81
76
 
82
77
 
83
78
resolve_action_registry = registry.Registry()
84
79
 
85
80
 
86
81
resolve_action_registry.register(
87
 
    'auto', 'auto', 'Detect whether conflict has been resolved by user.')
88
 
resolve_action_registry.register(
89
 
    'done', 'done', 'Marks the conflict as resolved.')
 
82
    'done', 'done', 'Marks the conflict as resolved' )
90
83
resolve_action_registry.register(
91
84
    'take-this', 'take_this',
92
 
    'Resolve the conflict preserving the version in the working tree.')
 
85
    'Resolve the conflict preserving the version in the working tree' )
93
86
resolve_action_registry.register(
94
87
    'take-other', 'take_other',
95
 
    'Resolve the conflict taking the merged version into account.')
 
88
    'Resolve the conflict taking the merged version into account' )
96
89
resolve_action_registry.default_key = 'done'
97
90
 
98
 
 
99
91
class ResolveActionOption(option.RegistryOption):
100
92
 
101
93
    def __init__(self):
111
103
    Merge will do its best to combine the changes in two branches, but there
112
104
    are some kinds of problems only a human can fix.  When it encounters those,
113
105
    it will mark a conflict.  A conflict means that you need to fix something,
114
 
    before you can commit.
 
106
    before you should commit.
115
107
 
116
 
    Once you have fixed a problem, use "brz resolve" to automatically mark
117
 
    text conflicts as fixed, "brz resolve FILE" to mark a specific conflict as
118
 
    resolved, or "brz resolve --all" to mark all conflicts as resolved.
 
108
    Once you have fixed a problem, use "bzr resolve" to automatically mark
 
109
    text conflicts as fixed, "bzr resolve FILE" to mark a specific conflict as
 
110
    resolved, or "bzr resolve --all" to mark all conflicts as resolved.
119
111
    """
120
112
    aliases = ['resolved']
121
113
    takes_args = ['file*']
122
114
    takes_options = [
123
 
        'directory',
124
 
        option.Option('all', help='Resolve all conflicts in this tree.'),
125
 
        ResolveActionOption(),
126
 
        ]
 
115
            option.Option('all', help='Resolve all conflicts in this tree.'),
 
116
            ResolveActionOption(),
 
117
            ]
127
118
    _see_also = ['conflicts']
128
 
 
129
 
    def run(self, file_list=None, all=False, action=None, directory=None):
 
119
    def run(self, file_list=None, all=False, action=None):
130
120
        if all:
131
121
            if file_list:
132
 
                raise errors.BzrCommandError(gettext("If --all is specified,"
133
 
                                                     " no FILE may be provided"))
134
 
            if directory is None:
135
 
                directory = u'.'
136
 
            tree = workingtree.WorkingTree.open_containing(directory)[0]
 
122
                raise errors.BzrCommandError("If --all is specified,"
 
123
                                             " no FILE may be provided")
 
124
            tree = workingtree.WorkingTree.open_containing('.')[0]
137
125
            if action is None:
138
126
                action = 'done'
139
127
        else:
140
 
            tree, file_list = workingtree.WorkingTree.open_containing_paths(
141
 
                file_list, directory)
142
 
            if action is None:
143
 
                if file_list is None:
 
128
            tree, file_list = builtins.tree_files(file_list)
 
129
            if file_list is None:
 
130
                if action is None:
 
131
                    # FIXME: There is a special case here related to the option
 
132
                    # handling that could be clearer and easier to discover by
 
133
                    # providing an --auto action (bug #344013 and #383396) and
 
134
                    # make it mandatory instead of implicit and active only
 
135
                    # when no file_list is provided -- vila 091229
144
136
                    action = 'auto'
145
 
                else:
 
137
            else:
 
138
                if action is None:
146
139
                    action = 'done'
147
 
        before, after = resolve(tree, file_list, action=action)
148
 
        # GZ 2012-07-27: Should unify UI below now that auto is less magical.
149
 
        if action == 'auto' and file_list is None:
150
 
            if after > 0:
151
 
                trace.note(
152
 
                    ngettext('%d conflict auto-resolved.',
153
 
                             '%d conflicts auto-resolved.', before - after),
154
 
                    before - after)
155
 
                trace.note(gettext('Remaining conflicts:'))
156
 
                for conflict in tree.conflicts():
157
 
                    trace.note(text_type(conflict))
158
 
                return 1
 
140
        if action == 'auto':
 
141
            if file_list is None:
 
142
                un_resolved, resolved = tree.auto_resolve()
 
143
                if len(un_resolved) > 0:
 
144
                    trace.note('%d conflict(s) auto-resolved.', len(resolved))
 
145
                    trace.note('Remaining conflicts:')
 
146
                    for conflict in un_resolved:
 
147
                        trace.note(conflict)
 
148
                    return 1
 
149
                else:
 
150
                    trace.note('All conflicts resolved.')
 
151
                    return 0
159
152
            else:
160
 
                trace.note(gettext('All conflicts resolved.'))
161
 
                return 0
 
153
                # FIXME: This can never occur but the block above needs some
 
154
                # refactoring to transfer tree.auto_resolve() to
 
155
                # conflict.auto(tree) --vila 091242
 
156
                pass
162
157
        else:
163
 
            trace.note(ngettext('{0} conflict resolved, {1} remaining',
164
 
                                '{0} conflicts resolved, {1} remaining',
165
 
                                before - after).format(before - after, after))
 
158
            resolve(tree, file_list, action=action)
166
159
 
167
160
 
168
161
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
180
173
        paths do not have conflicts.
181
174
    :param action: How the conflict should be resolved,
182
175
    """
183
 
    nb_conflicts_after = None
184
 
    with tree.lock_tree_write():
 
176
    tree.lock_tree_write()
 
177
    try:
185
178
        tree_conflicts = tree.conflicts()
186
 
        nb_conflicts_before = len(tree_conflicts)
187
179
        if paths is None:
188
180
            new_conflicts = ConflictList()
189
181
            to_process = tree_conflicts
197
189
            except NotImplementedError:
198
190
                new_conflicts.append(conflict)
199
191
        try:
200
 
            nb_conflicts_after = len(new_conflicts)
201
192
            tree.set_conflicts(new_conflicts)
202
193
        except errors.UnsupportedOperation:
203
194
            pass
204
 
    if nb_conflicts_after is None:
205
 
        nb_conflicts_after = nb_conflicts_before
206
 
    return nb_conflicts_before, nb_conflicts_after
 
195
    finally:
 
196
        tree.unlock()
207
197
 
208
198
 
209
199
def restore(filename):
215
205
    try:
216
206
        osutils.rename(filename + ".THIS", filename)
217
207
        conflicted = True
218
 
    except OSError as e:
 
208
    except OSError, e:
219
209
        if e.errno != errno.ENOENT:
220
210
            raise
221
211
    try:
222
212
        os.unlink(filename + ".BASE")
223
213
        conflicted = True
224
 
    except OSError as e:
 
214
    except OSError, e:
225
215
        if e.errno != errno.ENOENT:
226
216
            raise
227
217
    try:
228
218
        os.unlink(filename + ".OTHER")
229
219
        conflicted = True
230
 
    except OSError as e:
 
220
    except OSError, e:
231
221
        if e.errno != errno.ENOENT:
232
222
            raise
233
223
    if not conflicted:
289
279
    def to_strings(self):
290
280
        """Generate strings for the provided conflicts"""
291
281
        for conflict in self:
292
 
            yield text_type(conflict)
 
282
            yield str(conflict)
293
283
 
294
284
    def remove_files(self, tree):
295
285
        """Remove the THIS, BASE and OTHER files for listed conflicts"""
346
336
        if ignore_misses is not True:
347
337
            for path in [p for p in paths if p not in selected_paths]:
348
338
                if not os.path.exists(tree.abspath(path)):
349
 
                    print("%s does not exist" % path)
 
339
                    print "%s does not exist" % path
350
340
                else:
351
 
                    print("%s is not conflicted" % path)
 
341
                    print "%s is not conflicted" % path
352
342
        return new_conflicts, selected_conflicts
353
343
 
354
344
 
360
350
 
361
351
    def __init__(self, path, file_id=None):
362
352
        self.path = path
363
 
        # the factory blindly transfers the Stanza values to __init__ and
364
 
        # Stanza is purely a Unicode api.
365
 
        if isinstance(file_id, text_type):
366
 
            file_id = cache_utf8.encode(file_id)
367
 
        self.file_id = osutils.safe_file_id(file_id)
 
353
        # warn turned off, because the factory blindly transfers the Stanza
 
354
        # values to __init__ and Stanza is purely a Unicode api.
 
355
        self.file_id = osutils.safe_file_id(file_id, warn=False)
368
356
 
369
357
    def as_stanza(self):
370
358
        s = rio.Stanza(type=self.typestring, path=self.path)
379
367
    def __cmp__(self, other):
380
368
        if getattr(other, "_cmp_list", None) is None:
381
369
            return -1
382
 
        x = self._cmp_list()
383
 
        y = other._cmp_list()
384
 
        return (x > y) - (x < y)
 
370
        return cmp(self._cmp_list(), other._cmp_list())
385
371
 
386
372
    def __hash__(self):
387
373
        return hash((type(self), self.path, self.file_id))
392
378
    def __ne__(self, other):
393
379
        return not self.__eq__(other)
394
380
 
395
 
    def __unicode__(self):
396
 
        return self.describe()
397
 
 
398
381
    def __str__(self):
399
 
        return self.describe()
400
 
 
401
 
    def describe(self):
402
382
        return self.format % self.__dict__
403
383
 
404
384
    def __repr__(self):
440
420
        for fname in self.associated_filenames():
441
421
            try:
442
422
                osutils.delete_any(tree.abspath(fname))
443
 
            except OSError as e:
 
423
            except OSError, e:
444
424
                if e.errno != errno.ENOENT:
445
425
                    raise
446
426
 
447
 
    def action_auto(self, tree):
448
 
        raise NotImplementedError(self.action_auto)
449
 
 
450
427
    def action_done(self, tree):
451
428
        """Mark the conflict as solved once it has been handled."""
452
429
        # This method does nothing but simplifies the design of upper levels.
459
436
        raise NotImplementedError(self.action_take_other)
460
437
 
461
438
    def _resolve_with_cleanups(self, tree, *args, **kwargs):
462
 
        with tree.get_transform() as tt:
463
 
            self._resolve(tt, *args, **kwargs)
 
439
        tt = transform.TreeTransform(tree)
 
440
        op = cleanup.OperationWithCleanups(self._resolve)
 
441
        op.add_cleanup(tt.finalize)
 
442
        op.run_simple(tt, *args, **kwargs)
464
443
 
465
444
 
466
445
class PathConflict(Conflict):
497
476
        path_to_create = None
498
477
        if winner == 'this':
499
478
            if self.path == '<deleted>':
500
 
                return  # Nothing to do
 
479
                return # Nothing to do
501
480
            if self.conflict_path == '<deleted>':
502
481
                path_to_create = self.path
503
482
                revid = tt._tree.get_parent_ids()[0]
515
494
            raise AssertionError('bad winner: %r' % (winner,))
516
495
        if path_to_create is not None:
517
496
            tid = tt.trans_id_tree_path(path_to_create)
518
 
            tree = self._revision_tree(tt._tree, revid)
519
497
            transform.create_from_tree(
520
 
                tt, tid, tree, tree.id2path(file_id))
 
498
                tt, tt.trans_id_tree_path(path_to_create),
 
499
                self._revision_tree(tt._tree, revid), file_id)
521
500
            tt.version_file(file_id, tid)
522
 
        else:
523
 
            tid = tt.trans_id_file_id(file_id)
 
501
 
524
502
        # Adjust the path for the retained file id
 
503
        tid = tt.trans_id_file_id(file_id)
525
504
        parent_tid = tt.get_tree_parent(tid)
526
 
        tt.adjust_path(osutils.basename(path), parent_tid, tid)
 
505
        tt.adjust_path(path, parent_tid, tid)
527
506
        tt.apply()
528
507
 
529
508
    def _revision_tree(self, tree, revid):
536
515
        possible_paths = []
537
516
        for p in (self.path, self.conflict_path):
538
517
            if p == '<deleted>':
539
 
                # special hard-coded path
 
518
                # special hard-coded path 
540
519
                continue
541
520
            if p is not None:
542
521
                possible_paths.append(p)
592
571
        :param tt: The TreeTransform where the conflict is resolved.
593
572
        :param suffix_to_remove: Either 'THIS' or 'OTHER'
594
573
 
595
 
        The resolution is symmetric: when taking THIS, OTHER is deleted and
 
574
        The resolution is symmetric, when taking THIS, OTHER is deleted and
596
575
        item.THIS is renamed into item and vice-versa.
597
576
        """
598
577
        try:
605
584
            # never existed or was already deleted (including the case
606
585
            # where the user deleted it)
607
586
            pass
608
 
        try:
609
 
            this_path = tt._tree.id2path(self.file_id)
610
 
        except errors.NoSuchId:
611
 
            # The file is not present anymore. This may happen if the user
612
 
            # deleted the file either manually or when resolving a conflict on
613
 
            # the parent.  We may raise some exception to indicate that the
614
 
            # conflict doesn't exist anymore and as such doesn't need to be
615
 
            # resolved ? -- vila 20110615
616
 
            this_tid = None
617
 
        else:
618
 
            this_tid = tt.trans_id_tree_path(this_path)
619
 
        if this_tid is not None:
620
 
            # Rename 'item.suffix_to_remove' (note that if
621
 
            # 'item.suffix_to_remove' has been deleted, this is a no-op)
622
 
            parent_tid = tt.get_tree_parent(this_tid)
623
 
            tt.adjust_path(osutils.basename(self.path), parent_tid, this_tid)
624
 
            tt.apply()
 
587
        # Rename 'item.suffix_to_remove' (note that if
 
588
        # 'item.suffix_to_remove' has been deleted, this is a no-op)
 
589
        this_tid = tt.trans_id_file_id(self.file_id)
 
590
        parent_tid = tt.get_tree_parent(this_tid)
 
591
        tt.adjust_path(self.path, parent_tid, this_tid)
 
592
        tt.apply()
625
593
 
626
594
    def action_take_this(self, tree):
627
595
        self._resolve_with_cleanups(tree, 'OTHER')
630
598
        self._resolve_with_cleanups(tree, 'THIS')
631
599
 
632
600
 
 
601
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
 
602
# attribute so we shouldn't inherit from PathConflict but simply from Conflict
 
603
 
633
604
# TODO: There should be a base revid attribute to better inform the user about
634
605
# how the conflicts were generated.
635
 
class TextConflict(Conflict):
 
606
class TextConflict(PathConflict):
636
607
    """The merge algorithm could not resolve all differences encountered."""
637
608
 
638
609
    has_files = True
641
612
 
642
613
    format = 'Text conflict in %(path)s'
643
614
 
644
 
    rformat = '%(class)s(%(path)r, %(file_id)r)'
645
 
 
646
 
    _conflict_re = re.compile(b'^(<{7}|={7}|>{7})')
647
 
 
648
615
    def associated_filenames(self):
649
616
        return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
650
617
 
651
 
    def _resolve(self, tt, winner_suffix):
652
 
        """Resolve the conflict by copying one of .THIS or .OTHER into file.
653
 
 
654
 
        :param tt: The TreeTransform where the conflict is resolved.
655
 
        :param winner_suffix: Either 'THIS' or 'OTHER'
656
 
 
657
 
        The resolution is symmetric, when taking THIS, item.THIS is renamed
658
 
        into item and vice-versa. This takes one of the files as a whole
659
 
        ignoring every difference that could have been merged cleanly.
660
 
        """
661
 
        # To avoid useless copies, we switch item and item.winner_suffix, only
662
 
        # item will exist after the conflict has been resolved anyway.
663
 
        item_tid = tt.trans_id_file_id(self.file_id)
664
 
        item_parent_tid = tt.get_tree_parent(item_tid)
665
 
        winner_path = self.path + '.' + winner_suffix
666
 
        winner_tid = tt.trans_id_tree_path(winner_path)
667
 
        winner_parent_tid = tt.get_tree_parent(winner_tid)
668
 
        # Switch the paths to preserve the content
669
 
        tt.adjust_path(osutils.basename(self.path),
670
 
                       winner_parent_tid, winner_tid)
671
 
        tt.adjust_path(osutils.basename(winner_path),
672
 
                       item_parent_tid, item_tid)
673
 
        # Associate the file_id to the right content
674
 
        tt.unversion_file(item_tid)
675
 
        tt.version_file(self.file_id, winner_tid)
676
 
        tt.apply()
677
 
 
678
 
    def action_auto(self, tree):
679
 
        # GZ 2012-07-27: Using NotImplementedError to signal that a conflict
680
 
        #                can't be auto resolved does not seem ideal.
681
 
        try:
682
 
            kind = tree.kind(self.path)
683
 
        except errors.NoSuchFile:
684
 
            return
685
 
        if kind != 'file':
686
 
            raise NotImplementedError("Conflict is not a file")
687
 
        conflict_markers_in_line = self._conflict_re.search
688
 
        # GZ 2012-07-27: What if not tree.has_id(self.file_id) due to removal?
689
 
        with tree.get_file(self.path) as f:
690
 
            for line in f:
691
 
                if conflict_markers_in_line(line):
692
 
                    raise NotImplementedError("Conflict markers present")
693
 
 
694
 
    def action_take_this(self, tree):
695
 
        self._resolve_with_cleanups(tree, 'THIS')
696
 
 
697
 
    def action_take_other(self, tree):
698
 
        self._resolve_with_cleanups(tree, 'OTHER')
699
 
 
700
618
 
701
619
class HandledConflict(Conflict):
702
620
    """A path problem that has been provisionally resolved.
734
652
                 conflict_file_id=None):
735
653
        HandledConflict.__init__(self, action, path, file_id)
736
654
        self.conflict_path = conflict_path
737
 
        # the factory blindly transfers the Stanza values to __init__,
738
 
        # so they can be unicode.
739
 
        if isinstance(conflict_file_id, text_type):
740
 
            conflict_file_id = cache_utf8.encode(conflict_file_id)
741
 
        self.conflict_file_id = osutils.safe_file_id(conflict_file_id)
 
655
        # warn turned off, because the factory blindly transfers the Stanza
 
656
        # values to __init__.
 
657
        self.conflict_file_id = osutils.safe_file_id(conflict_file_id,
 
658
                                                     warn=False)
742
659
 
743
660
    def _cmp_list(self):
744
661
        return HandledConflict._cmp_list(self) + [self.conflict_path,
792
709
    format = 'Conflict moving %(path)s into %(conflict_path)s. %(action)s.'
793
710
 
794
711
    def action_take_this(self, tree):
795
 
        # just acccept brz proposal
 
712
        # just acccept bzr proposal
796
713
        pass
797
714
 
798
715
    def action_take_other(self, tree):
799
 
        with tree.get_transform() as tt:
 
716
        # FIXME: We shouldn't have to manipulate so many paths here (and there
 
717
        # is probably a bug or two...)
 
718
        base_path = osutils.basename(self.path)
 
719
        conflict_base_path = osutils.basename(self.conflict_path)
 
720
        tt = transform.TreeTransform(tree)
 
721
        try:
800
722
            p_tid = tt.trans_id_file_id(self.file_id)
801
723
            parent_tid = tt.get_tree_parent(p_tid)
802
724
            cp_tid = tt.trans_id_file_id(self.conflict_file_id)
803
725
            cparent_tid = tt.get_tree_parent(cp_tid)
804
 
            tt.adjust_path(osutils.basename(self.path), cparent_tid, cp_tid)
805
 
            tt.adjust_path(osutils.basename(self.conflict_path),
806
 
                           parent_tid, p_tid)
 
726
            tt.adjust_path(base_path, cparent_tid, cp_tid)
 
727
            tt.adjust_path(conflict_base_path, parent_tid, p_tid)
807
728
            tt.apply()
 
729
        finally:
 
730
            tt.finalize()
808
731
 
809
732
 
810
733
class UnversionedParent(HandledConflict):
843
766
        tree.remove([self.path], force=True, keep_files=False)
844
767
 
845
768
    def action_take_other(self, tree):
846
 
        # just acccept brz proposal
 
769
        # just acccept bzr proposal
847
770
        pass
848
771
 
849
772
 
862
785
    # MissingParent from the *user* pov.
863
786
 
864
787
    def action_take_this(self, tree):
865
 
        # just acccept brz proposal
 
788
        # just acccept bzr proposal
866
789
        pass
867
790
 
868
791
    def action_take_other(self, tree):
909
832
    for conflict_type in conflict_types:
910
833
        ctype[conflict_type.typestring] = conflict_type
911
834
 
912
 
 
913
835
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
914
836
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
915
837
               DeletingParent, NonDirectoryParent)